diff --git a/backend/character/handlers.go b/backend/character/handlers.go index d1bf102..7fb4361 100644 --- a/backend/character/handlers.go +++ b/backend/character/handlers.go @@ -182,83 +182,6 @@ func splitSkills(object []models.SkFertigkeit) ([]models.SkFertigkeit, []models. return normSkills, innateSkills, categories } -// GetLearnSkillCostOld is deprecated. Use GetLearnSkillCost instead. -// This function uses the old hardcoded learning cost system. -func GetLearnSkillCostOld(c *gin.Context) { - // Get the character ID from the request - charID := c.Param("id") - - // Load the character from the database - var character models.Char - if err := character.FirstID(charID); err != nil { - respondWithError(c, http.StatusInternalServerError, "Failed to retrieve character") - return - } - - // Load the skill from the request - var s models.SkFertigkeit - if err := c.ShouldBindJSON(&s); err != nil { - respondWithError(c, http.StatusBadRequest, err.Error()) - return - } - - var skill models.Skill - if err := skill.First(s.Name); err != nil { - respondWithError(c, http.StatusBadRequest, "can not find speel in gsmaster: "+err.Error()) - return - } - - cost, err := gsmaster.CalculateSkillLearnCostOld(skill.Name, character.Typ) - if err != nil { - respondWithError(c, http.StatusBadRequest, "error getting costs to learn skill: "+err.Error()) - return - } - - // Return the updated character - c.JSON(http.StatusOK, cost) -} - -// GetLearnSpellCostOld is deprecated. Use GetLearnSpellCost instead. -// This function uses the old hardcoded learning cost system. -func GetLearnSpellCostOld(c *gin.Context) { - // Get the character ID from the request - charID := c.Param("id") - - // Load the character from the database - var character models.Char - if err := character.FirstID(charID); err != nil { - respondWithError(c, http.StatusInternalServerError, "Failed to retrieve character") - return - } - - // Load the spell from the request - var s models.SkZauber - if err := c.ShouldBindJSON(&s); err != nil { - respondWithError(c, http.StatusBadRequest, err.Error()) - return - } - var spell models.Spell - if err := spell.First(s.Name); err != nil { - respondWithError(c, http.StatusBadRequest, "can not find speel in gsmaster: "+err.Error()) - return - } - sd := gsmaster.SpellDefinition{ - Name: spell.Name, - Stufe: spell.Stufe, - School: spell.Category, - } - - cost, err := gsmaster.CalculateSpellLearnCostOld(spell.Name, character.Typ) - if err != nil { - respondWithError(c, http.StatusBadRequest, "error getting costs to learn spell: "+err.Error()) - return - } - - sd.CostEP = cost - // Return the updated character - c.JSON(http.StatusOK, sd) -} - // ExperienceAndWealthResponse repräsentiert die Antwort für EP und Vermögen type ExperienceAndWealthResponse struct { ExperiencePoints int `json:"experience_points"` @@ -605,317 +528,16 @@ type LearnSpellRequest struct { Notes string `json:"notes,omitempty"` } -// calculateMultiLevelCostsOld is deprecated. Use the new database-based learning cost system instead. -// This function uses the old hardcoded learning cost system. -// calculateMultiLevelCostsOld berechnet die Kosten für mehrere Level-Verbesserungen mit gsmaster.GetLernCostNextLevel -func calculateMultiLevelCostsOld(character *models.Char, skillName string, currentLevel int, levelsToLearn []int, rewardType string, usePP, useGold int) (*models.LearnCost, error) { - if len(levelsToLearn) == 0 { - return nil, fmt.Errorf("keine Level zum Lernen angegeben") - } - - // Sortiere die Level aufsteigend - sortedLevels := make([]int, len(levelsToLearn)) - copy(sortedLevels, levelsToLearn) - for i := 0; i < len(sortedLevels)-1; i++ { - for j := i + 1; j < len(sortedLevels); j++ { - if sortedLevels[i] > sortedLevels[j] { - sortedLevels[i], sortedLevels[j] = sortedLevels[j], sortedLevels[i] - } - } - } - - // Erstelle LernCostRequest - var rewardTypePtr *string - if rewardType != "" { - rewardTypePtr = &rewardType - } - - request := gsmaster.LernCostRequest{ - CharId: uint(character.ID), - Name: skillName, - CurrentLevel: currentLevel, - Type: "skill", - Action: "improve", - TargetLevel: sortedLevels[len(sortedLevels)-1], // Höchstes Level als Ziel - UsePP: usePP, - UseGold: useGold, - Reward: rewardTypePtr, - } - - totalCost := &models.LearnCost{ - Stufe: sortedLevels[len(sortedLevels)-1], - LE: 0, - Ep: 0, - Money: 0, - } - - remainingPP := usePP - remainingGold := useGold - - // Berechne Kosten für jedes Level - for _, targetLevel := range sortedLevels { - classAbr := getCharacterClassOld(character) - cat, difficulty, _ := gsmaster.FindBestCategoryForSkillLearningOld(skillName, classAbr) - levelResult := gsmaster.SkillCostResultNew{ - CharacterID: fmt.Sprintf("%d", character.ID), - CharacterClass: classAbr, - SkillName: skillName, - Category: cat, - Difficulty: gsmaster.GetSkillDifficultyOld(difficulty, skillName), - TargetLevel: targetLevel, - } - - // Temporäre Request für dieses Level - tempRequest := request - tempRequest.CurrentLevel = targetLevel - 1 - tempRequest.UsePP = remainingPP - tempRequest.UseGold = remainingGold - - err := gsmaster.GetLernCostNextLevelOld(&tempRequest, &levelResult, rewardTypePtr, targetLevel, character.Typ) - if err != nil { - return nil, fmt.Errorf("fehler bei Level %d: %v", targetLevel, err) - } - - // Aktualisiere verbleibende Ressourcen - if levelResult.PPUsed > 0 { - remainingPP -= levelResult.PPUsed - if remainingPP < 0 { - remainingPP = 0 - } - } - if levelResult.GoldUsed > 0 { - remainingGold -= levelResult.GoldUsed - if remainingGold < 0 { - remainingGold = 0 - } - } - - totalCost.Ep += levelResult.EP - totalCost.Money += levelResult.GoldCost - totalCost.LE += levelResult.LE - } - - return totalCost, nil -} - -// getCharacterClassOld is deprecated. Use character.Klasse directly or appropriate database lookups. +// getCharacterClass is deprecated. Use character.Klasse directly or appropriate database lookups. // This function provides backwards compatibility for character class access. -// getCharacterClassOld gibt die Charakterklassen-Abkürzung zurück -func getCharacterClassOld(character *models.Char) string { +// getCharacterClass gibt die Charakterklassen-Abkürzung zurück +func getCharacterClass(character *models.Char) string { if len(character.Typ) > 3 { - return gsmaster.GetClassAbbreviationOld(character.Typ) + return gsmaster.GetClassAbbreviationNewSystem(character.Typ) } return character.Typ } -// LearnSkillOld is deprecated. Use LearnSkill instead. -// This function uses the old hardcoded learning cost system. -// LearnSkillOld lernt eine neue Fertigkeit und erstellt Audit-Log-Einträge -func LearnSkillOld(c *gin.Context) { - charID := c.Param("id") - var character models.Char - - if err := character.FirstID(charID); err != nil { - respondWithError(c, http.StatusNotFound, "Charakter nicht gefunden") - return - } - - // Verwende gsmaster.LernCostRequest direkt - var request gsmaster.LernCostRequest - if err := c.ShouldBindJSON(&request); err != nil { - respondWithError(c, http.StatusBadRequest, "Ungültige Anfrageparameter: "+err.Error()) - return - } - - // Setze Charakter-ID und Action für learning - request.CharId = character.ID - request.Action = "learn" - if request.Type == "" { - request.Type = "skill" // Default zu skill für Learning - } - // Verwende Klassenabkürzung wenn der Typ länger als 3 Zeichen ist - var characterClass string - if len(character.Typ) > 3 { - characterClass = gsmaster.GetClassAbbreviationOld(character.Typ) - } else { - characterClass = character.Typ - } - - // Bestimme das finale Level - finalLevel := request.TargetLevel - if finalLevel <= 0 { - finalLevel = 1 // Standard für neue Fertigkeit - } - - // Für Learning müssen wir von Level 0 (nicht gelernt) auf finalLevel lernen - var totalEP, totalGold, totalPP int - var err error - - // Loop für jeden Level von 0 bis finalLevel (für neue Fertigkeiten) - for tempLevel := 0; tempLevel < finalLevel; tempLevel++ { - nextLevel := tempLevel + 1 - - // Erstelle temporären Request für diesen Level - tempRequest := request - tempRequest.CurrentLevel = tempLevel - tempRequest.TargetLevel = nextLevel - - // Für das erste Level (0->1) ist es ein "learn", für weitere Level "improve" - if tempLevel == 0 { - tempRequest.Action = "learn" - } else { - tempRequest.Action = "improve" - } - - // Berechne Kosten für diesen einen Level - var costResult gsmaster.SkillCostResultNew - costResult.CharacterID = fmt.Sprintf("%d", character.ID) - costResult.CharacterClass = characterClass - costResult.SkillName = request.Name - - err = gsmaster.GetLernCostNextLevelOld(&tempRequest, &costResult, request.Reward, nextLevel, character.Rasse) - if err != nil { - respondWithError(c, http.StatusBadRequest, fmt.Sprintf("Fehler bei Level %d: %v", nextLevel, err)) - return - } - - // Addiere die Kosten - totalEP += costResult.EP - totalGold += costResult.GoldCost - totalPP += costResult.PPUsed - } - - // Prüfe, ob genügend EP vorhanden sind - currentEP := character.Erfahrungsschatz.EP - if currentEP < totalEP { - respondWithError(c, http.StatusBadRequest, "Nicht genügend Erfahrungspunkte vorhanden") - return - } - - // Prüfe, ob genügend Gold vorhanden ist - currentGold := character.Vermoegen.Goldstücke - if currentGold < totalGold { - respondWithError(c, http.StatusBadRequest, "Nicht genügend Gold vorhanden") - return - } - - // Prüfe, ob genügend PP vorhanden sind (PP der jeweiligen Fertigkeit) - für neue Fertigkeiten normalerweise 0 - currentPP := 0 - for _, skill := range character.Fertigkeiten { - if skill.Name == request.Name { - currentPP = skill.Pp - break - } - } - // Falls nicht in normalen Fertigkeiten gefunden, prüfe Waffenfertigkeiten - if currentPP == 0 { - for _, skill := range character.Waffenfertigkeiten { - if skill.Name == request.Name { - currentPP = skill.Pp - break - } - } - } - if totalPP > 0 && currentPP < totalPP { - respondWithError(c, http.StatusBadRequest, "Nicht genügend Praxispunkte vorhanden") - return - } - - // EP abziehen und Audit-Log erstellen - newEP := currentEP - totalEP - if totalEP > 0 { - var notes string - if finalLevel > 1 { - notes = fmt.Sprintf("Fertigkeit '%s' bis Level %d gelernt", request.Name, finalLevel) - } else { - notes = fmt.Sprintf("Fertigkeit '%s' gelernt", request.Name) - } - - err = CreateAuditLogEntry(character.ID, "experience_points", currentEP, newEP, ReasonSkillLearning, 0, notes) - if err != nil { - respondWithError(c, http.StatusInternalServerError, "Fehler beim Erstellen des Audit-Log-Eintrags") - return - } - character.Erfahrungsschatz.EP = newEP - if err := database.DB.Save(&character.Erfahrungsschatz).Error; err != nil { - respondWithError(c, http.StatusInternalServerError, "Fehler beim Speichern der Erfahrungspunkte") - return - } - } - - // Gold abziehen und Audit-Log erstellen - newGold := currentGold - totalGold - if totalGold > 0 { - notes := fmt.Sprintf("Gold für Fertigkeit '%s' ausgegeben", request.Name) - - err = CreateAuditLogEntry(character.ID, "gold", currentGold, newGold, ReasonSkillLearning, 0, notes) - if err != nil { - respondWithError(c, http.StatusInternalServerError, "Fehler beim Erstellen des Audit-Log-Eintrags") - return - } - character.Vermoegen.Goldstücke = newGold - if err := database.DB.Save(&character.Vermoegen).Error; err != nil { - respondWithError(c, http.StatusInternalServerError, "Fehler beim Speichern des Vermögens") - return - } - } - - // PP abziehen (falls vorhanden und erforderlich) - if totalPP > 0 { - // Suche die richtige Fertigkeit und ziehe PP ab - for i, skill := range character.Fertigkeiten { - if skill.Name == request.Name { - character.Fertigkeiten[i].Pp -= totalPP - break - } - } - // Falls nicht in normalen Fertigkeiten gefunden, prüfe Waffenfertigkeiten - for i, skill := range character.Waffenfertigkeiten { - if skill.Name == request.Name { - character.Waffenfertigkeiten[i].Pp -= totalPP - break - } - } - } - - // Erstelle die neue Fertigkeit mit dem finalen Level - if err := updateOrCreateSkill(&character, request.Name, finalLevel); err != nil { - respondWithError(c, http.StatusInternalServerError, "Fehler beim Hinzufügen der Fertigkeit: "+err.Error()) - return - } - - // Charakter speichern - if err := database.DB.Save(&character).Error; err != nil { - respondWithError(c, http.StatusInternalServerError, "Fehler beim Speichern des Charakters") - return - } - - // Response für Multi-Level oder Single-Level - response := gin.H{ - "message": "Fertigkeit erfolgreich gelernt", - "skill_name": request.Name, - "final_level": finalLevel, - "ep_cost": totalEP, - "gold_cost": totalGold, - "remaining_ep": newEP, - "remaining_gold": newGold, - } - - // Füge Multi-Level-spezifische Informationen hinzu - if finalLevel > 1 { - // Erstelle Array der gelernten Level für Kompatibilität - var levelsLearned []int - for i := 1; i <= finalLevel; i++ { - levelsLearned = append(levelsLearned, i) - } - response["levels_learned"] = levelsLearned - response["level_count"] = finalLevel - response["multi_level"] = true - } - - c.JSON(http.StatusOK, response) -} - // LearnSkill lernt eine neue Fertigkeit und erstellt Audit-Log-Einträge func LearnSkill(c *gin.Context) { charID := c.Param("id") @@ -1485,305 +1107,6 @@ func ImproveSkill(c *gin.Context) { c.JSON(http.StatusOK, responseData) } -// ImproveSkillOld is deprecated. Use ImproveSkill instead. -// This function uses the old hardcoded learning cost system. -// ImproveSkillOld verbessert eine bestehende Fertigkeit und erstellt Audit-Log-Einträge -func ImproveSkillOld(c *gin.Context) { - // Verwende gsmaster.LernCostRequest direkt - var request gsmaster.LernCostRequest - if err := c.ShouldBindJSON(&request); err != nil { - respondWithError(c, http.StatusBadRequest, "Ungültige Anfrageparameter: "+err.Error()) - return - } - - // Hole Charakter über die ID aus dem Request - var char models.Char - err := database.DB. - Preload("Fertigkeiten"). - Preload("Waffenfertigkeiten"). - Preload("Erfahrungsschatz"). - Preload("Vermoegen"). - First(&char, request.CharId).Error - if err != nil { - respondWithError(c, http.StatusNotFound, "Charakter nicht gefunden") - return - } - - // Verwende Klassenabkürzung wenn der Typ länger als 3 Zeichen ist - var characterClass string - if len(char.Typ) > 3 { - characterClass = gsmaster.GetClassAbbreviationOld(char.Typ) - } else { - characterClass = char.Typ - } - - // Aktuellen Level ermitteln, falls nicht angegeben - currentLevel := request.CurrentLevel - if currentLevel <= 0 { - currentLevel = getCurrentSkillLevel(&char, request.Name, "skill") - if currentLevel == -1 { - respondWithError(c, http.StatusBadRequest, "Fertigkeit nicht bei diesem Charakter vorhanden") - return - } - request.CurrentLevel = currentLevel - } - - // Bestimme das finale Level - finalLevel := request.TargetLevel - if finalLevel <= 0 { - finalLevel = currentLevel + 1 - } - - // Initialisiere Gesamtkosten - var totalEP, totalGold, totalPP int - - // Loop für jeden Level von currentLevel bis finalLevel - tempLevel := currentLevel - for tempLevel < finalLevel { - nextLevel := tempLevel + 1 - - // Erstelle temporären Request für diesen Level - tempRequest := request - tempRequest.CurrentLevel = tempLevel - tempRequest.TargetLevel = nextLevel - - // Berechne Kosten für diesen einen Level - var costResult gsmaster.SkillCostResultNew - costResult.CharacterID = fmt.Sprintf("%d", char.ID) - costResult.CharacterClass = characterClass - costResult.SkillName = request.Name - - err = gsmaster.GetLernCostNextLevelOld(&tempRequest, &costResult, request.Reward, nextLevel, char.Rasse) - if err != nil { - respondWithError(c, http.StatusBadRequest, fmt.Sprintf("Fehler bei Level %d: %v", nextLevel, err)) - return - } - - // Addiere die Kosten - totalEP += costResult.EP - totalGold += costResult.GoldCost - totalPP += costResult.PPUsed - - tempLevel++ - } - - // Prüfe, ob genügend EP vorhanden sind - currentEP := char.Erfahrungsschatz.EP - if currentEP < totalEP { - respondWithError(c, http.StatusBadRequest, "Nicht genügend Erfahrungspunkte vorhanden") - return - } - - // Prüfe, ob genügend Gold vorhanden ist - currentGold := char.Vermoegen.Goldstücke - if currentGold < totalGold { - respondWithError(c, http.StatusBadRequest, "Nicht genügend Gold vorhanden") - return - } - - // Prüfe, ob genügend PP vorhanden sind (PP der jeweiligen Fertigkeit) - currentPP := 0 - for _, skill := range char.Fertigkeiten { - if skill.Name == request.Name { - currentPP = skill.Pp - break - } - } - // Falls nicht in normalen Fertigkeiten gefunden, prüfe Waffenfertigkeiten - if currentPP == 0 { - for _, skill := range char.Waffenfertigkeiten { - if skill.Name == request.Name { - currentPP = skill.Pp - break - } - } - } - if totalPP > 0 && currentPP < totalPP { - respondWithError(c, http.StatusBadRequest, "Nicht genügend Praxispunkte vorhanden") - return - } - - // EP abziehen und Audit-Log erstellen - newEP := currentEP - totalEP - if totalEP > 0 { - // Erstelle Notiz für Multi-Level Improvement - levelCount := finalLevel - currentLevel - var notes string - if levelCount > 1 { - notes = fmt.Sprintf("Fertigkeit '%s' von %d auf %d verbessert (%d Level)", request.Name, currentLevel, finalLevel, levelCount) - } else { - notes = fmt.Sprintf("Fertigkeit '%s' von %d auf %d verbessert", request.Name, currentLevel, finalLevel) - } - - err = CreateAuditLogEntry(char.ID, "experience_points", currentEP, newEP, ReasonSkillImprovement, 0, notes) - if err != nil { - respondWithError(c, http.StatusInternalServerError, "Fehler beim Erstellen des Audit-Log-Eintrags") - return - } - char.Erfahrungsschatz.EP = newEP - if err := database.DB.Save(&char.Erfahrungsschatz).Error; err != nil { - respondWithError(c, http.StatusInternalServerError, "Fehler beim Speichern der Erfahrungspunkte") - return - } - } - - // Gold abziehen und Audit-Log erstellen - newGold := currentGold - totalGold - if totalGold > 0 { - notes := fmt.Sprintf("Gold für Verbesserung von '%s' ausgegeben", request.Name) - - err = CreateAuditLogEntry(char.ID, "gold", currentGold, newGold, ReasonSkillImprovement, 0, notes) - if err != nil { - respondWithError(c, http.StatusInternalServerError, "Fehler beim Erstellen des Audit-Log-Eintrags") - return - } - char.Vermoegen.Goldstücke = newGold - if err := database.DB.Save(&char.Vermoegen).Error; err != nil { - respondWithError(c, http.StatusInternalServerError, "Fehler beim Speichern des Vermögens") - return - } - } - - // PP abziehen wenn verwendet (PP der jeweiligen Fertigkeit) - if totalPP > 0 { - // Finde die richtige Fertigkeit und ziehe PP ab - for i := range char.Fertigkeiten { - if char.Fertigkeiten[i].Name == request.Name { - char.Fertigkeiten[i].Pp -= totalPP - if err := database.DB.Save(&char.Fertigkeiten[i]).Error; err != nil { - respondWithError(c, http.StatusInternalServerError, "Fehler beim Aktualisieren der Praxispunkte") - return - } - break - } - } - // Falls nicht in normalen Fertigkeiten gefunden, prüfe Waffenfertigkeiten - for i := range char.Waffenfertigkeiten { - if char.Waffenfertigkeiten[i].Name == request.Name { - char.Waffenfertigkeiten[i].Pp -= totalPP - if err := database.DB.Save(&char.Waffenfertigkeiten[i]).Error; err != nil { - respondWithError(c, http.StatusInternalServerError, "Fehler beim Aktualisieren der Praxispunkte") - return - } - break - } - } - } - - // Aktualisiere die Fertigkeit mit dem neuen Level - if err := updateOrCreateSkill(&char, request.Name, finalLevel); err != nil { - respondWithError(c, http.StatusInternalServerError, "Fehler beim Aktualisieren der Fertigkeit: "+err.Error()) - return - } - - // Charakter speichern - if err := database.DB.Save(&char).Error; err != nil { - respondWithError(c, http.StatusInternalServerError, "Fehler beim Speichern des Charakters") - return - } - - // Response für Multi-Level oder Single-Level - response := gin.H{ - "message": "Fertigkeit erfolgreich verbessert", - "skill_name": request.Name, - "from_level": currentLevel, - "to_level": finalLevel, - "ep_cost": totalEP, - "gold_cost": totalGold, - "remaining_ep": newEP, - "remaining_gold": newGold, - } - - // Füge Multi-Level-spezifische Informationen hinzu - levelCount := finalLevel - currentLevel - if levelCount > 1 { - // Erstelle Array der gelernten Level für Kompatibilität - var levelsLearned []int - for i := currentLevel + 1; i <= finalLevel; i++ { - levelsLearned = append(levelsLearned, i) - } - response["levels_learned"] = levelsLearned - response["level_count"] = levelCount - response["multi_level"] = true - } - - c.JSON(http.StatusOK, response) -} - -// LearnSpellOld is deprecated. Use LearnSpell instead. -// This function uses the old hardcoded learning cost system. -// LearnSpellOld lernt einen neuen Zauber und erstellt Audit-Log-Einträge -func LearnSpellOld(c *gin.Context) { - charID := c.Param("id") - var character models.Char - - if err := character.FirstID(charID); err != nil { - respondWithError(c, http.StatusNotFound, "Charakter nicht gefunden") - return - } - - var request LearnSpellRequest - if err := c.ShouldBindJSON(&request); err != nil { - respondWithError(c, http.StatusBadRequest, "Ungültige Anfrageparameter: "+err.Error()) - return - } - - // Berechne Kosten mit GetSkillCost - costRequest := SkillCostRequest{ - Name: request.Name, - Type: "spell", - Action: "learn", - } - - cost, _, _, err := calculateSingleCostOld(&character, &costRequest) - if err != nil { - respondWithError(c, http.StatusBadRequest, "Fehler bei der Kostenberechnung: "+err.Error()) - return - } - - // Prüfe, ob genügend EP vorhanden sind - currentEP := character.Erfahrungsschatz.EP - if currentEP < cost.Ep { - respondWithError(c, http.StatusBadRequest, "Nicht genügend Erfahrungspunkte vorhanden") - return - } - - // EP abziehen und Audit-Log erstellen - newEP := currentEP - cost.Ep - if cost.Ep > 0 { - notes := fmt.Sprintf("Zauber '%s' gelernt", request.Name) - if request.Notes != "" { - notes += " - " + request.Notes - } - - err = CreateAuditLogEntry(character.ID, "experience_points", currentEP, newEP, ReasonSpellLearning, 0, notes) - if err != nil { - respondWithError(c, http.StatusInternalServerError, "Fehler beim Erstellen des Audit-Log-Eintrags") - return - } - character.Erfahrungsschatz.EP = newEP - } - - // Füge den Zauber zum Charakter hinzu - if err := addSpellToCharacter(&character, request.Name); err != nil { - respondWithError(c, http.StatusInternalServerError, "Fehler beim Hinzufügen des Zaubers: "+err.Error()) - return - } - - // Charakter speichern - if err := database.DB.Save(&character).Error; err != nil { - respondWithError(c, http.StatusInternalServerError, "Fehler beim Speichern des Charakters") - return - } - - c.JSON(http.StatusOK, gin.H{ - "message": "Zauber erfolgreich gelernt", - "spell_name": request.Name, - "ep_cost": cost.Ep, - "remaining_ep": newEP, - }) -} - // validateSpellForLearning validiert Zauber-Namen für neue Zauber (Learning) func validateSpellForLearning(char *models.Char, request *gsmaster.LernCostRequest) (string, *models.SpellLearningInfo, int, error) { // Verwende Klassenabkürzung wenn der Typ länger als 3 Zeichen ist @@ -1946,10 +1269,10 @@ func LearnSpell(c *gin.Context) { c.JSON(http.StatusOK, responseData) } -// GetRewardTypesOld is deprecated. Use GetRewardTypes instead. +// GetRewardTypesStatic is deprecated. Use GetRewardTypes instead. // This function provides hardcoded reward type mappings. -// GetRewardTypesOld liefert verfügbare Belohnungsarten für ein bestimmtes Lernszenario -func GetRewardTypesOld(c *gin.Context) { +// GetRewardTypesStatic liefert verfügbare Belohnungsarten für ein bestimmtes Lernszenario +func GetRewardTypesStatic(c *gin.Context) { characterID := c.Param("id") learningType := c.Query("learning_type") // 'improve', 'learn', 'spell' skillName := c.Query("skill_name") @@ -2070,7 +1393,7 @@ func GetAvailableSkillsNewSystem(c *gin.Context) { if baseRequest.CharId != 0 { // Use existing character data characterID = fmt.Sprintf("%d", character.ID) - characterClass = getCharacterClassOld(&character) + characterClass = getCharacterClass(&character) } // For character creation, we don't have a character class yet, use empty string @@ -2581,7 +1904,13 @@ func GetAllSkillsWithLearningCosts(characterClass string) (map[string][]gin.H, e } // Try to get the best category and learning cost for this skill and character class - bestCategory, difficulty, err := gsmaster.FindBestCategoryForSkillLearningOld(skill.Name, characterClass) + + skillInfo, err := models.GetSkillCategoryAndDifficultyNewSystem(skill.Name, characterClass) + if err != nil { + return nil, err + } + bestCategory := skillInfo.CategoryName + difficulty := skillInfo.DifficultyName var learnCost int if err == nil && bestCategory != "" { @@ -2717,7 +2046,7 @@ func GetAvailableSpellsNewSystem(c *gin.Context) { return } - charakteClass := getCharacterClassOld(&character) + charakteClass := getCharacterClass(&character) // Hole alle verfügbaren Zauber aus der gsmaster Datenbank, aber filtere Placeholder aus var allSpells []models.Spell @@ -2849,120 +2178,6 @@ func GetSpellDetails(c *gin.Context) { }) } -// GetAvailableSkillsOld is deprecated. Use GetAvailableSkillsNewSystem instead. -// This function uses the old hardcoded learning cost system. -// GetAvailableSkillsOld gibt alle verfügbaren Fertigkeiten mit Lernkosten zurück -func GetAvailableSkillsOld(c *gin.Context) { - characterID := c.Param("id") - rewardType := c.Query("reward_type") - - var character models.Char - if err := database.DB.Preload("Fertigkeiten").Preload("Erfahrungsschatz").Preload("Vermoegen").First(&character, characterID).Error; err != nil { - respondWithError(c, http.StatusNotFound, "Character not found") - return - } - - // Hole alle verfügbaren Fertigkeiten aus der gsmaster Datenbank, aber filtere Placeholder aus - var allSkills []models.Skill - - allSkills, err := models.SelectSkills("", "") - if err != nil { - respondWithError(c, http.StatusInternalServerError, "Failed to retrieve skills from gsmaster") - return - } - /*if err := database.DB.Where("name != ?", "Placeholder").Find(&allSkills).Error; err != nil { - respondWithError(c, http.StatusInternalServerError, "Failed to retrieve skills") - return - } - */ - - // Erstelle eine Map der bereits gelernten Fertigkeiten - learnedSkills := make(map[string]bool) - for _, skill := range character.Fertigkeiten { - learnedSkills[skill.Name] = true - } - - // Organisiere Fertigkeiten nach Kategorien - skillsByCategory := make(map[string][]gin.H) - - for _, skill := range allSkills { - // Überspringe bereits gelernte Fertigkeiten - if learnedSkills[skill.Name] { - continue - } - - // Überspringe Placeholder-Fertigkeiten (zusätzliche Sicherheit) - if skill.Name == "Placeholder" { - continue - } - - // Berechne Lernkosten mit GetLernCostNextLevel - epCost, goldCost := calculateSkillLearningCostsOld(skill, character, rewardType) - - skillInfo := gin.H{ - "name": skill.Name, - "epCost": epCost, - "goldCost": goldCost, - } - - category := skill.Category - if category == "" { - category = "Sonstige" - } - - skillsByCategory[category] = append(skillsByCategory[category], skillInfo) - } - - c.JSON(http.StatusOK, gin.H{ - "skills_by_category": skillsByCategory, - }) -} - -// calculateSkillLearningCostsOld is deprecated. Use calculateSkillLearnCostNewSystem instead. -// This function uses the old hardcoded learning cost system. -// calculateSkillLearningCostsOld berechnet die EP- und Goldkosten für das Lernen einer Fertigkeit mit GetLernCostNextLevel -func calculateSkillLearningCostsOld(skill models.Skill, character models.Char, rewardType string) (int, int) { - // Erstelle LernCostRequest für das Lernen (Level 0 -> 1) - var rewardTypePtr *string - if rewardType != "" && rewardType != "default" { - rewardTypePtr = &rewardType - } - - request := gsmaster.LernCostRequest{ - CharId: character.ID, - Name: skill.Name, - CurrentLevel: 0, // Nicht gelernt - TargetLevel: 1, // Auf Level 1 lernen - Type: "skill", - Action: "learn", - UsePP: 0, - UseGold: 0, - Reward: rewardTypePtr, - } - - // Erstelle SkillCostResultNew - costResult := gsmaster.SkillCostResultNew{ - CharacterID: fmt.Sprintf("%d", character.ID), - CharacterClass: getCharacterClassOld(&character), - SkillName: skill.Name, - Category: skill.Category, - Difficulty: skill.Difficulty, - TargetLevel: 1, - } - - // Berechne Kosten mit GetLernCostNextLevel - err := gsmaster.GetLernCostNextLevelOld(&request, &costResult, rewardTypePtr, 1, character.Typ) - if err != nil { - // Fallback zu Standard-Kosten bei Fehler - epCost := 100 - goldCost := 50 - - return epCost, goldCost - } - - return costResult.EP, costResult.GoldCost -} - // Character Creation Session Management // CreateCharacterSession erstellt eine neue Charakter-Erstellungssession diff --git a/backend/character/handlers_test.go b/backend/character/handlers_test.go index a2e84b2..1c0f013 100644 --- a/backend/character/handlers_test.go +++ b/backend/character/handlers_test.go @@ -161,103 +161,6 @@ func TestImproveSkillHandler(t *testing.T) { t.Logf("Gold: %d -> %d (cost: %.0f)", 390, updatedChar.Vermoegen.Goldstücke, response["gold_cost"]) }) - t.Run("ImproveSkill with insufficient EP", func(t *testing.T) { - // Create character with insufficient EP - poorChar := models.Char{ - BamortBase: models.BamortBase{ - ID: 21, - Name: "Poor Test Character", - }, - Typ: "Krieger", - Rasse: "Mensch", - Grad: 1, - Erfahrungsschatz: models.Erfahrungsschatz{ - BamortCharTrait: models.BamortCharTrait{ - CharacterID: 21, - }, - ES: 5, // Insufficient EP - }, - Vermoegen: models.Vermoegen{ - BamortCharTrait: models.BamortCharTrait{ - CharacterID: 21, - }, - Goldstücke: 100, - }, - } - - // Add skill - skill := models.SkFertigkeit{ - BamortCharTrait: models.BamortCharTrait{ - BamortBase: models.BamortBase{ - Name: "Athletik", - }, - CharacterID: 21, - }, - Fertigkeitswert: 9, - } - poorChar.Fertigkeiten = append(poorChar.Fertigkeiten, skill) - - err = poorChar.Create() - assert.NoError(t, err) - - requestData := map[string]interface{}{ - "char_id": 21, - "name": "Athletik", - "current_level": 9, - "target_level": 10, - "type": "skill", - "action": "improve", - "reward": "default", - } - requestBody, _ := json.Marshal(requestData) - - req, _ := http.NewRequest("POST", "/api/characters/improve-skill", bytes.NewBuffer(requestBody)) - req.Header.Set("Content-Type", "application/json") - - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Request = req - - ImproveSkillOld(c) - - assert.Equal(t, http.StatusBadRequest, w.Code, "Should return 400 for insufficient EP") - - var response map[string]interface{} - err := json.Unmarshal(w.Body.Bytes(), &response) - assert.NoError(t, err) - assert.Contains(t, response, "error") - assert.Contains(t, response["error"], "Nicht genügend Erfahrungspunkte") - }) - - t.Run("ImproveSkill with nonexistent character", func(t *testing.T) { - requestData := map[string]interface{}{ - "char_id": 999, // Non-existent character - "name": "Athletik", - "current_level": 9, - "target_level": 10, - "type": "skill", - "action": "improve", - "reward": "default", - } - requestBody, _ := json.Marshal(requestData) - - req, _ := http.NewRequest("POST", "/api/characters/improve-skill", bytes.NewBuffer(requestBody)) - req.Header.Set("Content-Type", "application/json") - - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Request = req - - ImproveSkillOld(c) - - assert.Equal(t, http.StatusNotFound, w.Code, "Should return 404 for non-existent character") - - var response map[string]interface{} - err := json.Unmarshal(w.Body.Bytes(), &response) - assert.NoError(t, err) - assert.Contains(t, response, "error") - assert.Contains(t, response["error"], "Charakter nicht gefunden") - }) } func TestGetAvailableSkillsNewSystem(t *testing.T) { diff --git a/backend/character/lerncost_comparison_test.go b/backend/character/lerncost_comparison_test.go deleted file mode 100644 index a8c370e..0000000 --- a/backend/character/lerncost_comparison_test.go +++ /dev/null @@ -1,551 +0,0 @@ -package character - -import ( - "bamort/database" - "bamort/gsmaster" - "bamort/models" - "bytes" - "encoding/json" - "fmt" - "net/http" - "net/http/httptest" - "testing" - - "github.com/gin-gonic/gin" - "github.com/stretchr/testify/assert" -) - -// TestCompareOldVsNewLearningCostSystems vergleicht die Ergebnisse von GetLernCost und GetLernCostNewSystem -func TestCompareOldVsNewLearningCostSystems(t *testing.T) { - // Setup test database - database.SetupTestDB(true, true) - defer database.ResetTestDB() - - // Migrate the schema - err := models.MigrateStructure() - assert.NoError(t, err) - - // Setup Gin in test mode - gin.SetMode(gin.TestMode) - - testCases := []struct { - name string - charId uint - skillName string - currentLevel int - TargetLevel int `json:"target_level,omitempty"` // Zielwert (optional, für Kostenberechnung bis zu einem bestimmten Level) - usePP int - useGold int - description string - Type string `json:"type" binding:"required,oneof=skill spell weapon"` // 'skill', 'spell' oder 'weapon' Waffenfertigkeiten sind normale Fertigkeiten (evtl. kann hier später der Name der Waffe angegeben werden ) - Action string `json:"action" binding:"required,oneof=learn improve"` // 'learn' oder 'improve' - Reward *string `json:"reward" binding:"required,oneof=default noGold halveep halveepnoGold"` // Belohnungsoptionen Lernen als Belohnung - }{ - { - name: "improve Athletik Basic Test", - charId: 20, - skillName: "Athletik", - currentLevel: 9, - usePP: 0, - useGold: 0, - description: "Basic comparison for Athletik skill improvement", - Type: "skill", - Action: "improve", - TargetLevel: 0, // Calculate all levels - Reward: &[]string{"default"}[0], - }, - //"improve Athletik with PP", - { - name: "improve Athletik with PP", - charId: 20, - skillName: "Athletik", - currentLevel: 9, - usePP: 10, - useGold: 0, - description: "Comparison with Practice Points usage", - Type: "skill", - Action: "improve", - TargetLevel: 0, // Calculate all levels - Reward: &[]string{"default"}[0], - }, - //"improve Athletik with Gold", - { - name: "improve Athletik with Gold", - charId: 20, - skillName: "Athletik", - currentLevel: 9, - usePP: 0, - useGold: 100, - description: "Comparison with Gold usage", - Type: "skill", - Action: "improve", - TargetLevel: 0, // Calculate all levels - Reward: &[]string{"default"}[0], - }, - //improve Athletik with 2000 Gold - { - name: "improve Athletik with 2000 Gold", - charId: 20, - skillName: "Athletik", - currentLevel: 9, - usePP: 0, - useGold: 2000, - description: "Comparison with Gold usage", - Type: "skill", - Action: "improve", - TargetLevel: 0, // Calculate all levels - Reward: &[]string{"default"}[0], - }, - // improve Athletik with 15 PP and 150 Gold - { - name: "improve Athletik with PP and Gold", - charId: 20, - skillName: "Athletik", - currentLevel: 9, - usePP: 15, - useGold: 150, - description: "Comparison with both PP and Gold usage", - Type: "skill", - Action: "improve", - TargetLevel: 0, // Calculate all levels - Reward: &[]string{"default"}[0], - }, - // learn Naturkunde Basic Test - { - name: "learn Naturkunde Basic Test", - charId: 20, - skillName: "Naturkunde", - currentLevel: 9, - usePP: 0, - useGold: 0, - description: "Comparison Learn Basic", - Type: "skill", - Action: "learn", - TargetLevel: 0, // Calculate all levels - Reward: &[]string{"default"}[0], - }, - // learn Naturkunde Test 100 Gold - { - name: "learn Naturkunde Test 100 Gold", - charId: 20, - skillName: "Naturkunde", - currentLevel: 9, - usePP: 0, - useGold: 100, - description: "Comparison Learn 100 Gold", - Type: "skill", - Action: "learn", - TargetLevel: 0, // Calculate all levels - Reward: &[]string{"default"}[0], - }, - // learn Naturkunde Test 2000 Gold - { - name: "learn Naturkunde Test 2000 Gold", - charId: 20, - skillName: "Naturkunde", - currentLevel: 9, - usePP: 0, - useGold: 2000, - description: "Comparison Learn 2000 Gold", - Type: "skill", - Action: "learn", - TargetLevel: 0, // Calculate all levels - Reward: &[]string{"default"}[0], - }, - // learn Naturkunde Test 2 PP - { - name: "learn Naturkunde Test 2 PP", - charId: 20, - skillName: "Naturkunde", - currentLevel: 9, - usePP: 2, - useGold: 0, - description: "Comparison Learn 2 PP", - Type: "skill", - Action: "learn", - TargetLevel: 0, // Calculate all levels - Reward: &[]string{"default"}[0], - }, - // learn Beeinflussen Test Basic - { - name: "learn Beeinflussen Test Basic", - charId: 18, - skillName: "Beeinflussen", - currentLevel: 9, - usePP: 0, - useGold: 0, - description: "Comparison Learn ", - Type: "spell", - Action: "learn", - TargetLevel: 0, // Calculate all levels - Reward: &[]string{"default"}[0], - }, - - // learn Beeinflussen Test Basic 2 PP - { - name: "learn Beeinflussen Test Basic 2 PP", - charId: 18, - skillName: "Beeinflussen", - currentLevel: 9, - usePP: 2, - useGold: 0, - description: "Comparison Learn ", - Type: "spell", - Action: "learn", - TargetLevel: 0, // Calculate all levels - Reward: &[]string{"default"}[0], - }, - // learn Beeinflussen Test Basic 150 Gold - { - name: "learn Beeinflussen Test Basic", - charId: 18, - skillName: "Beeinflussen", - currentLevel: 9, - usePP: 0, - useGold: 150, - description: "Comparison Learn ", - Type: "spell", - Action: "learn", - TargetLevel: 0, // Calculate all levels - Reward: &[]string{"default"}[0], - }, - // learn Beeinflussen Test Basic 2PP and 150 Gold - { - name: "learn Beeinflussen Test Basic 2PP and 150 Gold", - charId: 18, - skillName: "Beeinflussen", - currentLevel: 9, - usePP: 2, - useGold: 150, - description: "Comparison Learn ", - Type: "spell", - Action: "learn", - TargetLevel: 0, // Calculate all levels - Reward: &[]string{"default"}[0], - }, - // learn Naturkunde Test 2 PP - { - name: "learn non existent Bogenbau Test Basic", - charId: 20, - skillName: "Bogenbau", - currentLevel: 0, - usePP: 0, - useGold: 0, - description: "Comparison Learn Bogenbau", - Type: "skill", - Action: "learn", - TargetLevel: 0, // Calculate all levels - Reward: &[]string{"default"}[0], - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - fmt.Printf("\n=== Comparing Old vs New System: %s ===\n", tc.description) - - // Prepare request data - requestData := gsmaster.LernCostRequest{ - CharId: tc.charId, - Name: tc.skillName, - CurrentLevel: tc.currentLevel, - Type: tc.Type, - Action: tc.Action, - TargetLevel: tc.TargetLevel, // Calculate all levels - UsePP: tc.usePP, - UseGold: tc.useGold, - Reward: tc.Reward, - } - requestBody, _ := json.Marshal(requestData) - - // Test Old System (GetLernCost) - reqOld, _ := http.NewRequest("POST", "/api/characters/lerncost", bytes.NewBuffer(requestBody)) - reqOld.Header.Set("Content-Type", "application/json") - - wOld := httptest.NewRecorder() - cOld, _ := gin.CreateTestContext(wOld) - cOld.Request = reqOld - - GetLernCost(cOld) - - // Test New System (GetLernCostNewSystem) - reqNew, _ := http.NewRequest("POST", "/api/characters/lerncost-new", bytes.NewBuffer(requestBody)) - reqNew.Header.Set("Content-Type", "application/json") - - wNew := httptest.NewRecorder() - cNew, _ := gin.CreateTestContext(wNew) - cNew.Request = reqNew - - GetLernCostNewSystem(cNew) - - // Check that both systems returned successful responses - fmt.Printf("Old System Status: %d, New System Status: %d\n", wOld.Code, wNew.Code) - - if wOld.Code != http.StatusOK && wNew.Code != http.StatusOK { - fmt.Printf("Both systems failed - Old: %s, New: %s\n", wOld.Body.String(), wNew.Body.String()) - t.Skip("Both systems failed, skipping comparison") - return - } - - if wOld.Code != http.StatusOK { - fmt.Printf("Old system failed: %s\n", wOld.Body.String()) - fmt.Printf("New system succeeded with status %d\n", wNew.Code) - t.Skip("Old system failed, new system comparison only") - return - } - - if wNew.Code != http.StatusOK { - fmt.Printf("New system failed: %s\n", wNew.Body.String()) - fmt.Printf("Old system succeeded with status %d\n", wOld.Code) - t.Skip("New system failed, old system comparison only") - return - } - - // Parse responses - var oldResponse []gsmaster.SkillCostResultNew - var newResponse []gsmaster.SkillCostResultNew - - err := json.Unmarshal(wOld.Body.Bytes(), &oldResponse) - assert.NoError(t, err, "Old system response should be valid JSON") - - err = json.Unmarshal(wNew.Body.Bytes(), &newResponse) - assert.NoError(t, err, "New system response should be valid JSON") - - // Compare basic structure - fmt.Printf("Old System: %d levels, New System: %d levels\n", len(oldResponse), len(newResponse)) - - // 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 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) - if len(newResponse) < maxLevels { - maxLevels = len(newResponse) - } - - exactMatches := 0 - totalComparisons := 0 - - for i := 0; i < maxLevels; i++ { - old := oldResponse[i] - new := newResponse[i] - - // Check if target levels match - /*if old.TargetLevel != new.TargetLevel { - fmt.Printf("Level mismatch at index %d: Old=%d, New=%d\n", i, old.TargetLevel, new.TargetLevel) - continue - }*/ - - totalComparisons++ - - // Compare costs - epMatch := old.EP == new.EP - goldMatch := old.GoldCost == new.GoldCost - leMatch := old.LE == new.LE - ppMatch := old.PPUsed == new.PPUsed - goldUsedMatch := old.GoldUsed == new.GoldUsed - - overallMatch := epMatch && goldMatch && leMatch && ppMatch && goldUsedMatch - if overallMatch { - exactMatches++ - } - - matchSymbol := "✓" - if !overallMatch { - matchSymbol = "✗" - } - - 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) - - // Individual field assertions for debugging - if !epMatch { - fmt.Printf(" EP mismatch at level %d: Old=%d, New=%d (diff=%d)\n", - old.TargetLevel, old.EP, new.EP, old.EP-new.EP) - } - if !goldMatch { - fmt.Printf(" GoldCost mismatch at level %d: Old=%d, New=%d (diff=%d)\n", - old.TargetLevel, old.GoldCost, new.GoldCost, old.GoldCost-new.GoldCost) - } - if !leMatch { - fmt.Printf(" LE mismatch at level %d: Old=%d, New=%d (diff=%d)\n", - old.TargetLevel, old.LE, new.LE, old.LE-new.LE) - } - if !ppMatch { - 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 { - fmt.Printf(" GoldUsed mismatch at level %d: Old=%d, New=%d (diff=%d)\n", - old.TargetLevel, old.GoldUsed, new.GoldUsed, old.GoldUsed-new.GoldUsed) - } - - // Verify basic consistency within each system - assert.GreaterOrEqual(t, old.EP, 0, "Old system EP should be non-negative for level %d", old.TargetLevel) - assert.GreaterOrEqual(t, new.EP, 0, "New system EP should be non-negative for level %d", new.TargetLevel) - assert.GreaterOrEqual(t, old.LE, 0, "Old system LE should be non-negative for level %d", old.TargetLevel) - assert.GreaterOrEqual(t, new.LE, 0, "New system LE should be non-negative for level %d", new.TargetLevel) - } - - // Summary statistics - matchPercentage := float64(exactMatches) / float64(totalComparisons) * 100 - fmt.Printf("\n=== Comparison Summary ===\n") - fmt.Printf("Total Comparisons: %d\n", totalComparisons) - fmt.Printf("Exact Matches: %d\n", exactMatches) - fmt.Printf("Match Percentage: %.1f%%\n", matchPercentage) - - // Check for levels that exist in one system but not the other - if len(oldResponse) != len(newResponse) { - fmt.Printf("Length Difference: Old=%d levels, New=%d levels\n", len(oldResponse), len(newResponse)) - - if len(oldResponse) > len(newResponse) { - fmt.Printf("Old system has additional levels:\n") - for i := len(newResponse); i < len(oldResponse); i++ { - fmt.Printf(" Level %d: EP=%d, Gold=%d, LE=%d\n", - oldResponse[i].TargetLevel, oldResponse[i].EP, oldResponse[i].GoldCost, oldResponse[i].LE) - } - } else { - fmt.Printf("New system has additional levels:\n") - for i := len(oldResponse); i < len(newResponse); i++ { - fmt.Printf(" Level %d: EP=%d, Gold=%d, LE=%d\n", - newResponse[i].TargetLevel, newResponse[i].EP, newResponse[i].GoldCost, newResponse[i].LE) - } - } - } - - // Basic assertions for test validation - assert.Greater(t, totalComparisons, 0, "Should have at least one level to compare") - - // If systems are supposed to be equivalent, we might want exact matches - // For now, we'll just document the differences and ensure both are reasonable - if matchPercentage < 50.0 { - t.Logf("WARNING: Low match percentage (%.1f%%) between old and new systems", matchPercentage) - } - - // Verify that both systems produce reasonable results - if len(oldResponse) > 0 { - assert.Equal(t, tc.skillName, oldResponse[0].SkillName, "Old system should return correct skill name") - assert.Equal(t, fmt.Sprintf("%d", tc.charId), oldResponse[0].CharacterID, "Old system should return correct character ID") - } - - if len(newResponse) > 0 { - assert.Equal(t, tc.skillName, newResponse[0].SkillName, "New system should return correct skill name") - assert.Equal(t, fmt.Sprintf("%d", tc.charId), newResponse[0].CharacterID, "New system should return correct character ID") - } - }) - } - - // Summary test to compare system performance - t.Run("System Performance Summary", func(t *testing.T) { - fmt.Printf("\n=== Overall System Comparison Summary ===\n") - fmt.Printf("Both systems tested with various configurations:\n") - fmt.Printf("- Basic skill improvement (no resources)\n") - fmt.Printf("- With Practice Points usage\n") - fmt.Printf("- With Gold usage\n") - fmt.Printf("- With combined PP and Gold usage\n") - fmt.Printf("\nKey observations should be documented above.\n") - fmt.Printf("Differences may indicate:\n") - fmt.Printf("1. Different calculation methods\n") - fmt.Printf("2. Different data sources (old vs new tables)\n") - fmt.Printf("3. Implementation bugs in either system\n") - fmt.Printf("4. Different business rules or assumptions\n") - }) -} - -// TestPerformanceComparison vergleicht die Performance der beiden Systeme -func TestPerformanceComparison(t *testing.T) { - // Setup test database - database.SetupTestDB(true, true) - defer database.ResetTestDB() - - // Migrate the schema - err := models.MigrateStructure() - assert.NoError(t, err) - - // Setup Gin in test mode - gin.SetMode(gin.TestMode) - - fmt.Printf("\n=== Performance Comparison ===\n") - - // Prepare test data - requestData := gsmaster.LernCostRequest{ - CharId: 20, - Name: "Athletik", - CurrentLevel: 5, - Type: "skill", - Action: "improve", - TargetLevel: 0, - UsePP: 0, - UseGold: 0, - Reward: &[]string{"default"}[0], - } - requestBody, _ := json.Marshal(requestData) - - // Test old system multiple times - oldSystemTimes := make([]int64, 10) - for i := 0; i < 10; i++ { - req, _ := http.NewRequest("POST", "/api/characters/lerncost", bytes.NewBuffer(requestBody)) - req.Header.Set("Content-Type", "application/json") - - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Request = req - - start := getCurrentTime() - GetLernCost(c) - oldSystemTimes[i] = getCurrentTime() - start - - if w.Code != http.StatusOK { - t.Logf("Old system failed on iteration %d: %s", i, w.Body.String()) - } - } - - // Test new system multiple times - newSystemTimes := make([]int64, 10) - for i := 0; i < 10; i++ { - req, _ := http.NewRequest("POST", "/api/characters/lerncost-new", bytes.NewBuffer(requestBody)) - req.Header.Set("Content-Type", "application/json") - - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Request = req - - start := getCurrentTime() - GetLernCostNewSystem(c) - newSystemTimes[i] = getCurrentTime() - start - - if w.Code != http.StatusOK { - t.Logf("New system failed on iteration %d: %s", i, w.Body.String()) - } - } - - // Calculate averages - oldAvg := calculateAverage(oldSystemTimes) - newAvg := calculateAverage(newSystemTimes) - - fmt.Printf("Old System Average Time: %d μs\n", oldAvg) - fmt.Printf("New System Average Time: %d μs\n", newAvg) - - if newAvg < oldAvg { - improvement := float64(oldAvg-newAvg) / float64(oldAvg) * 100 - fmt.Printf("New system is %.1f%% faster\n", improvement) - } else { - regression := float64(newAvg-oldAvg) / float64(oldAvg) * 100 - fmt.Printf("New system is %.1f%% slower\n", regression) - } -} - -// Helper functions for performance testing -func getCurrentTime() int64 { - return 0 // Placeholder - in real implementation would use time.Now().UnixNano() -} - -func calculateAverage(times []int64) int64 { - var sum int64 - for _, t := range times { - sum += t - } - return sum / int64(len(times)) -} diff --git a/backend/character/lerncost_handler.go b/backend/character/lerncost_handler.go index 1ec50eb..2f5ade3 100644 --- a/backend/character/lerncost_handler.go +++ b/backend/character/lerncost_handler.go @@ -66,92 +66,6 @@ type MultiLevelCostResponse struct { CanAffordTotal bool `json:"can_afford_total"` } -// GetLernCost -func GetLernCost(c *gin.Context) { - // Request-Parameter abrufen - var request gsmaster.LernCostRequest - if err := c.ShouldBindJSON(&request); err != nil { - respondWithError(c, http.StatusBadRequest, "Ungültige Anfrageparameter: "+err.Error()) - return - } - charID := fmt.Sprintf("%d", request.CharId) - var character models.Char - if err := character.FirstID(charID); err != nil { - respondWithError(c, http.StatusNotFound, "Charakter nicht gefunden") - return - } - var costResult gsmaster.SkillCostResultNew - costResult.CharacterID = charID - - // Verwende Klassenabkürzung wenn der Typ länger als 3 Zeichen ist - if len(character.Typ) > 3 { - costResult.CharacterClass = gsmaster.GetClassAbbreviationOld(character.Typ) - } else { - costResult.CharacterClass = character.Typ - } - - // Normalize skill name (trim whitespace, proper case) - costResult.SkillName = strings.TrimSpace(request.Name) - - // Lasse Kategorie und Schwierigkeit leer, damit CalcSkillLernCost die beste Option wählt - // costResult.Category = gsmaster.GetSkillCategory(request.Name) - // costResult.Difficulty = gsmaster.GetSkillDifficulty(costResult.Category, costResult.SkillName) - var response []gsmaster.SkillCostResultNew - - // Für "learn" Aktion: nur eine Berechnung, da Lernkosten einmalig sind - if request.Action == "learn" { - levelResult := gsmaster.SkillCostResultNew{ - CharacterID: costResult.CharacterID, - CharacterClass: costResult.CharacterClass, - SkillName: costResult.SkillName, - Category: costResult.Category, - Difficulty: costResult.Difficulty, - TargetLevel: 1, // Lernkosten sind für das Erlernen der Fertigkeit (Level 1) - } - err := gsmaster.GetLernCostNextLevelOld(&request, &levelResult, request.Reward, 1, character.Typ) - 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 - for i := request.CurrentLevel + 1; i <= 18; i++ { - levelResult := gsmaster.SkillCostResultNew{ - CharacterID: costResult.CharacterID, - CharacterClass: costResult.CharacterClass, - SkillName: costResult.SkillName, - Category: costResult.Category, - Difficulty: costResult.Difficulty, - TargetLevel: i, - } - err := gsmaster.GetLernCostNextLevelOld(&request, &levelResult, request.Reward, i, character.Typ) - 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) - } - } - - c.JSON(http.StatusOK, response) -} - // GetLernCostNewSystem verwendet das neue Datenbank-Lernkosten-System // und produziert die gleichen Ergebnisse wie GetLernCost. // @@ -530,134 +444,6 @@ func applySpellRewardNewSystem(result *gsmaster.SkillCostResultNew, reward *stri } } -// GetSkillCost berechnet die Kosten zum Erlernen oder Verbessern einer Fertigkeit -func GetSkillCost(c *gin.Context) { - // Charakter-ID aus der URL abrufen - charID := c.Param("id") - - // Charakter aus der Datenbank laden - var character models.Char - if err := character.FirstID(charID); err != nil { - respondWithError(c, http.StatusNotFound, "Charakter nicht gefunden") - return - } - - // Request-Parameter abrufen - var request SkillCostRequest - if err := c.ShouldBindJSON(&request); err != nil { - respondWithError(c, http.StatusBadRequest, "Ungültige Anfrageparameter: "+err.Error()) - return - } - - // Normalize skill name (trim whitespace, proper case) - request.Name = strings.TrimSpace(request.Name) - - // Validate current level for improvement - if request.Action == "improve" && request.CurrentLevel <= 0 { - // Try to get current level from character's skills - currentLevel := getCurrentSkillLevel(&character, request.Name, request.Type) - if currentLevel == -1 { - respondWithError(c, http.StatusBadRequest, "Fertigkeit nicht bei diesem Charakter vorhanden oder current_level erforderlich") - return - } - request.CurrentLevel = currentLevel - } - - // Handle multi-level cost calculation - if request.TargetLevel > 0 && request.Action == "improve" { - response := calculateMultiLevelCost(&character, &request) - if response == nil { - respondWithError(c, http.StatusBadRequest, "Fehler bei der Multi-Level-Kostenberechnung") - return - } - c.JSON(http.StatusOK, response) - return - } - - // Single cost calculation - cost, originalCost, skillInfo, err := calculateSingleCostOld(&character, &request) - if err != nil { - respondWithError(c, http.StatusBadRequest, "Fehler bei der Kostenberechnung: "+err.Error()) - return - } - - // Originalkosten berechnen (ohne PP-Reduktion) - originalRequest := request - originalRequest.UsePP = 0 - _, _, _, err = calculateSingleCostOld(&character, &originalRequest) - if err != nil { - respondWithError(c, http.StatusBadRequest, "Fehler bei der ursprünglichen Kostenberechnung: "+err.Error()) - return - } - - // PP-Informationen sammeln (fertigkeitsspezifisch) - availablePP := getPPForSkill(&character, request.Name) - ppUsed := request.UsePP - if ppUsed > availablePP { - ppUsed = availablePP - } - - // PP-Reduktion berechnen - var ppReduction int - if request.UsePP > 0 { - if request.Action == "improve" { - ppReduction = ppUsed // PP entsprechen direkt der TE-Reduktion - } else if request.Action == "learn" && request.Type == "spell" { - ppReduction = ppUsed // PP entsprechen direkt der LE-Reduktion - } - } - - // Check if character can afford it - canAfford := canCharacterAfford(&character, cost) - - // Belohnungsinformationen berechnen - var rewardApplied string - var savings *models.LearnCost - var goldUsedForEP int - - if request.Reward != nil && request.Reward.Type != "" { - rewardApplied = request.Reward.Type - - // Ersparnisse berechnen - savings = &models.LearnCost{ - Ep: originalCost.Ep - cost.Ep, - LE: originalCost.LE - cost.LE, - Money: originalCost.Money - cost.Money, - } - - // Gold für EP berechnen - if request.Reward.Type == "gold_for_ep" && request.Reward.UseGoldForEP { - goldUsedForEP = (cost.Money - originalCost.Money) / 10 - } - } - - // Create response - response := &SkillCostResponse{ - LearnCost: cost, - SkillName: request.Name, - SkillType: request.Type, - Action: request.Action, - CharacterID: character.ID, - CurrentLevel: request.CurrentLevel, - TargetLevel: request.TargetLevel, - Category: skillInfo.Category, - Difficulty: skillInfo.Difficulty, - CanAfford: canAfford, - Notes: generateNotes(&character, &request, cost), - PPUsed: ppUsed, - PPAvailable: availablePP, - PPReduction: ppReduction, - OriginalCost: originalCost.Ep, - FinalCost: cost.Ep, - RewardApplied: rewardApplied, - OriginalCostStruct: originalCost, - Savings: savings, - GoldUsedForEP: goldUsedForEP, - } - - c.JSON(http.StatusOK, response) -} - // Helper function to get current skill level from character func getCurrentSkillLevel(character *models.Char, skillName, skillType string) int { switch skillType { @@ -680,69 +466,6 @@ func getCurrentSkillLevel(character *models.Char, skillName, skillType string) i return -1 } -// calculateSingleCostOld is deprecated. Use calculateSkillLearnCostNewSystem or calculateSpellLearnCostNewSystem instead. -// This function uses the old hardcoded learning cost system. -func calculateSingleCostOld(character *models.Char, request *SkillCostRequest) (*models.LearnCost, *models.LearnCost, *skillInfo, error) { - var cost *models.LearnCost - var err error - var info skillInfo - - switch { - case request.Action == "learn" && request.Type == "skill": - cost, err = gsmaster.CalculateDetailedSkillLearningCostOld(request.Name, character.Typ) - if err == nil { - info = GetSkillInfoCategoryAndDifficultyOld(request.Name, request.Type) - } - - case request.Action == "improve" && request.Type == "skill": - cost, err = gsmaster.CalculateDetailedSkillImprovementCostOld(request.Name, character.Typ, request.CurrentLevel) - if err == nil { - info = GetSkillInfoCategoryAndDifficultyOld(request.Name, request.Type) - } - - case request.Action == "improve" && request.Type == "weapon": - cost, err = gsmaster.CalculateDetailedSkillImprovementCostOld(request.Name, character.Typ, request.CurrentLevel) - if err == nil { - info = GetSkillInfoCategoryAndDifficultyOld(request.Name, request.Type) - } - - case request.Action == "learn" && request.Type == "spell": - cost, err = gsmaster.CalculateDetailedSpellLearningCostOld(request.Name, character.Typ) - if err == nil { - info = getSpellInfo(request.Name) - } - - default: - return nil, nil, nil, fmt.Errorf("ungültige Kombination aus Aktion und Typ") - } - - if err != nil { - return nil, nil, nil, err - } - - // Belohnungen anwenden, falls spezifiziert - originalCost := *cost // Kopie der ursprünglichen Kosten - if request.Reward != nil && request.Reward.Type != "" { - cost = applyReward(cost, request) - } - - // Praxispunkte anwenden, falls angefordert (fertigkeitsspezifisch) - if request.UsePP > 0 { - availablePP := getPPForSkill(character, request.Name) - finalEP, finalLE, _ := applyPPReduction(request, cost, availablePP) - - // Erstelle eine neue LearnCost mit den reduzierten Werten - cost = &models.LearnCost{ - Stufe: cost.Stufe, - LE: finalLE, - Ep: finalEP, - Money: cost.Money, // Geldkosten bleiben unverändert - } - } - - return cost, &originalCost, &info, err -} - // applyReward wendet Belohnungen auf die Kosten an func applyReward(cost *models.LearnCost, request *SkillCostRequest) *models.LearnCost { if request.Reward == nil || request.Reward.Type == "" { @@ -793,150 +516,12 @@ func applyReward(cost *models.LearnCost, request *SkillCostRequest) *models.Lear return &newCost } -// Helper function to calculate multi-level costs -func calculateMultiLevelCost(character *models.Char, request *SkillCostRequest) *MultiLevelCostResponse { - if request.TargetLevel <= request.CurrentLevel { - return nil - } - - var levelCosts []SkillCostResponse - totalEP := 0 - totalMoney := 0 - remainingPP := request.UsePP - - for level := request.CurrentLevel; level < request.TargetLevel; level++ { - tempRequest := *request - tempRequest.CurrentLevel = level - tempRequest.Action = "improve" - - // Verteile die PP auf die verschiedenen Level - if remainingPP > 0 { - tempRequest.UsePP = 1 // Maximal 1 PP pro Level - remainingPP-- - } else { - tempRequest.UsePP = 0 - } - - cost, originalCost, skillInfo, err := calculateSingleCostOld(character, &tempRequest) - if err != nil { - continue - } - - // Originalkosten berechnen (ohne PP) - originalRequest := tempRequest - originalRequest.UsePP = 0 - _, _, _, _ = calculateSingleCostOld(character, &originalRequest) - - // PP-Informationen sammeln (fertigkeitsspezifisch) - availablePP := getPPForSkill(character, request.Name) - ppUsed := tempRequest.UsePP - if ppUsed > availablePP { - ppUsed = availablePP - } - - ppReduction := 0 - if tempRequest.UsePP > 0 { - ppReduction = ppUsed - } - - // Belohnungsinformationen für Level berechnen - var rewardApplied string - var savings *models.LearnCost - var goldUsedForEP int - - if tempRequest.Reward != nil && tempRequest.Reward.Type != "" { - rewardApplied = tempRequest.Reward.Type - - // Ersparnisse berechnen - savings = &models.LearnCost{ - Ep: originalCost.Ep - cost.Ep, - LE: originalCost.LE - cost.LE, - Money: originalCost.Money - cost.Money, - } - - // Gold für EP berechnen - if tempRequest.Reward.Type == "gold_for_ep" && tempRequest.Reward.UseGoldForEP { - goldUsedForEP = (cost.Money - originalCost.Money) / 10 - } - } - - levelCost := SkillCostResponse{ - LearnCost: cost, - SkillName: request.Name, - SkillType: request.Type, - Action: "improve", - CharacterID: character.ID, - CurrentLevel: level, - TargetLevel: level + 1, - Category: skillInfo.Category, - Difficulty: skillInfo.Difficulty, - CanAfford: canCharacterAfford(character, cost), - PPUsed: ppUsed, - PPAvailable: availablePP, - PPReduction: ppReduction, - OriginalCost: originalCost.Ep, - FinalCost: cost.Ep, - RewardApplied: rewardApplied, - OriginalCostStruct: originalCost, - Savings: savings, - GoldUsedForEP: goldUsedForEP, - } - - levelCosts = append(levelCosts, levelCost) - totalEP += cost.Ep - totalMoney += cost.Money - } - - totalCost := &models.LearnCost{ - Stufe: request.TargetLevel, - LE: 0, - Ep: totalEP, - Money: totalMoney, - } - - return &MultiLevelCostResponse{ - SkillName: request.Name, - SkillType: request.Type, - CharacterID: character.ID, - CurrentLevel: request.CurrentLevel, - TargetLevel: request.TargetLevel, - LevelCosts: levelCosts, - TotalCost: totalCost, - CanAffordTotal: canCharacterAfford(character, totalCost), - } -} - // Helper structures and functions type skillInfo struct { Category string Difficulty string } -// GetSkillInfoCategoryAndDifficultyOld is deprecated. Use models.GetSkillCategoryAndDifficulty instead. -// This function uses the old hardcoded skill categorization system. -func GetSkillInfoCategoryAndDifficultyOld(skillName, skillType string) skillInfo { - var skill models.Skill - if err := skill.First(skillName); err != nil { - return skillInfo{Category: "unknown", Difficulty: "unknown"} - } - - // Fallback für fehlende Category und Difficulty Werte - category := skill.Category - difficulty := skill.Difficulty - - if category == "" { - // Standard-Kategorien basierend auf Skill-Namen - category = gsmaster.GetDefaultCategoryOld(skillName) - } - - if difficulty == "" { - // Standard-Schwierigkeit für verschiedene Skills - difficulty = gsmaster.GetDefaultDifficultyOld(skillName) - } - - return skillInfo{Category: category, Difficulty: difficulty} -} - func getSpellInfo(spellName string) skillInfo { var spell models.Spell if err := spell.First(spellName); err != nil { @@ -1041,18 +626,3 @@ func applyPPReduction(request *SkillCostRequest, cost *models.LearnCost, availab return finalEP, finalLE, reduction } - -func CalcSkillLearnCost(req *gsmaster.LernCostRequest, skillCostInfo *gsmaster.SkillCostResultNew) error { - // Fallback-Werte für Skills ohne definierte Kategorie/Schwierigkeit - - result, err := gsmaster.CalculateSkillLearningCostsOld(skillCostInfo.CharacterClass, skillCostInfo.Category, skillCostInfo.Difficulty) - if err != nil { - return err - } - - //Stufe: 0, // Lernen startet bei Stufe 0 - skillCostInfo.LE = result.LE - skillCostInfo.EP = result.EP - skillCostInfo.GoldCost = result.GoldCost - return nil -} diff --git a/backend/character/lerncost_handler_test.go b/backend/character/lerncost_handler_test.go index a964d01..ffccc12 100644 --- a/backend/character/lerncost_handler_test.go +++ b/backend/character/lerncost_handler_test.go @@ -15,169 +15,6 @@ import ( "github.com/stretchr/testify/assert" ) -func TestImprovedSkillCostAPI(t *testing.T) { - // Setup test database - database.SetupTestDB() - defer database.ResetTestDB() - - // Migrate the schema - err := models.MigrateStructure() - assert.NoError(t, err) - - // Create test skill data - err = createTestSkillData() - assert.NoError(t, err) - defer cleanupTestSkillData() - - // Create test character - testChar := createChar() - testChar.ID = 1 // Set the ID to match our test requests - err = testChar.Create() - assert.NoError(t, err) - - // Setup Gin in test mode - gin.SetMode(gin.TestMode) - - // Create test cases - testCases := []struct { - name string - request SkillCostRequest - expectedStatus int - description string - }{ - { - name: "Learn new skill", - request: SkillCostRequest{ - Name: "Menschenkenntnis", - Type: "skill", - Action: "learn", - }, - expectedStatus: http.StatusOK, - description: "Should calculate costs for learning a new skill", - }, - { - name: "Improve existing skill", - request: SkillCostRequest{ - Name: "Menschenkenntnis", - Type: "skill", - Action: "improve", - CurrentLevel: 10, - }, - expectedStatus: http.StatusOK, - description: "Should calculate costs for improving an existing skill", - }, - { - name: "Multi-level improvement", - request: SkillCostRequest{ - Name: "Menschenkenntnis", - Type: "skill", - Action: "improve", - CurrentLevel: 10, - TargetLevel: 13, - }, - expectedStatus: http.StatusOK, - description: "Should calculate costs for multi-level improvement", - }, - { - name: "Invalid request - missing name", - request: SkillCostRequest{ - Type: "skill", - Action: "learn", - }, - expectedStatus: http.StatusBadRequest, - description: "Should return error for missing skill name", - }, - { - name: "Invalid request - invalid type", - request: SkillCostRequest{ - Name: "Test", - Type: "invalid", - Action: "learn", - }, - expectedStatus: http.StatusBadRequest, - description: "Should return error for invalid skill type", - }, - { - name: "Invalid request - invalid action", - request: SkillCostRequest{ - Name: "Test", - Type: "skill", - Action: "invalid", - }, - expectedStatus: http.StatusBadRequest, - description: "Should return error for invalid action", - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - // Create request body - requestBody, _ := json.Marshal(tc.request) - - // Create HTTP request - req, _ := http.NewRequest("POST", "/api/characters/1/skill-cost", bytes.NewBuffer(requestBody)) - req.Header.Set("Content-Type", "application/json") - - // Create response recorder - w := httptest.NewRecorder() - - // Create Gin context - c, _ := gin.CreateTestContext(w) - c.Request = req - c.Params = []gin.Param{{Key: "id", Value: "1"}} - - // Note: This test would need a proper database setup to work fully - // For now, we're just testing the request parsing and validation - - fmt.Printf("Test: %s\n", tc.description) - fmt.Printf("Request: %+v\n", tc.request) - fmt.Printf("Expected Status: %d\n", tc.expectedStatus) - - // Call the actual handler function - GetSkillCost(c) - - // Check the response status - assert.Equal(t, tc.expectedStatus, w.Code, "Status code should match expected") - - // If successful, validate response structure - if w.Code == http.StatusOK { - var response map[string]interface{} - err := json.Unmarshal(w.Body.Bytes(), &response) - assert.NoError(t, err, "Response should be valid JSON") - - // Check for expected fields in successful responses - if tc.request.TargetLevel > 0 { - // Multi-level response - assert.Contains(t, response, "LevelCosts", "Multi-level response should contain LevelCosts") - } else { - // Single-level response - assert.Contains(t, response, "SkillName", "Response should contain SkillName") - assert.Contains(t, response, "SkillType", "Response should contain SkillType") - assert.Contains(t, response, "Action", "Response should contain Action") - } - } - - // Validate request structure - var parsedRequest SkillCostRequest - err := json.Unmarshal(requestBody, &parsedRequest) - assert.NoError(t, err, "Request should be valid JSON") - - // Test validation logic - if tc.request.Name == "" { - assert.Empty(t, parsedRequest.Name, "Name should be empty when not provided") - } - - if tc.request.Type != "" { - assert.Equal(t, tc.request.Type, parsedRequest.Type, "Type should match") - } - - if tc.request.Action != "" { - assert.Equal(t, tc.request.Action, parsedRequest.Action, "Action should match") - } - }) - } -} - // Test the response structures func TestSkillCostResponseStructures(t *testing.T) { t.Run("SkillCostResponse structure", func(t *testing.T) { @@ -254,645 +91,7 @@ func TestHelperFunctions(t *testing.T) { }) } -// Test integration with gsmaster exported functions -func TestGSMasterIntegration(t *testing.T) { - t.Run("GetDefaultCategory integration", func(t *testing.T) { - // Test that we can access the exported function from gsmaster - category := gsmaster.GetDefaultCategoryOld("Menschenkenntnis") - assert.Equal(t, "Sozial", category, "Should return correct category for Menschenkenntnis") - - category = gsmaster.GetDefaultCategoryOld("Stichwaffen") - assert.Equal(t, "Waffen", category, "Should return correct category for Stichwaffen") - - // Test fallback for unknown skill - category = gsmaster.GetDefaultCategoryOld("NonExistentSkill") - assert.Equal(t, "Alltag", category, "Should return default category for unknown skill") - }) - - t.Run("GetDefaultDifficulty integration", func(t *testing.T) { - // Test that we can access the exported function from gsmaster - difficulty := gsmaster.GetDefaultDifficultyOld("Menschenkenntnis") - assert.Equal(t, "schwer", difficulty, "Should return correct difficulty for Menschenkenntnis") - - difficulty = gsmaster.GetDefaultDifficultyOld("Stichwaffen") - assert.Equal(t, "leicht", difficulty, "Should return correct difficulty for Stichwaffen") - - // Test fallback for unknown skill - difficulty = gsmaster.GetDefaultDifficultyOld("NonExistentSkill") - assert.Equal(t, "normal", difficulty, "Should return default difficulty for unknown skill") - }) - - t.Run("Reward system structures", func(t *testing.T) { - // Test RewardOptions structure - rewards := RewardOptions{ - Type: "free_learning", - UseGoldForEP: true, - MaxGoldEP: 50, - } - - // Test JSON marshaling - jsonData, err := json.Marshal(rewards) - assert.NoError(t, err, "RewardOptions should be marshallable to JSON") - - // Test JSON unmarshaling - var parsedRewards RewardOptions - err = json.Unmarshal(jsonData, &parsedRewards) - assert.NoError(t, err, "RewardOptions should be unmarshallable from JSON") - - assert.Equal(t, rewards.Type, parsedRewards.Type, "Type should match") - assert.Equal(t, rewards.UseGoldForEP, parsedRewards.UseGoldForEP, "UseGoldForEP should match") - assert.Equal(t, rewards.MaxGoldEP, parsedRewards.MaxGoldEP, "MaxGoldEP should match") - - // Test validation of reward types - validTypes := []string{"free_learning", "free_spell_learning", "half_ep_improvement", "gold_for_ep"} - for _, validType := range validTypes { - rewards.Type = validType - _, err := json.Marshal(rewards) - assert.NoError(t, err, fmt.Sprintf("Should marshal valid type: %s", validType)) - } - }) - - t.Run("Reward system integration with gsmaster functions", func(t *testing.T) { - // Test that the reward system works with the exported gsmaster functions - // This simulates the flow where we get skill info from gsmaster and apply rewards - - skillName := "Menschenkenntnis" - - // Get skill info using exported functions - category := gsmaster.GetDefaultCategoryOld(skillName) - difficulty := gsmaster.GetDefaultDifficultyOld(skillName) - - assert.Equal(t, "Sozial", category, "Should get correct category from gsmaster") - assert.Equal(t, "schwer", difficulty, "Should get correct difficulty from gsmaster") - - // Test reward structure that would be used in the actual API - rewards := RewardOptions{ - Type: "half_ep_improvement", - UseGoldForEP: false, - MaxGoldEP: 0, - } - - // Test that structure is valid - jsonData, err := json.Marshal(rewards) - assert.NoError(t, err, "Reward options should marshal correctly") - - var parsedRewards RewardOptions - err = json.Unmarshal(jsonData, &parsedRewards) - assert.NoError(t, err, "Reward options should unmarshal correctly") - assert.Equal(t, "half_ep_improvement", parsedRewards.Type, "Reward type should be preserved") - }) -} - // Test GetLernCost endpoint specifically with gsmaster.LernCostRequest structure -func TestGetLernCostEndpoint(t *testing.T) { - // Setup test database - database.SetupTestDB(true, true) - defer database.ResetTestDB() - - // Migrate the schema - err := models.MigrateStructure() - assert.NoError(t, err) - - // Setup Gin in test mode - gin.SetMode(gin.TestMode) - - t.Run("GetLernCost with Athletik for Krieger character", func(t *testing.T) { - // Create request body using gsmaster.LernCostRequest structure - requestData := gsmaster.LernCostRequest{ - CharId: 20, // CharacterID = 20 - Name: "Athletik", // SkillName = Athletik - CurrentLevel: 9, // CurrentLevel = 9 - Type: "skill", // Type = skill - Action: "improve", // Action = improve (since we have current level) - TargetLevel: 0, // TargetLevel = 0 (will calculate up to level 18) - UsePP: 0, // No practice points used - UseGold: 0, - Reward: &[]string{"default"}[0], // Default reward type - } - requestBody, _ := json.Marshal(requestData) - - // Create HTTP request - req, _ := http.NewRequest("POST", "/api/characters/lerncost", bytes.NewBuffer(requestBody)) - req.Header.Set("Content-Type", "application/json") - - // Create response recorder - w := httptest.NewRecorder() - - // Create Gin context - c, _ := gin.CreateTestContext(w) - c.Request = req - c.Params = []gin.Param{{Key: "id", Value: "20"}} - - fmt.Printf("Test: GetLernCost for Athletik improvement for Krieger character ID 20\n") - fmt.Printf("Request: CharId=%d, SkillName=%s, CurrentLevel=%d, TargetLevel=%d\n", - requestData.CharId, requestData.Name, requestData.CurrentLevel, requestData.TargetLevel) - - // Call the actual handler function - GetLernCost(c) - - // Print the actual response to see what we get - fmt.Printf("Response Status: %d\n", w.Code) - fmt.Printf("Response Body: %s\n", w.Body.String()) - - // Check if we got an error response first - if w.Code != http.StatusOK { - var errorResponse map[string]interface{} - err := json.Unmarshal(w.Body.Bytes(), &errorResponse) - if err == nil { - fmt.Printf("Error Response: %+v\n", errorResponse) - } - assert.Fail(t, "Expected successful response but got error: %s", w.Body.String()) - return - } - - // Parse and validate response for success case - var response []gsmaster.SkillCostResultNew - err := json.Unmarshal(w.Body.Bytes(), &response) - assert.NoError(t, err, "Response should be valid JSON array of SkillCostResultNew") - - // Should have costs for levels 10, 11, 12, ... up to 18 (from current level 9) - assert.Greater(t, len(response), 0, "Should return learning costs for multiple levels") - assert.LessOrEqual(t, len(response), 9, "Should not return more than 9 levels (10-18)") - - // Validate the first entry (level 10) - if len(response) > 0 { - firstResult := response[0] - assert.Equal(t, "20", firstResult.CharacterID, "Character ID should match") - assert.Equal(t, "Athletik", firstResult.SkillName, "Skill name should match") - assert.Equal(t, 10, firstResult.TargetLevel, "First target level should be 10") - - // Character class should be "Kr" (abbreviation for "Krieger") - assert.Equal(t, "Kr", firstResult.CharacterClass, "Character class should be abbreviated to 'Kr'") - - // Should have valid costs - assert.Greater(t, firstResult.EP, 0, "EP cost should be greater than 0") - assert.GreaterOrEqual(t, firstResult.GoldCost, 0, "Gold cost should be 0 or greater") - - fmt.Printf("Level 10 cost: EP=%d, GoldCost=%d, LE=%d\n", - firstResult.EP, firstResult.GoldCost, firstResult.LE) - fmt.Printf("Category=%s, Difficulty=%s\n", - firstResult.Category, firstResult.Difficulty) - } - - // Find cost for level 12 specifically to test mid-range - var level12Cost *gsmaster.SkillCostResultNew - for i := range response { - if response[i].TargetLevel == 12 { - level12Cost = &response[i] - break - } - } - - if level12Cost != nil { - assert.Equal(t, 12, level12Cost.TargetLevel, "Target level should be 12") - assert.Greater(t, level12Cost.EP, 0, "EP cost should be greater than 0 for level 12") - - fmt.Printf("Level 12 cost: EP=%d, GoldCost=%d, LE=%d\n", - level12Cost.EP, level12Cost.GoldCost, level12Cost.LE) - } else { - fmt.Printf("No cost found for level 12. Available levels: ") - for _, cost := range response { - fmt.Printf("%d ", cost.TargetLevel) - } - fmt.Println() - } - - // Verify all target levels are sequential and start from current level + 1 - expectedLevel := 10 // Current level 9 + 1 - for _, cost := range response { - assert.Equal(t, expectedLevel, cost.TargetLevel, - "Target levels should be sequential starting from %d", expectedLevel) - assert.Equal(t, "Athletik", cost.SkillName, "All entries should have correct skill name") - assert.Equal(t, "Kr", cost.CharacterClass, "All entries should have correct character class") - expectedLevel++ - } - }) - - t.Run("GetLernCost Athletik - Detailed Cost Analysis for Each Level", func(t *testing.T) { - requestData := gsmaster.LernCostRequest{ - CharId: 20, - Name: "Athletik", - CurrentLevel: 9, - Type: "skill", - Action: "improve", - TargetLevel: 0, // Calculate all levels - UsePP: 0, - UseGold: 0, - Reward: &[]string{"default"}[0], - } - requestBody, _ := json.Marshal(requestData) - - req, _ := http.NewRequest("POST", "/api/characters/lerncost", bytes.NewBuffer(requestBody)) - req.Header.Set("Content-Type", "application/json") - - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Request = req - - GetLernCost(c) - - assert.Equal(t, http.StatusOK, w.Code, "Request should succeed") - - var response []gsmaster.SkillCostResultNew - err := json.Unmarshal(w.Body.Bytes(), &response) - assert.NoError(t, err, "Response should be valid JSON") - - fmt.Printf("\n=== Detailed Cost Analysis for Athletik (Levels 10-18) ===\n") - fmt.Printf("Level | EP Cost | Gold Cost | LE Cost | PP Used | Gold Used\n") - fmt.Printf("------|---------|-----------|---------|---------|----------\n") - - for _, cost := range response { - fmt.Printf("%5d | %7d | %9d | %7d | %7d | %9d\n", - cost.TargetLevel, cost.EP, cost.GoldCost, cost.LE, cost.PPUsed, cost.GoldUsed) - - // Validate each level's costs - assert.Greater(t, cost.EP, 0, "EP cost should be positive for level %d", cost.TargetLevel) - assert.GreaterOrEqual(t, cost.GoldCost, 0, "Gold cost should be non-negative for level %d", cost.TargetLevel) - assert.GreaterOrEqual(t, cost.LE, 0, "LE cost should be non-negative for level %d", cost.TargetLevel) - assert.Equal(t, 0, cost.PPUsed, "PP Used should be 0 when UsePP=0 for level %d", cost.TargetLevel) - assert.Equal(t, 0, cost.GoldUsed, "Gold Used should be 0 when UseGold=0 for level %d", cost.TargetLevel) - - // Verify cost progression (higher levels should generally cost more) - if cost.TargetLevel > 10 { - prevLevel := cost.TargetLevel - 1 - var prevCost *gsmaster.SkillCostResultNew - for i := range response { - if response[i].TargetLevel == prevLevel { - prevCost = &response[i] - break - } - } - if prevCost != nil { - assert.GreaterOrEqual(t, cost.EP, prevCost.EP, - "EP cost should not decrease from level %d to %d", prevLevel, cost.TargetLevel) - } - } - } - }) - - t.Run("GetLernCost Athletik - With Practice Points Usage", func(t *testing.T) { - testCases := []struct { - usePP int - description string - }{ - {5, "Using 5 Practice Points"}, - {10, "Using 10 Practice Points"}, - {20, "Using 20 Practice Points"}, - } - - for _, tc := range testCases { - t.Run(tc.description, func(t *testing.T) { - requestData := gsmaster.LernCostRequest{ - CharId: 20, - Name: "Athletik", - CurrentLevel: 9, - Type: "skill", - Action: "improve", - TargetLevel: 0, - UsePP: tc.usePP, - UseGold: 0, - Reward: &[]string{"default"}[0], - } - requestBody, _ := json.Marshal(requestData) - - req, _ := http.NewRequest("POST", "/api/characters/lerncost", bytes.NewBuffer(requestBody)) - req.Header.Set("Content-Type", "application/json") - - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Request = req - - GetLernCost(c) - - assert.Equal(t, http.StatusOK, w.Code, "Request should succeed for UsePP=%d", tc.usePP) - - var response []gsmaster.SkillCostResultNew - err := json.Unmarshal(w.Body.Bytes(), &response) - assert.NoError(t, err, "Response should be valid JSON") - - fmt.Printf("\n=== Cost Analysis with %d Practice Points ===\n", tc.usePP) - fmt.Printf("Level | EP Cost | Gold Cost | LE Cost | PP Used | Gold Used\n") - fmt.Printf("------|---------|-----------|---------|---------|----------\n") - - for i, cost := range response { - fmt.Printf("%5d | %7d | %9d | %7d | %7d | %9d\n", - cost.TargetLevel, cost.EP, cost.GoldCost, cost.LE, cost.PPUsed, cost.GoldUsed) - - // Simple validation: PP should be reasonable and Gold should be 0 - assert.LessOrEqual(t, cost.PPUsed, 50, "PP Used should be reasonable for level %d", cost.TargetLevel) - assert.Equal(t, 0, cost.GoldUsed, "Gold Used should be 0 when UseGold=0 for level %d", cost.TargetLevel) - - // EP cost should be non-negative - assert.GreaterOrEqual(t, cost.EP, 0, "EP cost should be non-negative for level %d", cost.TargetLevel) - - // When enough PP are available, early levels should have 0 EP cost - if i == 0 && tc.usePP >= 2 { - assert.Equal(t, 0, cost.EP, "Level 10 should have 0 EP cost when enough PP available") - } - - // EP cost validation - if cost.PPUsed > 0 { - // When PP are used, EP should be reduced or zero - assert.GreaterOrEqual(t, cost.EP, 0, "EP cost should be non-negative for level %d", cost.TargetLevel) - } else { - // When no PP are used, EP should be positive - assert.Greater(t, cost.EP, 0, "EP cost should be positive when no PP used for level %d", cost.TargetLevel) - } - } - }) - } - }) - - t.Run("GetLernCost Athletik - With Gold Usage", func(t *testing.T) { - testCases := []struct { - useGold int - description string - }{ - {50, "Using 50 Gold"}, - {100, "Using 100 Gold"}, - {200, "Using 200 Gold"}, - } - - for _, tc := range testCases { - t.Run(tc.description, func(t *testing.T) { - requestData := gsmaster.LernCostRequest{ - CharId: 20, - Name: "Athletik", - CurrentLevel: 9, - Type: "skill", - Action: "improve", - TargetLevel: 0, - UsePP: 0, - UseGold: tc.useGold, - Reward: &[]string{"default"}[0], - } - requestBody, _ := json.Marshal(requestData) - - req, _ := http.NewRequest("POST", "/api/characters/lerncost", bytes.NewBuffer(requestBody)) - req.Header.Set("Content-Type", "application/json") - - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Request = req - - GetLernCost(c) - - assert.Equal(t, http.StatusOK, w.Code, "Request should succeed for UseGold=%d", tc.useGold) - - var response []gsmaster.SkillCostResultNew - err := json.Unmarshal(w.Body.Bytes(), &response) - assert.NoError(t, err, "Response should be valid JSON") - - fmt.Printf("\n=== Cost Analysis with %d Gold ===\n", tc.useGold) - fmt.Printf("Level | EP Cost | Gold Cost | LE Cost | PP Used | Gold Used\n") - fmt.Printf("------|---------|-----------|---------|---------|----------\n") - - for i, cost := range response { - fmt.Printf("%5d | %7d | %9d | %7d | %7d | %9d\n", - cost.TargetLevel, cost.EP, cost.GoldCost, cost.LE, cost.PPUsed, cost.GoldUsed) - - // Validate Gold usage based on EP needs and cumulative usage - remainingGold := tc.useGold - - // Calculate cumulative Gold usage for previous levels - for j := 0; j < i; j++ { - if j < len(response) { - remainingGold -= response[j].GoldUsed - } - } - - // Current level's expected Gold usage - epCostWithoutGold := cost.EP + (cost.GoldUsed / 10) // Reverse calculate original EP - maxGoldUsable := epCostWithoutGold * 10 // Max gold that can be used (10 gold = 1 EP) - - expectedGoldUsed := 0 - if remainingGold > 0 { - if remainingGold >= maxGoldUsable { - expectedGoldUsed = maxGoldUsable - } else { - expectedGoldUsed = remainingGold - } - } - - assert.Equal(t, expectedGoldUsed, cost.GoldUsed, "Gold Used should match calculated value for level %d (remaining Gold: %d, max usable: %d)", cost.TargetLevel, remainingGold, maxGoldUsable) - assert.Equal(t, 0, cost.PPUsed, "PP Used should be 0 when UsePP=0 for level %d", cost.TargetLevel) - - // EP cost validation - assert.GreaterOrEqual(t, cost.EP, 0, "EP cost should be non-negative for level %d", cost.TargetLevel) - } - }) - } - }) - - t.Run("GetLernCost Athletik - Combined PP and Gold Usage", func(t *testing.T) { - testCases := []struct { - usePP int - useGold int - description string - }{ - {10, 50, "Using 10 PP and 50 Gold"}, - {15, 100, "Using 15 PP and 100 Gold"}, - {25, 200, "Using 25 PP and 200 Gold"}, - } - - for _, tc := range testCases { - t.Run(tc.description, func(t *testing.T) { - requestData := gsmaster.LernCostRequest{ - CharId: 20, - Name: "Athletik", - CurrentLevel: 9, - Type: "skill", - Action: "improve", - TargetLevel: 0, - UsePP: tc.usePP, - UseGold: tc.useGold, - Reward: &[]string{"default"}[0], - } - requestBody, _ := json.Marshal(requestData) - - req, _ := http.NewRequest("POST", "/api/characters/lerncost", bytes.NewBuffer(requestBody)) - req.Header.Set("Content-Type", "application/json") - - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Request = req - - GetLernCost(c) - - assert.Equal(t, http.StatusOK, w.Code, "Request should succeed for UsePP=%d, UseGold=%d", tc.usePP, tc.useGold) - - var response []gsmaster.SkillCostResultNew - err := json.Unmarshal(w.Body.Bytes(), &response) - assert.NoError(t, err, "Response should be valid JSON") - - fmt.Printf("\n=== Cost Analysis with %d PP and %d Gold ===\n", tc.usePP, tc.useGold) - fmt.Printf("Level | EP Cost | Gold Cost | LE Cost | PP Used | Gold Used\n") - fmt.Printf("------|---------|-----------|---------|---------|----------\n") - - for _, cost := range response { - fmt.Printf("%5d | %7d | %9d | %7d | %7d | %9d\n", - cost.TargetLevel, cost.EP, cost.GoldCost, cost.LE, cost.PPUsed, cost.GoldUsed) - - // Calculate original TE needed (LE + PP Used = original TE) - teNeeded := cost.LE + cost.PPUsed - - // Calculate original EP before gold usage (EP + Gold/10 = original EP) - epAfterPP := cost.EP + (cost.GoldUsed / 10) - maxGoldUsable := epAfterPP * 10 - - // Validate that resources are used reasonably - assert.LessOrEqual(t, cost.PPUsed, teNeeded, "PP Used should not exceed TE needed for level %d", cost.TargetLevel) - assert.LessOrEqual(t, cost.GoldUsed, maxGoldUsable, "Gold Used should not exceed max usable for level %d", cost.TargetLevel) - - // EP cost should be non-negative - assert.GreaterOrEqual(t, cost.EP, 0, "EP cost should be non-negative for level %d", cost.TargetLevel) - } - }) - } - }) - - t.Run("GetLernCost Athletik - Cost Comparison Baseline vs Resources", func(t *testing.T) { - // First get baseline costs (no resources used) - baselineRequest := gsmaster.LernCostRequest{ - CharId: 20, - Name: "Athletik", - CurrentLevel: 9, - Type: "skill", - Action: "improve", - TargetLevel: 0, - UsePP: 0, - UseGold: 0, - Reward: &[]string{"default"}[0], - } - baselineBody, _ := json.Marshal(baselineRequest) - - req, _ := http.NewRequest("POST", "/api/characters/lerncost", bytes.NewBuffer(baselineBody)) - req.Header.Set("Content-Type", "application/json") - - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Request = req - - GetLernCost(c) - - var baselineResponse []gsmaster.SkillCostResultNew - err := json.Unmarshal(w.Body.Bytes(), &baselineResponse) - assert.NoError(t, err, "Baseline response should be valid JSON") - - // Now get costs with resources - resourceRequest := gsmaster.LernCostRequest{ - CharId: 20, - Name: "Athletik", - CurrentLevel: 9, - Type: "skill", - Action: "improve", - TargetLevel: 0, - UsePP: 15, - UseGold: 100, - Reward: &[]string{"default"}[0], - } - resourceBody, _ := json.Marshal(resourceRequest) - - req2, _ := http.NewRequest("POST", "/api/characters/lerncost", bytes.NewBuffer(resourceBody)) - req2.Header.Set("Content-Type", "application/json") - - w2 := httptest.NewRecorder() - c2, _ := gin.CreateTestContext(w2) - c2.Request = req2 - - GetLernCost(c2) - - var resourceResponse []gsmaster.SkillCostResultNew - err = json.Unmarshal(w2.Body.Bytes(), &resourceResponse) - assert.NoError(t, err, "Resource response should be valid JSON") - - // Compare the results - fmt.Printf("\n=== Cost Comparison: Baseline vs Using Resources ===\n") - fmt.Printf("Level | Baseline EP | Resource EP | EP Saved | PP Used | Gold Used\n") - fmt.Printf("------|-------------|-------------|----------|---------|----------\n") - - assert.Equal(t, len(baselineResponse), len(resourceResponse), "Both responses should have same number of levels") - - for i, baseline := range baselineResponse { - if i < len(resourceResponse) { - resource := resourceResponse[i] - assert.Equal(t, baseline.TargetLevel, resource.TargetLevel, "Target levels should match") - - epSaved := baseline.EP - resource.EP - fmt.Printf("%5d | %11d | %11d | %8d | %7d | %9d\n", - baseline.TargetLevel, baseline.EP, resource.EP, epSaved, resource.PPUsed, resource.GoldUsed) - - // Validate that using resources reduces EP cost (or at least doesn't increase it) - assert.LessOrEqual(t, resource.EP, baseline.EP, - "EP cost should not increase when using resources for level %d", baseline.TargetLevel) - } - } - }) - - t.Run("GetLernCost with invalid character ID", func(t *testing.T) { - // Test with non-existent character ID - requestData := gsmaster.LernCostRequest{ - CharId: 999, // Non-existent character - Name: "Athletik", - CurrentLevel: 9, - Type: "skill", - Action: "improve", - TargetLevel: 0, - UsePP: 0, - Reward: &[]string{"default"}[0], - } - requestBody, _ := json.Marshal(requestData) - - req, _ := http.NewRequest("POST", "/api/characters/999/lerncost", bytes.NewBuffer(requestBody)) - req.Header.Set("Content-Type", "application/json") - - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Request = req - c.Params = []gin.Param{{Key: "id", Value: "999"}} - - GetLernCost(c) - - // Should return 404 Not Found - assert.Equal(t, http.StatusNotFound, w.Code, "Status code should be 404 Not Found") - - var response map[string]interface{} - err := json.Unmarshal(w.Body.Bytes(), &response) - assert.NoError(t, err, "Response should be valid JSON") - assert.Contains(t, response, "error", "Response should contain error message") - - fmt.Printf("Error case - Invalid character ID: %s\n", response["error"]) - }) - - t.Run("GetLernCost with invalid request structure", func(t *testing.T) { - // Test with missing required fields - requestData := map[string]interface{}{ - "char_id": "invalid", // Invalid type - should be uint - "name": "", // Empty name - } - requestBody, _ := json.Marshal(requestData) - - req, _ := http.NewRequest("POST", "/api/characters/20/lerncost", bytes.NewBuffer(requestBody)) - req.Header.Set("Content-Type", "application/json") - - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Request = req - c.Params = []gin.Param{{Key: "id", Value: "20"}} - - GetLernCost(c) - - // Should return 400 Bad Request - assert.Equal(t, http.StatusBadRequest, w.Code, "Status code should be 400 Bad Request") - - var response map[string]interface{} - err := json.Unmarshal(w.Body.Bytes(), &response) - assert.NoError(t, err, "Response should be valid JSON") - assert.Contains(t, response, "error", "Response should contain error message") - - fmt.Printf("Error case - Invalid request: %s\n", response["error"]) - }) -} // Test GetLernCost endpoint specifically with gsmaster.LernCostRequest structure func TestGetLernCostEndpointNewSystem(t *testing.T) { @@ -1021,428 +220,4 @@ func TestGetLernCostEndpointNewSystem(t *testing.T) { } }) - t.Run("GetLernCost Athletik - Detailed Cost Analysis for Each Level", func(t *testing.T) { - requestData := gsmaster.LernCostRequest{ - CharId: 20, - Name: "Athletik", - CurrentLevel: 9, - Type: "skill", - Action: "improve", - TargetLevel: 0, // Calculate all levels - UsePP: 0, - UseGold: 0, - Reward: &[]string{"default"}[0], - } - requestBody, _ := json.Marshal(requestData) - - req, _ := http.NewRequest("POST", "/api/characters/lerncost", bytes.NewBuffer(requestBody)) - req.Header.Set("Content-Type", "application/json") - - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Request = req - - GetLernCost(c) - - assert.Equal(t, http.StatusOK, w.Code, "Request should succeed") - - var response []gsmaster.SkillCostResultNew - err := json.Unmarshal(w.Body.Bytes(), &response) - assert.NoError(t, err, "Response should be valid JSON") - - fmt.Printf("\n=== Detailed Cost Analysis for Athletik (Levels 10-18) ===\n") - fmt.Printf("Level | EP Cost | Gold Cost | LE Cost | PP Used | Gold Used\n") - fmt.Printf("------|---------|-----------|---------|---------|----------\n") - - for _, cost := range response { - fmt.Printf("%5d | %7d | %9d | %7d | %7d | %9d\n", - cost.TargetLevel, cost.EP, cost.GoldCost, cost.LE, cost.PPUsed, cost.GoldUsed) - - // Validate each level's costs - assert.Greater(t, cost.EP, 0, "EP cost should be positive for level %d", cost.TargetLevel) - assert.GreaterOrEqual(t, cost.GoldCost, 0, "Gold cost should be non-negative for level %d", cost.TargetLevel) - assert.GreaterOrEqual(t, cost.LE, 0, "LE cost should be non-negative for level %d", cost.TargetLevel) - assert.Equal(t, 0, cost.PPUsed, "PP Used should be 0 when UsePP=0 for level %d", cost.TargetLevel) - assert.Equal(t, 0, cost.GoldUsed, "Gold Used should be 0 when UseGold=0 for level %d", cost.TargetLevel) - - // Verify cost progression (higher levels should generally cost more) - if cost.TargetLevel > 10 { - prevLevel := cost.TargetLevel - 1 - var prevCost *gsmaster.SkillCostResultNew - for i := range response { - if response[i].TargetLevel == prevLevel { - prevCost = &response[i] - break - } - } - if prevCost != nil { - assert.GreaterOrEqual(t, cost.EP, prevCost.EP, - "EP cost should not decrease from level %d to %d", prevLevel, cost.TargetLevel) - } - } - } - }) - - t.Run("GetLernCost Athletik - With Practice Points Usage", func(t *testing.T) { - testCases := []struct { - usePP int - description string - }{ - {5, "Using 5 Practice Points"}, - {10, "Using 10 Practice Points"}, - {20, "Using 20 Practice Points"}, - } - - for _, tc := range testCases { - t.Run(tc.description, func(t *testing.T) { - requestData := gsmaster.LernCostRequest{ - CharId: 20, - Name: "Athletik", - CurrentLevel: 9, - Type: "skill", - Action: "improve", - TargetLevel: 0, - UsePP: tc.usePP, - UseGold: 0, - Reward: &[]string{"default"}[0], - } - requestBody, _ := json.Marshal(requestData) - - req, _ := http.NewRequest("POST", "/api/characters/lerncost", bytes.NewBuffer(requestBody)) - req.Header.Set("Content-Type", "application/json") - - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Request = req - - GetLernCost(c) - - assert.Equal(t, http.StatusOK, w.Code, "Request should succeed for UsePP=%d", tc.usePP) - - var response []gsmaster.SkillCostResultNew - err := json.Unmarshal(w.Body.Bytes(), &response) - assert.NoError(t, err, "Response should be valid JSON") - - fmt.Printf("\n=== Cost Analysis with %d Practice Points ===\n", tc.usePP) - fmt.Printf("Level | EP Cost | Gold Cost | LE Cost | PP Used | Gold Used\n") - fmt.Printf("------|---------|-----------|---------|---------|----------\n") - - for i, cost := range response { - fmt.Printf("%5d | %7d | %9d | %7d | %7d | %9d\n", - cost.TargetLevel, cost.EP, cost.GoldCost, cost.LE, cost.PPUsed, cost.GoldUsed) - - // Simple validation: PP should be reasonable and Gold should be 0 - assert.LessOrEqual(t, cost.PPUsed, 50, "PP Used should be reasonable for level %d", cost.TargetLevel) - assert.Equal(t, 0, cost.GoldUsed, "Gold Used should be 0 when UseGold=0 for level %d", cost.TargetLevel) - - // EP cost should be non-negative - assert.GreaterOrEqual(t, cost.EP, 0, "EP cost should be non-negative for level %d", cost.TargetLevel) - - // When enough PP are available, early levels should have 0 EP cost - if i == 0 && tc.usePP >= 2 { - assert.Equal(t, 0, cost.EP, "Level 10 should have 0 EP cost when enough PP available") - } - - // EP cost validation - if cost.PPUsed > 0 { - // When PP are used, EP should be reduced or zero - assert.GreaterOrEqual(t, cost.EP, 0, "EP cost should be non-negative for level %d", cost.TargetLevel) - } else { - // When no PP are used, EP should be positive - assert.Greater(t, cost.EP, 0, "EP cost should be positive when no PP used for level %d", cost.TargetLevel) - } - } - }) - } - }) - - t.Run("GetLernCost Athletik - With Gold Usage", func(t *testing.T) { - testCases := []struct { - useGold int - description string - }{ - {50, "Using 50 Gold"}, - {100, "Using 100 Gold"}, - {200, "Using 200 Gold"}, - } - - for _, tc := range testCases { - t.Run(tc.description, func(t *testing.T) { - requestData := gsmaster.LernCostRequest{ - CharId: 20, - Name: "Athletik", - CurrentLevel: 9, - Type: "skill", - Action: "improve", - TargetLevel: 0, - UsePP: 0, - UseGold: tc.useGold, - Reward: &[]string{"default"}[0], - } - requestBody, _ := json.Marshal(requestData) - - req, _ := http.NewRequest("POST", "/api/characters/lerncost", bytes.NewBuffer(requestBody)) - req.Header.Set("Content-Type", "application/json") - - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Request = req - - GetLernCost(c) - - assert.Equal(t, http.StatusOK, w.Code, "Request should succeed for UseGold=%d", tc.useGold) - - var response []gsmaster.SkillCostResultNew - err := json.Unmarshal(w.Body.Bytes(), &response) - assert.NoError(t, err, "Response should be valid JSON") - - fmt.Printf("\n=== Cost Analysis with %d Gold ===\n", tc.useGold) - fmt.Printf("Level | EP Cost | Gold Cost | LE Cost | PP Used | Gold Used\n") - fmt.Printf("------|---------|-----------|---------|---------|----------\n") - - for i, cost := range response { - fmt.Printf("%5d | %7d | %9d | %7d | %7d | %9d\n", - cost.TargetLevel, cost.EP, cost.GoldCost, cost.LE, cost.PPUsed, cost.GoldUsed) - - // Validate Gold usage based on EP needs and cumulative usage - remainingGold := tc.useGold - - // Calculate cumulative Gold usage for previous levels - for j := 0; j < i; j++ { - if j < len(response) { - remainingGold -= response[j].GoldUsed - } - } - - // Current level's expected Gold usage - epCostWithoutGold := cost.EP + (cost.GoldUsed / 10) // Reverse calculate original EP - maxGoldUsable := epCostWithoutGold * 10 // Max gold that can be used (10 gold = 1 EP) - - expectedGoldUsed := 0 - if remainingGold > 0 { - if remainingGold >= maxGoldUsable { - expectedGoldUsed = maxGoldUsable - } else { - expectedGoldUsed = remainingGold - } - } - - assert.Equal(t, expectedGoldUsed, cost.GoldUsed, "Gold Used should match calculated value for level %d (remaining Gold: %d, max usable: %d)", cost.TargetLevel, remainingGold, maxGoldUsable) - assert.Equal(t, 0, cost.PPUsed, "PP Used should be 0 when UsePP=0 for level %d", cost.TargetLevel) - - // EP cost validation - assert.GreaterOrEqual(t, cost.EP, 0, "EP cost should be non-negative for level %d", cost.TargetLevel) - } - }) - } - }) - - t.Run("GetLernCost Athletik - Combined PP and Gold Usage", func(t *testing.T) { - testCases := []struct { - usePP int - useGold int - description string - }{ - {10, 50, "Using 10 PP and 50 Gold"}, - {15, 100, "Using 15 PP and 100 Gold"}, - {25, 200, "Using 25 PP and 200 Gold"}, - } - - for _, tc := range testCases { - t.Run(tc.description, func(t *testing.T) { - requestData := gsmaster.LernCostRequest{ - CharId: 20, - Name: "Athletik", - CurrentLevel: 9, - Type: "skill", - Action: "improve", - TargetLevel: 0, - UsePP: tc.usePP, - UseGold: tc.useGold, - Reward: &[]string{"default"}[0], - } - requestBody, _ := json.Marshal(requestData) - - req, _ := http.NewRequest("POST", "/api/characters/lerncost", bytes.NewBuffer(requestBody)) - req.Header.Set("Content-Type", "application/json") - - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Request = req - - GetLernCost(c) - - assert.Equal(t, http.StatusOK, w.Code, "Request should succeed for UsePP=%d, UseGold=%d", tc.usePP, tc.useGold) - - var response []gsmaster.SkillCostResultNew - err := json.Unmarshal(w.Body.Bytes(), &response) - assert.NoError(t, err, "Response should be valid JSON") - - fmt.Printf("\n=== Cost Analysis with %d PP and %d Gold ===\n", tc.usePP, tc.useGold) - fmt.Printf("Level | EP Cost | Gold Cost | LE Cost | PP Used | Gold Used\n") - fmt.Printf("------|---------|-----------|---------|---------|----------\n") - - for _, cost := range response { - fmt.Printf("%5d | %7d | %9d | %7d | %7d | %9d\n", - cost.TargetLevel, cost.EP, cost.GoldCost, cost.LE, cost.PPUsed, cost.GoldUsed) - - // Calculate original TE needed (LE + PP Used = original TE) - teNeeded := cost.LE + cost.PPUsed - - // Calculate original EP before gold usage (EP + Gold/10 = original EP) - epAfterPP := cost.EP + (cost.GoldUsed / 10) - maxGoldUsable := epAfterPP * 10 - - // Validate that resources are used reasonably - assert.LessOrEqual(t, cost.PPUsed, teNeeded, "PP Used should not exceed TE needed for level %d", cost.TargetLevel) - assert.LessOrEqual(t, cost.GoldUsed, maxGoldUsable, "Gold Used should not exceed max usable for level %d", cost.TargetLevel) - - // EP cost should be non-negative - assert.GreaterOrEqual(t, cost.EP, 0, "EP cost should be non-negative for level %d", cost.TargetLevel) - } - }) - } - }) - - t.Run("GetLernCost Athletik - Cost Comparison Baseline vs Resources", func(t *testing.T) { - // First get baseline costs (no resources used) - baselineRequest := gsmaster.LernCostRequest{ - CharId: 20, - Name: "Athletik", - CurrentLevel: 9, - Type: "skill", - Action: "improve", - TargetLevel: 0, - UsePP: 0, - UseGold: 0, - Reward: &[]string{"default"}[0], - } - baselineBody, _ := json.Marshal(baselineRequest) - - req, _ := http.NewRequest("POST", "/api/characters/lerncost", bytes.NewBuffer(baselineBody)) - req.Header.Set("Content-Type", "application/json") - - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Request = req - - GetLernCost(c) - - var baselineResponse []gsmaster.SkillCostResultNew - err := json.Unmarshal(w.Body.Bytes(), &baselineResponse) - assert.NoError(t, err, "Baseline response should be valid JSON") - - // Now get costs with resources - resourceRequest := gsmaster.LernCostRequest{ - CharId: 20, - Name: "Athletik", - CurrentLevel: 9, - Type: "skill", - Action: "improve", - TargetLevel: 0, - UsePP: 15, - UseGold: 100, - Reward: &[]string{"default"}[0], - } - resourceBody, _ := json.Marshal(resourceRequest) - - req2, _ := http.NewRequest("POST", "/api/characters/lerncost", bytes.NewBuffer(resourceBody)) - req2.Header.Set("Content-Type", "application/json") - - w2 := httptest.NewRecorder() - c2, _ := gin.CreateTestContext(w2) - c2.Request = req2 - - GetLernCost(c2) - - var resourceResponse []gsmaster.SkillCostResultNew - err = json.Unmarshal(w2.Body.Bytes(), &resourceResponse) - assert.NoError(t, err, "Resource response should be valid JSON") - - // Compare the results - fmt.Printf("\n=== Cost Comparison: Baseline vs Using Resources ===\n") - fmt.Printf("Level | Baseline EP | Resource EP | EP Saved | PP Used | Gold Used\n") - fmt.Printf("------|-------------|-------------|----------|---------|----------\n") - - assert.Equal(t, len(baselineResponse), len(resourceResponse), "Both responses should have same number of levels") - - for i, baseline := range baselineResponse { - if i < len(resourceResponse) { - resource := resourceResponse[i] - assert.Equal(t, baseline.TargetLevel, resource.TargetLevel, "Target levels should match") - - epSaved := baseline.EP - resource.EP - fmt.Printf("%5d | %11d | %11d | %8d | %7d | %9d\n", - baseline.TargetLevel, baseline.EP, resource.EP, epSaved, resource.PPUsed, resource.GoldUsed) - - // Validate that using resources reduces EP cost (or at least doesn't increase it) - assert.LessOrEqual(t, resource.EP, baseline.EP, - "EP cost should not increase when using resources for level %d", baseline.TargetLevel) - } - } - }) - - t.Run("GetLernCost with invalid character ID", func(t *testing.T) { - // Test with non-existent character ID - requestData := gsmaster.LernCostRequest{ - CharId: 999, // Non-existent character - Name: "Athletik", - CurrentLevel: 9, - Type: "skill", - Action: "improve", - TargetLevel: 0, - UsePP: 0, - Reward: &[]string{"default"}[0], - } - requestBody, _ := json.Marshal(requestData) - - req, _ := http.NewRequest("POST", "/api/characters/999/lerncost", bytes.NewBuffer(requestBody)) - req.Header.Set("Content-Type", "application/json") - - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Request = req - c.Params = []gin.Param{{Key: "id", Value: "999"}} - - GetLernCost(c) - - // Should return 404 Not Found - assert.Equal(t, http.StatusNotFound, w.Code, "Status code should be 404 Not Found") - - var response map[string]interface{} - err := json.Unmarshal(w.Body.Bytes(), &response) - assert.NoError(t, err, "Response should be valid JSON") - assert.Contains(t, response, "error", "Response should contain error message") - - fmt.Printf("Error case - Invalid character ID: %s\n", response["error"]) - }) - - t.Run("GetLernCost with invalid request structure", func(t *testing.T) { - // Test with missing required fields - requestData := map[string]interface{}{ - "char_id": "invalid", // Invalid type - should be uint - "name": "", // Empty name - } - requestBody, _ := json.Marshal(requestData) - - req, _ := http.NewRequest("POST", "/api/characters/20/lerncost", bytes.NewBuffer(requestBody)) - req.Header.Set("Content-Type", "application/json") - - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Request = req - c.Params = []gin.Param{{Key: "id", Value: "20"}} - - GetLernCost(c) - - // Should return 400 Bad Request - assert.Equal(t, http.StatusBadRequest, w.Code, "Status code should be 400 Bad Request") - - var response map[string]interface{} - err := json.Unmarshal(w.Body.Bytes(), &response) - assert.NoError(t, err, "Response should be valid JSON") - assert.Contains(t, response, "error", "Response should contain error message") - - fmt.Printf("Error case - Invalid request: %s\n", response["error"]) - }) } diff --git a/backend/character/routes.go b/backend/character/routes.go index 0641e46..2894ba2 100644 --- a/backend/character/routes.go +++ b/backend/character/routes.go @@ -28,12 +28,12 @@ func RegisterRoutes(r *gin.RouterGroup) { // Lernen und Verbessern (mit automatischem Audit-Log) charGrp.POST("/:id/learn-skill-new", LearnSkill) // Fertigkeit lernen (neues System) - charGrp.POST("/:id/learn-skill", LearnSkillOld) // Fertigkeit lernen (altes System) + //charGrp.POST("/:id/learn-skill", LearnSkillOld) // Fertigkeit lernen (altes System) charGrp.POST("/:id/learn-spell-new", LearnSpell) // Zauber lernen (neues System) - charGrp.POST("/:id/learn-spell", LearnSpellOld) // Zauber lernen (altes System) + //charGrp.POST("/:id/learn-spell", LearnSpellOld) // Zauber lernen (altes System) // Fertigkeiten-Information - charGrp.GET("/:id/available-skills", GetAvailableSkillsOld) // Verfügbare Fertigkeiten mit Kosten (bereits gelernte ausgeschlossen) + //charGrp.GET("/:id/available-skills", GetAvailableSkillsOld) // Verfügbare Fertigkeiten mit Kosten (bereits gelernte ausgeschlossen) charGrp.POST("/available-skills-new", GetAvailableSkillsNewSystem) // Verfügbare Fertigkeiten mit Kosten (bereits gelernte ausgeschlossen) charGrp.POST("/available-skills-creation", GetAvailableSkillsForCreation) // Verfügbare Fertigkeiten mit Lernkosten für Charaktererstellung charGrp.POST("/available-spells-creation", GetAvailableSpellsForCreation) // Verfügbare Zauber mit Lernkosten für Charaktererstellung @@ -41,7 +41,7 @@ func RegisterRoutes(r *gin.RouterGroup) { charGrp.GET("/spell-details", GetSpellDetails) // Detaillierte Informationen zu einem bestimmten Zauber // Belohnungsarten für verschiedene Lernszenarien - charGrp.GET("/:id/reward-types", GetRewardTypesOld) // Verfügbare Belohnungsarten je nach Kontext + charGrp.GET("/:id/reward-types", GetRewardTypesStatic) // Verfügbare Belohnungsarten je nach Kontext // Praxispunkte-Verwaltung charGrp.GET("/:id/practice-points", GetPracticePoints) // NewSystem @@ -50,8 +50,8 @@ func RegisterRoutes(r *gin.RouterGroup) { charGrp.POST("/:id/practice-points/use", UsePracticePoint) // NewSystem // System-Information - charGrp.GET("/character-classes", GetCharacterClassesHandlerOld) - charGrp.GET("/skill-categories", GetSkillCategoriesHandlerOld) + //charGrp.GET("/character-classes", GetCharacterClassesHandlerOld) + charGrp.GET("/skill-categories", GetSkillCategoriesHandlerStatic) // Character Creation charGrp.GET("/create-sessions", ListCharacterSessions) // Aktive Sessions für Benutzer auflisten diff --git a/backend/character/skill_api_test.go b/backend/character/skill_api_test.go deleted file mode 100644 index e56c9be..0000000 --- a/backend/character/skill_api_test.go +++ /dev/null @@ -1,259 +0,0 @@ -package character - -import ( - "encoding/json" - "fmt" - "net/http" - "net/http/httptest" - "testing" - - "bamort/database" - "bamort/models" - - "github.com/gin-gonic/gin" - "github.com/stretchr/testify/assert" -) - -func TestGetAvailableSkills(t *testing.T) { - // Setup test database with real data - database.SetupTestDB(true, true) - defer database.ResetTestDB() - - // Setup Gin in test mode - gin.SetMode(gin.TestMode) - - t.Run("Get available skills for existing character - default reward type", func(t *testing.T) { - // Get a character ID from the test data - var testChar models.Char - err := database.DB.Preload("Fertigkeiten").Preload("Erfahrungsschatz").Preload("Vermoegen").First(&testChar).Error - assert.NoError(t, err, "Should find a test character") - - // Gib dem Charakter genug EP und Gold für Tests - testChar.Erfahrungsschatz.EP = 1000 - testChar.Vermoegen.Goldstücke = 1000 - database.DB.Save(&testChar.Erfahrungsschatz) - database.DB.Save(&testChar.Vermoegen) - - characterID := fmt.Sprintf("%d", testChar.ID) - - // Create HTTP request with default reward type - req, _ := http.NewRequest("GET", fmt.Sprintf("/api/characters/%s/available-skills?reward_type=default", characterID), nil) - req.Header.Set("Content-Type", "application/json") - - // Create response recorder - w := httptest.NewRecorder() - - // Create Gin context - c, _ := gin.CreateTestContext(w) - c.Request = req - c.Params = []gin.Param{{Key: "id", Value: characterID}} - - fmt.Printf("Test: Get available skills for character ID %s (default reward)\n", characterID) - fmt.Printf("Character EP: %d, Gold: %d\n", testChar.Erfahrungsschatz.EP, testChar.Vermoegen.Goldstücke) - - // Call the handler function - GetAvailableSkillsOld(c) - - // Print the response for debugging - fmt.Printf("Response Status: %d\n", w.Code) - fmt.Printf("Response Body: %s\n", w.Body.String()) - - // Assert response status - assert.Equal(t, http.StatusOK, w.Code, "Status code should be 200 OK") - - // Parse response - var response map[string]interface{} - err = json.Unmarshal(w.Body.Bytes(), &response) - assert.NoError(t, err, "Response should be valid JSON") - - // Check response structure - assert.Contains(t, response, "skills_by_category", "Response should contain skills_by_category field") - - // Check if skills_by_category is an object - skillsByCategory, ok := response["skills_by_category"].(map[string]interface{}) - assert.True(t, ok, "skills_by_category should be an object") - - fmt.Printf("Found %d skill categories\n", len(skillsByCategory)) - - // Check each category - totalSkills := 0 - for categoryName, categorySkills := range skillsByCategory { - fmt.Printf("Category: %s\n", categoryName) - - skills, ok := categorySkills.([]interface{}) - assert.True(t, ok, fmt.Sprintf("Category %s should contain an array of skills", categoryName)) - - totalSkills += len(skills) - fmt.Printf(" Skills in category: %d\n", len(skills)) - - // Check structure of first skill in category if exists - if len(skills) > 0 { - firstSkill, ok := skills[0].(map[string]interface{}) - assert.True(t, ok, "First skill should be an object") - - // Check required fields - assert.Contains(t, firstSkill, "name", "Skill should have name field") - assert.Contains(t, firstSkill, "epCost", "Skill should have epCost field") - assert.Contains(t, firstSkill, "goldCost", "Skill should have goldCost field") - - skillName, ok := firstSkill["name"].(string) - assert.True(t, ok, "Skill name should be a string") - assert.NotEmpty(t, skillName, "Skill name should not be empty") - - epCost, ok := firstSkill["epCost"].(float64) - assert.True(t, ok, "EP cost should be a number") - assert.GreaterOrEqual(t, epCost, float64(0), "EP cost should be non-negative") - - goldCost, ok := firstSkill["goldCost"].(float64) - assert.True(t, ok, "Gold cost should be a number") - assert.GreaterOrEqual(t, goldCost, float64(0), "Gold cost should be non-negative") - - fmt.Printf(" First skill: %s (EP: %.0f, Gold: %.0f)\n", - skillName, epCost, goldCost) - } - } - - fmt.Printf("Total available skills: %d\n", totalSkills) - }) - - t.Run("Get available skills for existing character - noGold reward type", func(t *testing.T) { - // Get a character ID from the test data - var testChar models.Char - err := database.DB.Preload("Fertigkeiten").Preload("Erfahrungsschatz").Preload("Vermoegen").First(&testChar).Error - assert.NoError(t, err, "Should find a test character") - - // Gib dem Charakter genug EP und Gold für Tests - testChar.Erfahrungsschatz.EP = 1000 - testChar.Vermoegen.Goldstücke = 1000 - database.DB.Save(&testChar.Erfahrungsschatz) - database.DB.Save(&testChar.Vermoegen) - - characterID := fmt.Sprintf("%d", testChar.ID) - - // Create HTTP request with noGold reward type - req, _ := http.NewRequest("GET", fmt.Sprintf("/api/characters/%s/available-skills?reward_type=noGold", characterID), nil) - req.Header.Set("Content-Type", "application/json") - - // Create response recorder - w := httptest.NewRecorder() - - // Create Gin context - c, _ := gin.CreateTestContext(w) - c.Request = req - c.Params = []gin.Param{{Key: "id", Value: characterID}} - - fmt.Printf("Test: Get available skills for character ID %s (noGold reward)\n", characterID) - - // Call the handler function - GetAvailableSkillsOld(c) - - // Print the response for debugging - fmt.Printf("Response Status: %d\n", w.Code) - fmt.Printf("Response Body: %s\n", w.Body.String()) - - // Assert response status - assert.Equal(t, http.StatusOK, w.Code, "Status code should be 200 OK") - - // Parse response - var response map[string]interface{} - err = json.Unmarshal(w.Body.Bytes(), &response) - assert.NoError(t, err, "Response should be valid JSON") - - // Check response structure - assert.Contains(t, response, "skills_by_category", "Response should contain skills_by_category field") - - skillsByCategory, ok := response["skills_by_category"].(map[string]interface{}) - assert.True(t, ok, "skills_by_category should be an object") - - // Check that skills have goldCost = 0 for noGold reward type - for categoryName, categorySkills := range skillsByCategory { - skills, ok := categorySkills.([]interface{}) - assert.True(t, ok, fmt.Sprintf("Category %s should contain an array of skills", categoryName)) - - if len(skills) > 0 { - firstSkill, ok := skills[0].(map[string]interface{}) - assert.True(t, ok, "First skill should be an object") - - goldCost, ok := firstSkill["goldCost"].(float64) - assert.True(t, ok, "Gold cost should be a number") - assert.Equal(t, float64(0), goldCost, "Gold cost should be 0 for noGold reward type") - - fmt.Printf("Category %s - First skill gold cost: %.0f (should be 0)\n", categoryName, goldCost) - } - } - }) - - t.Run("Error case - character not found", func(t *testing.T) { - // Test with non-existent character ID - req, _ := http.NewRequest("GET", "/api/characters/99999/available-skills", nil) - req.Header.Set("Content-Type", "application/json") - - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Request = req - c.Params = []gin.Param{{Key: "id", Value: "99999"}} - - GetAvailableSkillsOld(c) - - // Should return 404 - assert.Equal(t, http.StatusNotFound, w.Code, "Status code should be 404 Not Found") - - var response map[string]interface{} - err := json.Unmarshal(w.Body.Bytes(), &response) - assert.NoError(t, err, "Response should be valid JSON") - assert.Contains(t, response, "error", "Response should contain error message") - }) - - t.Run("Check that learned skills are excluded", func(t *testing.T) { - // Get a character with some skills - var testChar models.Char - err := database.DB.Preload("Fertigkeiten").First(&testChar).Error - assert.NoError(t, err, "Should find a test character") - - characterID := fmt.Sprintf("%d", testChar.ID) - - // Get list of learned skills - learnedSkillNames := make(map[string]bool) - for _, skill := range testChar.Fertigkeiten { - learnedSkillNames[skill.Name] = true - } - - // Make request - req, _ := http.NewRequest("GET", fmt.Sprintf("/api/characters/%s/available-skills", characterID), nil) - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Request = req - c.Params = []gin.Param{{Key: "id", Value: characterID}} - - GetAvailableSkillsOld(c) - - assert.Equal(t, http.StatusOK, w.Code, "Status code should be 200 OK") - - var response map[string]interface{} - err = json.Unmarshal(w.Body.Bytes(), &response) - assert.NoError(t, err, "Response should be valid JSON") - - skillsByCategory, ok := response["skills_by_category"].(map[string]interface{}) - assert.True(t, ok, "skills_by_category should be an object") - - // Check that no learned skills appear in available skills - for categoryName, categorySkills := range skillsByCategory { - skills, ok := categorySkills.([]interface{}) - assert.True(t, ok, fmt.Sprintf("Category %s should contain an array of skills", categoryName)) - - for _, skillInterface := range skills { - skill, ok := skillInterface.(map[string]interface{}) - assert.True(t, ok, "Skill should be an object") - - skillName, ok := skill["name"].(string) - assert.True(t, ok, "Skill name should be a string") - - // Assert that this skill is not in the learned skills list - assert.False(t, learnedSkillNames[skillName], - fmt.Sprintf("Learned skill '%s' should not appear in available skills", skillName)) - } - } - - fmt.Printf("Verified that %d learned skills are excluded from available skills\n", len(testChar.Fertigkeiten)) - }) -} diff --git a/backend/character/skill_learning_dialog_test.go b/backend/character/skill_learning_dialog_test.go deleted file mode 100644 index 8b30273..0000000 --- a/backend/character/skill_learning_dialog_test.go +++ /dev/null @@ -1,296 +0,0 @@ -package character - -import ( - "bamort/gsmaster" - "bamort/models" - "encoding/json" - "net/http" - "net/http/httptest" - "testing" - - "github.com/gin-gonic/gin" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -// TestSkillLearningDialogWorkflow testet den kompletten Workflow, den das SkillLearningDialog verwendet -func TestSkillLearningDialogWorkflow(t *testing.T) { - // Setup - gin.SetMode(gin.TestMode) - - t.Run("Reward Types API - entspricht Frontend loadRewardTypes()", func(t *testing.T) { - // Test verschiedene Lerntypen wie sie das Frontend sendet - testCases := []struct { - learningType string - skillName string - skillType string - expectedLen int - }{ - {"improve", "Menschenkenntnis", "skill", 4}, // EP, Gold, PP, Mixed - {"learn", "Klettern", "skill", 2}, // EP, Gold - //{"spell", "Licht", "spell", 4}, // EP, Gold, PP, Mixed - } - - for _, tc := range testCases { - t.Run(tc.learningType+"_"+tc.skillName, func(t *testing.T) { - router := gin.New() - router.GET("/api/characters/:id/reward-types", GetRewardTypesOld) - - url := "/api/characters/1/reward-types?learning_type=" + tc.learningType + - "&skill_name=" + tc.skillName + "&skill_type=" + tc.skillType - - req, _ := http.NewRequest("GET", url, nil) - w := httptest.NewRecorder() - router.ServeHTTP(w, req) - - assert.Equal(t, http.StatusOK, w.Code) - - var response map[string]interface{} - err := json.Unmarshal(w.Body.Bytes(), &response) - require.NoError(t, err) - - // Überprüfe Response-Struktur wie sie das Frontend erwartet - assert.Contains(t, response, "reward_types") - assert.Contains(t, response, "learning_type") - assert.Contains(t, response, "skill_name") - assert.Contains(t, response, "character_id") - - rewardTypes := response["reward_types"].([]interface{}) - assert.GreaterOrEqual(t, len(rewardTypes), tc.expectedLen-1) // Mindestens erwartete Anzahl - - // Überprüfe Struktur der Reward Types wie Frontend sie erwartet - if len(rewardTypes) > 0 { - firstReward := rewardTypes[0].(map[string]interface{}) - assert.Contains(t, firstReward, "value") - assert.Contains(t, firstReward, "label") - } - }) - } - }) - - t.Run("Skill All Level Costs API - entspricht Frontend loadLearningCosts()", func(t *testing.T) { - // Teste nur die Request-Struktur und Skip den DB-Teil - t.Skip("Skipping DB-dependent test - testing request structure only") - - // Test der Request-Struktur wie das Frontend sie sendet - requestData := LearnRequestStruct{ - SkillType: "skill", - Name: "Menschenkenntnis", - Stufe: 10, - } - - // Überprüfe, dass die Request-Struktur korrekt ist - requestBody, err := json.Marshal(requestData) - require.NoError(t, err) - - var parsedRequest LearnRequestStruct - err = json.Unmarshal(requestBody, &parsedRequest) - require.NoError(t, err) - - // Überprüfe, dass alle Felder korrekt geparst werden - assert.Equal(t, "skill", parsedRequest.SkillType) - assert.Equal(t, "Menschenkenntnis", parsedRequest.Name) - assert.Equal(t, 10, parsedRequest.Stufe) - - // Teste erwartete Response-Struktur (ohne DB-Aufruf) - expectedResponse := []models.LearnCost{ - {Stufe: 11, Ep: 120, Money: 60, LE: 2}, - {Stufe: 12, Ep: 140, Money: 70, LE: 2}, - } - - // Überprüfe Response-Format wie das Frontend es erwartet - responseBody, _ := json.Marshal(expectedResponse) - var response []models.LearnCost - err = json.Unmarshal(responseBody, &response) - require.NoError(t, err) - - if len(response) > 0 { - cost := response[0] - assert.NotZero(t, cost.Stufe) // target_level im Frontend - assert.NotZero(t, cost.Ep) // ep_cost im Frontend - assert.NotZero(t, cost.Money) // gold_cost im Frontend - // cost.LE wird als pp_cost verwendet - } - }) - - t.Run("Frontend Conversion Logic Test", func(t *testing.T) { - // Simuliere die Konvertierung wie sie das Frontend durchführt - mockApiResponse := []models.LearnCost{ - {Stufe: 11, Ep: 120, Money: 60, LE: 2}, - {Stufe: 12, Ep: 140, Money: 70, LE: 2}, - {Stufe: 13, Ep: 160, Money: 80, LE: 3}, - } - - // Simuliere Frontend-Konvertierung - availableEP := 1000 - availableGold := 500 - availablePP := 10 - - var convertedLevels []map[string]interface{} - cumulativeEP := 0 - cumulativeGold := 0 - cumulativePP := 0 - - for _, cost := range mockApiResponse { - cumulativeEP += cost.Ep - cumulativeGold += cost.Money - cumulativePP += cost.LE - - level := map[string]interface{}{ - "targetLevel": cost.Stufe, - "epCost": cost.Ep, - "goldCost": cost.Money, - "ppCost": cost.LE, - "totalEpCost": cumulativeEP, - "totalGoldCost": cumulativeGold, - "totalPpCost": cumulativePP, - "canAfford": map[string]bool{ - "ep": availableEP >= cumulativeEP, - "gold": availableGold >= cumulativeGold, - "pp": availablePP >= cumulativePP, - }, - } - convertedLevels = append(convertedLevels, level) - } - - // Überprüfe Konvertierung - assert.Len(t, convertedLevels, 3) - - firstLevel := convertedLevels[0] - assert.Equal(t, 11, firstLevel["targetLevel"]) - assert.Equal(t, 120, firstLevel["epCost"]) - assert.Equal(t, 120, firstLevel["totalEpCost"]) // Erste Stufe = einzelne Kosten - - lastLevel := convertedLevels[2] - assert.Equal(t, 420, lastLevel["totalEpCost"]) // 120 + 140 + 160 - assert.Equal(t, 210, lastLevel["totalGoldCost"]) // 60 + 70 + 80 - - // Verfügbarkeits-Test - canAfford := lastLevel["canAfford"].(map[string]bool) - assert.True(t, canAfford["ep"]) // 1000 >= 420 - assert.True(t, canAfford["gold"]) // 500 >= 210 - assert.True(t, canAfford["pp"]) // 10 >= 7 - }) - - t.Run("Skill Learning Execution Test", func(t *testing.T) { - // Test der Request-Struktur für Lernausführung - // (DB-Operationen werden übersprungen) - t.Skip("Skipping DB-dependent test - testing request structure only") - - // Request wie das Frontend ihn für executeDetailedLearning sendet - requestData := gsmaster.LernCostRequest{ - Name: "Menschenkenntnis", - CurrentLevel: 10, - Type: "skill", - Action: "improve", - } - - // Verify the request structure is correct - assert.Equal(t, "Menschenkenntnis", requestData.Name) - assert.Equal(t, 10, requestData.CurrentLevel) - assert.Equal(t, "skill", requestData.Type) - assert.Equal(t, "improve", requestData.Action) - - // Das Frontend erwartet nach erfolgreichem Lernen diese Response-Felder: - expectedResponseFields := []string{"message", "skill_name", "ep_cost", "remaining_ep"} - assert.NotEmpty(t, expectedResponseFields, "Response sollte diese Felder enthalten") - }) -} - -// Test für die Frontend-Authentifizierung -func TestSkillLearningDialogAuth(t *testing.T) { - t.Run("Authentication Error Handling", func(t *testing.T) { - // Test wie das Frontend mit Auth-Fehlern umgeht - router := gin.New() - - // Mock eines Auth-Middlewares der 401 zurückgibt - router.Use(func(c *gin.Context) { - c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"}) - c.Abort() - }) - - router.GET("/api/characters/:id/reward-types", GetRewardTypesOld) - - req, _ := http.NewRequest("GET", "/api/characters/1/reward-types", nil) - w := httptest.NewRecorder() - router.ServeHTTP(w, req) - - assert.Equal(t, http.StatusUnauthorized, w.Code) - - // Das Frontend sollte bei 401 das 'auth-error' Event emittieren - var response map[string]interface{} - err := json.Unmarshal(w.Body.Bytes(), &response) - require.NoError(t, err) - assert.Contains(t, response, "error") - }) -} - -// Test verschiedener Belohnungstypen -func TestRewardTypeVariations(t *testing.T) { - testCases := []struct { - name string - learningType string - skillType string - expectedRewards []string - }{ - { - name: "Improve Skill", - learningType: "improve", - skillType: "skill", - expectedRewards: []string{"ep", "gold", "pp", "mixed"}, - }, - { - name: "Learn Skill", - learningType: "learn", - skillType: "skill", - expectedRewards: []string{"ep", "gold"}, - }, - { - name: "Weapon Training", - learningType: "improve", - skillType: "weapon", - expectedRewards: []string{"ep", "gold", "pp", "mixed", "training"}, - }, - { - name: "Spell Learning", - learningType: "spell", - skillType: "spell", - expectedRewards: []string{"ep", "gold", "pp", "mixed"}, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - router := gin.New() - router.GET("/api/characters/:id/reward-types", GetRewardTypesOld) - - url := "/api/characters/1/reward-types?learning_type=" + tc.learningType + - "&skill_type=" + tc.skillType + "&skill_name=TestSkill" - - req, _ := http.NewRequest("GET", url, nil) - w := httptest.NewRecorder() - router.ServeHTTP(w, req) - - assert.Equal(t, http.StatusOK, w.Code) - - var response map[string]interface{} - err := json.Unmarshal(w.Body.Bytes(), &response) - require.NoError(t, err) - - rewardTypes := response["reward_types"].([]interface{}) - - // Überprüfe, dass erwartete Reward Types vorhanden sind - foundRewards := make(map[string]bool) - for _, reward := range rewardTypes { - rewardMap := reward.(map[string]interface{}) - value := rewardMap["value"].(string) - foundRewards[value] = true - } - - for _, expectedReward := range tc.expectedRewards { - assert.True(t, foundRewards[expectedReward], - "Erwartete Belohnungsart '%s' nicht gefunden für %s", expectedReward, tc.name) - } - }) - } -} diff --git a/backend/character/skill_update_test.go b/backend/character/skill_update_test.go index 4062f0d..5f85848 100644 --- a/backend/character/skill_update_test.go +++ b/backend/character/skill_update_test.go @@ -1,19 +1,6 @@ package character -import ( - "bytes" - "encoding/json" - "net/http" - "net/http/httptest" - "testing" - - "bamort/database" - "bamort/models" - - "github.com/gin-gonic/gin" - "github.com/stretchr/testify/assert" -) - +/* func TestImproveSkillUpdatesLevel(t *testing.T) { // Setup test database database.SetupTestDB() @@ -204,3 +191,4 @@ func TestImproveSkillUpdatesLevel(t *testing.T) { t.Logf("New skill 'Schwimmen' successfully created at level 1") }) } +*/ diff --git a/backend/character/system_information_handlers.go b/backend/character/system_information_handlers.go index de7db5a..7688f43 100644 --- a/backend/character/system_information_handlers.go +++ b/backend/character/system_information_handlers.go @@ -6,6 +6,7 @@ import ( "github.com/gin-gonic/gin" ) +/* // GetCharacterClassesHandlerOld gibt alle verfügbaren Charakterklassen zurück func GetCharacterClassesHandlerOld(c *gin.Context) { // Vereinfachte Antwort mit den drei Hauptklassen @@ -29,9 +30,9 @@ func GetCharacterClassesHandlerOld(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"character_classes": classes}) } - -// GetSkillCategoriesHandlerOld gibt alle verfügbaren Fertigkeitskategorien zurück -func GetSkillCategoriesHandlerOld(c *gin.Context) { +*/ +// GetSkillCategoriesHandlerStatic gibt alle verfügbaren Fertigkeitskategorien zurück +func GetSkillCategoriesHandlerStatic(c *gin.Context) { categories := map[string]interface{}{ "Alltag": map[string]interface{}{ "name": "Alltag", diff --git a/backend/cmd/test_learning_costs/main.go b/backend/cmd/test_learning_costs/main.go deleted file mode 100644 index bc17724..0000000 --- a/backend/cmd/test_learning_costs/main.go +++ /dev/null @@ -1,59 +0,0 @@ -package main - -import ( - "bamort/database" - "bamort/gsmaster" - "bamort/models" - "fmt" - "strings" -) - -func main() { - // Initialize config and database - database.SetupTestDB(true, true) - defer database.ResetTestDB() - - // Test some skills for Magier character class - testSkills := []string{"Schreiben", "Athletik", "Lesen von Zauberformeln", "Zaubern"} - characterClass := "Ma" // Magier abbreviation - - fmt.Printf("Testing learning costs for character class: %s\n", characterClass) - fmt.Println(strings.Repeat("=", 50)) - - for _, skillName := range testSkills { - // Test the same logic as our handler - fmt.Printf("\n--- Testing skill: %s ---\n", skillName) - - // Try gsmaster.FindBestCategoryForSkillLearningOld - bestCategory, difficulty, err := gsmaster.FindBestCategoryForSkillLearningOld(skillName, characterClass) - if err == nil && bestCategory != "" { - fmt.Printf("✅ FindBestCategory: Category=%s, Difficulty=%s\n", bestCategory, difficulty) - } else { - fmt.Printf("❌ FindBestCategory: ERROR: %v\n", err) - } - - // Try models.GetSkillCategoryAndDifficultyNewSystem - skillLearningInfo, err := models.GetSkillCategoryAndDifficultyNewSystem(skillName, characterClass) - if err == nil { - fmt.Printf("✅ GetSkillCategory: Category=%s, LearnCost=%d\n", - skillLearningInfo.CategoryName, skillLearningInfo.LearnCost) - } else { - fmt.Printf("❌ GetSkillCategory: ERROR: %v\n", err) - } - } - - // Also test with empty character class - fmt.Println("\nTesting with empty character class:") - for _, skillName := range testSkills { - skillLearningInfo, err := models.GetSkillCategoryAndDifficultyNewSystem(skillName, "") - if err != nil { - fmt.Printf("❌ Skill: %s (empty class) - ERROR: %v\n", skillName, err) - continue - } - - fmt.Printf("✅ Skill: %s (empty class) - Category: %s, LearnCost: %d\n", - skillName, - skillLearningInfo.CategoryName, - skillLearningInfo.LearnCost) - } -} diff --git a/backend/gsmaster/beidhändiger_kampf_test.go b/backend/gsmaster/beidhändiger_kampf_test.go deleted file mode 100644 index bed770e..0000000 --- a/backend/gsmaster/beidhändiger_kampf_test.go +++ /dev/null @@ -1,89 +0,0 @@ -package gsmaster - -import ( - "fmt" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestBeidhändigerKampfFürPS(t *testing.T) { - // Testfall für das Erlernen von "Beidhändiger Kampf" durch einen Priester Streiter (PS) - t.Run("Lernkosten für Beidhändiger Kampf als PS", func(t *testing.T) { - // Verwende exportierte Funktion zum Berechnen der Lernkosten - result, err := CalculateDetailedSkillLearningCostOld("Beidhändiger Kampf", "PS") - assert.NoError(t, err, "Es sollte keinen Fehler beim Berechnen der Lernkosten geben") - assert.True(t, result.LE > 0, "LE-Kosten sollten größer als 0 sein") - - // Ausgabe der Ergebnisse - fmt.Printf("Lernkosten für Beidhändiger Kampf als PS:\n") - fmt.Printf("Lerneinheiten (LE): %d\n", result.LE) - fmt.Printf("Erfahrungspunkte (EP): %d\n", result.Ep) - fmt.Printf("Geldkosten (GS): %d\n", result.Money) - - // Überprüfung der Werte basierend auf der aktuellen Implementierung - assert.Equal(t, 2, result.LE, "LE-Kosten sollten 2 sein") - assert.Equal(t, 40, result.Ep, "EP-Kosten sollten 40 sein") - assert.Equal(t, 40, result.Money, "Geldkosten sollten 40 GS sein") - }) - - // Testfall für das Verbessern von "Beidhändiger Kampf" durch einen Priester Streiter (PS) - // von Stufe 5 auf 6 - t.Run("Verbesserungskosten für Beidhändiger Kampf als PS von 5 auf 6", func(t *testing.T) { - // Verwende exportierte Funktion zum Berechnen der Verbesserungskosten - result, err := CalculateDetailedSkillImprovementCostOld("Beidhändiger Kampf", "PS", 5) - assert.NoError(t, err, "Es sollte keinen Fehler beim Berechnen der Verbesserungskosten geben") - assert.True(t, result.LE > 0, "LE-Kosten sollten größer als 0 sein") - - // Ausgabe der Ergebnisse - fmt.Printf("\nVerbesserungskosten für Beidhändiger Kampf als PS (von 5 auf 6):\n") - fmt.Printf("Trainingseinheiten (TE): %d\n", result.LE) - fmt.Printf("Erfahrungspunkte (EP): %d\n", result.Ep) - fmt.Printf("Geldkosten (GS): %d\n", result.Money) - - // Überprüfung der Werte basierend auf der aktuellen vereinfachten Implementierung - assert.Equal(t, 1, result.LE, "TE-Kosten sollten 1 sein (vereinfachte Implementierung)") - assert.Equal(t, 40, result.Ep, "EP-Kosten sollten 40 sein") - assert.Equal(t, 40, result.Money, "Geldkosten sollten 40 GS sein") - }) - - // Testfall für das Verbessern von "Beidhändiger Kampf" durch einen Priester Streiter (PS) - // von Stufe 6 auf 7 - t.Run("Verbesserungskosten für Beidhändiger Kampf als PS von 6 auf 7", func(t *testing.T) { - // Verwende exportierte Funktion zum Berechnen der Verbesserungskosten - result, err := CalculateDetailedSkillImprovementCostOld("Beidhändiger Kampf", "PS", 6) - assert.NoError(t, err, "Es sollte keinen Fehler beim Berechnen der Verbesserungskosten geben") - assert.True(t, result.LE > 0, "LE-Kosten sollten größer als 0 sein") - - // Ausgabe der Ergebnisse - fmt.Printf("\nVerbesserungskosten für Beidhändiger Kampf als PS (von 6 auf 7):\n") - fmt.Printf("Trainingseinheiten (TE): %d\n", result.LE) - fmt.Printf("Erfahrungspunkte (EP): %d\n", result.Ep) - fmt.Printf("Geldkosten (GS): %d\n", result.Money) - - // Überprüfung der Werte basierend auf der aktuellen vereinfachten Implementierung - assert.Equal(t, 1, result.LE, "TE-Kosten sollten 1 sein (vereinfachte Implementierung)") - assert.Equal(t, 40, result.Ep, "EP-Kosten sollten 40 sein") - assert.Equal(t, 40, result.Money, "Geldkosten sollten 40 GS sein") - }) - - // Testfall für das Verbessern von "Beidhändiger Kampf" durch einen Priester Streiter (PS) - // von Stufe 7 auf 8 - t.Run("Verbesserungskosten für Beidhändiger Kampf als PS von 7 auf 8", func(t *testing.T) { - // Verwende exportierte Funktion zum Berechnen der Verbesserungskosten - result, err := CalculateDetailedSkillImprovementCostOld("Beidhändiger Kampf", "PS", 7) - assert.NoError(t, err, "Es sollte keinen Fehler beim Berechnen der Verbesserungskosten geben") - assert.True(t, result.LE > 0, "LE-Kosten sollten größer als 0 sein") - - // Ausgabe der Ergebnisse - fmt.Printf("\nVerbesserungskosten für Beidhändiger Kampf als PS (von 7 auf 8):\n") - fmt.Printf("Trainingseinheiten (TE): %d\n", result.LE) - fmt.Printf("Erfahrungspunkte (EP): %d\n", result.Ep) - fmt.Printf("Geldkosten (GS): %d\n", result.Money) - - // Überprüfung der Werte basierend auf der aktuellen vereinfachten Implementierung - assert.Equal(t, 1, result.LE, "TE-Kosten sollten 1 sein (vereinfachte Implementierung)") - assert.Equal(t, 40, result.Ep, "EP-Kosten sollten 40 sein") - assert.Equal(t, 40, result.Money, "Geldkosten sollten 40 GS sein") - }) -} diff --git a/backend/gsmaster/learning_costs.go b/backend/gsmaster/learning_costs.go index df29ff9..36502a5 100644 --- a/backend/gsmaster/learning_costs.go +++ b/backend/gsmaster/learning_costs.go @@ -3,8 +3,6 @@ package gsmaster import ( "bamort/database" "bamort/models" - "errors" - "fmt" ) // LearningCostsTable strukturiert die Daten aus Lerntabellen.md @@ -322,98 +320,6 @@ func GetLearningCosts() *LearningCostsTable { return learningCosts } -// CalculateSkillLearningCostsOld is deprecated. Use the new database-based learning cost system instead. -// This function uses the old hardcoded learning cost data. -// CalculateSkillLearningCostsOld berechnet die Kosten für das Lernen einer Fertigkeit -func CalculateSkillLearningCostsOld(characterClass, category, difficulty string) (*SkillCostResult, error) { - //Überprüfe ob die tabelle vorhanden ist in der die EP-Kosten pro LE for die einzelnen Kategorien für jede Charakterklasse definiert sind - if learningCosts.EPPerTE == nil { - return nil, errors.New("keine EP-per-TE-Definition gefunden") - } - - // Konvertiere Vollnamen der Charakterklasse zu Abkürzungen falls nötig - classKey := GetClassAbbreviationOld(characterClass) - - // Hole die EP-Kosten pro TE für die angegebene Charakterklasse - classData, exists := learningCosts.EPPerTE[classKey] - if !exists { - return nil, fmt.Errorf("unbekannte Charakterklasse: %s (gesucht als %s)", characterClass, classKey) - } - - // Ermittle die EP pro TE für die angegebene Kategorie - epPerTE, exists := classData[category] - if !exists { - return nil, fmt.Errorf("unbekannte Kategorie '%s' für Klasse %s", category, characterClass) - } - - // 1 Lerneinheit(LE) kostet 3 mal so viel wie eine Trainingseinheit (TE) +6 EP when der Charakter ein Elf ist - - if learningCosts.BaseLearnCost == nil { - return nil, errors.New("keine LE-Definition gefunden") - } - - difficultyData, exists := learningCosts.BaseLearnCost[difficulty] - if !exists { - return nil, fmt.Errorf("unbekannte Schwierigkeit: %s", difficulty) - } - - le := difficultyData["LE"] - totalEP := epPerTE * le - - return &SkillCostResult{ - CharacterClass: characterClass, - SkillName: "", - Category: category, - Difficulty: difficulty, - EP: totalEP, - LE: le, - GoldCost: totalEP, // 1 EP = 1 GS - Details: map[string]interface{}{ - "ep_per_te": epPerTE, - "le_needed": le, - }, - }, nil -} - -// CalculateSpellLearningCostsOld is deprecated. Use the new database-based learning cost system instead. -// This function uses the old hardcoded learning cost data. -// CalculateSpellLearningCostsOld berechnet die Kosten für das Lernen eines Zaubers -func CalculateSpellLearningCostsOld(characterClass, spellSchool string, leNeeded int) (*SkillCostResult, error) { - if learningCosts.SpellEPPerLE == nil { - return nil, errors.New("keine Zauber-EP-Definition gefunden") - } - - // Konvertiere Vollnamen zu Abkürzungen falls nötig - classKey := GetClassAbbreviationOld(characterClass) - - classData, exists := learningCosts.SpellEPPerLE[classKey] - if !exists { - return nil, fmt.Errorf("Charakterklasse %s kann keine Zauber lernen", characterClass) - } - - epPerLE, exists := classData[spellSchool] - if !exists || epPerLE == 0 { - return nil, fmt.Errorf("Charakterklasse %s kann keine Zauber der Schule %s lernen", characterClass, spellSchool) - } - - totalEP := epPerLE * leNeeded - - return &SkillCostResult{ - CharacterClass: characterClass, - SkillName: "", - Category: spellSchool, - Difficulty: "", - EP: totalEP, - LE: leNeeded, - GoldCost: totalEP, // 1 EP = 1 GS - Details: map[string]interface{}{ - "ep_per_le": epPerLE, - "le_needed": leNeeded, - "spell_school": spellSchool, - }, - }, nil -} - // SkillCostResult definiert das Ergebnis einer Kostenberechnung type SkillCostResult struct { CharacterClass string `json:"character_class"` @@ -442,85 +348,6 @@ type SkillCostResultNew struct { Details map[string]interface{} `json:"details"` } -// CalculateDetailedSkillLearningCostOld is deprecated. Use the new database-based learning cost system instead. -// This function uses the old hardcoded learning cost data. -// CalculateDetailedSkillLearningCostOld berechnet die Kosten für das Lernen einer Fertigkeit mit Details -func CalculateDetailedSkillLearningCostOld(skillName, characterClass string) (*models.LearnCost, error) { - // Fallback-Werte für Skills ohne definierte Kategorie/Schwierigkeit - category := GetDefaultCategoryOld(skillName) - difficulty := GetDefaultDifficultyOld(skillName) - - result, err := CalculateSkillLearningCostsOld(characterClass, category, difficulty) - if err != nil { - return nil, err - } - - // Konvertiere SkillCostResult zu LearnCost - return &models.LearnCost{ - Stufe: 0, // Lernen startet bei Stufe 0 - LE: result.LE, - Ep: result.EP, - Money: result.GoldCost, - }, nil -} - -// CalculateDetailedSkillImprovementCostOld is deprecated. Use the new database-based learning cost system instead. -// This function uses the old hardcoded learning cost data. -func CalculateDetailedSkillImprovementCostOld(skillName, characterClass string, currentLevel int) (*models.LearnCost, error) { - // Fallback-Werte für Skills ohne definierte Kategorie/Schwierigkeit - category := GetDefaultCategoryOld(skillName) - difficulty := GetDefaultDifficultyOld(skillName) - - // Verwende die Lernkosten als Basis für Verbesserungen - // In einer vollständigen Implementierung würden hier die ImprovementCost-Tabellen verwendet - baseCost, err := CalculateSkillLearningCostsOld(characterClass, category, difficulty) - if err != nil { - return nil, err - } - - // Vereinfachte Verbesserungslogik: höhere Level = höhere Kosten - improvementFactor := 1.0 - if currentLevel > 10 { - improvementFactor = 1.5 - } else if currentLevel > 15 { - improvementFactor = 2.0 - } - - improvedEP := int(float64(baseCost.EP) * improvementFactor) - - // Konvertiere zu LearnCost - return &models.LearnCost{ - Stufe: currentLevel + 1, // Ziel-Stufe - LE: 1, // TE für Verbesserung (meist 1) - Ep: improvedEP, - Money: improvedEP, - }, nil -} - -// CalculateDetailedSpellLearningCostOld is deprecated. Use the new database-based learning cost system instead. -// This function uses the old hardcoded learning cost data. -// CalculateDetailedSpellLearningCostOld berechnet die Kosten für das Lernen eines Zaubers -func CalculateDetailedSpellLearningCostOld(spellName, characterClass string) (*models.LearnCost, error) { - // Standard-Zauberschule bestimmen - spellSchool := getDefaultSpellSchoolOld(spellName) - - // Standard LE für Zauber - standardLE := 4 - - result, err := CalculateSpellLearningCostsOld(characterClass, spellSchool, standardLE) - if err != nil { - return nil, err - } - - // Konvertiere SkillCostResult zu LearnCost - return &models.LearnCost{ - Stufe: 0, // Lernen startet bei Stufe 0 - LE: result.LE, - Ep: result.EP, - Money: result.GoldCost, - }, nil -} - // SkillCategoryOption definiert eine Kategorie-Schwierigkeit-Kombination für eine Fertigkeit type SkillCategoryOption struct { Category string `json:"category"` @@ -528,347 +355,6 @@ type SkillCategoryOption struct { LE int `json:"le"` } -// GetAvailableSkillCategories gibt alle verfügbaren Kategorie-Kombinationen für eine Fertigkeit zurück -func GetAvailableSkillCategories(skillName string) []SkillCategoryOption { - // Basierend auf den offiziellen Lerntabellen.md - alle möglichen Kombinationen - skillOptions := map[string][]SkillCategoryOption{ - "Klettern": { - {Category: "Alltag", Difficulty: "leicht", LE: 1}, - {Category: "Halbwelt", Difficulty: "leicht", LE: 1}, - {Category: "Körper", Difficulty: "leicht", LE: 1}, - }, - "Glücksspiel": { - {Category: "Alltag", Difficulty: "leicht", LE: 1}, - {Category: "Halbwelt", Difficulty: "leicht", LE: 1}, - }, - "Reiten": { - {Category: "Alltag", Difficulty: "leicht", LE: 1}, - {Category: "Kampf", Difficulty: "leicht", LE: 1}, - }, - "Anführen": { - {Category: "Kampf", Difficulty: "normal", LE: 2}, - {Category: "Sozial", Difficulty: "leicht", LE: 2}, - }, - "Etikette": { - {Category: "Alltag", Difficulty: "schwer", LE: 2}, - {Category: "Sozial", Difficulty: "leicht", LE: 2}, - }, - "Gassenwissen": { - {Category: "Halbwelt", Difficulty: "schwer", LE: 2}, - {Category: "Sozial", Difficulty: "normal", LE: 2}, - {Category: "Unterwelt", Difficulty: "leicht", LE: 2}, - }, - "Betäuben": { - {Category: "Halbwelt", Difficulty: "sehr schwer", LE: 10}, - {Category: "Kampf", Difficulty: "schwer", LE: 10}, - }, - "Athletik": { - {Category: "Kampf", Difficulty: "normal", LE: 2}, - {Category: "Körper", Difficulty: "schwer", LE: 2}, - }, - "Balancieren": { - {Category: "Halbwelt", Difficulty: "leicht", LE: 1}, - {Category: "Körper", Difficulty: "leicht", LE: 1}, - }, - "Akrobatik": { - {Category: "Halbwelt", Difficulty: "normal", LE: 2}, - {Category: "Körper", Difficulty: "schwer", LE: 2}, - }, - "Schleichen": { - {Category: "Freiland", Difficulty: "schwer", LE: 4}, - {Category: "Unterwelt", Difficulty: "normal", LE: 4}, - }, - "Spurensuche": { - {Category: "Freiland", Difficulty: "schwer", LE: 4}, - {Category: "Unterwelt", Difficulty: "normal", LE: 4}, - }, - "Tarnen": { - {Category: "Freiland", Difficulty: "schwer", LE: 4}, - {Category: "Unterwelt", Difficulty: "normal", LE: 4}, - }, - "Stehlen": { - {Category: "Halbwelt", Difficulty: "schwer", LE: 2}, - {Category: "Unterwelt", Difficulty: "leicht", LE: 2}, - }, - "Verhören": { - {Category: "Sozial", Difficulty: "normal", LE: 2}, - {Category: "Unterwelt", Difficulty: "normal", LE: 4}, - }, - "Menschenkenntnis": { - {Category: "Sozial", Difficulty: "schwer", LE: 4}, - {Category: "Unterwelt", Difficulty: "schwer", LE: 4}, - }, - "Schreiben": { - {Category: "Alltag", Difficulty: "normal", LE: 1}, - {Category: "Wissen", Difficulty: "leicht", LE: 1}, - }, - "Sprache": { - {Category: "Alltag", Difficulty: "normal", LE: 1}, - {Category: "Wissen", Difficulty: "leicht", LE: 1}, - }, - "Erste Hilfe": { - {Category: "Alltag", Difficulty: "schwer", LE: 2}, - {Category: "Wissen", Difficulty: "normal", LE: 2}, - }, - "Meditieren": { - {Category: "Körper", Difficulty: "schwer", LE: 2}, - {Category: "Wissen", Difficulty: "normal", LE: 2}, - }, - "Naturkunde": { - {Category: "Freiland", Difficulty: "normal", LE: 2}, - {Category: "Wissen", Difficulty: "schwer", LE: 2}, - }, - "Pflanzenkunde": { - {Category: "Freiland", Difficulty: "normal", LE: 2}, - {Category: "Wissen", Difficulty: "schwer", LE: 2}, - }, - "Tierkunde": { - {Category: "Freiland", Difficulty: "normal", LE: 2}, - {Category: "Wissen", Difficulty: "schwer", LE: 2}, - }, - } - - if options, exists := skillOptions[skillName]; exists { - return options - } - - // Fallback: verwende Standard-Mapping (erste gefundene Kategorie) - category := GetDefaultCategoryOld(skillName) - difficulty := GetDefaultDifficultyOld(skillName) - - // Bestimme LE basierend auf Schwierigkeit - le := 2 // Standard - switch difficulty { - case "leicht": - le = 1 - case "normal": - le = 2 - case "schwer": - le = 4 - case "sehr schwer": - le = 10 - } - - return []SkillCategoryOption{ - {Category: category, Difficulty: difficulty, LE: le}, - } -} - -// GetDefaultCategoryOld is deprecated. Use models.GetSkillCategoryAndDifficulty instead. -// This function uses the old hardcoded skill categorization system. -// GetDefaultCategoryOld gibt die erste (bevorzugte) Kategorie für eine Fertigkeit zurück -func GetDefaultCategoryOld(skillName string) string { - // WICHTIG: Wir verwenden bewusst die erste gefundene Kategorie als Standard. - // Für das Lernen ist es unerheblich, aber später wird es für andere Dinge wichtig werden. - // Die Reihenfolge der Kategorien ist nach Wichtigkeit/Häufigkeit sortiert. - categoryMap := map[string]string{ - "Stichwaffen": "Waffen", - "Einhandschlagwaffen": "Waffen", - "Zweihandschlagwaffen": "Waffen", - "Geländelauf": "Körper", - "Klettern": "Körper", - "Schwimmen": "Körper", - "Reiten": "Körper", - "Balancieren": "Körper", - "Tauchen": "Körper", - "Akrobatik": "Körper", - "Athletik": "Körper", - "Laufen": "Körper", - "Meditieren": "Körper", - "Seilkunst": "Alltag", - "Bootfahren": "Alltag", - "Glücksspiel": "Alltag", - "Wagenlenken": "Alltag", - "Schreiben": "Alltag", - "Sprache": "Alltag", - "Erste Hilfe": "Alltag", - "Etikette": "Alltag", - "Gerätekunde": "Alltag", - "Geschäftssinn": "Alltag", - "Musizieren": "Alltag", - "Schleichen": "Freiland", - "Spurensuche": "Freiland", - "Tarnen": "Freiland", - "Überleben": "Freiland", - "Naturkunde": "Freiland", - "Pflanzenkunde": "Freiland", - "Tierkunde": "Freiland", - "Gassenwissen": "Unterwelt", - "Stehlen": "Unterwelt", - "Fallen entdecken": "Unterwelt", - "Schlösser öffnen": "Unterwelt", - "Fallenmechanik": "Unterwelt", - "Meucheln": "Unterwelt", - "Anführen": "Sozial", - "Verführen": "Sozial", - "Verstellen": "Sozial", - "Beredsamkeit": "Sozial", - "Verhören": "Sozial", - "Menschenkenntnis": "Sozial", - "Lesen von Zauberschrift": "Wissen", - "Alchimie": "Wissen", - "Heilkunde": "Wissen", - "Landeskunde": "Wissen", - "Zauberkunde": "Wissen", - } - - if category, exists := categoryMap[skillName]; exists { - return category - } - - // Standard-Fallback - return "Alltag" -} - -func GetDifficulty(skillName string, category string) string { - // aktuell nur ein Wrapper das die Stantruktur noch keine Fettigkeiten in mehreren Kategorien enthält - return GetDefaultDifficultyOld(skillName) -} - -// GetDefaultDifficultyOld is deprecated. Use models.GetSkillCategoryAndDifficulty instead. -// This function uses the old hardcoded skill categorization system. -// GetDefaultDifficultyOld gibt die erste (bevorzugte) Schwierigkeit für eine Fertigkeit zurück -func GetDefaultDifficultyOld(skillName string) string { - // WICHTIG: Korrespondiert mit getDefaultCategory() - verwendet die Schwierigkeit - // der ersten (bevorzugten) Kategorie für konsistente Ergebnisse. - // Schwierigkeitszuordnung basierend auf dem Fertigkeitsnamen und Lerntabellen.md - difficultyMap := map[string]string{ - // Waffen (aus Waffen-Kategorie) - "Stichwaffen": "leicht", // 2 LE - "Einhandschlagwaffen": "normal", // 4 LE - "Zweihandschlagwaffen": "schwer", // 6 LE - - // Körper-Fertigkeiten - "Geländelauf": "leicht", // Körper: leicht (1 LE) - "Klettern": "leicht", // Körper: leicht (1 LE) - "Schwimmen": "leicht", // Körper: leicht (1 LE) - "Balancieren": "leicht", // Körper: leicht (1 LE) - "Reiten": "leicht", // Alltag: leicht (1 LE) - "Tauchen": "normal", // Körper: normal (1 LE) - "Akrobatik": "schwer", // Körper: schwer (2 LE) - "Athletik": "schwer", // Körper: schwer (2 LE) - "Laufen": "schwer", // Körper: schwer (2 LE) - "Meditieren": "schwer", // Körper: schwer (2 LE) - - // Alltag-Fertigkeiten - "Seilkunst": "leicht", // Alltag: leicht (1 LE) - "Bootfahren": "leicht", // Alltag: leicht (1 LE) - "Glücksspiel": "leicht", // Alltag: leicht (1 LE) - "Wagenlenken": "leicht", // Alltag: leicht (1 LE) - "Musizieren": "leicht", // Alltag: leicht (1 LE) - "Schreiben": "normal", // Alltag: normal (1 LE) - "Sprache": "normal", // Alltag: normal (1 LE) - "Erste Hilfe": "schwer", // Alltag: schwer (2 LE) - "Etikette": "schwer", // Alltag: schwer (2 LE) - "Gerätekunde": "sehr schwer", // Alltag: sehr schwer (10 LE) - "Geschäftssinn": "sehr schwer", // Alltag: sehr schwer (10 LE) - - // Freiland-Fertigkeiten - "Überleben": "leicht", // Freiland: leicht (1 LE) - "Naturkunde": "normal", // Freiland: normal (2 LE) - "Pflanzenkunde": "normal", // Freiland: normal (2 LE) - "Tierkunde": "normal", // Freiland: normal (2 LE) - "Schleichen": "schwer", // Freiland: schwer (4 LE) - "Spurensuche": "schwer", // Freiland: schwer (4 LE) - "Tarnen": "schwer", // Freiland: schwer (4 LE) - - // Unterwelt-Fertigkeiten - "Gassenwissen": "leicht", // Unterwelt: leicht (2 LE) - "Stehlen": "leicht", // Unterwelt: leicht (2 LE) - "Fallen entdecken": "normal", // Unterwelt: normal (4 LE) - "Schlösser öffnen": "normal", // Unterwelt: normal (4 LE) - "Fallenmechanik": "schwer", // Unterwelt: schwer (10 LE) - "Meucheln": "schwer", // Unterwelt: schwer (10 LE) - - // Sozial-Fertigkeiten - "Anführen": "leicht", // Sozial: leicht (2 LE) - "Verführen": "leicht", // Sozial: leicht (2 LE) - "Verstellen": "leicht", // Sozial: leicht (2 LE) - "Beredsamkeit": "normal", // Sozial: normal (2 LE) - "Verhören": "normal", // Sozial: normal (2 LE) - "Menschenkenntnis": "schwer", // Sozial: schwer (4 LE) - - // Wissen-Fertigkeiten - "Lesen von Zauberschrift": "leicht", // Wissen: leicht (1 LE) - "Alchimie": "schwer", // Wissen: schwer (2 LE) - "Heilkunde": "schwer", // Wissen: schwer (2 LE) - "Landeskunde": "schwer", // Wissen: schwer (2 LE) - "Zauberkunde": "schwer", // Wissen: schwer (2 LE) - } - - if difficulty, exists := difficultyMap[skillName]; exists { - return difficulty - } - - // Standard-Fallback - return "normal" -} - -// getDefaultSpellSchoolOld is deprecated. Use the new database-based spell categorization system instead. -// This function uses hardcoded spell-to-school mapping. -// getDefaultSpellSchoolOld gibt eine Standard-Zauberschule für einen Zauber zurück -func getDefaultSpellSchoolOld(spellName string) string { - // Vereinfachte Zuordnung von Zauber zu Schulen - spellSchoolMap := map[string]string{ - "Licht": "Erschaffen", - "Sehen": "Erkennen", - "Heilen": "Verändern", - "Schutz": "Beherrschen", - "Unsichtbarkeit": "Verändern", - "Feuerlanze": "Zerstören", - "Eislanze": "Zerstören", - "Blitze": "Zerstören", - "Schweben": "Bewegen", - "Teleportation": "Bewegen", - } - - if school, exists := spellSchoolMap[spellName]; exists { - return school - } - - // Standard-Fallback - return "Verändern" -} - -// GetClassAbbreviationOld is deprecated. Use standardized class names or database lookups instead. -// This function uses hardcoded class name mapping. -// GetClassAbbreviationOld konvertiert Charakterklassen-Vollnamen zu Abkürzungen -func GetClassAbbreviationOld(characterClass string) string { - // Mapping von Vollnamen zu Abkürzungen - classMap := map[string]string{ - // Abenteurer-Klassen - "Assassine": "As", - "Barbar": "Bb", - "Glücksritter": "Gl", - "Händler": "Hä", - "Krieger": "Kr", - "Spitzbube": "Sp", - "Waldläufer": "Wa", - // Zauberer-Klassen - "Barde": "Ba", - "Ordenskrieger": "Or", - "Druide": "Dr", - "Hexer": "Hx", - "Magier": "Ma", - "Priester": "PB", // Standard Priester = Beschützer - "Priester Beschützer": "PB", - "Priester Streiter": "PS", - "Schamane": "Sc", - } - - // Prüfe ob es bereits eine Abkürzung ist - if len(characterClass) <= 2 { - return characterClass - } - - // Suche nach Vollname - if abbrev, exists := classMap[characterClass]; exists { - return abbrev - } - - // Fallback: originale Eingabe zurückgeben - return characterClass -} func GetClassAbbreviationNewSystem(characterClass string) string { // Try to find by code first (e.g., "Kr" -> "Kr") var charClass models.CharacterClass diff --git a/backend/gsmaster/lernkosten_maps.go b/backend/gsmaster/lernkosten_maps.go index e65be8e..10e6e97 100644 --- a/backend/gsmaster/lernkosten_maps.go +++ b/backend/gsmaster/lernkosten_maps.go @@ -491,48 +491,6 @@ var learningCostsData = &LearningCostsTable2{ }, } -// GetSkillCategoryOld is deprecated. Use models.GetSkillCategoryAndDifficulty instead. -// This function uses the old hardcoded skill categorization system. -func GetSkillCategoryOld(skillName string) string { - - for category, difficulties := range learningCostsData.ImprovementCost { - for _, data := range difficulties { - if contains(data.Skills, skillName) { - return category - } - } - } - return "Unbekannt" -} - -// GetSkillDifficultyOld is deprecated. Use models.GetSkillCategoryAndDifficulty instead. -// This function uses the old hardcoded skill categorization system. -func GetSkillDifficultyOld(category string, skillName string) string { - // Wenn eine Kategorie angegeben ist, suche nur in dieser Kategorie - if category != "" { - difficulties, ok := learningCostsData.ImprovementCost[category] - if !ok { - return "Unbekannt" // Kategorie nicht gefunden - } - for difficulty, data := range difficulties { - if contains(data.Skills, skillName) { - return difficulty - } - } - return "Unbekannt" // Skill in der angegebenen Kategorie nicht gefunden - } - - // Wenn keine Kategorie angegeben ist, durchsuche alle Kategorien und gib das erste Vorkommen zurück - for _, difficulties := range learningCostsData.ImprovementCost { - for difficulty, data := range difficulties { - if contains(data.Skills, skillName) { - return difficulty - } - } - } - return "Unbekannt" -} - // contains checks if a slice contains a specific string func contains(slice []string, item string) bool { for _, s := range slice { @@ -558,351 +516,3 @@ func GetSpellInfoNewSystem(spellName string) (string, int, error) { return spell.Category, spell.Stufe, nil } - -// GetSpecializationOld is deprecated. Use appropriate character data access methods instead. -// This function is a placeholder that should not be used in production. -// GetSpecializationOld returns the specialization school for a character (placeholder) -// This should be implemented to get the actual specialization from character data -func GetSpecializationOld(characterID string) string { - // TODO: Implement actual character specialization lookup - // For now, return a default specialization - return "Beherrschen" -} - -// findBestCategoryForSkillImprovementOld is deprecated. Use the new database-based learning cost system instead. -// This function uses the old hardcoded learning cost data. -// findBestCategoryForSkillImprovementOld findet die Kategorie mit den niedrigsten EP-Kosten für eine Fertigkeit -func findBestCategoryForSkillImprovementOld(skillName, characterClass string, level int) (string, string, error) { - classKey := characterClass - - // Sammle alle Kategorien und Schwierigkeiten, in denen die Fertigkeit verfügbar ist - type categoryOption struct { - category string - difficulty string - epCost int - } - - var options []categoryOption - - for category, difficulties := range learningCostsData.ImprovementCost { - for difficulty, data := range difficulties { - if contains(data.Skills, skillName) { - // Prüfe ob EP-Kosten für diese Kategorie und Klasse existieren - epPerTE, exists := learningCostsData.EPPerTE[classKey][category] - if exists { - // Hole die Trainingskosten für level - trainCost, hasCost := data.TrainCosts[level] - if hasCost { - totalEP := epPerTE * trainCost - options = append(options, categoryOption{ - category: category, - difficulty: difficulty, - epCost: totalEP, - }) - } - } - } - } - } - - if len(options) == 0 { - return "", "", fmt.Errorf("keine verfügbare Kategorie für Fertigkeit '%s' und Klasse '%s' auf Level %d gefunden", skillName, characterClass, level) - } - - // Finde die Option mit den niedrigsten EP-Kosten - bestOption := options[0] - for _, option := range options[1:] { - if option.epCost < bestOption.epCost { - bestOption = option - } - } - - return bestOption.category, bestOption.difficulty, nil -} - -// FindBestCategoryForSkillLearningOld is deprecated. Use the new database-based learning cost system instead. -// This function uses the old hardcoded learning cost data. -// FindBestCategoryForSkillLearningOld findet die Kategorie mit den niedrigsten EP-Kosten für das Lernen einer Fertigkeit -func FindBestCategoryForSkillLearningOld(skillName, characterClass string) (string, string, error) { - classKey := characterClass - - // Sammle alle Kategorien und Schwierigkeiten, in denen die Fertigkeit verfügbar ist - type categoryOption struct { - category string - difficulty string - epCost int - } - - var options []categoryOption - - for category, difficulties := range learningCostsData.ImprovementCost { - for difficulty, data := range difficulties { - if contains(data.Skills, skillName) { - // Prüfe ob EP-Kosten für diese Kategorie und Klasse existieren - epPerTE, exists := learningCostsData.EPPerTE[classKey][category] - if exists { - // Für das Lernen verwenden wir LearnCost * 3 - learnCost := data.LearnCost - totalEP := epPerTE * learnCost * 3 - options = append(options, categoryOption{ - category: category, - difficulty: difficulty, - epCost: totalEP, - }) - } - } - } - } - - if len(options) == 0 { - return "", "", fmt.Errorf("keine verfügbare Kategorie für Fertigkeit '%s' und Klasse '%s' gefunden", skillName, characterClass) - } - - // Finde die Option mit den niedrigsten EP-Kosten - bestOption := options[0] - for _, option := range options[1:] { - if option.epCost < bestOption.epCost { - bestOption = option - } - } - - return bestOption.category, bestOption.difficulty, nil -} - -// CalcSkillLernCostOld is deprecated. Use CalcSkillLernCost instead. -// This function uses the old hardcoded learning cost system. -func CalcSkillLernCostOld(costResult *SkillCostResultNew, reward *string) error { - // Berechne die Lernkosten basierend auf den aktuellen Werten im costResult - // Hier sollte die Logik zur Berechnung der Lernkosten implementiert werden - //Finde EP kosten für die Kategorie für die Charakterklasse aus learningCostsData.EPPerTE - // Konvertiere Vollnamen der Charakterklasse zu Abkürzungen falls nötig - //classKey := getClassAbbreviation(costResult.CharacterClass) - classKey := costResult.CharacterClass - - // Wenn Kategorie und Schwierigkeit noch nicht gesetzt sind, finde die beste Option - if costResult.Category == "" || costResult.Difficulty == "" { - bestCategory, bestDifficulty, err := FindBestCategoryForSkillLearningOld(costResult.SkillName, classKey) - if err != nil { - return err - } - costResult.Category = bestCategory - costResult.Difficulty = bestDifficulty - } - - epPerTE, ok := learningCostsData.EPPerTE[classKey][costResult.Category] - if !ok { - return fmt.Errorf("EP-Kosten für Kategorie '%s' und Klasse '%s' nicht gefunden", costResult.Category, costResult.CharacterClass) - } - // finde LE für den Skill aufgrund der Kategorie und schwierigkeit aus DifficultyData - learnCost, ok := learningCostsData.ImprovementCost[costResult.Category][costResult.Difficulty] - if !ok { - return fmt.Errorf("Lernkosten für Kategorie '%s' und Schwierigkeit '%s' nicht gefunden", costResult.Category, costResult.Difficulty) - } - costResult.LE = learnCost.LearnCost - costResult.EP = epPerTE * costResult.LE * 3 - costResult.GoldCost = costResult.LE * 200 // Beispiel: 200 Gold pro LE - - // Apply reward logic - if reward != nil { - switch *reward { - case "noGold": - costResult.GoldCost = 0 // Keine Goldkosten für diese Belohnung - case "halveep": - costResult.EP = costResult.EP / 2 // Halbe EP-Kosten - costResult.GoldCost = 0 // Keine Goldkosten bei halven EP - case "halveepnoGold": - costResult.GoldCost = 0 // Keine Goldkosten für diese Belohnung - costResult.EP = costResult.EP / 2 // Halbe EP-Kosten - case "default": - // Keine Änderungen, normale Kosten - } - } - - return nil -} - -// CalcSkillImproveCostOld is deprecated. Use CalcSkillImproveCost instead. -// This function uses the old hardcoded learning cost system. -// CalcSkillImproveCostOld berechnet die Kosten für die Verbesserung einer Fertigkeit -func CalcSkillImproveCostOld(costResult *SkillCostResultNew, currentLevel int, reward *string) error { - // Für Skill-Verbesserung könnten die Kosten vom aktuellen Level abhängen - - //Finde EP kosten für die Kategorie für die Charakterklasse aus learningCostsData.EPPerTE - //classKey := getClassAbbreviation(costResult.CharacterClass) - classKey := costResult.CharacterClass - - if costResult.TargetLevel > 0 { - currentLevel = costResult.TargetLevel - 1 // Wenn ein Ziellevel angegeben ist, verwende dieses - } - - // Wenn Kategorie und Schwierigkeit noch nicht gesetzt sind, finde die beste Option - if costResult.Category == "" || costResult.Difficulty == "" { - bestCategory, bestDifficulty, err := findBestCategoryForSkillImprovementOld(costResult.SkillName, classKey, currentLevel+1) - if err != nil { - return err - } - costResult.Category = bestCategory - costResult.Difficulty = bestDifficulty - } - - epPerTE, ok := learningCostsData.EPPerTE[classKey][costResult.Category] - if !ok { - return fmt.Errorf("EP-Kosten für Kategorie '%s' und Klasse '%s' nicht gefunden", costResult.Category, costResult.CharacterClass) - } - - diffData := learningCostsData.ImprovementCost[costResult.Category][costResult.Difficulty] - - trainCost := diffData.TrainCosts[currentLevel+1] - if trainCost < costResult.PPUsed { - costResult.PPUsed = trainCost //maximal so viele PP verwenden wie TE benötigt werden - trainCost = 0 // Wenn PP verwendet werden, setze die Kosten auf - } else if costResult.PPUsed > 0 { - trainCost -= costResult.PPUsed // Wenn PP verwendet werden, setze die Kosten auf die PP - } - // Apply reward logic - costResult.LE = trainCost - costResult.EP = epPerTE * trainCost - costResult.GoldCost = trainCost * 20 // Beispiel: 20 Gold pro TE - - if reward != nil && *reward == "halveep" { - costResult.EP = costResult.EP / 2 // Halbiere die EP-Kosten für diese Belohnung - } - if reward != nil && *reward == "halveepnoGold" { - costResult.GoldCost = 0 // Keine Goldkosten für diese Belohnung - costResult.EP = costResult.EP / 2 // Halbiere die EP-Kosten für diese Belohnung - } - if costResult.GoldUsed > 0 { - // 10 Gold = 1 EP, aber maximal EP/2 kann durch Gold ersetzt werden - maxEPFromGold := costResult.EP / 2 - epFromGold := costResult.GoldUsed / 10 - - if epFromGold > maxEPFromGold { - // Beschränke auf maximal EP/2 - epFromGold = maxEPFromGold - costResult.GoldUsed = epFromGold * 10 - } - - // Reduziere EP um die durch Gold ersetzte Menge - costResult.EP -= epFromGold - } - - return nil -} - -// CalcSpellLernCostOld is deprecated. Use CalcSpellLernCost instead. -// This function uses the old hardcoded learning cost system. -// CalcSpellLernCostOld berechnet die Kosten für das Erlernen eines Zaubers -func CalcSpellLernCostOld(costResult *SkillCostResultNew, reward *string) error { - // Für Zauber verwenden wir eine ähnliche Logik wie für Skills - // TODO: Implementiere spezifische Zauber-Kostenlogik wenn verfügbar - classKey := costResult.CharacterClass - spellCategory, spellLevel, err := GetSpellInfoNewSystem(costResult.SkillName) - if err != nil { - return fmt.Errorf("failed to get spell info: %w", err) - } - SpellEPPerLE, ok := learningCostsData.SpellEPPerLE[classKey][spellCategory] - if !ok { - return fmt.Errorf("EP-Kosten für Zauber '%s' und Klasse '%s' nicht gefunden", costResult.SkillName, classKey) - } - if classKey == "Ma" { - spezialgebiet := GetSpecializationOld(costResult.CharacterID) - if spellCategory == spezialgebiet { - SpellEPPerLE = 30 // Spezialgebiet für Magier - } - } - - trainCost := learningCostsData.SpellLEPerLevel[spellLevel] // LE pro Stufe des Zaubers - if costResult.PPUsed > 0 { - trainCost -= costResult.PPUsed // Wenn PP verwendet werden, reduziere die LE-Kosten - if trainCost < 0 { - trainCost = 0 // Verhindere negative LE-Kosten - } - } - costResult.LE = trainCost // Setze die LE-Kosten - costResult.EP = trainCost * SpellEPPerLE // EP-Kosten für das Lernen des Zaubers - costResult.GoldCost = trainCost * 100 // Beispiel: 100 Gold pro LE - costResult.Category = spellCategory - costResult.Difficulty = fmt.Sprintf("Stufe %d", spellLevel) // Zauber haben keine Schwierigkeit, sondern eine Stufe - if reward != nil && *reward == "spruchrolle" { - costResult.GoldCost = 20 // 20 Gold für Jeden Versuch - costResult.EP = costResult.EP / 3 // 1/3 EP-Kosten bei Erfolg - } else { - - if reward != nil && *reward == "halveep" { - costResult.EP = costResult.EP / 2 // Halbiere die EP-Kosten für diese Belohnung - } - if reward != nil && *reward == "halveepnoGold" { - costResult.EP = costResult.EP / 2 // Halbiere die EP-Kosten für diese Belohnung - costResult.GoldCost = 0 // Keine Goldkosten für diese Belohnung - } - } - - // Anwenden von Gold für EP Konvertierung (falls Gold verwendet wird) - if costResult.GoldUsed > 0 { - // 10 Gold = 1 EP, aber maximal EP/2 kann durch Gold ersetzt werden - maxEPFromGold := costResult.EP / 2 - epFromGold := costResult.GoldUsed / 10 - - if epFromGold > maxEPFromGold { - // Beschränke auf maximal EP/2 - epFromGold = maxEPFromGold - costResult.GoldUsed = epFromGold * 10 - } - - // Reduziere EP um die durch Gold ersetzte Menge - costResult.EP -= epFromGold - } - - return nil -} - -// GetLernCostNextLevelOld is deprecated. Use GetLernCostNextLevel instead. -// This function uses the old hardcoded learning cost system. -func GetLernCostNextLevelOld(request *LernCostRequest, costResult *SkillCostResultNew, reward *string, level int, characterRasse string) error { - // Diese Funktion berechnet die Kosten für das Erlernen oder Verbessern einer Fertigkeit oder eines Zaubers - // abhängig von der Aktion (learn/improve) und der Belohnung. - // die Berechnung erfolgt immer für genau 1 Level - // Diese Funktion wird in GetLernCost aufgerufen. - - // Übertrage PP aus dem Request für die Kostenberechnung - // PP sind nur bei "improve" und "spell learn" erlaubt, nicht bei "skill learn" - costResult.PPUsed = 0 - // Gold für EP wird nur bei "improve" Aktionen und "spell learn" verwendet, nicht beim "skill learn" - costResult.GoldUsed = 0 - - switch { - case request.Action == "learn" && request.Type == "skill": - // Skill-Lernen: Kein PP und kein Gold für EP erlaubt - err := CalcSkillLernCostOld(costResult, request.Reward) - if err != nil { - return fmt.Errorf("fehler bei der Kostenberechnung: %w", err) - } - // extrakosten für elfen - if characterRasse == "Elf" { - costResult.EP += 6 - } - case request.Action == "learn" && request.Type == "spell": - // Zauber-Lernen: PP und Gold für EP ist erlaubt - costResult.PPUsed = request.UsePP - costResult.GoldUsed = request.UseGold - err := CalcSpellLernCostOld(costResult, request.Reward) - if err != nil { - return fmt.Errorf("fehler bei der Kostenberechnung: %w", err) - } - // extrakosten für elfen - if characterRasse == "Elf" { - costResult.EP += 6 - } - case request.Action == "improve" && request.Type == "skill": - // Skill-Verbesserung: PP und Gold für EP ist erlaubt - costResult.PPUsed = request.UsePP - costResult.GoldUsed = request.UseGold - err := CalcSkillImproveCostOld(costResult, request.CurrentLevel, request.Reward) - if err != nil { - return fmt.Errorf("fehler bei der Kostenberechnung: %w", err) - } - - default: - } - return nil -} diff --git a/backend/gsmaster/lernkosten_maps_test.go b/backend/gsmaster/lernkosten_maps_test.go index 79d11d1..158f3ec 100644 --- a/backend/gsmaster/lernkosten_maps_test.go +++ b/backend/gsmaster/lernkosten_maps_test.go @@ -11,180 +11,6 @@ func stringPtr(s string) *string { return &s } -// TestGetSkillCategory tests the GetSkillCategory function -func TestGetSkillCategory(t *testing.T) { - tests := []struct { - name string - skillName string - expected []string // Allow multiple valid categories for skills that appear in multiple places - }{ - { - name: "Skill in multiple categories", - skillName: "Klettern", // appears in Alltag, Halbwelt, and Körper - expected: []string{"Alltag", "Halbwelt", "Körper"}, - }, - { - name: "Skill in Freiland category", - skillName: "Überleben", - expected: []string{"Freiland"}, - }, - { - name: "Skill in Waffen category", - skillName: "Stichwaffen", - expected: []string{"Waffen"}, - }, - { - name: "Skill in Wissen category", - skillName: "Alchimie", - expected: []string{"Wissen"}, - }, - { - name: "Skill unique to one category", - skillName: "Gerätekunde", // only in Alltag sehr schwer - expected: []string{"Alltag"}, - }, - { - name: "Non-existent skill", - skillName: "NichtExistierendeFertigkeit", - expected: []string{"Unbekannt"}, - }, - { - name: "Empty skill name", - skillName: "", - expected: []string{"Unbekannt"}, - }, - { - name: "Case sensitive test", - skillName: "klettern", // lowercase - expected: []string{"Unbekannt"}, // should not match "Klettern" - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - - result := GetSkillCategoryOld(tt.skillName) - - // Check if result is in the list of expected values - found := false - for _, expected := range tt.expected { - if result == expected { - found = true - break - } - } - - if !found { - t.Errorf("GetSkillCategory(%q) = %q, want one of %v", tt.skillName, result, tt.expected) - } - }) - } -} - -// TestGetSkillDifficulty tests the GetSkillDifficulty function -func TestGetSkillDifficulty(t *testing.T) { - tests := []struct { - name string - category string - skillName string - expected string - }{ - { - name: "Skill with specific category", - category: "Alltag", - skillName: "Klettern", - expected: "leicht", - }, - { - name: "Skill with specific category - normal difficulty", - category: "Alltag", - skillName: "Schreiben", - expected: "normal", - }, - { - name: "Skill with specific category - schwer difficulty", - category: "Alltag", - skillName: "Erste Hilfe", - expected: "schwer", - }, - { - name: "Skill with specific category - sehr schwer difficulty", - category: "Alltag", - skillName: "Gerätekunde", - expected: "sehr schwer", - }, - { - name: "Skill without category - should return first occurrence", - category: "", - skillName: "Klettern", - expected: "leicht", // appears as leicht in all categories where it exists - }, - { - name: "Skill without category - another skill", - category: "", - skillName: "Überleben", - expected: "leicht", // in Freiland - }, - { - name: "Skill in wrong category", - category: "Waffen", - skillName: "Klettern", // Klettern is not in Waffen category - expected: "Unbekannt", - }, - { - name: "Non-existent category", - category: "NichtExistierendeKategorie", - skillName: "Klettern", - expected: "Unbekannt", - }, - { - name: "Non-existent skill with valid category", - category: "Alltag", - skillName: "NichtExistierendeFertigkeit", - expected: "Unbekannt", - }, - { - name: "Non-existent skill without category", - category: "", - skillName: "NichtExistierendeFertigkeit", - expected: "Unbekannt", - }, - { - name: "Empty skill name with category", - category: "Alltag", - skillName: "", - expected: "Unbekannt", - }, - { - name: "Empty skill name without category", - category: "", - skillName: "", - expected: "Unbekannt", - }, - { - name: "Skill that appears in multiple categories - specific category", - category: "Halbwelt", - skillName: "Klettern", // also exists in Alltag and Körper - expected: "leicht", - }, - { - name: "Skill in Freiland schwer", - category: "Freiland", - skillName: "Schleichen", - expected: "schwer", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - result := GetSkillDifficultyOld(tt.category, tt.skillName) - if result != tt.expected { - t.Errorf("GetSkillDifficulty(%q, %q) = %q, want %q", tt.category, tt.skillName, result, tt.expected) - } - }) - } -} - // TestContains tests the contains helper function func TestContains(t *testing.T) { tests := []struct { @@ -253,121 +79,6 @@ func TestContains(t *testing.T) { } } -// TestCalcSkillLernCost tests the CalcSkillLernCost function -func TestCalcSkillLernCost(t *testing.T) { - tests := []struct { - name string - costResult *SkillCostResultNew - expectError bool - expectedLE int - expectedEP int - expectedGold int - }{ - { - name: "Valid calculation for Assassine Alltag leicht", - costResult: &SkillCostResultNew{ - CharacterClass: "As", - Category: "Alltag", - Difficulty: "leicht", - }, - expectError: false, - expectedLE: 1, // LearnCost for leicht in Alltag - expectedEP: 60, // 20 (EP per TE for As/Alltag) * 1 (LE) * 3 - expectedGold: 200, // 1 (LE) * 200 - }, - { - name: "Valid calculation for Krieger Waffen schwer", - costResult: &SkillCostResultNew{ - CharacterClass: "Kr", - Category: "Waffen", - Difficulty: "schwer", - }, - expectError: false, - expectedLE: 6, // LearnCost for schwer in Waffen - expectedEP: 180, // 10 (EP per TE for Kr/Waffen) * 6 (LE) * 3 - expectedGold: 1200, // 6 (LE) * 200 - }, - { - name: "Valid calculation for Magier Wissen normal", - costResult: &SkillCostResultNew{ - CharacterClass: "Ma", - Category: "Wissen", - Difficulty: "normal", - }, - expectError: false, - expectedLE: 2, // LearnCost for normal in Wissen - expectedEP: 60, // 10 (EP per TE for Ma/Wissen) * 2 (LE) * 3 - expectedGold: 400, // 2 (LE) * 200 - }, - { - name: "Invalid character class", - costResult: &SkillCostResultNew{ - CharacterClass: "InvalidClass", - Category: "Alltag", - Difficulty: "leicht", - }, - expectError: true, - }, - { - name: "Invalid category", - costResult: &SkillCostResultNew{ - CharacterClass: "As", - Category: "InvalidCategory", - Difficulty: "leicht", - }, - expectError: true, - }, - { - name: "Invalid difficulty", - costResult: &SkillCostResultNew{ - CharacterClass: "As", - Category: "Alltag", - Difficulty: "InvalidDifficulty", - }, - expectError: true, - }, - { - name: "Valid but category not in character class", - costResult: &SkillCostResultNew{ - CharacterClass: "As", - Category: "Schilde und Parierwaffen", // This category might not have EP costs for As - Difficulty: "normal", - }, - expectError: true, // Should fail because EP costs not found - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := CalcSkillLernCostOld(tt.costResult, nil) // nil reward for original tests - - if tt.expectError { - if err == nil { - t.Errorf("CalcSkillLernCost() expected error but got none") - } - return - } - - if err != nil { - t.Errorf("CalcSkillLernCost() unexpected error: %v", err) - return - } - - if tt.costResult.LE != tt.expectedLE { - t.Errorf("CalcSkillLernCost() LE = %d, want %d", tt.costResult.LE, tt.expectedLE) - } - - if tt.costResult.EP != tt.expectedEP { - t.Errorf("CalcSkillLernCost() EP = %d, want %d", tt.costResult.EP, tt.expectedEP) - } - - if tt.costResult.GoldCost != tt.expectedGold { - t.Errorf("CalcSkillLernCost() GoldCost = %d, want %d", tt.costResult.GoldCost, tt.expectedGold) - } - }) - } -} - // TestLearningCostsDataIntegrity tests the integrity of the learning costs data structure func TestLearningCostsDataIntegrity(t *testing.T) { // Test that learningCostsData is not nil @@ -422,324 +133,6 @@ func TestLearningCostsDataIntegrity(t *testing.T) { } } -// TestSkillCoverage tests that all skills in the data structure can be found by the functions -func TestSkillCoverage(t *testing.T) { - skillsFound := make(map[string]bool) - - // Collect all skills from the data structure - for category, difficulties := range learningCostsData.ImprovementCost { - for difficulty, data := range difficulties { - for _, skill := range data.Skills { - if skill != "" { // Skip empty skill names - skillsFound[skill] = false - - // Test that GetSkillCategory can find this skill - foundCategory := GetSkillCategoryOld(skill) - if foundCategory == "Unbekannt" { - t.Errorf("GetSkillCategory could not find skill: %s (should be in %s)", skill, category) - } - - // Test that GetSkillDifficulty can find this skill without category - foundDifficulty := GetSkillDifficultyOld("", skill) - if foundDifficulty == "Unbekannt" { - t.Errorf("GetSkillDifficulty could not find skill: %s (should have difficulty %s)", skill, difficulty) - } - - // Test that GetSkillDifficulty can find this skill with category - foundDifficultyWithCategory := GetSkillDifficultyOld(category, skill) - if foundDifficultyWithCategory == "Unbekannt" { - t.Errorf("GetSkillDifficulty could not find skill: %s in category %s (should have difficulty %s)", skill, category, difficulty) - } - - skillsFound[skill] = true - } - } - } - } - - t.Logf("Tested coverage for %d unique skills", len(skillsFound)) -} - -// TestFindBestCategoryForSkill tests the findBestCategoryForSkill function -func TestFindBestCategoryForSkill(t *testing.T) { - tests := []struct { - name string - skillName string - characterClass string - currentLevel int - expectedCategory string - expectError bool - }{ - { - name: "Klettern - should choose cheapest category", - skillName: "Klettern", - characterClass: "Kr", // Krieger - currentLevel: 13, // Level 13->14 - // Klettern ist in: Alltag (leicht), Halbwelt (leicht), Körper (leicht) - // Für Kr: Alltag=20 EP/TE, Halbwelt=30 EP/TE, Körper=20 EP/TE - // Level 13->14 kostet in allen 1 TE, also 20*1=20 EP für Alltag und Körper, 30*1=30 EP für Halbwelt - // Sollte Alltag oder Körper wählen (beide gleich günstig) - expectedCategory: "Alltag", // oder "Körper" - beide sind gleich günstig - expectError: false, - }, - { - name: "Non-existent skill", - skillName: "NichtExistierendeFertigkeit", - characterClass: "Kr", - currentLevel: 10, - expectError: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - category, difficulty, err := findBestCategoryForSkillImprovementOld(tt.skillName, tt.characterClass, tt.currentLevel) - - if tt.expectError { - if err == nil { - t.Errorf("Expected error but got none") - } - return - } - - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } - - // Für Klettern sind mehrere Kategorien gleich günstig, also akzeptieren wir alle - if tt.skillName == "Klettern" { - validCategories := []string{"Alltag", "Körper"} // Beide haben 20 EP/TE für Kr - found := false - for _, validCat := range validCategories { - if category == validCat { - found = true - break - } - } - if !found { - t.Errorf("Expected category to be one of %v, got %s", validCategories, category) - } - } else { - if category != tt.expectedCategory { - t.Errorf("Expected category %s, got %s", tt.expectedCategory, category) - } - } - - t.Logf("Skill %s for class %s at level %d: category=%s, difficulty=%s", - tt.skillName, tt.characterClass, tt.currentLevel, category, difficulty) - }) - } -} - -// TestCalcSkillLernCostWithRewards tests the reward logic in CalcSkillLernCost -func TestCalcSkillLernCostWithRewards(t *testing.T) { - tests := []struct { - name string - skillName string - characterClass string - reward *string - expectedGold int - expectedEPMult float64 // multiplier for EP (1.0 = normal, 0.5 = half) - }{ - { - name: "Default reward - normal costs", - skillName: "Klettern", - characterClass: "Kr", // Use abbreviation - reward: stringPtr("default"), - expectedGold: 200, // 1 LE * 200 Gold per LE - expectedEPMult: 1.0, - }, - { - name: "NoGold reward - no gold cost", - skillName: "Klettern", - characterClass: "Kr", // Use abbreviation - reward: stringPtr("noGold"), - expectedGold: 0, // Should be 0 with noGold reward - expectedEPMult: 1.0, - }, - { - name: "No reward - normal costs", - skillName: "Klettern", - characterClass: "Kr", // Use abbreviation - reward: nil, - expectedGold: 200, // 1 LE * 200 Gold per LE - expectedEPMult: 1.0, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Create cost result - cat, difficulty, _ := FindBestCategoryForSkillLearningOld(tt.skillName, tt.characterClass) - costResult := &SkillCostResultNew{ - CharacterClass: tt.characterClass, - SkillName: tt.skillName, - Category: cat, - Difficulty: difficulty, - } - - // Calculate normal costs first to get baseline EP - baselineResult := &SkillCostResultNew{ - CharacterClass: tt.characterClass, - SkillName: tt.skillName, - Category: costResult.Category, - Difficulty: costResult.Difficulty, - } - err := CalcSkillLernCostOld(baselineResult, stringPtr("default")) - if err != nil { - t.Fatalf("Failed to calculate baseline costs: %v", err) - } - - // Calculate costs with reward - err = CalcSkillLernCostOld(costResult, tt.reward) - if err != nil { - t.Fatalf("Failed to calculate costs: %v", err) - } - - // Check gold cost - if costResult.GoldCost != tt.expectedGold { - t.Errorf("Expected gold cost %d, got %d", tt.expectedGold, costResult.GoldCost) - } - - // Check EP cost - expectedEP := int(float64(baselineResult.EP) * tt.expectedEPMult) - if costResult.EP != expectedEP { - t.Errorf("Expected EP %d (baseline %d * %.1f), got %d", expectedEP, baselineResult.EP, tt.expectedEPMult, costResult.EP) - } - - // LE should always be the same regardless of reward - if costResult.LE != baselineResult.LE { - t.Errorf("LE should be unchanged by rewards. Expected %d, got %d", baselineResult.LE, costResult.LE) - } - }) - } -} - -// TestCalcSpellLernCostWithRewards tests the reward logic in CalcSpellLernCost -/* -func TestCalcSpellLernCostWithRewards(t *testing.T) { - costResult := &SkillCostResultNew{ - CharacterClass: "Ma", // Use abbreviation - SkillName: "TestSpell", - Category: "Hellsicht", // Use existing category - Difficulty: "Schwer", - } - - // Test with noGold reward - err := CalcSpellLernCost(costResult, stringPtr("noGold")) - if err != nil { - t.Fatalf("Failed to calculate spell costs: %v", err) - } - - if costResult.GoldCost != 0 { - t.Errorf("Expected gold cost 0 with noGold reward, got %d", costResult.GoldCost) - } -} -*/ - -// TestCalcSkillImproveCostWithRewards tests the reward logic in CalcSkillImproveCost -func TestCalcSkillImproveCostWithRewards(t *testing.T) { - tests := []struct { - name string - skillName string - characterClass string - currentLevel int // represents the level the character currently has must be incremented by 1 when calculating the costs - ppUsed int - reward *string - expectedEP int - expectedGold int - }{ - { - name: "Normal improvement to 13 without reward", - skillName: "Klettern", - characterClass: "Kr", - currentLevel: 12, - ppUsed: 0, - reward: nil, - expectedEP: 20, // Kr has 20 EP/TE for Alltag, level 12->13 costs 0 TE, so 20*0=0 - expectedGold: 20, // 0 TE * 20 Gold per TE - }, - { - name: "Normal improvement to 14 without reward", - skillName: "Klettern", - characterClass: "Kr", - currentLevel: 13, - ppUsed: 0, - reward: nil, - expectedEP: 40, // Kr has 20 EP/TE for Alltag, level 13->14 costs 1 TE, so 20*1=20 - expectedGold: 40, // 1 TE * 20 Gold per TE - }, - { - name: "Improvement with halveep reward", - skillName: "Klettern", - characterClass: "Kr", - currentLevel: 13, - ppUsed: 0, - reward: stringPtr("halveep"), - expectedEP: 20, // Kr has 20 EP/TE for Alltag, level 13->14 costs 1 TE, so 20*1=20, halved = 10 - expectedGold: 40, // Gold cost not affected by halveep - }, - - { - name: "Improvement to 15 without reward", - skillName: "Klettern", - characterClass: "Kr", - currentLevel: 14, - ppUsed: 0, - reward: nil, - expectedEP: 100, // Kr has 20 EP/TE for Alltag, level 14->15 costs 2 TE, minus 1 PP = 1 TE, so 20*1=20 - expectedGold: 100, // 1 TE * 20 Gold per TE - }, - { - name: "Improvement to 15 with PP used", - skillName: "Klettern", - characterClass: "Kr", - currentLevel: 14, - ppUsed: 1, - reward: nil, - expectedEP: 80, // Kr has 20 EP/TE for Alltag, level 14->15 costs 2 TE, minus 1 PP = 1 TE, so 20*1=20 - expectedGold: 80, // 1 TE * 20 Gold per TE - }, - { - name: "Improvement with halveepnoGold reward", - skillName: "Klettern", - characterClass: "Kr", - currentLevel: 15, - ppUsed: 0, - reward: stringPtr("halveepnoGold"), - expectedEP: 100, // Kr has 20 EP/TE for Alltag, level 15->16 costs 5 TE, so 20*5=100, halved = 50 - expectedGold: 0, // Should be 0 with halveepnoGold reward - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - costResult := &SkillCostResultNew{ - CharacterClass: tt.characterClass, - SkillName: tt.skillName, - PPUsed: tt.ppUsed, - // Lassen Sie Kategorie und Schwierigkeit leer, damit die Funktion die beste auswählt - } - - err := CalcSkillImproveCostOld(costResult, tt.currentLevel, tt.reward) - if err != nil { - t.Fatalf("Failed to calculate improvement costs: %v", err) - } - - // Log the chosen category for debugging - t.Logf("Skill: %s, Class %s, Chosen category: %s, difficulty: %s", costResult.SkillName, costResult.CharacterClass, costResult.Category, costResult.Difficulty) - - if costResult.EP != tt.expectedEP { - t.Errorf("Expected EP %d, got %d", tt.expectedEP, costResult.EP) - } - - if costResult.GoldCost != tt.expectedGold { - t.Errorf("Expected gold cost %d, got %d", tt.expectedGold, costResult.GoldCost) - } - }) - } -} - // TestGetSpellInfo tests the GetSpellInfo function func TestGetSpellInfo(t *testing.T) { @@ -840,345 +233,3 @@ func TestGetSpellInfo(t *testing.T) { }) } } - -// TestCalcSpellLernCostWithRewards tests the reward logic in CalcSpellLernCost -func TestCalcSpellLernCostWithRewards(t *testing.T) { - // Initialize test database with migration (but no test data since we don't have the preparedTestDB file) - database.SetupTestDB(true, false) // Use in-memory SQLite, no test data loading - defer database.ResetTestDB() - models.MigrateStructure() - - // Create minimal test spell data for our test - testSpells := []models.Spell{ - { - GameSystem: "midgard", - Name: "Schlummer", - Beschreibung: "Test spell for GetSpellInfo", - Quelle: "Test", - Stufe: 1, - Category: "Beherrschen", - }, - { - GameSystem: "midgard", - Name: "Erkennen von Krankheit", - Beschreibung: "Test spell for GetSpellInfo", - Quelle: "Test", - Stufe: 2, - Category: "Dweomer", - }, - { - GameSystem: "midgard", - Name: "Das Loblied", - Beschreibung: "Test spell for GetSpellInfo", - Quelle: "Test", - Stufe: 3, - Category: "Zauberlied", - }, - } - // Insert test data directly - for _, spell := range testSpells { - if err := database.DB.Create(&spell).Error; err != nil { - t.Fatalf("Failed to create test spell: %v", err) - } - } - - tests := []struct { - name string - spellName string - characterClass string - reward *string - expectedEP int - expectedGold int - }{ - { - name: "Simple spell for Magier without but specialized", - spellName: "Schlummer", - characterClass: "Ma", - reward: nil, - expectedEP: 30, // Ma has 60 EP/LE for Beherrschen, Furcht is level 1 = 1 LE, so 1*60=60 - expectedGold: 100, // 1 LE * 100 Gold per LE - }, - { - name: "Spell with spruchrolle no reward", - spellName: "Erkennen von Krankheit", - characterClass: "Ma", - reward: nil, - expectedEP: 120, // 60/3 for spruchrolle - expectedGold: 100, // Fixed 20 Gold for spruchrolle - }, - { - name: "Spell with spruchrolle reward", - spellName: "Erkennen von Krankheit", - characterClass: "Ma", - reward: stringPtr("spruchrolle"), - expectedEP: 40, // 60/3 for spruchrolle - expectedGold: 20, // Fixed 20 Gold for spruchrolle - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - costResult := &SkillCostResultNew{ - CharacterClass: tt.characterClass, - SkillName: tt.spellName, - CharacterID: "test-character", - } - - err := CalcSpellLernCostOld(costResult, tt.reward) - if err != nil { - t.Fatalf("Failed to calculate spell costs: %v", err) - } - - if costResult.EP != tt.expectedEP { - t.Errorf("Expected EP %d, got %d", tt.expectedEP, costResult.EP) - } - - if costResult.GoldCost != tt.expectedGold { - t.Errorf("Expected gold cost %d, got %d", tt.expectedGold, costResult.GoldCost) - } - }) - } -} - -// TestGetSpecialization tests the GetSpecialization function -func TestGetSpecialization(t *testing.T) { - tests := []struct { - name string - characterID string - expectedSpec string - }{ - { - name: "Default specialization", - characterID: "123", - expectedSpec: "Beherrschen", - }, - { - name: "Another character", - characterID: "456", - expectedSpec: "Beherrschen", // Currently returns default - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - result := GetSpecializationOld(tt.characterID) - if result != tt.expectedSpec { - t.Errorf("Expected specialization %s, got %s", tt.expectedSpec, result) - } - }) - } -} - -// TestFindBestCategoryForSkillLearning tests the findBestCategoryForSkillLearning function -func TestFindBestCategoryForSkillLearning(t *testing.T) { - tests := []struct { - name string - skillName string - characterClass string - expectedCat string - expectedDiff string - expectError bool - }{ - { - name: "Klettern for Assassine - should find best category", - skillName: "Klettern", - characterClass: "As", - expectedCat: "Körper", // Should prefer Körper (10 EP/TE * 1 LE * 3 = 30 EP) over Alltag (20 EP/TE * 1 LE * 3 = 60 EP) - expectedDiff: "leicht", - expectError: false, - }, - { - name: "Schleichen for Spitzbube - should find Unterwelt", - skillName: "Schleichen", - characterClass: "Sp", - expectedCat: "Unterwelt", // Sp has 10 EP/TE for Unterwelt vs 30 EP/TE for Freiland - expectedDiff: "normal", - expectError: false, - }, - { - name: "Invalid skill", - skillName: "NonExistentSkill", - characterClass: "As", - expectError: true, - }, - { - name: "Invalid character class", - skillName: "Klettern", - characterClass: "InvalidClass", - expectError: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - category, difficulty, err := FindBestCategoryForSkillLearningOld(tt.skillName, tt.characterClass) - - if tt.expectError { - if err == nil { - t.Error("Expected an error but got none") - } - return - } - - if err != nil { - t.Errorf("Unexpected error: %v", err) - return - } - - if category != tt.expectedCat { - t.Errorf("Expected category %s, got %s", tt.expectedCat, category) - } - - if difficulty != tt.expectedDiff { - t.Errorf("Expected difficulty %s, got %s", tt.expectedDiff, difficulty) - } - }) - } -} - -// TestGetLernCostNextLevel tests the GetLernCostNextLevel function -func TestGetLernCostNextLevel(t *testing.T) { - tests := []struct { - name string - request *LernCostRequest - costResult *SkillCostResultNew - reward *string - level int - characterTyp string - expectError bool - expectedEP int - expectedGold int - expectedElfEP int // Expected EP bonus for Elves - }{ - { - name: "Learn skill as Human", - request: &LernCostRequest{ - Action: "learn", - Type: "skill", - Reward: stringPtr("default"), - }, - costResult: &SkillCostResultNew{ - CharacterClass: "As", - SkillName: "Klettern", - Category: "Körper", - Difficulty: "leicht", - }, - level: 1, - characterTyp: "Mensch", - expectError: false, - expectedEP: 30, // 10 * 1 * 3 - expectedGold: 200, // 1 * 200 - }, - { - name: "Learn skill as Human Kr", - request: &LernCostRequest{ - Action: "learn", - Type: "skill", - Reward: stringPtr("default"), - }, - costResult: &SkillCostResultNew{ - CharacterClass: "Kr", - SkillName: "Abrichten", - Category: "Körper", - Difficulty: "leicht", - }, - level: 1, - characterTyp: "Mensch", - expectError: true, // TODO Abrichten kommt im Mysterium mit dem Tiermeister - expectedEP: 30, // 10 * 1 * 3 - expectedGold: 200, // 1 * 200 - }, - { - name: "Learn skill as Elf - should have EP bonus", - request: &LernCostRequest{ - Action: "learn", - Type: "skill", - Reward: stringPtr("default"), - }, - costResult: &SkillCostResultNew{ - CharacterClass: "As", - SkillName: "Klettern", - Category: "Körper", - Difficulty: "leicht", - }, - level: 1, - characterTyp: "Elf", - expectError: false, - expectedEP: 30, - expectedElfEP: 6, // Additional 6 EP for Elves - expectedGold: 200, - }, - { - name: "Improve skill as human", - request: &LernCostRequest{ - Action: "improve", - Type: "skill", - CurrentLevel: 12, - Reward: stringPtr("default"), - }, - costResult: &SkillCostResultNew{ - CharacterClass: "As", - SkillName: "Klettern", - Category: "Körper", - Difficulty: "leicht", - }, - level: 13, - characterTyp: "Mensch", - expectError: false, - expectedEP: 10, // 10 * 1 (TE cost for level 13) - expectedGold: 20, // 1 * 20 - }, - - { - name: "Improve skill as Elf", - request: &LernCostRequest{ - Action: "improve", - Type: "skill", - CurrentLevel: 12, - Reward: stringPtr("default"), - }, - costResult: &SkillCostResultNew{ - CharacterClass: "As", - SkillName: "Klettern", - Category: "Körper", - Difficulty: "leicht", - }, - level: 13, - characterTyp: "Elf", - expectError: false, - expectedEP: 10, // 10 * 1 (TE cost for level 13) - expectedGold: 20, // 1 * 20 - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := GetLernCostNextLevelOld(tt.request, tt.costResult, tt.reward, tt.level, tt.characterTyp) - - if tt.expectError { - if err == nil { - t.Error("Expected an error but got none") - } - return - } - - if err != nil { - t.Errorf("Unexpected error: %v", err) - return - } - - expectedTotalEP := tt.expectedEP - if tt.characterTyp == "Elf" && (tt.request.Action == "learn") { - expectedTotalEP += tt.expectedElfEP - } - - if tt.costResult.EP != expectedTotalEP { - t.Errorf("Expected EP %d, got %d", expectedTotalEP, tt.costResult.EP) - } - - if tt.costResult.GoldCost != tt.expectedGold { - t.Errorf("Expected gold cost %d, got %d", tt.expectedGold, tt.costResult.GoldCost) - } - }) - } -} diff --git a/backend/gsmaster/lerntabellen_test.go b/backend/gsmaster/lerntabellen_test.go deleted file mode 100644 index 5e507b1..0000000 --- a/backend/gsmaster/lerntabellen_test.go +++ /dev/null @@ -1,53 +0,0 @@ -package gsmaster - -import ( - "fmt" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestLerntabellenForHexer(t *testing.T) { - // Testfall für Menschenkenntnis als Hexer - Lernkosten - t.Run("Lernkosten für Menschenkenntnis als Hexer", func(t *testing.T) { - // Verwende die exportierte Funktion CalculateDetailedSkillLearningCost - result, err := CalculateDetailedSkillLearningCostOld("Menschenkenntnis", "Hexer") - assert.NoError(t, err, "CalculateDetailedSkillLearningCost sollte keinen Fehler zurückgeben") - assert.NotNil(t, result, "Ergebnis sollte nicht nil sein") - - // Ausgabe der Ergebnisse - fmt.Printf("Lernkosten für Menschenkenntnis als Hexer:\n") - fmt.Printf("Lerneinheiten (LE): %d\n", result.LE) - fmt.Printf("Erfahrungspunkte (EP): %d\n", result.Ep) - fmt.Printf("Geldkosten (GS): %d\n", result.Money) - - // Überprüfung der Werte basierend auf aktueller Implementierung - // Menschenkenntnis ist Sozial/schwer: 4 LE - // Hexer EP-Kosten für Sozial: 20 EP/TE - // Money-Kosten: 20 GS/LE - assert.Equal(t, 4, result.LE, "LE-Kosten sollten 4 sein") - assert.Equal(t, 80, result.Ep, "EP-Kosten sollten 80 sein (4 LE * 20 EP)") - assert.Equal(t, 80, result.Money, "Geldkosten sollten 80 GS sein (4 LE * 20 GS)") - }) - - // Testfall für Menschenkenntnis als Hexer - Verbesserungskosten - t.Run("Verbesserungskosten für Menschenkenntnis als Hexer", func(t *testing.T) { - // Verwende die exportierte Funktion CalculateDetailedSkillImprovementCost - currentLevel := 10 - result, err := CalculateDetailedSkillImprovementCostOld("Menschenkenntnis", "Hexer", currentLevel) - assert.NoError(t, err, "CalculateDetailedSkillImprovementCost sollte keinen Fehler zurückgeben") - assert.NotNil(t, result, "Ergebnis sollte nicht nil sein") - - // Ausgabe der Ergebnisse - fmt.Printf("\nVerbesserungskosten für Menschenkenntnis als Hexer (von %d auf %d):\n", currentLevel, currentLevel+1) - fmt.Printf("Zielstufe: %d\n", result.Stufe) - fmt.Printf("Erfahrungspunkte (EP): %d\n", result.Ep) - fmt.Printf("Geldkosten (GS): %d\n", result.Money) - - // Überprüfung der Werte basierend auf aktueller Implementierung - // Result.Stufe ist die Zielstufe (11), nicht die benötigten TE - assert.Equal(t, 11, result.Stufe, "Zielstufe sollte 11 sein (von 10 auf 11)") - assert.Equal(t, 80, result.Ep, "EP-Kosten sollten 80 sein") - assert.Equal(t, 80, result.Money, "Geldkosten sollten 80 GS sein") - }) -} diff --git a/backend/gsmaster/levelup.go b/backend/gsmaster/levelup.go index 44af604..63179f9 100644 --- a/backend/gsmaster/levelup.go +++ b/backend/gsmaster/levelup.go @@ -2,10 +2,8 @@ package gsmaster import ( "bamort/models" - "encoding/json" "errors" "fmt" - "os" ) type SkillGroup string @@ -69,106 +67,6 @@ var AllowedGroups = map[CharClass]map[SkillGroup]bool} var Config LevelConfig // holds all loaded data -// loadLevelingConfigOld is deprecated. Use the new database-based learning cost system instead. -// This function loads data from static JSON files. -func loadLevelingConfigOld(opts ...string) error { - // Adjust path as needed - filePath := "../testdata/leveldata.json" - if len(opts) > 0 { - filePath = opts[0] - } - file, err := os.Open(filePath) - if err != nil { - return fmt.Errorf("failed to open JSON file: %w", err) - } - defer file.Close() - - // Decode the JSON file into the ExportData structure - decoder := json.NewDecoder(file) - if err := decoder.Decode(&Config); err != nil { - return fmt.Errorf("failed to decode JSON file: %w", err) - } - return nil -} - -// CalculateSpellLearnCostOld is deprecated. Use CalcSpellLernCost instead. -// This function uses the old hardcoded learning cost system. -// CalculateSpellLearnCostOld combines SpellLearnCost with SpellEPPerSchoolByClass -func CalculateSpellLearnCostOld(spell string, class string) (int, error) { - if Config.AllowedSchools == nil { - loadLevelingConfigOld() - } - - var spl models.Spell - if err := spl.First(spell); err != nil { - return 0, errors.New("unbekannter Zauberspruch") - } - - if !Config.AllowedSchools[CharClass(class)][spl.Category] { - return 0, fmt.Errorf("die Klasse %s darf die Schule %s nicht lernen", class, spl.Category) - } - neededLE, ok := Config.SpellLearnCost[spl.Stufe] - if !ok { - return 0, fmt.Errorf("ungültige Zauberstufe: %d", spl.Stufe) - } - - classMap, ok := Config.SpellEPPerSchoolByClass[CharClass(class)] - if !ok { - return 0, fmt.Errorf("keine EP-Tabelle für Klasse: %s", class) - } - - epPerTE, found := classMap[spl.Category] - if !found { - return 0, fmt.Errorf("unbekannte Schule '%s' bei Klasse '%s'", spl.Category, class) - } - - // Gesamt-EP = benötigte LE * EP pro LE. - totalEP := neededLE * (epPerTE * 3) - // +6 EP for elves - if class == "Elf" { - totalEP += 6 - } - - return totalEP, nil -} - -// CalculateSkillLearnCostOld is deprecated. Use CalcSkillLernCost instead. -// This function uses the old hardcoded learning cost system. -// CalculateSkillLearnCostOld: erstmalige Kosten in EP -// Then refer to Config in your calculations: -func CalculateSkillLearnCostOld(skill string, class string) (int, error) { - /* - if !Config.AllowedGroups[class][skill.Group] { - return 0, fmt.Errorf("die Klasse %s darf %s nicht lernen", class, skill.Group) - } - */ - var skl models.Skill - if err := skl.First(skill); err != nil { - return 0, errors.New("unbekannte Fertigkeit") - } - - groupMap, ok := Config.BaseLearnCost[SkillGroup(skl.Category)] - if !ok { - return 0, errors.New("unbekannte Gruppe") - } - - baseLE, ok := groupMap[Difficulty(skl.Difficulty)] - if !ok { - return 0, errors.New("keine LE-Definition für diese Schwierigkeit") - } - epPerTE, ok := Config.EPPerTE[CharClass(class)][SkillGroup(skl.Category)] - if !ok { - return 0, fmt.Errorf("keine EP-Kosten für %s bei %s", class, skl.Category) - } - totalEP := baseLE * (epPerTE * 3) - // +6 EP for elves - if class == "Elf" { - totalEP += 6 - } - - return totalEP, nil -} - // CalculateImprovementCost: Kosten zum Steigern von +X auf +X+1 func CalculateSkillImprovementCost(skill string, class string, currentSkillLevel int) (*models.LearnCost, error) { return CalculateImprovementCost(skill, class, currentSkillLevel) @@ -209,12 +107,3 @@ func CalculateImprovementCost(skill string, class string, currentSkillLevel int) lCost.Money = lCost.Ep return &lCost, nil } - -func CalculateLearnCost(skillType string, name string, class string) (int, error) { - if skillType == "skill" { - return CalculateSkillLearnCostOld(name, class) - } else if skillType == "spell" { - return CalculateSpellLearnCostOld(name, class) - } - return 0, errors.New("unknown skill type") -} diff --git a/backend/gsmaster/levelup_test.go b/backend/gsmaster/levelup_test.go index bbab36c..00b37d9 100644 --- a/backend/gsmaster/levelup_test.go +++ b/backend/gsmaster/levelup_test.go @@ -56,269 +56,8 @@ func setupTestDB(opts ...bool) { } } -func TestLoadLevelingConfig(t *testing.T) { - // Save original Config - originalConfig := Config - - // Test invalid file path - os.Setenv("CONFIG_PATH", "/invalid/path/leveldata.json") - defer func() { - if r := recover(); r == nil { - t.Error("Expected panic with invalid file path") - } - // Restore original Config - Config = originalConfig - }() - - // Call init() which should panic - loadLevelingConfigOld("/invalid/path/leveldata.json") -} - -func TestInitValidConfig(t *testing.T) { - loadLevelingConfigOld() - setupTestDB(false) - /* - // Save original Config - originalConfig := Config - defer func() { - Config = originalConfig - }() - */ - - // Test with valid config file - os.Setenv("CONFIG_PATH", "/data/dev/bamort/config/leveldata.json") - loadLevelingConfigOld("../testdata/leveldata.json") - - // Verify Config was populated - assert.LessOrEqual(t, 1, len(Config.BaseLearnCost), "Expected BaseLearnCost to be populated") - assert.LessOrEqual(t, 1, len(Config.ImprovementCost), "Expected BaseLearImprovementCostnCost to be populated") - assert.LessOrEqual(t, 1, len(Config.EPPerTE), "Expected EPPerTE to be populated") -} - -func TestCalculateSpellLearnCost(t *testing.T) { - loadLevelingConfigOld() - setupTestDB(false) - /* - // Save original Config - originalConfig := Config - defer func() { - Config = originalConfig - }() - - // Set up test config - Config = LevelConfig{ - SpellLearnCost: map[int]int{ - 1: 1, - 2: 2, - }, - SpellEPPerSchoolByClass: map[CharClass]map[string]int{ - "Magier": {"Beweg": 10}, - "Elfe": {"Beweg": 15}, - }, - AllowedSchools: map[CharClass]map[string]bool{ - "Magier": {"Beweg": true}, - "Elfe": {"Beweg": true}, - }, - } - */ - - tests := []struct { - name string - spell SpellDefinition - //class CharClass - class string - wantEP int - wantErr bool - errContains string - }{ - { - name: "valid spell for magier", - spell: SpellDefinition{Name: "Angst", Stufe: 2, School: "Beherrschen"}, - class: "Magier", - wantEP: 180, // 1 LE * (10 EP * 3) - }, - { - name: "valid spell for elf", - spell: SpellDefinition{Name: "Angst", Stufe: 2, School: "Beherrschen"}, - class: "Elfe", - wantEP: 51, // (1 LE * (15 EP * 3)) + 6 - wantErr: true, - }, - { - name: "invalid spell level", - spell: SpellDefinition{Name: "Angst", Stufe: 99, School: "Beherrschen"}, - class: "Magier", - wantErr: true, - errContains: "ungültige Zauberstufe", - }, - { - name: "invalid class", - spell: SpellDefinition{Name: "Angst", Stufe: 2, School: "Beherrschen"}, - class: "InvalidClass", - wantErr: true, - errContains: "keine EP-Tabelle für Klasse", - }, - { - name: "invalid school", - spell: SpellDefinition{Name: "Angst", Stufe: 2, School: "Beherrschen"}, - class: "Magier", - wantErr: true, - errContains: "unbekannte Schule", - }, - { - name: "not allowed school", - spell: SpellDefinition{Name: "Angst", Stufe: 2, School: "Beherrschen"}, - class: "Krieger", - wantErr: true, - errContains: "darf die Schule", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := CalculateSpellLearnCostOld(tt.spell.Name, tt.class) - if tt.wantErr { - if err == nil { - assert.Error(t, err, "CalculateSpellLearnCost() expected error") - } else if tt.errContains != "" && !strings.Contains(err.Error(), tt.errContains) { - assert.Error(t, err, "CalculateSpellLearnCost() error = %v, should contain %v", err, tt.errContains) - //t.Errorf("CalculateSpellLearnCost() error = %v, should contain %v", err, tt.errContains) - } - return - } - if err != nil { - assert.NoError(t, err, "CalculateSpellLearnCost() unexpected error = %v", err) - //t.Errorf("CalculateSpellLearnCost() unexpected error = %v", err) - return - } - if got != tt.wantEP { - assert.Equal(t, tt.wantEP, got, "CalculateSpellLearnCost() = %v, want %v", got, tt.wantEP) - //t.Errorf("CalculateSpellLearnCost() = %v, want %v", got, tt.wantEP) - } - }) - } -} - -func TestCalculateLearnCost(t *testing.T) { - loadLevelingConfigOld() - setupTestDB(false) - // Save original Config - /* - originalConfig := Config - defer func() { - Config = originalConfig - }() - */ - - // Set up test config - /* - Config = LevelConfig{ - BaseLearnCost: map[SkillGroup]map[Difficulty]int{ - "Alltag": { - "leicht": 1, - "normal": 1, - "schwer": 2, - "sehr_schwer": 10, - }, - }, - EPPerTE: map[CharClass]map[SkillGroup]int{ - "Krieger": {"Alltag": 20}, - "Elf": {"Alltag": 30}, - }, - } - */ - - tests := []struct { - name string - skill SkillDefinition - class string - wantEP int - wantErr bool - errContains string - }{ - { - name: "valid skill for warrior", - skill: SkillDefinition{ - Name: "Bootfahren", - Group: "Alltag", - Difficulty: "leicht", - }, - class: "Krieger", - wantEP: 60, // 1 LE * (20 EP * 3) - }, - { - name: "valid skill for elf", - skill: SkillDefinition{ - Name: "Bootfahren", - Group: "Alltag", - Difficulty: "leicht", - }, - class: "Elf", - wantEP: 96, // (1 LE * (30 EP * 3)) + 6 - wantErr: true, - }, - { - name: "invalid group", - skill: SkillDefinition{ - Name: "Erste Hilfe", - Group: "InvalidGroup", - Difficulty: "leicht", - }, - class: "Krieger", - wantErr: true, - errContains: "unbekannte Gruppe", - }, - { - name: "invalid difficulty", - skill: SkillDefinition{ - Name: "Geländelauf", - Group: "Körper", - Difficulty: "invalid", - }, - class: "Krieger", - wantErr: true, - errContains: "keine LE-Definition für diese Schwierigkeit", - }, - { - name: "invalid class", - skill: SkillDefinition{ - Name: "Test", - Group: "Alltag", - Difficulty: "leicht", - }, - class: "InvalidClass", - wantErr: true, - errContains: "keine EP-Kosten für", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := CalculateSkillLearnCostOld(tt.skill.Name, tt.class) - if tt.wantErr { - if err == nil { - assert.Error(t, err, "CalculateLearnCost() expected error") - } else if tt.errContains != "" && !strings.Contains(err.Error(), tt.errContains) { - assert.Error(t, err, "CalculateLearnCost() error = %v, should contain %v", err, tt.errContains) - //t.Errorf("CalculateLearnCost() error = %v, should contain %v", err, tt.errContains) - } - return - } - if err != nil { - assert.NoError(t, err, "CalculateLearnCost() unexpected error = %v", err) - //t.Errorf("CalculateLearnCost() unexpected error = %v", err) - return - } - if got != tt.wantEP { - assert.Equal(t, tt.wantEP, got, "CalculateLearnCost() = %v, want %v", got, tt.wantEP) - //t.Errorf("CalculateLearnCost() = %v, want %v", got, tt.wantEP) - } - }) - } -} - func TestCalculateImprovementCost(t *testing.T) { - loadLevelingConfigOld() + //loadLevelingConfigOld() setupTestDB(false) /* // Save original Config diff --git a/backend/testdata/prepared_test_data.db.backup b/backend/testdata/prepared_test_data.db.backup index 89e3f8e..5246985 100644 Binary files a/backend/testdata/prepared_test_data.db.backup and b/backend/testdata/prepared_test_data.db.backup differ