Zauber auswählen
This commit is contained in:
@@ -0,0 +1,169 @@
|
||||
# GetAllSpellsWithLE Funktion
|
||||
|
||||
## Überblick
|
||||
|
||||
Die `GetAllSpellsWithLE` Funktion wurde implementiert, um alle verfügbaren Zauber mit ihren Lerneinheiten (LE) und Erfahrungspunkten (EP) gruppiert nach Kategorien aus der Datenbank abzurufen.
|
||||
|
||||
## Funktionssignatur
|
||||
|
||||
```go
|
||||
func GetAllSpellsWithLE(characterClass string, maxLevel int) (map[string][]gin.H, error)
|
||||
```
|
||||
|
||||
### Parameter
|
||||
|
||||
- `characterClass`: Die Charakterklasse (z.B. "Magier", "Hexer", "Druide")
|
||||
- `maxLevel`: Maximale Zauberstufe, die abgerufen werden soll
|
||||
|
||||
### Rückgabe
|
||||
|
||||
- `map[string][]gin.H`: Eine Map mit Zauberschulen als Schlüssel und Arrays von Zaubern als Werte
|
||||
- `error`: Fehlermeldung falls etwas schiefgeht
|
||||
|
||||
## Features
|
||||
|
||||
### 1. Charakterklassen-Zauberschulen-Mapping
|
||||
|
||||
Die Funktion verwendet ein vordefiniertes Mapping, das festlegt, welche Zauberschulen eine Charakterklasse erlernen kann:
|
||||
|
||||
```go
|
||||
"Magier": {
|
||||
"Beherrschen": true,
|
||||
"Bewegen": true,
|
||||
"Dweomer": true,
|
||||
"Erkennen": true,
|
||||
"Erschaffen": true,
|
||||
"Verändern": true,
|
||||
"Zerstören": true,
|
||||
},
|
||||
"Hexer": {
|
||||
"Beherrschen": true,
|
||||
"Dweomer": true,
|
||||
"Erkennen": true,
|
||||
"Verändern": true,
|
||||
},
|
||||
```
|
||||
|
||||
### 2. Lerneinheiten-Berechnung
|
||||
|
||||
Die LE-Kosten werden aus der Datenbank-Tabelle `learning_spell_level_le_costs` abgerufen, die die Zuordnung von Zauberstufen zu LE-Kosten enthält.
|
||||
|
||||
**Datenbankstruktur für SpellLevelLECost:**
|
||||
```go
|
||||
type SpellLevelLECost struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
Level int `gorm:"uniqueIndex;not null" json:"level"`
|
||||
LERequired int `gorm:"not null" json:"le_required"`
|
||||
GameSystem string `gorm:"index;default:midgard" json:"game_system"`
|
||||
}
|
||||
```
|
||||
|
||||
**Fallback bei fehlenden Datenbankeinträgen:**
|
||||
Falls keine Einträge in der Datenbank gefunden werden, verwendet die Funktion Standard Midgard-Werte:
|
||||
|
||||
```go
|
||||
Stufe 1: 1 LE Stufe 7: 8 LE
|
||||
Stufe 2: 2 LE Stufe 8: 10 LE
|
||||
Stufe 3: 3 LE Stufe 9: 12 LE
|
||||
Stufe 4: 4 LE Stufe 10: 15 LE
|
||||
Stufe 5: 5 LE Stufe 11: 18 LE
|
||||
Stufe 6: 6 LE Stufe 12: 21 LE
|
||||
```
|
||||
|
||||
### 3. EP-Kostenberechnung
|
||||
|
||||
EP-Kosten werden basierend auf Charakterklasse und Zauberschule berechnet:
|
||||
|
||||
- **Magier**: 10 EP pro LE für alle Schulen
|
||||
- **Hexer**: 30 EP pro LE für erlaubte Schulen
|
||||
- **Druide**: 20 EP pro LE für erlaubte Schulen
|
||||
- **Schamane**: 40 EP pro LE für erlaubte Schulen
|
||||
- **Priester**: 30 EP pro LE für erlaubte Schulen
|
||||
- **Barde**: 60 EP pro LE für erlaubte Schulen
|
||||
- **Ordenskrieger**: 90 EP pro LE für erlaubte Schulen
|
||||
|
||||
## Beispiel-Nutzung
|
||||
|
||||
```go
|
||||
// Alle Zauber bis Stufe 3 für einen Hexer
|
||||
spells, err := GetAllSpellsWithLE("Hexer", 3)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Ausgabe: Zauber gruppiert nach Schulen (Beherrschen, Verändern, etc.)
|
||||
```
|
||||
|
||||
## Beispiel-Ausgabe für Hexer (maxLevel: 3)
|
||||
|
||||
```json
|
||||
{
|
||||
"Beherrschen": [
|
||||
{
|
||||
"description": "Hebt andere Zauber auf",
|
||||
"ep_cost": 30,
|
||||
"id": 1,
|
||||
"le_cost": 1,
|
||||
"learning_category": "Beherrschen",
|
||||
"level": 1,
|
||||
"name": "Bannen von Zauberwerk",
|
||||
"school": "Beherrschen"
|
||||
},
|
||||
{
|
||||
"description": "Beruhigt den Geist",
|
||||
"ep_cost": 60,
|
||||
"id": 2,
|
||||
"le_cost": 2,
|
||||
"learning_category": "Beherrschen",
|
||||
"level": 2,
|
||||
"name": "Geistesruhe",
|
||||
"school": "Beherrschen"
|
||||
}
|
||||
],
|
||||
"Verändern": [
|
||||
{
|
||||
"description": "Heilt körperliche Verletzungen",
|
||||
"ep_cost": 30,
|
||||
"id": 6,
|
||||
"le_cost": 1,
|
||||
"learning_category": "Verändern",
|
||||
"level": 1,
|
||||
"name": "Heilen von Wunden",
|
||||
"school": "Verändern"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Datenbankstruktur
|
||||
|
||||
Die Funktion erwartet folgende Felder in der `gsm_spells` Tabelle:
|
||||
|
||||
- `id`: Eindeutige ID des Zaubers
|
||||
- `name`: Name des Zaubers
|
||||
- `beschreibung`: Beschreibung des Zaubers
|
||||
- `stufe`: Zauberstufe (1-12)
|
||||
- `category`: Anzeige-Kategorie des Zaubers
|
||||
- `learning_category`: Interne Lernkategorie für EP-Berechnung
|
||||
- Zusätzliche Felder: `ap`, `art`, `zauberdauer`, `reichweite`, `wirkungsziel`, `wirkungsbereich`, `wirkungsdauer`
|
||||
|
||||
## Beispiel-Testdaten
|
||||
|
||||
Siehe `example_spell_data.sql` für vollständige Beispieldaten verschiedener Zauberschulen und Stufen.
|
||||
|
||||
## Sortierung
|
||||
|
||||
Zauber werden innerhalb jeder Kategorie zuerst nach Stufe, dann alphabetisch nach Name sortiert.
|
||||
|
||||
## Unterstützte Charakterklassen
|
||||
|
||||
- Magier (Ma) - Vollzugriff auf alle Schulen
|
||||
- Hexer (Hx) - Beherrschen, Dweomer, Erkennen, Verändern
|
||||
- Druide (Dr) - Bewegen, Erkennen, Erschaffen, Verändern
|
||||
- Schamane (Sc) - Beherrschen, Erkennen, Verändern
|
||||
- Priester Beschützer (PB) - Dweomer, Erkennen, Verändern
|
||||
- Priester Streiter (PS) - Dweomer, Erkennen, Verändern
|
||||
- Barde (Ba) - Beherrschen, Dweomer, Erkennen
|
||||
- Ordenskrieger (Or) - Dweomer, Erkennen
|
||||
|
||||
Charakterklassen, die nicht in der Mapping-Tabelle stehen, erhalten eine leere Antwort (können keine Zauber lernen).
|
||||
@@ -2137,7 +2137,10 @@ func getCharacterClassCode(className string) (string, error) {
|
||||
var characterClass models.CharacterClass
|
||||
err := characterClass.FirstByName(className)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("character class '%s' not found: %w", className, err)
|
||||
err := characterClass.FirstByCode(className)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("character class '%s' not found: %w", className, err)
|
||||
}
|
||||
}
|
||||
return characterClass.Code, nil
|
||||
}
|
||||
@@ -2178,6 +2181,14 @@ func GetAvailableSpellsForCreation(c *gin.Context) {
|
||||
|
||||
logger.Info("GetAvailableSpellsForCreation - Gefundene Kategorien: %d", len(spellsByCategory))
|
||||
|
||||
if len(spellsByCategory) == 0 {
|
||||
logger.Warn("GetAvailableSpellsForCreation - Keine Zauber für Klasse %s gefunden", request.CharacterClass)
|
||||
c.JSON(http.StatusNotFound, gin.H{
|
||||
"spells_by_category": map[string][]gin.H{},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"spells_by_category": spellsByCategory,
|
||||
})
|
||||
@@ -2219,18 +2230,37 @@ func GetAvailableSkillsForCreation(c *gin.Context) {
|
||||
|
||||
func GetAllSpellsWithLE(characterClass string, maxLevel int) (map[string][]gin.H, error) {
|
||||
// Create mapping of character classes to allowed learning categories
|
||||
allowedLearningCategories := getCharacterClassSpellSchoolMapping()
|
||||
allowedCategories := getCharacterClassSpellSchoolMapping()
|
||||
allowedLearningCategories := getCharacterClassSpellLearningCategoriesMapping()
|
||||
|
||||
// Check if character class has allowed spell schools
|
||||
|
||||
allowedSchools, exists := allowedLearningCategories[characterClass]
|
||||
allowedSchools, exists := allowedCategories[characterClass]
|
||||
if !exists {
|
||||
return map[string][]gin.H{}, nil // Return empty map if class can't learn spells
|
||||
}
|
||||
allowedSpellType, exists := allowedLearningCategories[characterClass]
|
||||
if !exists {
|
||||
return map[string][]gin.H{}, nil // Return empty map if class can't learn spells
|
||||
}
|
||||
// Extract allowed school names and spell types from maps
|
||||
var allowedSchoolNames []string
|
||||
for school, allowed := range allowedSchools {
|
||||
if allowed {
|
||||
allowedSchoolNames = append(allowedSchoolNames, school)
|
||||
}
|
||||
}
|
||||
|
||||
var allowedSpellTypeNames []string
|
||||
for spellType, allowed := range allowedSpellType {
|
||||
if allowed {
|
||||
allowedSpellTypeNames = append(allowedSpellTypeNames, spellType)
|
||||
}
|
||||
}
|
||||
|
||||
// Get all spells from database with level filter
|
||||
var spells []models.Spell
|
||||
err := database.DB.Where("stufe <= ? AND learning_category != '' AND learning_category IS NOT NULL", maxLevel).Find(&spells).Error
|
||||
err := database.DB.Where("stufe <= ? AND category in (?) and learning_category in (?) AND category IS NOT NULL AND learning_category IS NOT NULL", maxLevel, allowedSchoolNames, allowedSpellTypeNames).Find(&spells).Error
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to fetch spells: %w", err)
|
||||
}
|
||||
@@ -2240,9 +2270,9 @@ func GetAllSpellsWithLE(characterClass string, maxLevel int) (map[string][]gin.H
|
||||
|
||||
for _, spell := range spells {
|
||||
// Check if this character class can learn this spell school
|
||||
if !allowedSchools[spell.LearningCategory] {
|
||||
continue // Skip spells from schools this class can't learn
|
||||
}
|
||||
//if !allowedSchools[spell.Category] || !allowedSpellType[spell.LearningCategory] {
|
||||
// continue // Skip spells from schools this class can't learn
|
||||
//}
|
||||
|
||||
// Calculate learning cost for this spell
|
||||
leCost := getSpellLECost(spell.Stufe)
|
||||
@@ -2287,6 +2317,46 @@ func GetAllSpellsWithLE(characterClass string, maxLevel int) (map[string][]gin.H
|
||||
|
||||
return spellsByCategory, nil
|
||||
}
|
||||
func getCharacterClassSpellLearningCategoriesMapping() map[string]map[string]bool {
|
||||
return map[string]map[string]bool{
|
||||
"Ma": { // Magier
|
||||
"Spruch": true,
|
||||
//"Salz": true,
|
||||
//"Runenstab": true,
|
||||
},
|
||||
"Hx": { // Hexer
|
||||
"Spruch": true,
|
||||
"Salz": true,
|
||||
//"Runenstab": true,
|
||||
},
|
||||
"Dr": { // Druide
|
||||
"Spruch": true,
|
||||
//"Salz": true,
|
||||
//"Runenstab": true,
|
||||
"Dweomer": true,
|
||||
},
|
||||
"Sc": { // Schamane
|
||||
"Spruch": true,
|
||||
//"Salz": true,
|
||||
//"Runenstab": true,
|
||||
"Dweomer": true,
|
||||
},
|
||||
"PB": { // Priester Beschützer
|
||||
"Salz": true,
|
||||
"Wundertat": true,
|
||||
},
|
||||
"PS": { // Priester Streiter
|
||||
"Salz": true,
|
||||
"Wundertat": true,
|
||||
},
|
||||
"Ba": { // Barde
|
||||
"Lied": true,
|
||||
},
|
||||
"Or": { // Ordenskrieger
|
||||
"Wundertat": true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// getCharacterClassSpellSchoolMapping returns the mapping of character classes to allowed spell schools
|
||||
func getCharacterClassSpellSchoolMapping() map[string]map[string]bool {
|
||||
@@ -2302,9 +2372,12 @@ func getCharacterClassSpellSchoolMapping() map[string]map[string]bool {
|
||||
},
|
||||
"Hx": { // Hexer
|
||||
"Beherrschen": true,
|
||||
"Dweomer": true,
|
||||
"Zerstören": true,
|
||||
"Erkennen": true,
|
||||
"Verändern": true,
|
||||
"Erschaffen": true,
|
||||
"Bewegen": true,
|
||||
"Formen": true,
|
||||
},
|
||||
"Dr": { // Druide
|
||||
"Bewegen": true,
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
|
||||
"bamort/database"
|
||||
"bamort/models"
|
||||
"bamort/user"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -549,3 +550,119 @@ func TestGetAvailableSkillsForCreation(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetAvailableSpellsForCreation(t *testing.T) {
|
||||
// Setup test database
|
||||
database.SetupTestDB(true, true)
|
||||
defer database.ResetTestDB()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
characterClass string
|
||||
expectStatus int
|
||||
expectError bool
|
||||
findspells bool
|
||||
}{
|
||||
{
|
||||
name: "ValidCharacterClass",
|
||||
characterClass: "As",
|
||||
expectStatus: http.StatusNotFound,
|
||||
expectError: false,
|
||||
findspells: false,
|
||||
},
|
||||
{
|
||||
name: "MagierCharacterClass",
|
||||
characterClass: "Ma",
|
||||
expectStatus: http.StatusOK,
|
||||
expectError: false,
|
||||
findspells: true,
|
||||
},
|
||||
{
|
||||
name: "NonMagicCharacterClass",
|
||||
characterClass: "Kr",
|
||||
expectStatus: http.StatusNotFound,
|
||||
expectError: true,
|
||||
findspells: false,
|
||||
},
|
||||
{
|
||||
name: "EmptyCharacterClass",
|
||||
characterClass: "",
|
||||
expectStatus: http.StatusBadRequest,
|
||||
expectError: true,
|
||||
findspells: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Create request
|
||||
requestData := gin.H{
|
||||
"characterClass": tt.characterClass,
|
||||
}
|
||||
requestBody, _ := json.Marshal(requestData)
|
||||
|
||||
u := user.User{}
|
||||
u.FirstId(1)
|
||||
token := user.GenerateToken(&u)
|
||||
|
||||
// Create HTTP request
|
||||
req, _ := http.NewRequest("POST", "/api/characters/available-spells-creation", bytes.NewBuffer(requestBody))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Authorization", "Bearer "+token)
|
||||
|
||||
// Create response recorder
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := gin.CreateTestContext(w)
|
||||
c.Request = req
|
||||
|
||||
// Call the handler directly (it will handle JSON parsing internally)
|
||||
GetAvailableSpellsForCreation(c)
|
||||
|
||||
// Verify response
|
||||
assert.Equal(t, tt.expectStatus, w.Code)
|
||||
|
||||
if !tt.expectError {
|
||||
var response map[string]interface{}
|
||||
err := json.Unmarshal(w.Body.Bytes(), &response)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Check response structure
|
||||
assert.Contains(t, response, "spells_by_category")
|
||||
|
||||
spellsByCategory, ok := response["spells_by_category"].(map[string]interface{})
|
||||
assert.True(t, ok)
|
||||
if tt.findspells {
|
||||
assert.Greater(t, len(spellsByCategory), 0, "Should have at least some spell categories")
|
||||
|
||||
// Verify spells have learnCost field
|
||||
for categoryName, spells := range spellsByCategory {
|
||||
if spellsList, ok := spells.([]interface{}); ok {
|
||||
for _, spell := range spellsList {
|
||||
if spellMap, ok := spell.(map[string]interface{}); ok {
|
||||
assert.Contains(t, spellMap, "name", "Spell should have name")
|
||||
assert.Contains(t, spellMap, "le_cost", "Spell should have learnCost")
|
||||
|
||||
learnCost := spellMap["le_cost"].(float64)
|
||||
assert.Greater(t, learnCost, 0.0, "Learn cost should be positive")
|
||||
assert.Less(t, learnCost, 500.0, "Learn cost should be reasonable for character creation")
|
||||
}
|
||||
}
|
||||
}
|
||||
_ = categoryName // Mark as used
|
||||
}
|
||||
// Log some sample data for verification
|
||||
t.Logf("Character creation spells loaded for class %s: %d categories", tt.characterClass, len(spellsByCategory))
|
||||
} else {
|
||||
assert.Equal(t, len(spellsByCategory), 0, "Should not have any spell categories")
|
||||
}
|
||||
|
||||
} else {
|
||||
// For error cases, verify error response
|
||||
var response map[string]interface{}
|
||||
err := json.Unmarshal(w.Body.Bytes(), &response)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, response, "error")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user