diff --git a/backend/GetAllSpellsWithLE_Documentation.md b/backend/GetAllSpellsWithLE_Documentation.md new file mode 100644 index 0000000..f1aa64a --- /dev/null +++ b/backend/GetAllSpellsWithLE_Documentation.md @@ -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). diff --git a/backend/character/handlers.go b/backend/character/handlers.go index 5ba9857..b0dd3ef 100644 --- a/backend/character/handlers.go +++ b/backend/character/handlers.go @@ -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, diff --git a/backend/character/handlers_test.go b/backend/character/handlers_test.go index ae05612..35c9e78 100644 --- a/backend/character/handlers_test.go +++ b/backend/character/handlers_test.go @@ -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") + } + }) + } +} diff --git a/frontend/src/components/CharacterCreation/CharacterSpells.vue b/frontend/src/components/CharacterCreation/CharacterSpells.vue index 9ebe16c..a110415 100644 --- a/frontend/src/components/CharacterCreation/CharacterSpells.vue +++ b/frontend/src/components/CharacterCreation/CharacterSpells.vue @@ -1,74 +1,207 @@ @@ -82,10 +215,6 @@ export default { sessionData: { type: Object, required: true - }, - skillCategories: { - type: Array, - required: true } }, @@ -93,110 +222,397 @@ export default { data() { return { - availableSpells: [], + // Loading states + isLoading: false, + isLoadingSpells: false, + error: null, + + // Learning points data + learningPointsData: null, + + // Spells data + availableSpellsByCategory: null, + spellCategories: [], + + // Spells selection + selectedCategory: null, selectedSpells: [], + + // Spell points tracking + spellPoints: { + total: 0, + remaining: 0 + }, + + // Debug + showDebug: false // Set to true for debugging } }, - async mounted() { - await this.loadSpells() - this.loadSelectedSpells() + computed: { + // Get character class from session data + characterClass() { + return this.sessionData?.typ || '' + }, + + // Available spells for the selected category + availableSpellsForSelectedCategory() { + if (!this.selectedCategory || !this.availableSpellsByCategory) { + return [] + } + + // Try to find the matching category key + const categoryKey = this.findCategoryKey(this.selectedCategory) + if (!categoryKey) { + return [] + } + + // Get spells from the selected category + const categorySpells = this.availableSpellsByCategory[categoryKey] || [] + + const filteredSpells = categorySpells.map(spell => ({ + ...spell, + cost: this.getSpellCost(spell), + category: categoryKey // Use the actual category key from availableSpellsByCategory + })) + .filter(spell => { + // Remove already selected spells + const selectedSpellNames = this.selectedSpells.map(s => s.name) + return !selectedSpellNames.includes(spell.name) + }) + .sort((a, b) => { + const aLeCost = this.getSpellCost(a) + const bLeCost = this.getSpellCost(b) + + // First sort by LE cost (ascending) + if (aLeCost !== bLeCost) { + return aLeCost - bLeCost + } + + // If costs are equal, sort by level, then alphabetically + if (a.level !== b.level) { + return a.level - b.level + } + + return a.name.localeCompare(b.name) + }) + + return filteredSpells + }, + + totalSelectedLE() { + return this.selectedSpells.reduce((total, spell) => total + this.getSpellCost(spell), 0) + }, + + debugInfo() { + return { + // Session Data + hasSessionData: !!this.sessionData, + sessionDataKeys: this.sessionData ? Object.keys(this.sessionData) : null, + + // Character Info + characterClass: this.characterClass, + + // Loading States + isLoading: this.isLoading, + isLoadingSpells: this.isLoadingSpells, + error: this.error, + + // Data States + hasAvailableSpells: !!this.availableSpellsByCategory, + spellCategoriesCount: this.spellCategories.length, + selectedSpellsCount: this.selectedSpells.length, + selectedCategory: this.selectedCategory, + totalSelectedLE: this.totalSelectedLE, + + // Raw Data (for debugging) + availableSpellsByCategory: this.availableSpellsByCategory, + spellCategories: this.spellCategories + } + } + }, + + watch: { + selectedSpells: { + handler(newSpells) { + // Save spells automatically when they change + this.$emit('save', { + spells: newSpells, + spell_points: { + total: this.spellPoints.total, + remaining: this.spellPoints.remaining + }, + spells_meta: { + selectedCategory: this.selectedCategory, + totalLE: this.totalSelectedLE + } + }) + }, + deep: true + } + }, + + created() { + // Initialize with existing session data if available + if (this.sessionData.spells && Array.isArray(this.sessionData.spells)) { + this.selectedSpells = [...this.sessionData.spells] + } + + // Restore selected category if available + if (this.sessionData.spells_meta?.selectedCategory) { + this.selectedCategory = this.sessionData.spells_meta.selectedCategory + } + + // Initialize component + this.initializeComponent() }, methods: { - async loadSpells() { + async initializeComponent() { + try { + this.isLoading = true + this.error = null + + // Load learning points data first + await this.loadLearningPoints() + + // Load available spells + await this.loadAvailableSpells() + + // Restore previously selected spells if any + this.restoreSelectedSpells() + + // Update spell points based on selected spells + this.updateSpellPoints() + + } catch (error) { + console.error('Error initializing component:', error) + this.error = 'Fehler beim Laden der Zauber. Bitte versuchen Sie es erneut.' + } finally { + this.isLoading = false + } + }, + + async loadLearningPoints() { + if (!this.characterClass) { + throw new Error('Charakterklasse nicht verfügbar') + } + + try { + const params = { + class: this.characterClass + } + + // Add stand if available + if (this.sessionData.stand) { + params.stand = this.sessionData.stand + } + + const response = await API.get('/api/characters/classes/learning-points', { params }) + this.learningPointsData = response.data + + // Initialize spell points from learning points data + const spellPoints = this.learningPointsData.spell_points || 0 + this.spellPoints = { + total: spellPoints, + remaining: spellPoints + } + + } catch (error) { + console.error('Error loading learning points:', error) + // Set default spell points if API fails + this.spellPoints = { + total: 10, // Default fallback + remaining: 10 + } + } + }, + + async loadAvailableSpells() { + if (!this.characterClass) { + throw new Error('Charakterklasse nicht verfügbar') + } + + this.isLoadingSpells = true try { const token = localStorage.getItem('token') const request = { - characterClass: this.sessionData.typ || 'Abenteurer', - characterId: '0', // Dummy for new character + characterClass: this.characterClass } - const response = await API.post('/api/characters/available-spells-new', request, { + const response = await API.post('/api/characters/available-spells-creation', request, { headers: { Authorization: `Bearer ${token}` }, }) - this.availableSpells = response.data.spells || [] + this.availableSpellsByCategory = response.data.spells_by_category || {} + this.processSpellCategories() + } catch (error) { console.error('Error loading spells:', error) - // Fallback dummy data - this.availableSpells = [ - { name: 'Licht', cost: 50, description: 'Erzeugt ein helles Licht' }, - { name: 'Magisches Geschoss', cost: 60, description: 'Feuert ein magisches Projektil ab' }, - { name: 'Schildzauber', cost: 70, description: 'Erzeugt einen magischen Schutzschild' }, - { name: 'Heilung', cost: 80, description: 'Heilt leichte Wunden' }, - { name: 'Unsichtbarkeit', cost: 120, description: 'Macht den Zauberer unsichtbar' }, + this.generateSampleSpells() + } finally { + this.isLoadingSpells = false + } + }, + + processSpellCategories() { + // Convert spell categories from the backend response + this.spellCategories = Object.entries(this.availableSpellsByCategory || {}).map(([categoryKey, spells]) => ({ + name: categoryKey.toLowerCase(), + displayName: categoryKey, + spellCount: spells.length + })) + + // Sort categories by name + this.spellCategories.sort((a, b) => a.displayName.localeCompare(b.displayName)) + }, + + generateSampleSpells() { + // Fallback for testing + this.availableSpellsByCategory = { + 'Dweomer': [ + { name: 'Licht', level: 1, school: 'Dweomer', le_cost: 1, description: 'Erzeugt ein helles Licht' }, + { name: 'Zauberschutz', level: 1, school: 'Dweomer', le_cost: 1, description: 'Schutz vor Magie' } + ], + 'Erkennen': [ + { name: 'Zaubersicht', level: 1, school: 'Erkennen', le_cost: 1, description: 'Erkennt magische Auren' }, + { name: 'Gedankenlesen', level: 2, school: 'Erkennen', le_cost: 2, description: 'Liest Oberflächengedanken' } + ], + 'Verändern': [ + { name: 'Heilen von Wunden', level: 1, school: 'Verändern', le_cost: 1, description: 'Heilt leichte Verletzungen' }, + { name: 'Stärke', level: 2, school: 'Verändern', le_cost: 2, description: 'Erhöht die körperliche Stärke' } ] } + + this.processSpellCategories() + console.log('Generated sample spells:', this.availableSpellsByCategory) }, - loadSelectedSpells() { - // Load from session data if available - if (this.sessionData.spells) { + restoreSelectedSpells() { + if (this.sessionData.spells && Array.isArray(this.sessionData.spells)) { this.selectedSpells = [...this.sessionData.spells] - this.updateSpellPoints() } }, - getZauberCategory() { - return this.skillCategories.find(c => c.name === 'zauber') + async selectCategory(categoryName) { + this.selectedCategory = categoryName + // Spells are now provided by computed property availableSpellsForSelectedCategory }, - canAddSpell(spell) { - const category = this.getZauberCategory() - const alreadySelected = this.isSpellSelected(spell) + getSelectedCategoryName() { + const category = this.spellCategories.find(cat => cat.name === this.selectedCategory) + return category ? category.displayName : this.selectedCategory + }, + + findCategoryKey(selectedCategoryName) { + if (!this.availableSpellsByCategory) return null - return category && category.points >= spell.cost && !alreadySelected + // Try to find by spell category mapping first (most likely scenario) + const spellCategory = this.spellCategories?.find(cat => cat.name === selectedCategoryName) + if (spellCategory && this.availableSpellsByCategory[spellCategory.displayName]) { + return spellCategory.displayName + } + + // Try direct match + if (this.availableSpellsByCategory[selectedCategoryName]) { + return selectedCategoryName + } + + // Try case-insensitive search + const availableKeys = Object.keys(this.availableSpellsByCategory) + const foundKey = availableKeys.find(key => + key.toLowerCase() === selectedCategoryName.toLowerCase() + ) + + return foundKey || null }, - + + getSpellCost(spell) { + // Unified method to get spell cost from various possible properties + return spell.cost || spell.le_cost || spell.leCost || 0 + }, + + canAffordSpellInCategory(spell) { + // Check if already selected + if (this.isSpellSelected(spell)) { + return false + } + + // Check if enough spell points remaining + const spellCost = this.getSpellCost(spell) + return this.spellPoints.remaining >= spellCost + }, + isSpellSelected(spell) { return this.selectedSpells.some(s => s.name === spell.name) }, - - addSpell(spell) { - if (this.canAddSpell(spell)) { - this.selectedSpells.push({ ...spell }) - this.updateSpellPoints() - this.saveData() - } - }, - - removeSpell(spell) { - const index = this.selectedSpells.findIndex(s => s.name === spell.name) - if (index >= 0) { - this.selectedSpells.splice(index, 1) - this.updateSpellPoints() - this.saveData() - } - }, - + updateSpellPoints() { - const zauberCategory = this.getZauberCategory() - if (zauberCategory) { - // Reset to max points - zauberCategory.points = zauberCategory.max_points - - // Deduct points for selected spells - this.selectedSpells.forEach(spell => { - zauberCategory.points -= spell.cost - }) - } + // Reset to total points + this.spellPoints.remaining = this.spellPoints.total + + // Deduct points for selected spells + this.selectedSpells.forEach(spell => { + this.spellPoints.remaining -= this.getSpellCost(spell) + }) + + // Ensure remaining points don't go below 0 + this.spellPoints.remaining = Math.max(0, this.spellPoints.remaining) }, - - saveData() { - const data = { - spells: this.selectedSpells, - spell_points: { - zauber: this.getZauberCategory()?.points || 0 - } + + restoreSelectedSpells() { + if (this.sessionData.spells && Array.isArray(this.sessionData.spells)) { + this.selectedSpells = [...this.sessionData.spells] } - this.$emit('save', data) + // Also restore spell points if saved + if (this.sessionData.spell_points) { + this.spellPoints.remaining = this.sessionData.spell_points.remaining || this.spellPoints.total + } + }, + + selectSpellForLearning(spell) { + if (this.isSpellSelected(spell)) { + return + } + + // Check if the spell can be afforded + if (!this.canAffordSpellInCategory(spell)) { + return + } + + // Add spell to selected list with proper cost and category + const spellToAdd = { + ...spell, + cost: this.getSpellCost(spell) + } + + this.selectedSpells.push(spellToAdd) + + // Update spell points + this.updateSpellPoints() + + console.log('Spell selected for learning:', spell.name, 'Cost:', spellToAdd.cost) + }, + + removeSpellFromSelection(spell) { + const index = this.selectedSpells.findIndex(s => s.name === spell.name) + if (index !== -1) { + this.selectedSpells.splice(index, 1) + + // Update spell points + this.updateSpellPoints() + + console.log('Spell removed from selection:', spell.name) + } + }, + + async retry() { + await this.initializeComponent() }, handlePrevious() { - this.saveData() this.$emit('previous') }, @@ -204,218 +620,382 @@ export default { const data = { spells: this.selectedSpells, spell_points: { - zauber: this.getZauberCategory()?.points || 0 + total: this.spellPoints.total, + remaining: this.spellPoints.remaining + }, + spells_meta: { + selectedCategory: this.selectedCategory, + totalLE: this.totalSelectedLE } } + console.log('Finalizing with data:', data) this.$emit('finalize', data) - }, - }, + } + } }