From c4416008b1b7731da8795a3f5c2108a7789d5f0c Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 30 Jul 2025 13:23:35 +0200 Subject: [PATCH] Old PP used | New PP used added to test --- backend/character/lerncost_comparison_test.go | 8 +- backend/character/lerncost_handler.go | 255 ++++++++++++------ backend/models/model_gsmaster.go | 2 +- backend/models/model_learning_costs.go | 19 +- 4 files changed, 199 insertions(+), 85 deletions(-) diff --git a/backend/character/lerncost_comparison_test.go b/backend/character/lerncost_comparison_test.go index 365bc7b..5099e44 100644 --- a/backend/character/lerncost_comparison_test.go +++ b/backend/character/lerncost_comparison_test.go @@ -249,8 +249,8 @@ func TestCompareOldVsNewLearningCostSystems(t *testing.T) { // Create comparison table fmt.Printf("\n=== Detailed Cost Comparison ===\n") - fmt.Printf("Level | Old EP | New EP | Old Gold | New Gold | Old LE | New LE | Old PP | New PP | Old Gold used | New Gold used | Match?\n") - fmt.Printf("------|--------|--------|----------|----------|--------|--------|--------|--------|---------------|---------------|-------\n") + fmt.Printf("Level | Old EP | New EP | Old Gold | New Gold | Old LE | New LE | Old PP used | New PP used | Old Gold used | New Gold used | Match?\n") + fmt.Printf("------|--------|--------|----------|----------|--------|--------|-------------|-------------|---------------|---------------|-------\n") // Compare each level that exists in both systems maxLevels := len(oldResponse) @@ -290,7 +290,7 @@ func TestCompareOldVsNewLearningCostSystems(t *testing.T) { matchSymbol = "✗" } - fmt.Printf("%5d | %6d | %6d | %8d | %8d | %6d | %6d | %6d | %6d | %13d | %13d | %7s\n", + fmt.Printf("%5d | %6d | %6d | %8d | %8d | %6d | %6d | %11d | %11d | %13d | %13d | %7s\n", old.TargetLevel, old.EP, new.EP, old.GoldCost, new.GoldCost, old.LE, new.LE, old.PPUsed, new.PPUsed, old.GoldUsed, new.GoldUsed, matchSymbol) @@ -308,7 +308,7 @@ func TestCompareOldVsNewLearningCostSystems(t *testing.T) { old.TargetLevel, old.LE, new.LE, old.LE-new.LE) } if !ppMatch { - fmt.Printf(" PPUsed mismatch at level %d: Old=%d, New=%d (diff=%d)\n", + fmt.Printf(" PP used mismatch at level %d: Old=%d, New=%d (diff=%d)\n", old.TargetLevel, old.PPUsed, new.PPUsed, old.PPUsed-new.PPUsed) } if !goldUsedMatch { diff --git a/backend/character/lerncost_handler.go b/backend/character/lerncost_handler.go index 292dcfa..f1d0b59 100644 --- a/backend/character/lerncost_handler.go +++ b/backend/character/lerncost_handler.go @@ -185,40 +185,73 @@ func GetLernCostNewSystem(c *gin.Context) { characterClass = character.Typ } - // Normalize skill name (trim whitespace, proper case) + // Normalize skill/spell name (trim whitespace, proper case) skillName := strings.TrimSpace(request.Name) - // Hole die beste Kategorie und Schwierigkeit für diese Fertigkeit und Klasse aus der neuen Datenbank - skillInfo, err := models.GetSkillCategoryAndDifficulty(skillName, characterClass) - if err != nil { - respondWithError(c, http.StatusBadRequest, fmt.Sprintf("Fertigkeit '%s' nicht gefunden oder nicht für Klasse '%s' verfügbar: %v", skillName, characterClass, err)) - return - } - var response []gsmaster.SkillCostResultNew remainingPP := request.UsePP remainingGold := request.UseGold // Für "learn" Aktion: nur eine Berechnung, da Lernkosten einmalig sind if request.Action == "learn" { - levelResult := gsmaster.SkillCostResultNew{ - CharacterID: charID, - CharacterClass: characterClass, - SkillName: skillName, - Category: skillInfo.CategoryName, - Difficulty: skillInfo.DifficultyName, - TargetLevel: 1, // Lernkosten sind für das Erlernen der Fertigkeit (Level 1) - } + if request.Type == "spell" { + // Spell learning logic + spellInfo, err := models.GetSpellLearningInfo(skillName, characterClass) + if err != nil { + respondWithError(c, http.StatusBadRequest, fmt.Sprintf("Zauber '%s' nicht gefunden oder nicht für Klasse '%s' verfügbar: %v", skillName, characterClass, err)) + return + } - err = calculateSkillLearnCostNewSystem(&request, &levelResult, &remainingPP, &remainingGold, skillInfo) + levelResult := gsmaster.SkillCostResultNew{ + CharacterID: charID, + CharacterClass: characterClass, + SkillName: skillName, + Category: spellInfo.SchoolName, + Difficulty: fmt.Sprintf("Stufe %d", spellInfo.SpellLevel), + TargetLevel: 1, // Lernkosten sind für das Erlernen des Zaubers (Level 1) + } + + err = calculateSpellLearnCostNewSystem(&request, &levelResult, &remainingPP, &remainingGold, spellInfo) + if err != nil { + respondWithError(c, http.StatusBadRequest, "Fehler bei der Kostenberechnung: "+err.Error()) + return + } + + response = append(response, levelResult) + } else { + // Skill learning logic + skillInfo, err := models.GetSkillCategoryAndDifficulty(skillName, characterClass) + if err != nil { + respondWithError(c, http.StatusBadRequest, fmt.Sprintf("Fertigkeit '%s' nicht gefunden oder nicht für Klasse '%s' verfügbar: %v", skillName, characterClass, err)) + return + } + + levelResult := gsmaster.SkillCostResultNew{ + CharacterID: charID, + CharacterClass: characterClass, + SkillName: skillName, + Category: skillInfo.CategoryName, + Difficulty: skillInfo.DifficultyName, + TargetLevel: 1, // Lernkosten sind für das Erlernen der Fertigkeit (Level 1) + } + + err = calculateSkillLearnCostNewSystem(&request, &levelResult, &remainingPP, &remainingGold, skillInfo) + if err != nil { + respondWithError(c, http.StatusBadRequest, "Fehler bei der Kostenberechnung: "+err.Error()) + return + } + + response = append(response, levelResult) + } + } else { + // Für "improve" Aktion: berechne für jedes Level von current+1 bis 18 + // Improvement only works on skills, not spells + skillInfo, err := models.GetSkillCategoryAndDifficulty(skillName, characterClass) if err != nil { - respondWithError(c, http.StatusBadRequest, "Fehler bei der Kostenberechnung: "+err.Error()) + respondWithError(c, http.StatusBadRequest, fmt.Sprintf("Fertigkeit '%s' nicht gefunden oder nicht für Klasse '%s' verfügbar: %v", skillName, characterClass, err)) return } - response = append(response, levelResult) - } else { - // Für "improve" Aktion: berechne für jedes Level von current+1 bis 18 for i := request.CurrentLevel + 1; i <= 18; i++ { levelResult := gsmaster.SkillCostResultNew{ CharacterID: charID, @@ -229,12 +262,26 @@ func GetLernCostNewSystem(c *gin.Context) { TargetLevel: i, } - err = calculateSkillImproveCostNewSystem(&request, &levelResult, i, &remainingPP, &remainingGold, skillInfo) + err := calculateSkillImproveCostNewSystem(&request, &levelResult, i, &remainingPP, &remainingGold, skillInfo) if err != nil { respondWithError(c, http.StatusBadRequest, "Fehler bei der Kostenberechnung: "+err.Error()) return } - + // für die nächste Runde die PP und Gold reduzieren die zum Lernen genutzt werden sollen + if levelResult.PPUsed > 0 { + request.UsePP -= levelResult.PPUsed + // Sicherstellen, dass PP nicht unter 0 fallen + if request.UsePP < 0 { + request.UsePP = 0 + } + } + if levelResult.GoldUsed > 0 { + request.UseGold -= levelResult.GoldUsed + // Sicherstellen, dass Gold nicht unter 0 fällt + if request.UseGold < 0 { + request.UseGold = 0 + } + } response = append(response, levelResult) } } @@ -317,7 +364,7 @@ func calculateSkillImproveCostNewSystem(request *gsmaster.LernCostRequest, resul return nil } -// calculateSkillLearnCostNewSystem berechnet die Kosten für das Erlernen einer Fertigkeit (Action: "learn") +// calculateSkillLearnCostNewSystem berechnet die Kosten für das Erlernen einer Fertigkeit (Action: "learn", Type: "skill") func calculateSkillLearnCostNewSystem(request *gsmaster.LernCostRequest, result *gsmaster.SkillCostResultNew, remainingPP *int, remainingGold *int, skillInfo *models.SkillLearningInfo) error { // 1. Hole die EP-Kosten pro TE für diese Klasse und Kategorie epPerTE, err := models.GetEPPerTEForClassAndCategory(result.CharacterClass, skillInfo.CategoryName) @@ -338,58 +385,8 @@ func calculateSkillLearnCostNewSystem(request *gsmaster.LernCostRequest, result applyRewardNewSystem(result, request.Reward, result.EP) } - // 5. PP und Gold-für-EP Konvertierung beim Lernen - if request.Type == "spell" { - // PP-Anwendung für Zauber-Lernen: 1 PP = 1 LE Reduktion - ppUsed := 0 - if *remainingPP > 0 { - if result.LE <= *remainingPP { - ppUsed = result.LE // Maximal so viele PP verwenden wie LE benötigt werden - result.LE = 0 // Wenn PP alle LE abdecken - } else { - ppUsed = *remainingPP // Verwende alle verfügbaren PP - result.LE -= ppUsed // Reduziere LE um verwendete PP - } - - result.PPUsed = ppUsed - *remainingPP -= ppUsed - - if *remainingPP < 0 { - *remainingPP = 0 - } - - // EP neu berechnen basierend auf reduzierter LE - result.EP = epPerTE * result.LE * 3 - result.GoldCost = result.LE * 200 - } - - // Gold-für-EP Konvertierung für Zauber-Lernen - goldUsed := 0 - if *remainingGold > 0 { - // 10 Gold = 1 EP, aber maximal EP/2 kann durch Gold ersetzt werden - maxEPFromGold := result.EP / 2 - epFromGold := *remainingGold / 10 - - if epFromGold > maxEPFromGold { - // Beschränke auf maximal EP/2 - epFromGold = maxEPFromGold - goldUsed = epFromGold * 10 - } else { - // Verwende das verfügbare Gold - goldUsed = *remainingGold - } - - // Reduziere EP um die durch Gold ersetzte Menge - result.EP -= epFromGold - result.GoldUsed = goldUsed - *remainingGold -= goldUsed - - if *remainingGold < 0 { - *remainingGold = 0 - } - } - } - // Für Skill-Lernen: Keine PP oder Gold-für-EP Anwendung erlaubt + // 5. Für Skill-Lernen: Keine PP oder Gold-für-EP Anwendung erlaubt (im Gegensatz zu Spell-Lernen) + // PP und Gold bleiben unverändert, da sie bei Skill-Lernen nicht verwendet werden return nil } @@ -424,6 +421,112 @@ func applyRewardNewSystem(result *gsmaster.SkillCostResultNew, reward *string, o } } +// calculateSpellLearnCostNewSystem berechnet die Kosten für das Erlernen eines Zaubers (Action: "learn", Type: "spell") +func calculateSpellLearnCostNewSystem(request *gsmaster.LernCostRequest, result *gsmaster.SkillCostResultNew, remainingPP *int, remainingGold *int, spellInfo *models.SpellLearningInfo) error { + // 1. Setze die grundlegenden Zauber-Informationen + result.Category = spellInfo.SchoolName + result.Difficulty = fmt.Sprintf("Stufe %d", spellInfo.SpellLevel) + + // 2. Berechne die LE-Kosten basierend auf der Zaubergrad + leRequired := spellInfo.LERequired + + // 3. Anwenden von PP (Practice Points): 1 PP = 1 LE Reduktion (bei Zauber-Lernen erlaubt) + ppUsed := 0 + if *remainingPP > 0 { + if leRequired <= *remainingPP { + ppUsed = leRequired // Maximal so viele PP verwenden wie LE benötigt werden + leRequired = 0 // Wenn PP alle LE abdecken + } else { + ppUsed = *remainingPP // Verwende alle verfügbaren PP + leRequired -= ppUsed // Reduziere LE um verwendete PP + } + + result.PPUsed = ppUsed + *remainingPP -= ppUsed + + if *remainingPP < 0 { + *remainingPP = 0 + } + } + + // 4. Setze die finalen LE-Kosten + result.LE = leRequired + + // 5. Berechne EP-Kosten basierend auf LE und EP-pro-LE für diese Klasse/Schule + result.EP = result.LE * spellInfo.EPPerLE + + // 6. Berechne Gold-Kosten (Beispiel: 100 Gold pro LE wie im alten System) + result.GoldCost = result.LE * 100 + + // 7. Anwenden von Belohnungen (spruchrolle, halveep, etc.) + if request.Reward != nil { + applySpellRewardNewSystem(result, request.Reward) + } + + // 8. Gold-für-EP Konvertierung für Zauber-Lernen (erlaubt) + goldUsed := 0 + if *remainingGold > 0 { + // 10 Gold = 1 EP, aber maximal EP/2 kann durch Gold ersetzt werden + maxEPFromGold := result.EP / 2 + epFromGold := *remainingGold / 10 + + if epFromGold > maxEPFromGold { + // Beschränke auf maximal EP/2 + epFromGold = maxEPFromGold + goldUsed = epFromGold * 10 + } else { + // Verwende das verfügbare Gold + goldUsed = *remainingGold + } + + // Reduziere EP um die durch Gold ersetzte Menge + result.EP -= epFromGold + result.GoldUsed = goldUsed + *remainingGold -= goldUsed + + if *remainingGold < 0 { + *remainingGold = 0 + } + } + + return nil +} + +// applySpellRewardNewSystem wendet zauber-spezifische Belohnungen an +func applySpellRewardNewSystem(result *gsmaster.SkillCostResultNew, reward *string) { + if reward == nil || *reward == "" { + return + } + + switch *reward { + case "spruchrolle": + // Spruchrolle: 20 Gold für jeden Versuch und 1/3 EP-Kosten bei Erfolg + result.GoldCost = 20 + result.EP = result.EP / 3 + + case "halveep": + // Halbe EP für Zauber-Lernen + result.EP = result.EP / 2 + + case "halveepnoGold": + // Halbe EP und kein Gold + result.EP = result.EP / 2 + result.GoldCost = 0 + + case "noGold": + // Nur Geld ist 0, EP bleiben + result.GoldCost = 0 + + case "default": + // Keine Änderungen + break + + default: + // Unbekannte Belohnung - ignorieren + break + } +} + // GetSkillCost berechnet die Kosten zum Erlernen oder Verbessern einer Fertigkeit func GetSkillCost(c *gin.Context) { // Charakter-ID aus der URL abrufen diff --git a/backend/models/model_gsmaster.go b/backend/models/model_gsmaster.go index be8222d..05b7dd1 100644 --- a/backend/models/model_gsmaster.go +++ b/backend/models/model_gsmaster.go @@ -80,7 +80,7 @@ type Spell struct { Wirkungsbereich string `json:"wirkungsbereich"` Wirkungsdauer string `json:"wirkungsdauer"` Ursprung string `json:"ursprung"` - Category string `gorm:"default:normal" json:"category"` + Category string `gorm:"default:normal" json:"category"` // spell_school } type Equipment struct { diff --git a/backend/models/model_learning_costs.go b/backend/models/model_learning_costs.go index 07e09f8..c0eacf3 100644 --- a/backend/models/model_learning_costs.go +++ b/backend/models/model_learning_costs.go @@ -2,6 +2,7 @@ package models import ( "bamort/database" + "fmt" "gorm.io/gorm" ) @@ -357,14 +358,14 @@ func GetSpellLearningInfo(spellName string, classCode string) (*SpellLearningInf s.id as spell_id, s.name as spell_name, s.stufe as spell_level, - s.spell_school as school_name, + s.category as school_name, cssec.character_class as class_code, cssec.character_class as class_name, cssec.ep_per_le, - sllc.le_required + COALESCE(sllc.le_required, 0) as le_required FROM gsm_spells s - JOIN learning_class_spell_school_ep_costs cssec ON s.spell_school = cssec.spell_school - JOIN learning_spell_level_le_costs sllc ON s.stufe = sllc.level + JOIN learning_class_spell_school_ep_costs cssec ON s.category = cssec.spell_school + LEFT JOIN learning_spell_level_le_costs sllc ON s.stufe = sllc.level WHERE s.name = ? AND cssec.character_class = ? `, spellName, classCode).Scan(&result).Error @@ -372,6 +373,16 @@ func GetSpellLearningInfo(spellName string, classCode string) (*SpellLearningInf return nil, err } + // Validate spell level - level 0 is not a valid spell level + if result.SpellLevel <= 0 { + return nil, fmt.Errorf("invalid spell level %d for spell '%s' - spell levels must be 1 or higher", result.SpellLevel, spellName) + } + + // Validate that we found a spell (spell_id should be > 0) + if result.SpellID == 0 { + return nil, gorm.ErrRecordNotFound + } + return &result, nil }