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:
@@ -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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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,16 +1332,17 @@ 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,
|
||||||
CurrentLevel: exp.CurrentLevel,
|
DifficultyID: difficultyID,
|
||||||
TERequired: exp.TERequired,
|
CurrentLevel: exp.CurrentLevel,
|
||||||
|
TERequired: exp.TERequired,
|
||||||
}
|
}
|
||||||
if err := database.DB.Create(&cost).Error; err != nil {
|
if err := database.DB.Create(&cost).Error; err != nil {
|
||||||
return fmt.Errorf("failed to create skill improvement cost: %w", err)
|
return fmt.Errorf("failed to create skill improvement cost: %w", err)
|
||||||
|
|||||||
@@ -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,16 +93,17 @@ 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,
|
||||||
CurrentLevel: exp.CurrentLevel,
|
DifficultyID: difficultyID,
|
||||||
TERequired: exp.TERequired,
|
CurrentLevel: exp.CurrentLevel,
|
||||||
|
TERequired: exp.TERequired,
|
||||||
}
|
}
|
||||||
if err := database.DB.Create(&cost).Error; err != nil {
|
if err := database.DB.Create(&cost).Error; err != nil {
|
||||||
return fmt.Errorf("failed to create skill improvement cost: %w", err)
|
return fmt.Errorf("failed to create skill improvement cost: %w", err)
|
||||||
|
|||||||
@@ -893,11 +893,12 @@ 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,
|
||||||
CurrentLevel: 15, // Use unique level to avoid conflicts
|
DifficultyID: difficulty.ID,
|
||||||
TERequired: 5,
|
CurrentLevel: 15, // Use unique level to avoid conflicts
|
||||||
|
TERequired: 5,
|
||||||
}
|
}
|
||||||
database.DB.Create(&improvementCost)
|
database.DB.Create(&improvementCost)
|
||||||
|
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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(
|
||||||
CurrentLevel: level,
|
"skill_category_id = ? AND skill_difficulty_id = ? AND current_level = ?",
|
||||||
TERequired: teCost,
|
skillCategory.ID, skillDifficulty.ID, level,
|
||||||
|
).First(&improvementCost).Error
|
||||||
|
|
||||||
|
if err == gorm.ErrRecordNotFound {
|
||||||
|
improvementCost = models.SkillImprovementCost2{
|
||||||
|
CategoryID: skillCategory.ID,
|
||||||
|
DifficultyID: skillDifficulty.ID,
|
||||||
|
CurrentLevel: level,
|
||||||
|
TERequired: teCost,
|
||||||
|
}
|
||||||
|
if err := improvementCost.Create(); err != nil {
|
||||||
|
return fmt.Errorf("failed to create improvement cost for %s level %d: %w", skillName, level, err)
|
||||||
|
}
|
||||||
|
} 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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := improvementCost.Create(); err != nil {
|
log.Printf("Upserted improvement cost: %s - %s - %s Level %d = %d TE",
|
||||||
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",
|
|
||||||
skillName, categoryName, difficultyName, level, teCost)
|
skillName, categoryName, difficultyName, level, teCost)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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{},
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -306,7 +306,7 @@ func TestLearningMigrateStructure_VerifyStructures(t *testing.T) {
|
|||||||
&ClassSpellSchoolEPCost{},
|
&ClassSpellSchoolEPCost{},
|
||||||
&SpellLevelLECost{},
|
&SpellLevelLECost{},
|
||||||
&SkillCategoryDifficulty{},
|
&SkillCategoryDifficulty{},
|
||||||
&SkillImprovementCost{},
|
&SkillImprovementCost2{},
|
||||||
&AuditLogEntry{},
|
&AuditLogEntry{},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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,15 +654,20 @@ 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 gsm_skills s ON scd.skill_id = s.id
|
JOIN learning_skill_difficulties ld ON ld.id = sic.skill_difficulty_id
|
||||||
WHERE s.name = ? AND scd.skill_category = ? AND scd.skill_difficulty = ? AND sic.current_level = ?
|
JOIN learning_skill_category_difficulties scd ON scd.skill_category = lc.name AND scd.skill_difficulty = ld.name
|
||||||
`, skillName, categoryName, difficultyName, currentLevel).Scan(&result).Error
|
JOIN gsm_skills s ON scd.skill_id = s.id
|
||||||
|
WHERE s.name = ?
|
||||||
|
AND lc.name = ?
|
||||||
|
AND ld.name = ?
|
||||||
|
AND sic.current_level = ?
|
||||||
|
`, skillName, categoryName, difficultyName, currentLevel).Scan(&result).Error
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
Reference in New Issue
Block a user