Merge branch 'Lernkosten-Fortsetzung'
This commit is contained in:
@@ -237,6 +237,7 @@ func GetLearnSpellCost(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, sd)
|
||||
}
|
||||
|
||||
/*
|
||||
func GetSkillNextLevelCosts(c *gin.Context) {
|
||||
// Get the character ID from the request
|
||||
charID := c.Param("id")
|
||||
@@ -267,7 +268,8 @@ func GetSkillNextLevelCosts(c *gin.Context) {
|
||||
// Return the updated character
|
||||
c.JSON(http.StatusOK, character.Fertigkeiten)
|
||||
}
|
||||
|
||||
*/
|
||||
/*
|
||||
func GetSkillAllLevelCosts(c *gin.Context) {
|
||||
// Get the character ID from the request
|
||||
charID := c.Param("id")
|
||||
@@ -322,7 +324,7 @@ func GetSkillAllLevelCosts(c *gin.Context) {
|
||||
// Return the updated character
|
||||
c.JSON(http.StatusOK, costArr)
|
||||
}
|
||||
|
||||
*/
|
||||
// ExperienceAndWealthResponse repräsentiert die Antwort für EP und Vermögen
|
||||
type ExperienceAndWealthResponse struct {
|
||||
ExperiencePoints int `json:"experience_points"`
|
||||
@@ -892,6 +894,8 @@ func LearnSpell(c *gin.Context) {
|
||||
}
|
||||
|
||||
// ImproveSpell verbessert einen bestehenden Zauber und erstellt Audit-Log-Einträge
|
||||
// Zauber können nicht verbessert werden
|
||||
/*
|
||||
func ImproveSpell(c *gin.Context) {
|
||||
charID := c.Param("id")
|
||||
var character Char
|
||||
@@ -971,7 +975,7 @@ func ImproveSpell(c *gin.Context) {
|
||||
"remaining_ep": newEP,
|
||||
})
|
||||
}
|
||||
|
||||
*/
|
||||
// GetRewardTypes liefert verfügbare Belohnungsarten für ein bestimmtes Lernszenario
|
||||
func GetRewardTypes(c *gin.Context) {
|
||||
characterID := c.Param("id")
|
||||
@@ -985,28 +989,24 @@ func GetRewardTypes(c *gin.Context) {
|
||||
// Je nach Lerntyp verschiedene Belohnungsarten anbieten
|
||||
switch learningType {
|
||||
case "learn":
|
||||
// Neue Fertigkeit lernen - meist nur EP oder Gold
|
||||
// Neue Fertigkeit lernen - noGold Belohnung verfügbar
|
||||
rewardTypes = append(rewardTypes,
|
||||
gin.H{"value": "ep", "label": "Erfahrungspunkte verwenden", "description": "Verwende EP zum Lernen"},
|
||||
gin.H{"value": "gold", "label": "Gold verwenden", "description": "Bezahle einen Lehrer mit Gold"},
|
||||
gin.H{"value": "default", "label": "Standard (EP + Gold)", "description": "Normale EP- und Goldkosten"},
|
||||
gin.H{"value": "noGold", "label": "Ohne Gold (nur EP)", "description": "Keine Goldkosten, nur EP als Belohnung"},
|
||||
)
|
||||
|
||||
case "spell":
|
||||
// Zauber - mehr Optionen including Ritual
|
||||
// Zauber lernen - halveepnoGold verfügbar
|
||||
rewardTypes = append(rewardTypes,
|
||||
gin.H{"value": "ep", "label": "Erfahrungspunkte verwenden", "description": "Verwende EP zum Verbessern"},
|
||||
gin.H{"value": "gold", "label": "Gold verwenden", "description": "Bezahle einen Zauberlehrer"},
|
||||
gin.H{"value": "pp", "label": "Praxispunkte verwenden", "description": "Nutze gesammelte Praxis"},
|
||||
gin.H{"value": "mixed", "label": "Gemischt (EP + PP)", "description": "Kombiniere EP und PP für reduzierten Aufwand"},
|
||||
gin.H{"value": "default", "label": "Standard (EP)", "description": "Normale EP-Kosten"},
|
||||
gin.H{"value": "halveepnoGold", "label": "Halbe EP ohne Gold", "description": "Halbe EP-Kosten, kein Gold als Belohnung"},
|
||||
)
|
||||
|
||||
case "improve":
|
||||
// Fertigkeit verbessern - Standard-Optionen
|
||||
// Fertigkeit verbessern - halveepnoGold verfügbar
|
||||
rewardTypes = append(rewardTypes,
|
||||
gin.H{"value": "ep", "label": "Erfahrungspunkte verwenden", "description": "Verwende EP zum Verbessern"},
|
||||
gin.H{"value": "gold", "label": "Gold verwenden", "description": "Bezahle einen Lehrer"},
|
||||
gin.H{"value": "pp", "label": "Praxispunkte verwenden", "description": "Nutze gesammelte Praxis"},
|
||||
gin.H{"value": "mixed", "label": "Gemischt (EP + PP)", "description": "Kombiniere EP und PP für reduzierten Aufwand"},
|
||||
gin.H{"value": "default", "label": "Standard (EP + Gold)", "description": "Normale EP- und Goldkosten"},
|
||||
gin.H{"value": "halveepnoGold", "label": "Halbe EP ohne Gold", "description": "Halbe EP-Kosten, kein Gold als Belohnung"},
|
||||
)
|
||||
|
||||
// Spezielle Optionen für bestimmte Fertigkeiten
|
||||
|
||||
@@ -65,25 +65,10 @@ type MultiLevelCostResponse struct {
|
||||
CanAffordTotal bool `json:"can_afford_total"`
|
||||
}
|
||||
|
||||
type LernCostRequest struct {
|
||||
CharId uint `json:"char_id" binding:"required"` // Charakter-ID
|
||||
Name string `json:"name" binding:"required"` // Name der Fertigkeit / des Zaubers
|
||||
CurrentLevel int `json:"current_level,omitempty"` // Aktueller Wert (nur für Verbesserung)
|
||||
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'
|
||||
TargetLevel int `json:"target_level,omitempty"` // Zielwert (optional, für Kostenberechnung bis zu einem bestimmten Level)
|
||||
UsePP int `json:"use_pp,omitempty"` // Anzahl der zu verwendenden Praxispunkte
|
||||
// Belohnungsoptionen
|
||||
Reward *string `json:"reward" binding:"required,oneof=default noGold halveep"` // Belohnungsoptionen Lernen als Belohnung
|
||||
// default
|
||||
// learn: ohne Gold
|
||||
// improve/spell: halbe EP kein Gold
|
||||
}
|
||||
|
||||
// GetLernCost
|
||||
func GetLernCost(c *gin.Context) {
|
||||
// Request-Parameter abrufen
|
||||
var request LernCostRequest
|
||||
var request gsmaster.LernCostRequest
|
||||
if err := c.ShouldBindJSON(&request); err != nil {
|
||||
respondWithError(c, http.StatusBadRequest, "Ungültige Anfrageparameter: "+err.Error())
|
||||
return
|
||||
@@ -96,44 +81,37 @@ func GetLernCost(c *gin.Context) {
|
||||
}
|
||||
var costResult gsmaster.SkillCostResultNew
|
||||
costResult.CharacterID = charID
|
||||
costResult.CharacterClass = character.Typ
|
||||
|
||||
// Verwende Klassenabkürzung wenn der Typ länger als 3 Zeichen ist
|
||||
if len(character.Typ) > 3 {
|
||||
costResult.CharacterClass = gsmaster.GetClassAbbreviation(character.Typ)
|
||||
} else {
|
||||
costResult.CharacterClass = character.Typ
|
||||
}
|
||||
|
||||
// Normalize skill name (trim whitespace, proper case)
|
||||
costResult.SkillName = strings.TrimSpace(request.Name)
|
||||
costResult.Category = gsmaster.GetSkillCategory(request.Name)
|
||||
costResult.Difficulty = gsmaster.GetSkillDifficulty(costResult.Category, costResult.SkillName)
|
||||
|
||||
switch {
|
||||
case request.Action == "learn" && request.Type == "skill":
|
||||
err := gsmaster.CalcSkillLernCost(&costResult, request.Reward)
|
||||
var response []gsmaster.SkillCostResultNew
|
||||
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.GetLernCostNextLevel(&request, &levelResult, request.Reward, i, character.Typ)
|
||||
if err != nil {
|
||||
respondWithError(c, http.StatusBadRequest, "Fehler bei der Kostenberechnung: "+err.Error())
|
||||
return
|
||||
}
|
||||
// extrakosten für elfen
|
||||
if character.Typ == "Elf" {
|
||||
costResult.EP += 6
|
||||
}
|
||||
case request.Action == "learn" && request.Type == "spell":
|
||||
err := gsmaster.CalcSpellLernCost(&costResult, request.Reward)
|
||||
if err != nil {
|
||||
respondWithError(c, http.StatusBadRequest, "Fehler bei der Kostenberechnung: "+err.Error())
|
||||
return
|
||||
}
|
||||
// extrakosten für elfen
|
||||
if character.Typ == "Elf" {
|
||||
costResult.EP += 6
|
||||
}
|
||||
case request.Action == "improve" && request.Type == "skill":
|
||||
err := gsmaster.CalcSkillImproveCost(&costResult, request.CurrentLevel, request.Reward)
|
||||
if err != nil {
|
||||
respondWithError(c, http.StatusBadRequest, "Fehler bei der Kostenberechnung: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
default:
|
||||
response = append(response, levelResult)
|
||||
}
|
||||
c.JSON(http.StatusOK, costResult)
|
||||
|
||||
c.JSON(http.StatusOK, response)
|
||||
}
|
||||
|
||||
// GetSkillCost berechnet die Kosten zum Erlernen oder Verbessern einer Fertigkeit
|
||||
@@ -645,7 +623,7 @@ func applyPPReduction(request *SkillCostRequest, cost *gsmaster.LearnCost, avail
|
||||
return finalEP, finalLE, reduction
|
||||
}
|
||||
|
||||
func CalcSkillLearnCost(req *LernCostRequest, skillCostInfo *gsmaster.SkillCostResultNew) error {
|
||||
func CalcSkillLearnCost(req *gsmaster.LernCostRequest, skillCostInfo *gsmaster.SkillCostResultNew) error {
|
||||
// Fallback-Werte für Skills ohne definierte Kategorie/Schwierigkeit
|
||||
|
||||
result, err := gsmaster.CalculateSkillLearningCosts(skillCostInfo.CharacterClass, skillCostInfo.Category, skillCostInfo.Difficulty)
|
||||
+224
-1
@@ -4,7 +4,6 @@ import (
|
||||
"bamort/database"
|
||||
"bamort/equipment"
|
||||
"bamort/gsmaster"
|
||||
"bamort/models"
|
||||
"bamort/skills"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
@@ -354,6 +353,7 @@ func TestGSMasterIntegration(t *testing.T) {
|
||||
}
|
||||
|
||||
// Test the GetSkillAllLevelCosts endpoint (GET /:id/improve/skill)
|
||||
/*
|
||||
func TestGetSkillAllLevelCostsEndpoint(t *testing.T) {
|
||||
// Setup test database
|
||||
database.SetupTestDB()
|
||||
@@ -594,3 +594,226 @@ func TestGetSkillAllLevelCostsEndpoint(t *testing.T) {
|
||||
assert.Contains(t, response, "error", "Response should contain error message")
|
||||
})
|
||||
}
|
||||
*/
|
||||
// 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 := MigrateStructure()
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Also migrate skills and equipment to avoid preload errors
|
||||
err = skills.MigrateStructure()
|
||||
assert.NoError(t, err)
|
||||
err = equipment.MigrateStructure()
|
||||
assert.NoError(t, err)
|
||||
err = gsmaster.MigrateStructure()
|
||||
assert.NoError(t, err)
|
||||
/*
|
||||
// Create test skill data
|
||||
err = createTestSkillData()
|
||||
assert.NoError(t, err)
|
||||
defer cleanupTestSkillData()
|
||||
// Create test character with ID 20 and class "Krieger"
|
||||
testChar := createChar()
|
||||
testChar.ID = 20
|
||||
testChar.Typ = "Krieger" // Set character class to "Krieger"
|
||||
|
||||
// Add Athletik skill at level 9
|
||||
skillName := "Athletik"
|
||||
skill := skills.Fertigkeit{
|
||||
BamortCharTrait: models.BamortCharTrait{
|
||||
BamortBase: models.BamortBase{
|
||||
Name: skillName,
|
||||
},
|
||||
CharacterID: 20,
|
||||
},
|
||||
Fertigkeitswert: 9,
|
||||
}
|
||||
testChar.Fertigkeiten = append(testChar.Fertigkeiten, skill)
|
||||
|
||||
err = testChar.Create()
|
||||
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
|
||||
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 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"])
|
||||
})
|
||||
}
|
||||
@@ -24,15 +24,15 @@ func RegisterRoutes(r *gin.RouterGroup) {
|
||||
charGrp.POST("/lerncost", GetLernCost) // neuer Hauptendpunkt für alle Kostenberechnungen
|
||||
|
||||
// Kostenberechnung (konsolidiert)
|
||||
charGrp.POST("/:id/skill-cost", GetSkillCost) // Hauptendpunkt für alle Kostenberechnungen
|
||||
charGrp.GET("/:id/improve", GetSkillNextLevelCosts) // Legacy - für nächste Stufe
|
||||
charGrp.GET("/:id/improve/skill", GetSkillAllLevelCosts) // Legacy - für alle Stufen
|
||||
//charGrp.POST("/:id/skill-cost", GetSkillCost) // Hauptendpunkt für alle Kostenberechnungen
|
||||
//charGrp.GET("/:id/improve", GetSkillNextLevelCosts) // Legacy - für nächste Stufe
|
||||
//charGrp.GET("/:id/improve/skill", GetSkillAllLevelCosts) // Legacy - für alle Stufen
|
||||
|
||||
// Lernen und Verbessern (mit automatischem Audit-Log)
|
||||
charGrp.POST("/:id/learn-skill", LearnSkill) // Fertigkeit lernen
|
||||
charGrp.POST("/:id/improve-skill", ImproveSkill) // Fertigkeit verbessern
|
||||
charGrp.POST("/:id/learn-spell", LearnSpell) // Zauber lernen
|
||||
charGrp.POST("/:id/improve-spell", ImproveSpell) // Zauber verbessern
|
||||
//charGrp.POST("/:id/improve-spell", ImproveSpell) // Zauber verbessern
|
||||
|
||||
// Belohnungsarten für verschiedene Lernszenarien
|
||||
charGrp.GET("/:id/reward-types", GetRewardTypes) // Verfügbare Belohnungsarten je nach Kontext
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
package database
|
||||
|
||||
// Empty test helper file
|
||||
@@ -1,6 +1,7 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -47,6 +48,25 @@ func copyFile(src, dst string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// LoadCopyOfPredefinedTestData loads predefined test data from a specific file into the provided database
|
||||
func LoadCopyOfPredefinedTestData(targetDB *gorm.DB, dataFile string) error {
|
||||
// Check if file exists
|
||||
if _, err := os.Stat(dataFile); os.IsNotExist(err) {
|
||||
return fmt.Errorf("predefined test data file not found: %s", dataFile)
|
||||
}
|
||||
testpath := filepath.Join(filepath.Dir(dataFile), "tmp_test_data.db")
|
||||
if _, err := os.Stat(testpath); os.IsExist(err) {
|
||||
os.Remove(testpath) // delete existing test data file
|
||||
}
|
||||
copyFile(dataFile, testpath)
|
||||
targetDB, err := gorm.Open(sqlite.Open(testpath), &gorm.Config{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open temporary test database: %w", err)
|
||||
}
|
||||
DB = targetDB
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetupTestDB creates an in-memory SQLite database for testing
|
||||
// Parameters:
|
||||
// - opts[0]: isTestDb (bool) - whether to use in-memory SQLite (true) or persistent MariaDB (false)
|
||||
@@ -134,6 +154,12 @@ func SetupTestDB(opts ...bool) {
|
||||
if err != nil {
|
||||
panic("failed to load test data: " + err.Error())
|
||||
}
|
||||
} else if isTestDb {
|
||||
// If no test data loader is set, we can still use the default test data loading
|
||||
err := LoadCopyOfPredefinedTestData(DB, "../testdata/prepared_test_data.db")
|
||||
if err != nil {
|
||||
panic("failed to load predefined test data: " + err.Error())
|
||||
}
|
||||
}
|
||||
migrationDone = true
|
||||
}
|
||||
|
||||
@@ -328,7 +328,7 @@ func CalculateSkillLearningCosts(characterClass, category, difficulty string) (*
|
||||
}
|
||||
|
||||
// Konvertiere Vollnamen der Charakterklasse zu Abkürzungen falls nötig
|
||||
classKey := getClassAbbreviation(characterClass)
|
||||
classKey := GetClassAbbreviation(characterClass)
|
||||
|
||||
// Hole die EP-Kosten pro TE für die angegebene Charakterklasse
|
||||
classData, exists := learningCosts.EPPerTE[classKey]
|
||||
@@ -378,7 +378,7 @@ func CalculateSpellLearningCosts(characterClass, spellSchool string, leNeeded in
|
||||
}
|
||||
|
||||
// Konvertiere Vollnamen zu Abkürzungen falls nötig
|
||||
classKey := getClassAbbreviation(characterClass)
|
||||
classKey := GetClassAbbreviation(characterClass)
|
||||
|
||||
classData, exists := learningCosts.SpellEPPerLE[classKey]
|
||||
if !exists {
|
||||
@@ -430,6 +430,8 @@ type SkillCostResultNew struct {
|
||||
EP int `json:"ep"`
|
||||
LE int `json:"le"`
|
||||
GoldCost int `json:"gold_cost"`
|
||||
PPUsed int `json:"pp_used"`
|
||||
TargetLevel int `json:"target_level"`
|
||||
Details map[string]interface{} `json:"details"`
|
||||
}
|
||||
|
||||
@@ -810,8 +812,8 @@ func getDefaultSpellSchool(spellName string) string {
|
||||
return "Verändern"
|
||||
}
|
||||
|
||||
// getClassAbbreviation konvertiert Charakterklassen-Vollnamen zu Abkürzungen
|
||||
func getClassAbbreviation(characterClass string) string {
|
||||
// GetClassAbbreviation konvertiert Charakterklassen-Vollnamen zu Abkürzungen
|
||||
func GetClassAbbreviation(characterClass string) string {
|
||||
// Mapping von Vollnamen zu Abkürzungen
|
||||
classMap := map[string]string{
|
||||
// Abenteurer-Klassen
|
||||
|
||||
@@ -1,6 +1,23 @@
|
||||
package gsmaster
|
||||
|
||||
import "fmt"
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type LernCostRequest struct {
|
||||
CharId uint `json:"char_id" binding:"required"` // Charakter-ID
|
||||
Name string `json:"name" binding:"required"` // Name der Fertigkeit / des Zaubers
|
||||
CurrentLevel int `json:"current_level,omitempty"` // Aktueller Wert (nur für Verbesserung)
|
||||
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'
|
||||
TargetLevel int `json:"target_level,omitempty"` // Zielwert (optional, für Kostenberechnung bis zu einem bestimmten Level)
|
||||
UsePP int `json:"use_pp,omitempty"` // Anzahl der zu verwendenden Praxispunkte
|
||||
// Belohnungsoptionen
|
||||
Reward *string `json:"reward" binding:"required,oneof=default noGold halveep halveepnoGold"` // Belohnungsoptionen Lernen als Belohnung
|
||||
// default
|
||||
// learn: ohne Gold
|
||||
// improve/spell: halbe EP kein Gold
|
||||
}
|
||||
|
||||
// DifficultyData enthält Skills und Trainingskosten für eine Schwierigkeitsstufe
|
||||
type DifficultyData struct {
|
||||
@@ -15,7 +32,8 @@ type LearningCostsTable2 struct {
|
||||
EPPerTE map[string]map[string]int
|
||||
|
||||
// EP-Kosten für 1 Lerneinheit (LE) für Zauber pro Charakterklasse und Zauberschule
|
||||
SpellEPPerLE map[string]map[string]int
|
||||
SpellEPPerLE map[string]map[string]int
|
||||
SpellLEPerLevel map[int]int
|
||||
|
||||
// LE-Kosten für Fertigkeiten basierend auf Schwierigkeit
|
||||
BaseLearnCost map[string]map[string]int
|
||||
@@ -282,6 +300,9 @@ var learningCostsData = &LearningCostsTable2{
|
||||
"Lied": 30,
|
||||
},
|
||||
},
|
||||
// Lernen von Zaubern
|
||||
// LE pro Stufe des Zaubers
|
||||
SpellLEPerLevel: map[int]int{1: 1, 2: 1, 3: 2, 4: 3, 5: 5, 6: 10, 7: 15, 8: 20, 9: 30, 10: 40, 11: 60, 12: 90},
|
||||
|
||||
// TE-Kosten für Verbesserungen basierend auf Kategorie, Schwierigkeit und aktuellem Wert
|
||||
ImprovementCost: map[string]map[string]DifficultyData{
|
||||
@@ -506,11 +527,155 @@ func GetSkillDifficulty(category string, skillName string) string {
|
||||
return "Unbekannt"
|
||||
}
|
||||
|
||||
// contains checks if a slice contains a specific string
|
||||
func contains(slice []string, item string) bool {
|
||||
for _, s := range slice {
|
||||
if s == item {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
//### End of Helper functions ###
|
||||
|
||||
// GetSpellInfo returns the school and level of a spell from the database
|
||||
func GetSpellInfo(spellName string) (string, int, error) {
|
||||
// Create a Spell instance to search in the database
|
||||
var spell Spell
|
||||
|
||||
// Search for the spell in the database
|
||||
err := spell.First(spellName)
|
||||
if err != nil {
|
||||
return "", 0, fmt.Errorf("spell '%s' not found in database: %w", spellName, err)
|
||||
}
|
||||
|
||||
return spell.Category, spell.Stufe, nil
|
||||
}
|
||||
|
||||
// GetSpecialization returns the specialization school for a character (placeholder)
|
||||
// This should be implemented to get the actual specialization from character data
|
||||
func GetSpecialization(characterID string) string {
|
||||
// TODO: Implement actual character specialization lookup
|
||||
// For now, return a default specialization
|
||||
return "Beherrschen"
|
||||
}
|
||||
|
||||
// findBestCategoryForSkillImprovement findet die Kategorie mit den niedrigsten EP-Kosten für eine Fertigkeit
|
||||
func findBestCategoryForSkillImprovement(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
|
||||
}
|
||||
|
||||
// findBestCategoryForSkillLearning findet die Kategorie mit den niedrigsten EP-Kosten für das Lernen einer Fertigkeit
|
||||
func findBestCategoryForSkillLearning(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
|
||||
}
|
||||
|
||||
func CalcSkillLernCost(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
|
||||
epPerTE, ok := learningCostsData.EPPerTE[costResult.CharacterClass][costResult.Category]
|
||||
// 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 := findBestCategoryForSkillLearning(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)
|
||||
}
|
||||
@@ -524,8 +689,68 @@ func CalcSkillLernCost(costResult *SkillCostResultNew, reward *string) error {
|
||||
costResult.GoldCost = costResult.LE * 200 // Beispiel: 200 Gold pro LE
|
||||
|
||||
// Apply reward logic
|
||||
if reward != nil && *reward == "noGold" {
|
||||
costResult.GoldCost = 0 // Keine Goldkosten für diese Belohnung
|
||||
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
|
||||
}
|
||||
|
||||
// CalcSkillImproveCost berechnet die Kosten für die Verbesserung einer Fertigkeit
|
||||
func CalcSkillImproveCost(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 := findBestCategoryForSkillImprovement(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 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
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -535,24 +760,78 @@ func CalcSkillLernCost(costResult *SkillCostResultNew, reward *string) error {
|
||||
func CalcSpellLernCost(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
|
||||
// Für jetzt verwenden wir die gleiche Logik wie für Skills
|
||||
return CalcSkillLernCost(costResult, reward)
|
||||
}
|
||||
|
||||
// CalcSkillImproveCost berechnet die Kosten für die Verbesserung einer Fertigkeit
|
||||
func CalcSkillImproveCost(costResult *SkillCostResultNew, currentLevel int, reward *string) error {
|
||||
// Für Skill-Verbesserung könnten die Kosten vom aktuellen Level abhängen
|
||||
// TODO: Implementiere spezifische Verbesserungslogik
|
||||
// Für jetzt verwenden wir die gleiche Logik wie für das Lernen
|
||||
return CalcSkillLernCost(costResult, reward)
|
||||
}
|
||||
|
||||
// contains checks if a slice contains a specific string
|
||||
func contains(slice []string, item string) bool {
|
||||
for _, s := range slice {
|
||||
if s == item {
|
||||
return true
|
||||
classKey := costResult.CharacterClass
|
||||
spellCategory, spellLevel, err := GetSpellInfo(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 := GetSpecialization(costResult.CharacterID)
|
||||
if spellCategory == spezialgebiet {
|
||||
SpellEPPerLE = 30 // Spezialgebiet für Magier
|
||||
}
|
||||
}
|
||||
return false
|
||||
|
||||
trainCost := learningCostsData.SpellLEPerLevel[spellLevel] // LE pro Stufe des Zaubers
|
||||
if costResult.PPUsed > 0 {
|
||||
trainCost -= costResult.PPUsed // Wenn PP verwendet werden, setze die Kosten auf die PP
|
||||
}
|
||||
costResult.EP = trainCost * SpellEPPerLE // EP-Kosten für das Lernen des Zaubers
|
||||
costResult.GoldCost = trainCost * 100 // Beispiel: 200 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
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetLernCostNextLevel(request *LernCostRequest, costResult *SkillCostResultNew, reward *string, level int, characterTyp 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.
|
||||
switch {
|
||||
case request.Action == "learn" && request.Type == "skill":
|
||||
err := CalcSkillLernCost(costResult, request.Reward)
|
||||
if err != nil {
|
||||
return fmt.Errorf("fehler bei der Kostenberechnung: %w", err)
|
||||
}
|
||||
// extrakosten für elfen
|
||||
if characterTyp == "Elf" {
|
||||
costResult.EP += 6
|
||||
}
|
||||
case request.Action == "learn" && request.Type == "spell":
|
||||
err := CalcSpellLernCost(costResult, request.Reward)
|
||||
if err != nil {
|
||||
return fmt.Errorf("fehler bei der Kostenberechnung: %w", err)
|
||||
}
|
||||
// extrakosten für elfen
|
||||
if characterTyp == "Elf" {
|
||||
costResult.EP += 6
|
||||
}
|
||||
case request.Action == "improve" && request.Type == "skill":
|
||||
err := CalcSkillImproveCost(costResult, request.CurrentLevel, request.Reward)
|
||||
if err != nil {
|
||||
return fmt.Errorf("fehler bei der Kostenberechnung: %w", err)
|
||||
}
|
||||
|
||||
default:
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
package gsmaster
|
||||
|
||||
import (
|
||||
"bamort/database"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Helper function to create string pointers
|
||||
func stringPtr(s string) *string {
|
||||
return &s
|
||||
}
|
||||
|
||||
// TestGetSkillCategory tests the GetSkillCategory function
|
||||
func TestGetSkillCategory(t *testing.T) {
|
||||
tests := []struct {
|
||||
@@ -452,6 +458,77 @@ func TestSkillCoverage(t *testing.T) {
|
||||
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 := findBestCategoryForSkillImprovement(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 {
|
||||
@@ -535,11 +612,6 @@ func TestCalcSkillLernCostWithRewards(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to create string pointers
|
||||
func stringPtr(s string) *string {
|
||||
return &s
|
||||
}
|
||||
|
||||
// TestCalcSpellLernCostWithRewards tests the reward logic in CalcSpellLernCost
|
||||
/*
|
||||
func TestCalcSpellLernCostWithRewards(t *testing.T) {
|
||||
@@ -563,23 +635,540 @@ func TestCalcSpellLernCostWithRewards(t *testing.T) {
|
||||
*/
|
||||
|
||||
// TestCalcSkillImproveCostWithRewards tests the reward logic in CalcSkillImproveCost
|
||||
/*
|
||||
func TestCalcSkillImproveCostWithRewards(t *testing.T) {
|
||||
costResult := &SkillCostResultNew{
|
||||
CharacterClass: "Kr", // Use abbreviation
|
||||
SkillName: "Klettern",
|
||||
Category: GetSkillCategory("Klettern"),
|
||||
Difficulty: GetSkillDifficulty(GetSkillCategory("Klettern"), "Klettern"),
|
||||
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
|
||||
},
|
||||
}
|
||||
|
||||
// Test with halveep reward
|
||||
err := CalcSkillImproveCost(costResult, 5, stringPtr("halveep"))
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to calculate improvement costs: %v", err)
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
if costResult.GoldCost != 0 {
|
||||
t.Errorf("Expected gold cost 0 with halveep reward, got %d", costResult.GoldCost)
|
||||
err := CalcSkillImproveCost(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) {
|
||||
|
||||
// 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()
|
||||
MigrateStructure()
|
||||
|
||||
// Create minimal test spell data for our test
|
||||
testSpells := []Spell{
|
||||
{
|
||||
LookupList: LookupList{
|
||||
GameSystem: "midgard",
|
||||
Name: "Schlummer",
|
||||
Beschreibung: "Test spell for GetSpellInfo",
|
||||
Quelle: "Test",
|
||||
},
|
||||
Stufe: 1,
|
||||
Category: "Beherrschen",
|
||||
},
|
||||
{
|
||||
LookupList: LookupList{
|
||||
GameSystem: "midgard",
|
||||
Name: "Erkennen von Krankheit",
|
||||
Beschreibung: "Test spell for GetSpellInfo",
|
||||
Quelle: "Test",
|
||||
},
|
||||
Stufe: 2,
|
||||
Category: "Dweomerzauber",
|
||||
},
|
||||
{
|
||||
LookupList: LookupList{
|
||||
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 {
|
||||
spellName string
|
||||
expectedSchool string
|
||||
expectedLevel int
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
spellName: "Schlummer",
|
||||
expectedSchool: "Beherrschen",
|
||||
expectedLevel: 1,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
spellName: "Erkennen von Krankheit",
|
||||
expectedSchool: "Dweomerzauber",
|
||||
expectedLevel: 2,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
spellName: "Das Loblied",
|
||||
expectedSchool: "Zauberlied",
|
||||
expectedLevel: 3,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
spellName: "Unknown Spell",
|
||||
expectedSchool: "", // Should error for unknown spell
|
||||
expectedLevel: 0, // Should error for unknown spell
|
||||
expectError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.spellName, func(t *testing.T) {
|
||||
school, level, err := GetSpellInfo(tt.spellName)
|
||||
|
||||
if tt.expectError {
|
||||
if err == nil {
|
||||
t.Errorf("Expected error for unknown spell, but got none")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get spell info: %v", err)
|
||||
}
|
||||
|
||||
if school != tt.expectedSchool {
|
||||
t.Errorf("Expected school %s, got %s", tt.expectedSchool, school)
|
||||
}
|
||||
|
||||
if level != tt.expectedLevel {
|
||||
t.Errorf("Expected level %d, got %d", tt.expectedLevel, level)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 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()
|
||||
MigrateStructure()
|
||||
|
||||
// Create minimal test spell data for our test
|
||||
testSpells := []Spell{
|
||||
{
|
||||
LookupList: LookupList{
|
||||
GameSystem: "midgard",
|
||||
Name: "Schlummer",
|
||||
Beschreibung: "Test spell for GetSpellInfo",
|
||||
Quelle: "Test",
|
||||
},
|
||||
Stufe: 1,
|
||||
Category: "Beherrschen",
|
||||
},
|
||||
{
|
||||
LookupList: LookupList{
|
||||
GameSystem: "midgard",
|
||||
Name: "Erkennen von Krankheit",
|
||||
Beschreibung: "Test spell for GetSpellInfo",
|
||||
Quelle: "Test",
|
||||
},
|
||||
Stufe: 2,
|
||||
Category: "Dweomer",
|
||||
},
|
||||
{
|
||||
LookupList: LookupList{
|
||||
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 := CalcSpellLernCost(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 := GetSpecialization(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 := findBestCategoryForSkillLearning(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 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 := GetLernCostNextLevel(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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
package integration_test
|
||||
|
||||
import (
|
||||
"bamort/database"
|
||||
"bamort/gsmaster"
|
||||
_ "bamort/maintenance" // Import for init() function that sets up test data callbacks
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestGetSpellInfoIntegration tests the GetSpellInfo function with full database integration
|
||||
func TestGetSpellInfoIntegration(t *testing.T) {
|
||||
// The maintenance package init() already sets up:
|
||||
// database.SetTestDataLoader(LoadPredefinedTestDataFromFile)
|
||||
// database.SetMigrationCallback(migrateAllStructures)
|
||||
|
||||
// Initialize test database with test data loading
|
||||
database.SetupTestDB(true, true) // Use in-memory SQLite with test data
|
||||
defer database.ResetTestDB()
|
||||
|
||||
// Test with real spell names that should exist in the prepared test database
|
||||
// If the prepared test database doesn't exist, these tests will fail gracefully
|
||||
tests := []struct {
|
||||
spellName string
|
||||
expectedSchool string
|
||||
expectedLevel int
|
||||
expectError bool
|
||||
description string
|
||||
}{
|
||||
{
|
||||
spellName: "Licht",
|
||||
expectedSchool: "Erschaffen", // Common light spell in Midgard
|
||||
expectedLevel: 1,
|
||||
expectError: false,
|
||||
description: "Basic light spell should exist in prepared database",
|
||||
},
|
||||
{
|
||||
spellName: "Erkennen von Krankheit",
|
||||
expectedSchool: "Erkennen",
|
||||
expectedLevel: 2,
|
||||
expectError: false,
|
||||
description: "Common diagnostic spell should exist in prepared database",
|
||||
},
|
||||
{
|
||||
spellName: "Furcht",
|
||||
expectedSchool: "Beherrschen",
|
||||
expectedLevel: 1,
|
||||
expectError: false,
|
||||
description: "Fear spell should exist in prepared database",
|
||||
},
|
||||
{
|
||||
spellName: "Unknown Spell That Should Not Exist",
|
||||
expectedSchool: "", // Should error for unknown spell
|
||||
expectedLevel: 0, // Should error for unknown spell
|
||||
expectError: true,
|
||||
description: "Non-existent spell should return error",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.spellName, func(t *testing.T) {
|
||||
school, level, err := gsmaster.GetSpellInfo(tt.spellName)
|
||||
|
||||
if tt.expectError {
|
||||
if err == nil {
|
||||
t.Errorf("Expected error for %s, but got none. %s", tt.spellName, tt.description)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
// If we get an error for a spell we expect to exist, it might mean
|
||||
// the prepared test database doesn't exist or doesn't contain this spell
|
||||
t.Logf("Warning: Failed to get spell info for %s: %v. %s", tt.spellName, err, tt.description)
|
||||
t.Logf("This might indicate that the prepared test database is missing or incomplete.")
|
||||
t.Skip("Skipping test due to missing prepared test data")
|
||||
return
|
||||
}
|
||||
|
||||
if school != tt.expectedSchool {
|
||||
t.Errorf("Expected school %s, got %s for spell %s", tt.expectedSchool, school, tt.spellName)
|
||||
}
|
||||
|
||||
if level != tt.expectedLevel {
|
||||
t.Errorf("Expected level %d, got %d for spell %s", tt.expectedLevel, level, tt.spellName)
|
||||
}
|
||||
|
||||
t.Logf("✅ Successfully found spell %s: school=%s, level=%d", tt.spellName, school, level)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -34,28 +34,50 @@
|
||||
<div class="resource-info">
|
||||
<div class="resource-label">Praxispunkte</div>
|
||||
<div class="resource-amount">{{ skill?.pp || 0 }} PP</div>
|
||||
<div class="resource-remaining">
|
||||
<small>
|
||||
Verwendet: {{ ppUsed || 0 }} PP |
|
||||
Verbleibend: {{ Math.max(0, (skill?.pp || 0) - (ppUsed || 0)) }} PP
|
||||
</small>
|
||||
</div>
|
||||
<div v-if="selectedLevel && selectedPPCost > 0" class="resource-remaining">
|
||||
<small :class="{ 'text-warning': remainingPP < 5, 'text-danger': remainingPP <= 0 }">
|
||||
Verbleibend: {{ remainingPP }} PP
|
||||
Nach Lernen: {{ remainingPP }} PP
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div> <!-- Belohnungsart auswählen -->
|
||||
<div class="form-group">
|
||||
<label>Belohnungsart:</label>
|
||||
<select v-model="selectedRewardType" :disabled="isLoadingRewardTypes">
|
||||
<option value="" disabled>
|
||||
{{ isLoadingRewardTypes ? 'Lade Belohnungsarten...' : 'Belohnungsart wählen' }}
|
||||
</option>
|
||||
<option
|
||||
v-for="rewardType in availableRewardTypes"
|
||||
:key="rewardType.value"
|
||||
:value="rewardType.value"
|
||||
>
|
||||
{{ rewardType.label }}
|
||||
</option>
|
||||
</select>
|
||||
</div> <!-- Belohnungsart und PP-Eingabe nebeneinander -->
|
||||
<div class="form-group form-row">
|
||||
<div class="form-col">
|
||||
<label>Lernen als Belohnung:</label>
|
||||
<select v-model="selectedRewardType" :disabled="isLoadingRewardTypes">
|
||||
<option value="" disabled>
|
||||
{{ isLoadingRewardTypes ? 'Lade Belohnungsarten...' : 'Belohnungsart wählen' }}
|
||||
</option>
|
||||
<option
|
||||
v-for="rewardType in availableRewardTypes"
|
||||
:key="rewardType.value"
|
||||
:value="rewardType.value"
|
||||
>
|
||||
{{ rewardType.label }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-col">
|
||||
<label>Praxispunkte verwenden:</label>
|
||||
<input
|
||||
v-model.number="ppUsed"
|
||||
type="number"
|
||||
min="0"
|
||||
:max="skill?.pp || 0"
|
||||
placeholder="PP verwenden"
|
||||
@input="updatePPUsage"
|
||||
/>
|
||||
<small class="help-text">
|
||||
{{ ppUsed || 0 }} / {{ skill?.pp || 0 }} PP
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Lernbare Stufen -->
|
||||
@@ -71,10 +93,12 @@
|
||||
>
|
||||
<div class="level-header">
|
||||
<span class="level-target">{{ skill?.fertigkeitswert || 0 }} → {{ level.targetLevel }}</span>
|
||||
<span class="level-cost" v-if="selectedRewardType === 'ep'">{{ level.epCost }} EP</span>
|
||||
<span class="level-cost" v-else-if="selectedRewardType === 'gold'">{{ level.goldCost }} GS</span>
|
||||
<span class="level-cost" v-if="selectedRewardType === 'default'">{{ level.epCost }} EP + {{ level.goldCost }} GS</span>
|
||||
<span class="level-cost" v-else-if="selectedRewardType === 'noGold'">{{ level.epCost }} EP</span>
|
||||
<span class="level-cost" v-else-if="selectedRewardType === 'halveepnoGold'">{{ Math.floor(level.epCost / 2) }} EP</span>
|
||||
<span class="level-cost" v-else-if="selectedRewardType === 'pp'">{{ level.ppCost }} PP</span>
|
||||
<span class="level-cost" v-else>{{ level.epCost }} EP + {{ level.ppUsed }} PP</span>
|
||||
<span class="level-cost" v-else-if="selectedRewardType === 'mixed'">{{ level.epCost }} EP + {{ level.ppUsed || 0 }} PP</span>
|
||||
<span class="level-cost" v-else>{{ level.epCost }} EP + {{ level.goldCost }} GS</span>
|
||||
</div>
|
||||
<div class="level-details" v-if="selectedRewardType === 'mixed'">
|
||||
<small>PP verwenden: {{ level.ppUsed }} / {{ skill?.pp || 0 }}</small>
|
||||
@@ -83,19 +107,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- PP Eingabe für gemischte Belohnung -->
|
||||
<div v-if="selectedRewardType === 'mixed'" class="form-group">
|
||||
<label>Praxispunkte verwenden (optional):</label>
|
||||
<input
|
||||
v-model.number="ppUsed"
|
||||
type="number"
|
||||
min="0"
|
||||
:max="skill?.pp || 0"
|
||||
placeholder="Anzahl PP verwenden"
|
||||
@input="updateMixedCosts"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Notizen -->
|
||||
<div class="form-group">
|
||||
<label>Notizen (optional):</label>
|
||||
@@ -286,6 +297,21 @@
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.form-row {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.form-col {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.form-col:last-child {
|
||||
flex: 0 0 180px;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
@@ -309,6 +335,14 @@
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
.help-text {
|
||||
display: block;
|
||||
margin-top: 5px;
|
||||
font-size: 12px;
|
||||
color: #6c757d;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.modal-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
@@ -403,20 +437,11 @@ export default {
|
||||
if (!this.selectedLevel) return 0;
|
||||
|
||||
const level = this.availableLevels.find(l => l.targetLevel === this.selectedLevel);
|
||||
if (!level) return 0;
|
||||
|
||||
if (this.selectedRewardType === 'ep') {
|
||||
return level.epCost;
|
||||
} else if (this.selectedRewardType === 'mixed') {
|
||||
const adjustedEPCost = Math.max(level.epCost - (this.ppUsed * 10), level.epCost * 0.5);
|
||||
return Math.ceil(adjustedEPCost);
|
||||
}
|
||||
|
||||
return 0;
|
||||
return level ? level.epCost : 0;
|
||||
},
|
||||
|
||||
selectedGoldCost() {
|
||||
if (!this.selectedLevel || this.selectedRewardType !== 'gold') return 0;
|
||||
if (!this.selectedLevel) return 0;
|
||||
|
||||
const level = this.availableLevels.find(l => l.targetLevel === this.selectedLevel);
|
||||
return level ? level.goldCost : 0;
|
||||
@@ -426,15 +451,7 @@ export default {
|
||||
if (!this.selectedLevel) return 0;
|
||||
|
||||
const level = this.availableLevels.find(l => l.targetLevel === this.selectedLevel);
|
||||
if (!level) return 0;
|
||||
|
||||
if (this.selectedRewardType === 'pp') {
|
||||
return level.ppCost;
|
||||
} else if (this.selectedRewardType === 'mixed') {
|
||||
return this.ppUsed;
|
||||
}
|
||||
|
||||
return 0;
|
||||
return level ? (level.ppUsed || level.ppCost) : 0;
|
||||
},
|
||||
|
||||
remainingEP() {
|
||||
@@ -457,7 +474,7 @@ export default {
|
||||
handler(newSkill) {
|
||||
if (newSkill) {
|
||||
this.loadRewardTypes();
|
||||
this.loadLearningCosts(); // Verwende die neue Methode
|
||||
// loadLearningCosts wird durch selectedRewardType watcher ausgelöst
|
||||
}
|
||||
},
|
||||
immediate: true
|
||||
@@ -466,13 +483,16 @@ export default {
|
||||
handler() {
|
||||
if (this.skill) {
|
||||
this.loadRewardTypes();
|
||||
this.loadLearningCosts(); // Verwende die neue Methode
|
||||
// loadLearningCosts wird durch selectedRewardType watcher ausgelöst
|
||||
}
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
selectedRewardType() {
|
||||
this.updateAffordability();
|
||||
// Nur hier die Lernkosten laden, wenn Belohnungsart geändert wird
|
||||
if (this.selectedRewardType && this.skill) {
|
||||
this.loadLearningCosts();
|
||||
}
|
||||
this.selectedLevel = null; // Reset selection when reward type changes
|
||||
},
|
||||
isVisible(newValue) {
|
||||
@@ -498,7 +518,7 @@ export default {
|
||||
this.availableRewardTypes = [];
|
||||
if (this.skill) {
|
||||
this.loadRewardTypes();
|
||||
this.loadLearningCosts(); // Verwende die neue Methode
|
||||
// loadLearningCosts wird automatisch durch selectedRewardType watcher ausgelöst
|
||||
}
|
||||
},
|
||||
|
||||
@@ -512,6 +532,7 @@ export default {
|
||||
this.availableRewardTypes = this.getDefaultRewardTypes();
|
||||
if (this.availableRewardTypes.length > 0 && !this.selectedRewardType) {
|
||||
this.selectedRewardType = this.availableRewardTypes[0].value;
|
||||
// loadLearningCosts wird automatisch durch selectedRewardType watcher ausgelöst
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -546,11 +567,13 @@ export default {
|
||||
if (this.availableRewardTypes.length > 0 && !this.selectedRewardType) {
|
||||
this.selectedRewardType = this.availableRewardTypes[0].value;
|
||||
console.log('Set default reward type to:', this.selectedRewardType);
|
||||
// loadLearningCosts wird automatisch durch selectedRewardType watcher ausgelöst
|
||||
} else if (this.availableRewardTypes.length === 0) {
|
||||
console.warn('No reward types received from API, using fallback');
|
||||
this.availableRewardTypes = this.getDefaultRewardTypes();
|
||||
if (this.availableRewardTypes.length > 0 && !this.selectedRewardType) {
|
||||
this.selectedRewardType = this.availableRewardTypes[0].value;
|
||||
// loadLearningCosts wird automatisch durch selectedRewardType watcher ausgelöst
|
||||
}
|
||||
}
|
||||
|
||||
@@ -584,6 +607,7 @@ export default {
|
||||
this.availableRewardTypes = this.getDefaultRewardTypes();
|
||||
if (this.availableRewardTypes.length > 0 && !this.selectedRewardType) {
|
||||
this.selectedRewardType = this.availableRewardTypes[0].value;
|
||||
// loadLearningCosts wird automatisch durch selectedRewardType watcher ausgelöst
|
||||
}
|
||||
console.log('Using fallback reward types:', this.availableRewardTypes);
|
||||
} finally {
|
||||
@@ -600,13 +624,13 @@ export default {
|
||||
switch (this.learningType) {
|
||||
case 'learn':
|
||||
rewardTypes = [
|
||||
{ value: 'ep', label: 'Erfahrungspunkte verwenden' },
|
||||
{ value: 'default', label: 'Erfahrungspunkte verwenden' },
|
||||
{ value: 'gold', label: 'Gold verwenden' }
|
||||
];
|
||||
break;
|
||||
case 'spell':
|
||||
rewardTypes = [
|
||||
{ value: 'ep', label: 'Erfahrungspunkte verwenden' },
|
||||
{ value: 'default', label: 'Erfahrungspunkte verwenden' },
|
||||
{ value: 'gold', label: 'Gold verwenden' },
|
||||
{ value: 'pp', label: 'Praxispunkte verwenden' },
|
||||
{ value: 'mixed', label: 'Gemischt (EP + PP)' },
|
||||
@@ -616,7 +640,7 @@ export default {
|
||||
case 'improve':
|
||||
default:
|
||||
rewardTypes = [
|
||||
{ value: 'ep', label: 'Erfahrungspunkte verwenden' },
|
||||
{ value: 'default', label: 'Erfahrungspunkte verwenden' },
|
||||
{ value: 'gold', label: 'Gold verwenden' },
|
||||
{ value: 'pp', label: 'Praxispunkte verwenden' },
|
||||
{ value: 'mixed', label: 'Gemischt (EP + PP)' }
|
||||
@@ -631,12 +655,13 @@ export default {
|
||||
calculateAvailableLevels() {
|
||||
if (!this.skill) return;
|
||||
|
||||
// Verwende die API für echte Kostenberechnung
|
||||
this.loadLearningCosts();
|
||||
// Diese Methode ist jetzt redundant, da loadLearningCosts()
|
||||
// automatisch durch den selectedRewardType watcher aufgerufen wird
|
||||
console.warn('calculateAvailableLevels() ist veraltet - verwende selectedRewardType watcher');
|
||||
},
|
||||
|
||||
async loadLearningCosts() {
|
||||
if (!this.skill) return;
|
||||
if (!this.skill || !this.selectedRewardType) return;
|
||||
|
||||
this.isLoading = true;
|
||||
try {
|
||||
@@ -652,55 +677,64 @@ export default {
|
||||
skill_name: this.skill.name,
|
||||
skill_type: this.skill.type || 'skill',
|
||||
learning_type: this.learningType,
|
||||
current_level: this.skill.fertigkeitswert || 0
|
||||
current_level: this.skill.fertigkeitswert || 0,
|
||||
reward_type: this.selectedRewardType
|
||||
});
|
||||
|
||||
// Verwende die bestehende Route /:id/improve/skill mit POST
|
||||
// Verwende den neuen /lerncost Endpunkt mit gsmaster.LernCostRequest Struktur
|
||||
const requestData = {
|
||||
skillType: this.skill.type || 'skill',
|
||||
char_id: parseInt(this.character.id),
|
||||
name: this.skill.name,
|
||||
stufe: this.skill.fertigkeitswert || 0
|
||||
current_level: this.skill.fertigkeitswert || 0,
|
||||
type: this.skill.type || 'skill',
|
||||
action: this.learningType === 'learn' ? 'learn' : 'improve',
|
||||
target_level: 0, // Wird vom Backend automatisch bis Level 18 berechnet
|
||||
use_pp: this.selectedRewardType === 'mixed' ? this.ppUsed : 0,
|
||||
reward: this.selectedRewardType
|
||||
};
|
||||
|
||||
const response = await this.$api.post(`/api/characters/${this.character.id}/improve/skill`, requestData);
|
||||
const response = await this.$api.post(`/api/characters/lerncost`, requestData);
|
||||
|
||||
console.log('Learning costs API response:', response.data);
|
||||
|
||||
if (response.data && Array.isArray(response.data) && response.data.length > 0) {
|
||||
// Konvertiere gsmaster.LearnCost Array zu unserem internen Format
|
||||
// Konvertiere gsmaster.SkillCostResultNew Array zu unserem internen Format
|
||||
const availableEP = this.character.erfahrungsschatz?.value || 0;
|
||||
const availableGold = this.character.vermoegen?.goldstücke || 0;
|
||||
const availablePP = this.skill?.pp || 0;
|
||||
|
||||
let cumulativeEP = 0;
|
||||
let cumulativeGold = 0;
|
||||
let cumulativePP = 0;
|
||||
|
||||
this.availableLevels = response.data.map(cost => {
|
||||
cumulativeEP += cost.ep;
|
||||
cumulativeGold += cost.money;
|
||||
cumulativePP += cost.le; // LE als PP-Äquivalent
|
||||
// Backend liefert bereits die korrekten Kosten basierend auf dem Belohnungstyp
|
||||
let canAfford = false;
|
||||
|
||||
switch (this.selectedRewardType) {
|
||||
case 'noGold':
|
||||
case 'halveepnoGold':
|
||||
canAfford = availableEP >= cost.ep;
|
||||
break;
|
||||
case 'pp':
|
||||
canAfford = availablePP >= cost.le;
|
||||
break;
|
||||
case 'mixed':
|
||||
canAfford = availableEP >= cost.ep && availablePP >= (cost.pp_used || 0);
|
||||
break;
|
||||
case 'default':
|
||||
default:
|
||||
canAfford = availableEP >= cost.ep && availableGold >= cost.gold_cost;
|
||||
break;
|
||||
}
|
||||
|
||||
return {
|
||||
targetLevel: cost.stufe,
|
||||
targetLevel: cost.target_level,
|
||||
epCost: cost.ep,
|
||||
goldCost: cost.money,
|
||||
goldCost: cost.gold_cost,
|
||||
ppCost: cost.le,
|
||||
totalEpCost: cumulativeEP,
|
||||
totalGoldCost: cumulativeGold,
|
||||
totalPpCost: cumulativePP,
|
||||
canAfford: {
|
||||
ep: availableEP >= cumulativeEP,
|
||||
gold: availableGold >= cumulativeGold,
|
||||
pp: availablePP >= cumulativePP
|
||||
}
|
||||
ppUsed: cost.pp_used || 0,
|
||||
canAfford: canAfford
|
||||
};
|
||||
});
|
||||
|
||||
// Aktualisiere Verfügbarkeit basierend auf dem gewählten Belohnungstyp
|
||||
this.updateAffordability();
|
||||
|
||||
console.log('Converted level costs:', this.availableLevels);
|
||||
console.log('Processed level costs for reward type', this.selectedRewardType, ':', this.availableLevels);
|
||||
} else {
|
||||
console.warn('No level costs returned from API, using fallback');
|
||||
this.generateFallbackLevels();
|
||||
@@ -728,7 +762,7 @@ export default {
|
||||
},
|
||||
|
||||
generateFallbackLevels() {
|
||||
// Fallback-Methode für Kostenberechnung
|
||||
// Einfache Fallback-Methode für Kostenberechnung (nur für Notfälle)
|
||||
const currentLevel = this.skill.fertigkeitswert || 0;
|
||||
const maxLevel = 20;
|
||||
const availableEP = this.character.erfahrungsschatz?.value || 0;
|
||||
@@ -740,60 +774,39 @@ export default {
|
||||
for (let targetLevel = currentLevel + 1; targetLevel <= Math.min(currentLevel + 5, maxLevel); targetLevel++) {
|
||||
const levelDiff = targetLevel - currentLevel;
|
||||
|
||||
// Basis-Kosten (vereinfacht)
|
||||
const baseEPCost = levelDiff * 100;
|
||||
const baseGoldCost = levelDiff * 50;
|
||||
const ppReduction = Math.floor(levelDiff * 10);
|
||||
// Sehr einfache Basis-Kosten (nur als Fallback)
|
||||
const epCost = levelDiff * 100;
|
||||
const goldCost = levelDiff * 50;
|
||||
const ppCost = levelDiff * 20;
|
||||
|
||||
// Kosten berechnen
|
||||
const epCost = Math.max(baseEPCost - (availablePP * 10), baseEPCost * 0.5);
|
||||
const goldCost = baseGoldCost;
|
||||
const ppCost = Math.min(levelDiff * 2, availablePP);
|
||||
// Verfügbarkeit basierend auf Belohnungstyp
|
||||
let canAfford = false;
|
||||
switch (this.selectedRewardType) {
|
||||
case 'noGold':
|
||||
case 'halveepnoGold':
|
||||
canAfford = availableEP >= epCost;
|
||||
break;
|
||||
case 'pp':
|
||||
canAfford = availablePP >= ppCost;
|
||||
break;
|
||||
case 'mixed':
|
||||
canAfford = availableEP >= epCost && availablePP >= Math.min(levelDiff * 5, availablePP);
|
||||
break;
|
||||
case 'default':
|
||||
default:
|
||||
canAfford = availableEP >= epCost && availableGold >= goldCost;
|
||||
break;
|
||||
}
|
||||
|
||||
this.availableLevels.push({
|
||||
targetLevel,
|
||||
epCost: Math.ceil(epCost),
|
||||
epCost,
|
||||
goldCost,
|
||||
ppCost,
|
||||
ppUsed: 0,
|
||||
canAfford: {
|
||||
ep: availableEP >= epCost,
|
||||
gold: availableGold >= goldCost,
|
||||
pp: availablePP >= ppCost
|
||||
}
|
||||
canAfford
|
||||
});
|
||||
}
|
||||
|
||||
this.updateAffordability();
|
||||
},
|
||||
|
||||
updateAffordability() {
|
||||
const availableEP = this.character.erfahrungsschatz?.value || 0;
|
||||
const availableGold = this.character.vermoegen?.goldstücke || 0;
|
||||
const availablePP = this.skill?.pp || 0;
|
||||
|
||||
this.availableLevels.forEach(level => {
|
||||
switch (this.selectedRewardType) {
|
||||
case 'ep':
|
||||
level.canAfford = level.canAfford?.ep || (availableEP >= (level.totalEpCost || level.epCost));
|
||||
break;
|
||||
case 'gold':
|
||||
level.canAfford = level.canAfford?.gold || (availableGold >= (level.totalGoldCost || level.goldCost));
|
||||
break;
|
||||
case 'pp':
|
||||
level.canAfford = level.canAfford?.pp || (availablePP >= (level.totalPpCost || level.ppCost));
|
||||
break;
|
||||
case 'mixed':
|
||||
const adjustedEPCost = Math.max((level.epCost || 0) - (this.ppUsed * 10), (level.epCost || 0) * 0.5);
|
||||
level.canAfford = availableEP >= adjustedEPCost && availablePP >= this.ppUsed;
|
||||
level.ppUsed = this.ppUsed;
|
||||
break;
|
||||
default:
|
||||
// Standardmäßig EP-Verfügbarkeit verwenden
|
||||
level.canAfford = level.canAfford?.ep || (availableEP >= (level.totalEpCost || level.epCost));
|
||||
break;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
selectLevel(level) {
|
||||
@@ -802,8 +815,25 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
updatePPUsage() {
|
||||
// Stelle sicher, dass PP-Verwendung die verfügbaren PP nicht überschreitet
|
||||
const maxPP = this.skill?.pp || 0;
|
||||
if (this.ppUsed > maxPP) {
|
||||
this.ppUsed = maxPP;
|
||||
}
|
||||
if (this.ppUsed < 0) {
|
||||
this.ppUsed = 0;
|
||||
}
|
||||
|
||||
// Bei gemischten Kosten oder PP-Belohnung: Neue Kosten vom Backend laden
|
||||
if (this.selectedRewardType === 'mixed' || this.selectedRewardType === 'pp') {
|
||||
this.loadLearningCosts();
|
||||
}
|
||||
},
|
||||
|
||||
updateMixedCosts() {
|
||||
this.updateAffordability();
|
||||
// Diese Methode ist jetzt redundant, da updatePPUsage() alles übernimmt
|
||||
this.updatePPUsage();
|
||||
},
|
||||
|
||||
async executeDetailedLearning() {
|
||||
|
||||
Reference in New Issue
Block a user