New test TestCalculateSkillImproveCostNewSystem in chain of learning system

Updated learning_skill_category_difficulties to match rule set
removed SkillImprovementCost replaced by SkillImprovementCost2
This commit is contained in:
2026-02-01 13:21:16 +01:00
parent 3297c408d2
commit 39659bcb3e
20 changed files with 8095 additions and 90 deletions
+113
View File
@@ -368,3 +368,116 @@ func TestCalculateSpellLearnCostNewSystem(t *testing.T) {
}) })
} }
} }
// ToDo: add more test cases for other classes dificulties and higher TEs
func TestCalculateSkillImproveCostNewSystem(t *testing.T) {
// Ensure DB is initialized so GetImprovementCost can run without nil DB
database.SetupTestDB()
defer database.ResetTestDB()
tests := []struct {
name string
request gsmaster.LernCostRequest
skillInfo models.SkillLearningInfo
targetLevel int
requiredTE int
requiredEP int
requiredGold int
availablePP int
availableGold int
}{
{
name: "Base cost calculation without rewards",
request: gsmaster.LernCostRequest{},
skillInfo: models.SkillLearningInfo{
SkillName: "Schwimmen",
CategoryName: "Körper",
DifficultyName: "leicht",
ClassCode: "Bb",
EPPerTE: 10,
LearnCost: 1,
},
requiredTE: 1,
requiredEP: 10,
requiredGold: 200,
targetLevel: 13,
availablePP: 0,
availableGold: 0,
}, {
name: "applies PP reduction",
request: gsmaster.LernCostRequest{},
skillInfo: models.SkillLearningInfo{
SkillName: "Schwimmen",
CategoryName: "Körper",
DifficultyName: "leicht",
ClassCode: "Bb",
EPPerTE: 10,
LearnCost: 1,
},
requiredTE: 0,
requiredEP: 0,
requiredGold: 0,
targetLevel: 13,
availablePP: 1,
availableGold: 0,
},
{
name: "converts gold up to half EP",
request: gsmaster.LernCostRequest{},
skillInfo: models.SkillLearningInfo{
SkillName: "Schwimmen",
CategoryName: "Körper",
DifficultyName: "leicht",
ClassCode: "Bb",
EPPerTE: 10,
},
requiredTE: 1,
requiredEP: 5,
targetLevel: 13,
availablePP: 0,
availableGold: 50,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
remainingPP := tt.availablePP
remainingGold := tt.availableGold
result := gsmaster.SkillCostResultNew{
CharacterClass: tt.skillInfo.ClassCode,
SkillName: tt.skillInfo.SkillName,
Category: tt.skillInfo.CategoryName,
Difficulty: tt.skillInfo.DifficultyName,
TargetLevel: tt.targetLevel,
}
err := CalculateSkillImproveCostNewSystem(&tt.request, &result, tt.targetLevel, &remainingPP, &remainingGold, &tt.skillInfo)
assert.NoError(t, err)
// Base EP before gold conversion is computed from post-PP LE
baseEP := tt.skillInfo.EPPerTE * result.LE
originalTrain := result.LE + result.PPUsed
assert.Greater(t, originalTrain, 0, "should have positive training cost")
assert.Equal(t, tt.availablePP-result.PPUsed, remainingPP, "PP tracking should be consistent")
assert.Equal(t, tt.availableGold-result.GoldUsed, remainingGold, "Gold tracking should match usage")
if tt.availablePP > 0 {
assert.Equal(t, tt.availablePP-remainingPP, result.PPUsed, "PP used should reduce remaining PP")
assert.Equal(t, baseEP, result.EP, "No gold in this case; EP equals base")
assert.Equal(t, tt.skillInfo.EPPerTE*result.LE, result.EP)
} else {
expectedGoldUsed := tt.availableGold
halfEPCap := (baseEP / 2) * 10
if expectedGoldUsed > halfEPCap {
expectedGoldUsed = halfEPCap
}
assert.Equal(t, expectedGoldUsed, result.GoldUsed)
assert.Equal(t, baseEP-expectedGoldUsed/10, result.EP)
assert.Equal(t, tt.availableGold-expectedGoldUsed, remainingGold)
}
})
}
}
+29 -21
View File
@@ -1254,30 +1254,37 @@ func ImportSpellLevelLECosts(inputDir string) error {
// ExportSkillImprovementCosts exports all skill improvement costs to a JSON file // ExportSkillImprovementCosts exports all skill improvement costs to a JSON file
func ExportSkillImprovementCosts(outputDir string) error { func ExportSkillImprovementCosts(outputDir string) error {
var costs []models.SkillImprovementCost var costs []models.SkillImprovementCost2
if err := database.DB.Preload("SkillCategoryDifficulty.Skill"). if err := database.DB.Find(&costs).Error; err != nil {
Preload("SkillCategoryDifficulty.SkillCategory").
Preload("SkillCategoryDifficulty.SkillDifficulty").
Find(&costs).Error; err != nil {
return fmt.Errorf("failed to fetch skill improvement costs: %w", err) return fmt.Errorf("failed to fetch skill improvement costs: %w", err)
} }
exportable := make([]ExportableSkillImprovementCost, 0, len(costs)) exportable := make([]ExportableSkillImprovementCost, 0, len(costs))
for _, cost := range costs { for _, cost := range costs {
// Skip records with incomplete relationships var lc models.SkillCategory
if cost.SkillCategoryDifficulty.Skill.Name == "" || if err := database.DB.First(&lc, cost.CategoryID).Error; err != nil {
cost.SkillCategoryDifficulty.SkillCategory.Name == "" || continue
cost.SkillCategoryDifficulty.SkillDifficulty.Name == "" { }
var ld models.SkillDifficulty
if err := database.DB.First(&ld, cost.DifficultyID).Error; err != nil {
continue
}
var scd models.SkillCategoryDifficulty
if err := database.DB.Where("skill_category = ? AND skill_difficulty = ?", lc.Name, ld.Name).First(&scd).Error; err != nil {
continue
}
var skill models.Skill
if err := database.DB.First(&skill, scd.SkillID).Error; err != nil {
continue continue
} }
exportable = append(exportable, ExportableSkillImprovementCost{ exportable = append(exportable, ExportableSkillImprovementCost{
SkillName: cost.SkillCategoryDifficulty.Skill.Name, SkillName: skill.Name,
SkillSystem: cost.SkillCategoryDifficulty.Skill.GameSystem, SkillSystem: skill.GameSystem,
CategoryName: cost.SkillCategoryDifficulty.SkillCategory.Name, CategoryName: lc.Name,
CategorySystem: cost.SkillCategoryDifficulty.SkillCategory.GameSystem, CategorySystem: lc.GameSystem,
DifficultyName: cost.SkillCategoryDifficulty.SkillDifficulty.Name, DifficultyName: ld.Name,
DifficultySystem: cost.SkillCategoryDifficulty.SkillDifficulty.GameSystem, DifficultySystem: ld.GameSystem,
CurrentLevel: cost.CurrentLevel, CurrentLevel: cost.CurrentLevel,
TERequired: cost.TERequired, TERequired: cost.TERequired,
}) })
@@ -1325,14 +1332,15 @@ func ImportSkillImprovementCosts(inputDir string) error {
exp.SkillName, exp.CategoryName, exp.DifficultyName, err) exp.SkillName, exp.CategoryName, exp.DifficultyName, err)
} }
// Find or create SkillImprovementCost // Find or create SkillImprovementCost2 using category/difficulty IDs
var cost models.SkillImprovementCost var cost models.SkillImprovementCost2
result := database.DB.Where("skill_category_difficulty_id = ? AND current_level = ?", result := database.DB.Where("skill_category_id = ? AND skill_difficulty_id = ? AND current_level = ?",
scd.ID, exp.CurrentLevel).First(&cost) categoryID, difficultyID, exp.CurrentLevel).First(&cost)
if result.Error == gorm.ErrRecordNotFound { if result.Error == gorm.ErrRecordNotFound {
cost = models.SkillImprovementCost{ cost = models.SkillImprovementCost2{
SkillCategoryDifficultyID: scd.ID, CategoryID: categoryID,
DifficultyID: difficultyID,
CurrentLevel: exp.CurrentLevel, CurrentLevel: exp.CurrentLevel,
TERequired: exp.TERequired, TERequired: exp.TERequired,
} }
+31 -21
View File
@@ -13,30 +13,39 @@ import (
// ExportSkillImprovementCosts exports all skill improvement costs to a JSON file // ExportSkillImprovementCosts exports all skill improvement costs to a JSON file
func ExportSkillImprovementCosts(outputDir string) error { func ExportSkillImprovementCosts(outputDir string) error {
var costs []models.SkillImprovementCost var costs []models.SkillImprovementCost2
if err := database.DB.Preload("SkillCategoryDifficulty.Skill"). if err := database.DB.Find(&costs).Error; err != nil {
Preload("SkillCategoryDifficulty.SkillCategory").
Preload("SkillCategoryDifficulty.SkillDifficulty").
Find(&costs).Error; err != nil {
return fmt.Errorf("failed to fetch skill improvement costs: %w", err) return fmt.Errorf("failed to fetch skill improvement costs: %w", err)
} }
exportable := make([]ExportableSkillImprovementCost, 0, len(costs)) exportable := make([]ExportableSkillImprovementCost, 0, len(costs))
for _, cost := range costs { for _, cost := range costs {
// Skip records with incomplete relationships // Resolve category and difficulty names
if cost.SkillCategoryDifficulty.Skill.Name == "" || var lc models.SkillCategory
cost.SkillCategoryDifficulty.SkillCategory.Name == "" || if err := database.DB.First(&lc, cost.CategoryID).Error; err != nil {
cost.SkillCategoryDifficulty.SkillDifficulty.Name == "" { continue
}
var ld models.SkillDifficulty
if err := database.DB.First(&ld, cost.DifficultyID).Error; err != nil {
continue
}
// Find skill via learning_skill_category_difficulties
var scd models.SkillCategoryDifficulty
if err := database.DB.Where("skill_category = ? AND skill_difficulty = ?", lc.Name, ld.Name).First(&scd).Error; err != nil {
continue
}
var skill models.Skill
if err := database.DB.First(&skill, scd.SkillID).Error; err != nil {
continue continue
} }
exportable = append(exportable, ExportableSkillImprovementCost{ exportable = append(exportable, ExportableSkillImprovementCost{
SkillName: cost.SkillCategoryDifficulty.Skill.Name, SkillName: skill.Name,
SkillSystem: cost.SkillCategoryDifficulty.Skill.GameSystem, SkillSystem: skill.GameSystem,
CategoryName: cost.SkillCategoryDifficulty.SkillCategory.Name, CategoryName: lc.Name,
CategorySystem: cost.SkillCategoryDifficulty.SkillCategory.GameSystem, CategorySystem: lc.GameSystem,
DifficultyName: cost.SkillCategoryDifficulty.SkillDifficulty.Name, DifficultyName: ld.Name,
DifficultySystem: cost.SkillCategoryDifficulty.SkillDifficulty.GameSystem, DifficultySystem: ld.GameSystem,
CurrentLevel: cost.CurrentLevel, CurrentLevel: cost.CurrentLevel,
TERequired: cost.TERequired, TERequired: cost.TERequired,
}) })
@@ -84,14 +93,15 @@ func ImportSkillImprovementCosts(inputDir string) error {
exp.SkillName, exp.CategoryName, exp.DifficultyName, err) exp.SkillName, exp.CategoryName, exp.DifficultyName, err)
} }
// Find or create SkillImprovementCost // Find or create SkillImprovementCost2 using category/difficulty IDs
var cost models.SkillImprovementCost var cost models.SkillImprovementCost2
result := database.DB.Where("skill_category_difficulty_id = ? AND current_level = ?", result := database.DB.Where("skill_category_id = ? AND skill_difficulty_id = ? AND current_level = ?",
scd.ID, exp.CurrentLevel).First(&cost) categoryID, difficultyID, exp.CurrentLevel).First(&cost)
if result.Error == gorm.ErrRecordNotFound { if result.Error == gorm.ErrRecordNotFound {
cost = models.SkillImprovementCost{ cost = models.SkillImprovementCost2{
SkillCategoryDifficultyID: scd.ID, CategoryID: categoryID,
DifficultyID: difficultyID,
CurrentLevel: exp.CurrentLevel, CurrentLevel: exp.CurrentLevel,
TERequired: exp.TERequired, TERequired: exp.TERequired,
} }
+7 -6
View File
@@ -893,9 +893,10 @@ func TestExportImportSkillImprovementCosts(t *testing.T) {
t.Fatalf("Failed to query SkillCategoryDifficulty: %v", err) t.Fatalf("Failed to query SkillCategoryDifficulty: %v", err)
} }
// Create SkillImprovementCost // Create SkillImprovementCost2 (new model)
improvementCost := models.SkillImprovementCost{ improvementCost := models.SkillImprovementCost2{
SkillCategoryDifficultyID: scd.ID, CategoryID: category.ID,
DifficultyID: difficulty.ID,
CurrentLevel: 15, // Use unique level to avoid conflicts CurrentLevel: 15, // Use unique level to avoid conflicts
TERequired: 5, TERequired: 5,
} }
@@ -923,10 +924,10 @@ func TestExportImportSkillImprovementCosts(t *testing.T) {
t.Fatalf("ImportSkillImprovementCosts failed: %v", err) t.Fatalf("ImportSkillImprovementCosts failed: %v", err)
} }
var imported models.SkillImprovementCost var imported models.SkillImprovementCost2
result := database.DB.Where("skill_category_difficulty_id = ? AND current_level = ?", scd.ID, 15).First(&imported) result := database.DB.Where("skill_category_id = ? AND skill_difficulty_id = ? AND current_level = ?", category.ID, difficulty.ID, 15).First(&imported)
if result.Error != nil { if result.Error != nil {
t.Fatalf("SkillImprovementCost not found after import: %v", result.Error) t.Fatalf("SkillImprovementCost2 not found after import: %v", result.Error)
} }
// Should be restored to original value from export // Should be restored to original value from export
@@ -291,7 +291,7 @@ func CreateLearningCostsTables() error {
&models.ClassSpellSchoolEPCost{}, &models.ClassSpellSchoolEPCost{},
&models.SpellLevelLECost{}, &models.SpellLevelLECost{},
&models.SkillCategoryDifficulty{}, &models.SkillCategoryDifficulty{},
&models.SkillImprovementCost{}, &models.SkillImprovementCost2{},
} }
// Erstelle oder migriere alle Tabellen // Erstelle oder migriere alle Tabellen
+22 -4
View File
@@ -5,6 +5,8 @@ import (
"bamort/models" "bamort/models"
"fmt" "fmt"
"log" "log"
"gorm.io/gorm"
) )
// MigrateLearningCostsToDatabase überträgt die Daten aus learningCostsData in die Datenbank // MigrateLearningCostsToDatabase überträgt die Daten aus learningCostsData in die Datenbank
@@ -473,16 +475,32 @@ func migrateSkillImprovementCosts() error {
// Für jeden Level in TrainCosts // Für jeden Level in TrainCosts
for level, teCost := range data.TrainCosts { for level, teCost := range data.TrainCosts {
improvementCost := models.SkillImprovementCost{ var improvementCost models.SkillImprovementCost2
SkillCategoryDifficultyID: categoryDifficulty.ID, err := database.DB.Where(
"skill_category_id = ? AND skill_difficulty_id = ? AND current_level = ?",
skillCategory.ID, skillDifficulty.ID, level,
).First(&improvementCost).Error
if err == gorm.ErrRecordNotFound {
improvementCost = models.SkillImprovementCost2{
CategoryID: skillCategory.ID,
DifficultyID: skillDifficulty.ID,
CurrentLevel: level, CurrentLevel: level,
TERequired: teCost, TERequired: teCost,
} }
if err := improvementCost.Create(); err != nil { if err := improvementCost.Create(); err != nil {
return fmt.Errorf("failed to create improvement cost for %s level %d: %w", skillName, level, err) return fmt.Errorf("failed to create improvement cost for %s level %d: %w", skillName, level, err)
} }
log.Printf("Created improvement cost: %s - %s - %s Level %d = %d TE", } else if err != nil {
return fmt.Errorf("failed to query improvement cost for %s level %d: %w", skillName, level, err)
} else {
improvementCost.TERequired = teCost
if err := database.DB.Save(&improvementCost).Error; err != nil {
return fmt.Errorf("failed to update improvement cost for %s level %d: %w", skillName, level, err)
}
}
log.Printf("Upserted improvement cost: %s - %s - %s Level %d = %d TE",
skillName, categoryName, difficultyName, level, teCost) skillName, categoryName, difficultyName, level, teCost)
} }
} }
+8 -7
View File
@@ -248,7 +248,7 @@ func copyMariaDBToSQLite(mariaDB, sqliteDB *gorm.DB) error {
&models.SpellLevelLECost{}, &models.SpellLevelLECost{},
&models.SkillCategoryDifficulty{}, &models.SkillCategoryDifficulty{},
&models.WeaponSkillCategoryDifficulty{}, &models.WeaponSkillCategoryDifficulty{},
&models.SkillImprovementCost{}, &models.SkillImprovementCost2{},
&models.ClassCategoryLearningPoints{}, &models.ClassCategoryLearningPoints{},
&models.ClassSpellPoints{}, &models.ClassSpellPoints{},
&models.ClassTypicalSkill{}, &models.ClassTypicalSkill{},
@@ -368,10 +368,11 @@ func copyTableData(sourceDB, targetDB *gorm.DB, model interface{}) error {
} }
// Batch in SQLite einfügen // Batch in SQLite einfügen
// Use Save() instead of Create() to avoid GORM applying default values to zero values (e.g., false for booleans) // Use Save() with SkipHooks to preserve raw values and avoid callbacks that rely on global DB state
db := targetDB.Session(&gorm.Session{SkipHooks: true})
for i := 0; i < recordsVal.Len(); i++ { for i := 0; i < recordsVal.Len(); i++ {
record := recordsVal.Index(i).Addr().Interface() record := recordsVal.Index(i).Addr().Interface()
if err := targetDB.Save(record).Error; err != nil { if err := db.Save(record).Error; err != nil {
logger.Error("Fehler beim Speichern von Datensatz in Batch %d für %s: %s", batchNum, tableName, err.Error()) logger.Error("Fehler beim Speichern von Datensatz in Batch %d für %s: %s", batchNum, tableName, err.Error())
return err return err
} }
@@ -770,7 +771,7 @@ func copySQLiteToMariaDB(sqliteDB, mariaDB *gorm.DB) error {
&models.SpellLevelLECost{}, &models.SpellLevelLECost{},
&models.SkillCategoryDifficulty{}, // Jetzt nach Skills &models.SkillCategoryDifficulty{}, // Jetzt nach Skills
&models.WeaponSkillCategoryDifficulty{}, &models.WeaponSkillCategoryDifficulty{},
&models.SkillImprovementCost{}, &models.SkillImprovementCost2{},
&models.ClassCategoryLearningPoints{}, &models.ClassCategoryLearningPoints{},
&models.ClassSpellPoints{}, &models.ClassSpellPoints{},
&models.ClassTypicalSkill{}, &models.ClassTypicalSkill{},
@@ -923,8 +924,8 @@ func copyTableDataReverse(sourceDB, targetDB *gorm.DB, model interface{}) error
return fmt.Errorf("failed to read batch from source: %w", err) return fmt.Errorf("failed to read batch from source: %w", err)
} }
records = batch records = batch
case *models.SkillImprovementCost: case *models.SkillImprovementCost2:
var batch []models.SkillImprovementCost var batch []models.SkillImprovementCost2
if err := sourceDB.Limit(batchSize).Offset(offset).Find(&batch).Error; err != nil { if err := sourceDB.Limit(batchSize).Offset(offset).Find(&batch).Error; err != nil {
return fmt.Errorf("failed to read batch from source: %w", err) return fmt.Errorf("failed to read batch from source: %w", err)
} }
@@ -1162,7 +1163,7 @@ func clearMariaDBData(db *gorm.DB) error {
&models.Char{}, &models.Char{},
// Learning Costs System - Abhängige Tabellen (vor Skills/Spells löschen) // Learning Costs System - Abhängige Tabellen (vor Skills/Spells löschen)
&models.SkillImprovementCost{}, &models.SkillImprovementCost2{},
&models.WeaponSkillCategoryDifficulty{}, &models.WeaponSkillCategoryDifficulty{},
&models.SkillCategoryDifficulty{}, &models.SkillCategoryDifficulty{},
&models.SpellLevelLECost{}, &models.SpellLevelLECost{},
+4 -4
View File
@@ -32,7 +32,7 @@ func TestTableListCompleteness(t *testing.T) {
"*models.SpellLevelLECost": true, "*models.SpellLevelLECost": true,
"*models.SkillCategoryDifficulty": true, "*models.SkillCategoryDifficulty": true,
"*models.WeaponSkillCategoryDifficulty": true, "*models.WeaponSkillCategoryDifficulty": true,
"*models.SkillImprovementCost": true, "*models.SkillImprovementCost2": true,
// GSMaster Base Data // GSMaster Base Data
"*models.Skill": true, "*models.Skill": true,
@@ -88,7 +88,7 @@ func TestTableListCompleteness(t *testing.T) {
&models.SpellLevelLECost{}, &models.SpellLevelLECost{},
&models.SkillCategoryDifficulty{}, &models.SkillCategoryDifficulty{},
&models.WeaponSkillCategoryDifficulty{}, &models.WeaponSkillCategoryDifficulty{},
&models.SkillImprovementCost{}, &models.SkillImprovementCost2{},
&models.Skill{}, &models.Skill{},
&models.WeaponSkill{}, &models.WeaponSkill{},
&models.Spell{}, &models.Spell{},
@@ -301,8 +301,8 @@ func getModelTypeName(model interface{}) string {
return "*models.SkillCategoryDifficulty" return "*models.SkillCategoryDifficulty"
case *models.WeaponSkillCategoryDifficulty: case *models.WeaponSkillCategoryDifficulty:
return "*models.WeaponSkillCategoryDifficulty" return "*models.WeaponSkillCategoryDifficulty"
case *models.SkillImprovementCost: case *models.SkillImprovementCost2:
return "*models.SkillImprovementCost" return "*models.SkillImprovementCost2"
case *models.Skill: case *models.Skill:
return "*models.Skill" return "*models.Skill"
case *models.WeaponSkill: case *models.WeaponSkill:
+1 -1
View File
@@ -180,12 +180,12 @@ func learningMigrateStructure(db ...*gorm.DB) error {
&SpellLevelLECost{}, &SpellLevelLECost{},
&SkillCategoryDifficulty{}, &SkillCategoryDifficulty{},
&WeaponSkillCategoryDifficulty{}, &WeaponSkillCategoryDifficulty{},
&SkillImprovementCost{},
&ClassCategoryLearningPoints{}, &ClassCategoryLearningPoints{},
&ClassSpellPoints{}, &ClassSpellPoints{},
&ClassTypicalSkill{}, &ClassTypicalSkill{},
&ClassTypicalSpell{}, &ClassTypicalSpell{},
&AuditLogEntry{}, &AuditLogEntry{},
&SkillImprovementCost2{},
) )
if err != nil { if err != nil {
return err return err
+1 -1
View File
@@ -306,7 +306,7 @@ func TestLearningMigrateStructure_VerifyStructures(t *testing.T) {
&ClassSpellSchoolEPCost{}, &ClassSpellSchoolEPCost{},
&SpellLevelLECost{}, &SpellLevelLECost{},
&SkillCategoryDifficulty{}, &SkillCategoryDifficulty{},
&SkillImprovementCost{}, &SkillImprovementCost2{},
&AuditLogEntry{}, &AuditLogEntry{},
} }
+34 -5
View File
@@ -132,6 +132,7 @@ type WeaponSkillCategoryDifficulty struct {
SCategory string `gorm:"column:skill_category;size:25;not null;index;default:Waffen" json:"skillCategory"` SCategory string `gorm:"column:skill_category;size:25;not null;index;default:Waffen" json:"skillCategory"`
} }
/*
// SkillImprovementCost definiert TE-Kosten für Verbesserungen basierend auf Kategorie, Schwierigkeit und aktuellem Wert // SkillImprovementCost definiert TE-Kosten für Verbesserungen basierend auf Kategorie, Schwierigkeit und aktuellem Wert
type SkillImprovementCost struct { type SkillImprovementCost struct {
ID uint `gorm:"primaryKey" json:"id"` ID uint `gorm:"primaryKey" json:"id"`
@@ -139,6 +140,17 @@ type SkillImprovementCost struct {
CurrentLevel int `gorm:"not null;index" json:"current_level"` // Aktueller Fertigkeitswert CurrentLevel int `gorm:"not null;index" json:"current_level"` // Aktueller Fertigkeitswert
TERequired int `gorm:"not null" json:"te_required"` // Benötigte Trainingseinheiten TERequired int `gorm:"not null" json:"te_required"` // Benötigte Trainingseinheiten
SkillCategoryDifficulty SkillCategoryDifficulty `gorm:"foreignKey:SkillCategoryDifficultyID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE" json:"skill_category_difficulty"` SkillCategoryDifficulty SkillCategoryDifficulty `gorm:"foreignKey:SkillCategoryDifficultyID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE" json:"skill_category_difficulty"`
CategoryID uint `gorm:"column:skill_category_id;not null;index;default:1" json:"skillCategoryId"` // SkillCategoryID
DifficultyID uint `gorm:"column:skill_difficulty_id;not null;index;default:1" json:"skillDifficultyId"` // SkillDifficultyID
}
*/
// SkillImprovementCost definiert TE-Kosten für Verbesserungen basierend auf Kategorie, Schwierigkeit und aktuellem Wert
type SkillImprovementCost2 struct {
ID uint `gorm:"primaryKey" json:"id"`
CurrentLevel int `gorm:"not null;index" json:"current_level"` // Aktueller Fertigkeitswert
TERequired int `gorm:"not null" json:"te_required"` // Benötigte Trainingseinheiten
CategoryID uint `gorm:"column:skill_category_id;not null;index;default:1" json:"skillCategoryId"` // SkillCategoryID
DifficultyID uint `gorm:"column:skill_difficulty_id;not null;index;default:1" json:"skillDifficultyId"` // SkillDifficultyID
} }
// Für komplexere Queries: View oder Helper-Strukturen // Für komplexere Queries: View oder Helper-Strukturen
@@ -151,7 +163,7 @@ type SkillLearningInfo struct {
CategoryName string `json:"category_name"` CategoryName string `json:"category_name"`
DifficultyID uint `json:"difficulty_id"` DifficultyID uint `json:"difficulty_id"`
DifficultyName string `json:"difficulty_name"` DifficultyName string `json:"difficulty_name"`
LearnCost int `json:"learn_cost"` // LE-Kosten für das Erlernen LearnCost int `json:"learn_cost"` // LE-Kosten / TE-Kosten für das Erlernen
CharacterClassID uint `json:"character_class_id"` CharacterClassID uint `json:"character_class_id"`
ClassCode string `json:"class_code"` ClassCode string `json:"class_code"`
ClassName string `json:"class_name"` ClassName string `json:"class_name"`
@@ -233,9 +245,15 @@ func (WeaponSkillCategoryDifficulty) TableName() string {
return "learning_weaponskill_category_difficulties" return "learning_weaponskill_category_difficulties"
} }
/*
func (SkillImprovementCost) TableName() string { func (SkillImprovementCost) TableName() string {
return "learning_skill_improvement_costs" return "learning_skill_improvement_costs"
} }
*/
func (SkillImprovementCost2) TableName() string {
return "skill_improvement_cost2"
}
func (sllc *SpellLevelLECost) ensureGameSystem() { func (sllc *SpellLevelLECost) ensureGameSystem() {
gs := GetGameSystem(sllc.GameSystemId, sllc.GameSystem) gs := GetGameSystem(sllc.GameSystemId, sllc.GameSystem)
@@ -476,9 +494,15 @@ func (wscd *WeaponSkillCategoryDifficulty) Create() error {
return database.DB.Create(wscd).Error return database.DB.Create(wscd).Error
} }
/*
func (sic *SkillImprovementCost) Create() error { func (sic *SkillImprovementCost) Create() error {
return database.DB.Create(sic).Error return database.DB.Create(sic).Error
} }
*/
func (sic *SkillImprovementCost2) Create() error {
return database.DB.Create(sic).Error
}
// Komplexere Query-Methoden // Komplexere Query-Methoden
@@ -630,14 +654,19 @@ func GetSpellLearningInfoNewSystem(spellName string, classCode string) (*SpellLe
// GetImprovementCost holt die Verbesserungskosten für eine Fertigkeit // GetImprovementCost holt die Verbesserungskosten für eine Fertigkeit
func GetImprovementCost(skillName string, categoryName string, difficultyName string, currentLevel int) (int, error) { func GetImprovementCost(skillName string, categoryName string, difficultyName string, currentLevel int) (int, error) {
var result SkillImprovementCost var result SkillImprovementCost2
err := database.DB.Raw(` err := database.DB.Raw(`
SELECT sic.te_required SELECT sic.te_required
FROM learning_skill_improvement_costs sic FROM skill_improvement_cost2 sic
JOIN learning_skill_category_difficulties scd ON sic.skill_category_difficulty_id = scd.id JOIN learning_skill_categories lc ON lc.id = sic.skill_category_id
JOIN learning_skill_difficulties ld ON ld.id = sic.skill_difficulty_id
JOIN learning_skill_category_difficulties scd ON scd.skill_category = lc.name AND scd.skill_difficulty = ld.name
JOIN gsm_skills s ON scd.skill_id = s.id JOIN gsm_skills s ON scd.skill_id = s.id
WHERE s.name = ? AND scd.skill_category = ? AND scd.skill_difficulty = ? AND sic.current_level = ? WHERE s.name = ?
AND lc.name = ?
AND ld.name = ?
AND sic.current_level = ?
`, skillName, categoryName, difficultyName, currentLevel).Scan(&result).Error `, skillName, categoryName, difficultyName, currentLevel).Scan(&result).Error
if err != nil { if err != nil {
+1 -1
View File
@@ -989,7 +989,7 @@ func TestLearningCostsWorkflow_CompleteDataValidation(t *testing.T) {
assert.Equal(t, "leicht", skillInfo.DifficultyName) assert.Equal(t, "leicht", skillInfo.DifficultyName)
assert.Equal(t, "Bb", skillInfo.ClassCode) assert.Equal(t, "Bb", skillInfo.ClassCode)
assert.Equal(t, 10, skillInfo.EPPerTE, "EP per TE should match the class/category cost") assert.Equal(t, 10, skillInfo.EPPerTE, "EP per TE should match the class/category cost")
assert.Equal(t, 5, skillInfo.LearnCost, "Learn cost should be 5 for leicht difficulty") assert.Equal(t, 1, skillInfo.LearnCost, "Learn cost should be 5 for leicht difficulty")
// 3. Verify active source codes include expected values // 3. Verify active source codes include expected values
sourceCodes, err := GetActiveSourceCodes() sourceCodes, err := GetActiveSourceCodes()
+1 -1
View File
@@ -64,7 +64,7 @@ type DatabaseExport struct {
ClassSpellSchoolEPCosts []models.ClassSpellSchoolEPCost `json:"learning_class_spell_school_ep_costs"` ClassSpellSchoolEPCosts []models.ClassSpellSchoolEPCost `json:"learning_class_spell_school_ep_costs"`
SpellLevelLECosts []models.SpellLevelLECost `json:"learning_spell_level_le_costs"` SpellLevelLECosts []models.SpellLevelLECost `json:"learning_spell_level_le_costs"`
SkillCategoryDifficulties []models.SkillCategoryDifficulty `json:"learning_skill_category_difficulties"` SkillCategoryDifficulties []models.SkillCategoryDifficulty `json:"learning_skill_category_difficulties"`
SkillImprovementCosts []models.SkillImprovementCost `json:"learning_skill_improvement_costs"` SkillImprovementCosts []models.SkillImprovementCost2 `json:"learning_skill_improvement_costs"`
AuditLogEntries []models.AuditLogEntry `json:"audit_log_entries"` AuditLogEntries []models.AuditLogEntry `json:"audit_log_entries"`
} }
+1 -1
View File
@@ -80,7 +80,7 @@ func TestImportDatabaseHandler_Success(t *testing.T) {
assert.Equal(t, "Database imported successfully", response["message"]) assert.Equal(t, "Database imported successfully", response["message"])
assert.Greater(t, response["record_count"], float64(0)) assert.Greater(t, response["record_count"], float64(0))
assert.Equal(t, response["record_count"], exportResult) assert.Equal(t, float64(exportResult.RecordCount), response["record_count"])
} }
func TestImportDatabaseHandler_MissingFilepath(t *testing.T) { func TestImportDatabaseHandler_MissingFilepath(t *testing.T) {
+3 -3
View File
@@ -31,7 +31,7 @@ type LearningDataExport struct {
ClassSpellSchoolEPCosts []models.ClassSpellSchoolEPCost `json:"class_spell_school_ep_costs"` ClassSpellSchoolEPCosts []models.ClassSpellSchoolEPCost `json:"class_spell_school_ep_costs"`
SpellLevelLECosts []models.SpellLevelLECost `json:"spell_level_le_costs"` SpellLevelLECosts []models.SpellLevelLECost `json:"spell_level_le_costs"`
SkillCategoryDifficulties []models.SkillCategoryDifficulty `json:"skill_category_difficulties"` SkillCategoryDifficulties []models.SkillCategoryDifficulty `json:"skill_category_difficulties"`
SkillImprovementCosts []models.SkillImprovementCost `json:"skill_improvement_costs"` SkillImprovementCosts []models.SkillImprovementCost2 `json:"skill_improvement_costs"`
} }
// ExportCharacter exports a complete character with all related data // ExportCharacter exports a complete character with all related data
@@ -177,7 +177,7 @@ func ExportCharacter(characterID uint) (*CharacterExport, error) {
ClassSpellSchoolEPCosts: make([]models.ClassSpellSchoolEPCost, 0), ClassSpellSchoolEPCosts: make([]models.ClassSpellSchoolEPCost, 0),
SpellLevelLECosts: make([]models.SpellLevelLECost, 0), SpellLevelLECosts: make([]models.SpellLevelLECost, 0),
SkillCategoryDifficulties: make([]models.SkillCategoryDifficulty, 0), SkillCategoryDifficulties: make([]models.SkillCategoryDifficulty, 0),
SkillImprovementCosts: make([]models.SkillImprovementCost, 0), SkillImprovementCosts: make([]models.SkillImprovementCost2, 0),
} }
database.DB.Find(&export.LearningData.Sources) database.DB.Find(&export.LearningData.Sources)
@@ -189,7 +189,7 @@ func ExportCharacter(characterID uint) (*CharacterExport, error) {
database.DB.Preload("CharacterClass").Preload("SpellSchool").Find(&export.LearningData.ClassSpellSchoolEPCosts) database.DB.Preload("CharacterClass").Preload("SpellSchool").Find(&export.LearningData.ClassSpellSchoolEPCosts)
database.DB.Find(&export.LearningData.SpellLevelLECosts) database.DB.Find(&export.LearningData.SpellLevelLECosts)
database.DB.Preload("Skill").Preload("SkillCategory").Preload("SkillDifficulty").Find(&export.LearningData.SkillCategoryDifficulties) database.DB.Preload("Skill").Preload("SkillCategory").Preload("SkillDifficulty").Find(&export.LearningData.SkillCategoryDifficulties)
database.DB.Preload("SkillCategoryDifficulty").Find(&export.LearningData.SkillImprovementCosts) database.DB.Find(&export.LearningData.SkillImprovementCosts)
// Load audit log entries // Load audit log entries
export.AuditLogEntries = make([]models.AuditLogEntry, 0) export.AuditLogEntries = make([]models.AuditLogEntry, 0)
+5
View File
@@ -170,6 +170,11 @@ func importOrUpdateSkill(tx *gorm.DB, skill *models.Skill) error {
skill.SourceID = 1 skill.SourceID = 1
} }
// Ensure game system fields are populated so queries match existing records
gs := models.GetGameSystem(skill.GameSystemId, skill.GameSystem)
skill.GameSystem = gs.Name
skill.GameSystemId = gs.ID
var existing models.Skill var existing models.Skill
err := tx.Where("name = ? AND game_system = ?", skill.Name, skill.GameSystem).First(&existing).Error err := tx.Where("name = ? AND game_system = ?", skill.Name, skill.GameSystem).First(&existing).Error
+25
View File
@@ -0,0 +1,25 @@
# Environment variables for Bamort development environment
# API Configuration
# API_URL=http://localhost:8180
# Database Configuration (for development)
DATABASE_TYPE=mysql
DATABASE_URL=bamort:bG4)efozrc@tcp(mariadb-dev:3306)/bamort?charset=utf8mb4&parseTime=True&loc=Local
# MariaDB Configuration (development)
MARIADB_ROOT_PASSWORD=root_password_dev
MARIADB_PASSWORD=bG4)efozrc
MARIADB_DATABASE=bamort
MARIADB_USER=bamort
# Frontend Configuration
API_URL=http://192.168.0.36:8180
VITE_API_URL=http://192.168.0.36:8180
API_PORT=8180
BASE_URL=http://localhost:5173
TEMPLATES_DIR=./templates
EXPORT_TEMP_DIR=./export_temp
GIT_COMMIT=d0c177b
LOG_LEVEL=debug
COMPOSE_PROJECT_NAME=bamort
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long