package pdfrender import ( "fmt" "bamort/character" "bamort/database" "bamort/models" ) // MapCharacterToViewModel converts a domain Character to a CharacterSheetViewModel func MapCharacterToViewModel(char *models.Char) (*CharacterSheetViewModel, error) { vm := &CharacterSheetViewModel{ Skills: make([]SkillViewModel, 0), Weapons: make([]WeaponViewModel, 0), Spells: make([]SpellViewModel, 0), MagicItems: make([]MagicItemViewModel, 0), Equipment: make([]EquipmentViewModel, 0), GameResults: make([]GameResultViewModel, 0), } // Map basic character info vm.Character = CharacterInfo{ Name: char.Name, Type: char.Typ, Grade: char.Grad, Age: char.Alter, Height: char.Groesse, Weight: char.Gewicht, Gender: char.Gender, Hand: char.Hand, Herkunft: char.Herkunft, Glaube: char.Glaube, SocialClass: char.SocialClass, IconBase64: "", // Will be set later if image exists Vermoegen: WealthInfo{ Goldstuecke: char.Vermoegen.Goldstuecke, Silberstuecke: char.Vermoegen.Silberstuecke, Kupferstuecke: char.Vermoegen.Kupferstuecke, }, } // Map attributes vm.Attributes = mapAttributes(char) // Map derived values vm.DerivedValues = mapDerivedValues(char) // Map skills vm.Skills = mapSkills(char) // Map weapons vm.Weapons = mapWeapons(char) // Map spells vm.Spells = mapSpells(char) // Map equipment vm.Equipment = mapEquipment(char) return vm, nil } // mapAttributes extracts attribute values from Eigenschaften slice func mapAttributes(char *models.Char) AttributeValues { attrs := AttributeValues{} for _, e := range char.Eigenschaften { switch e.Name { case "St": attrs.St = e.Value case "Gs": attrs.Gs = e.Value case "Gw": attrs.Gw = e.Value case "Ko": attrs.Ko = e.Value case "In": attrs.In = e.Value case "Zt": attrs.Zt = e.Value case "Au": attrs.Au = e.Value case "PA": attrs.PA = e.Value case "Wk": attrs.Wk = e.Value } } attrs.B = char.B.Max return attrs } // mapDerivedValues extracts derived values like LP, AP, etc. func mapDerivedValues(char *models.Char) DerivedValueSet { // Get attributes for bonus calculations attrs := mapAttributes(char) // Get Bonus values from character logic bonusValues := character.CalculateStaticFieldsLogic(character.CalculateStaticFieldsRequest{ St: attrs.St, Gs: attrs.Gs, Gw: attrs.Gw, Ko: attrs.Ko, In: attrs.In, Zt: attrs.Zt, Au: attrs.Au, Rasse: char.Rasse, Typ: char.Typ, Grad: char.Grad, }) return DerivedValueSet{ LPMax: char.Lp.Max, LPAktuell: char.Lp.Value, APMax: char.Ap.Max, APAktuell: char.Ap.Value, AusdauerBonus: bonusValues.AusdauerBonus, SchadenBonus: bonusValues.SchadensBonus, AngriffBonus: bonusValues.AngriffsBonus, Abwehr: bonusValues.Abwehr, AbwehrBonus: bonusValues.AbwehrBonus, Zaubern: bonusValues.Zaubern, ZauberBonus: bonusValues.ZauberBonus, ResistenzKoerper: bonusValues.ResistenzKoerper, ResistenzBonusKoerper: bonusValues.ResistenzBonusKoerper, ResistenzGeist: bonusValues.ResistenzGeist, ResistenzBonusGeist: bonusValues.ResistenzBonusGeist, Raufen: bonusValues.Raufen, GG: char.B.Max, SG: char.B.Max / 2, Sehen: 0, // TODO: Add to character model Horen: 0, // TODO: Add to character model Riechen: 0, // TODO: Add to character model Sechster: 0, // TODO: Add to character model RKMalus: 0, RKSave: 2, RKShort: "LR", RKName: "Lederrüstung", } } // mapSkills converts character skills to SkillViewModel func mapSkills(char *models.Char) []SkillViewModel { skills := make([]SkillViewModel, 0, len(char.Fertigkeiten)+len(char.Waffenfertigkeiten)) for _, skill := range char.Fertigkeiten { skl := SkillViewModel{ Name: skill.Name, Category: skill.Category, Value: skill.Fertigkeitswert, Bonus: skill.Bonus, PracticePoints: skill.Pp, IsLearned: skill.Fertigkeitswert > 0, Bemerkung: skill.Bemerkung, } if skl.Name == "Schreiben" || skl.Name == "Sprache" { skl.Category = "Sprache" } skills = append(skills, skl) } // Append weapon skills to the skill list for _, skill := range char.Waffenfertigkeiten { skl := SkillViewModel{ Name: skill.Name, Category: "Waffenfertigkeit", Value: skill.Fertigkeitswert, Bonus: skill.Bonus, PracticePoints: skill.Pp, IsLearned: skill.Fertigkeitswert > 0, Bemerkung: skill.Bemerkung, } skills = append(skills, skl) } return skills } // mapWeapons converts equipped weapons to WeaponViewModel // EW = Waffenfertigkeit.Fertigkeitswert + Character.AngriffBonus + Weapon.Anb func mapWeapons(char *models.Char) []WeaponViewModel { weapons := make([]WeaponViewModel, 0, len(char.Waffen)+1) // Calculate character's bonuses using character logic attrs := mapAttributes(char) bonusValues := character.CalculateStaticFieldsLogic(character.CalculateStaticFieldsRequest{ St: attrs.St, Gs: attrs.Gs, Gw: attrs.Gw, Ko: attrs.Ko, In: attrs.In, Zt: attrs.Zt, Au: attrs.Au, Rasse: char.Rasse, Typ: char.Typ, Grad: char.Grad, }) // Add Raufen as the first weapon raufenDamage := fmt.Sprintf("1W6%+d", bonusValues.SchadensBonus) weapons = append(weapons, WeaponViewModel{ Name: "Raufen", Value: bonusValues.Raufen, Damage: raufenDamage, IsRanged: false, }) // Create a map of weapon skills for quick lookup weaponSkills := make(map[string]int) for _, skill := range char.Waffenfertigkeiten { weaponSkills[skill.Name] = skill.Fertigkeitswert } // Iterate over equipped weapons for _, equippedWeapon := range char.Waffen { vm := WeaponViewModel{ Name: equippedWeapon.Name, } // Load weapon from gsm_weapons to get base stats and required skill baseWeapon := &models.Weapon{} err := baseWeapon.First(equippedWeapon.Name) if err == nil && baseWeapon.ID > 0 { // Calculate attack value: skill + character bonus + weapon bonus skillValue := 0 if baseWeapon.SkillRequired != "" { skillValue = weaponSkills[baseWeapon.SkillRequired] } vm.Value = skillValue + bonusValues.AngriffsBonus + equippedWeapon.Anb // Calculate damage: Base weapon damage + character bonus + weapon damage bonus vm.Damage = calculateWeaponDamageWithBase(baseWeapon, bonusValues.SchadensBonus, equippedWeapon.Schb) // Add range information for ranged weapons if baseWeapon.IsRanged() { vm.Range = fmt.Sprintf("%d/%d/%d", baseWeapon.RangeNear, baseWeapon.RangeMiddle, baseWeapon.RangeFar) vm.IsRanged = true } } else { // Weapon not found in gsm_weapons, use basic info vm.Value = bonusValues.AngriffsBonus + equippedWeapon.Anb } weapons = append(weapons, vm) } return weapons } // mapSpells converts character spells to SpellViewModel func mapSpells(char *models.Char) []SpellViewModel { spells := make([]SpellViewModel, 0, len(char.Zauber)) for _, charSpell := range char.Zauber { vm := SpellViewModel{ Name: charSpell.Name, Bonus: charSpell.Bonus, } // Try to load spell details from gsmaster (only if database is available) // In test environments without DB, we skip this enrichment if database.DB != nil { masterSpell := &models.Spell{} err := masterSpell.First(charSpell.Name) // If master spell found, add all details if err == nil && masterSpell.ID > 0 { vm.Stufe = masterSpell.Stufe vm.AP = masterSpell.AP vm.Art = masterSpell.Art vm.Zauberdauer = masterSpell.Zauberdauer vm.Reichweite = masterSpell.Reichweite vm.Wirkungsziel = masterSpell.Wirkungsziel vm.Wirkungsbereich = masterSpell.Wirkungsbereich vm.Wirkungsdauer = masterSpell.Wirkungsdauer vm.Ursprung = masterSpell.Ursprung vm.Category = masterSpell.Category vm.LearningCategory = masterSpell.LearningCategory vm.Beschreibung = masterSpell.Beschreibung // If description is empty, use source code and page number if vm.Beschreibung == "" && masterSpell.SourceID > 0 && masterSpell.PageNumber > 0 { source := &models.Source{} if err := database.DB.First(source, masterSpell.SourceID).Error; err == nil { vm.Beschreibung = source.Code + " S." + fmt.Sprintf("%d", masterSpell.PageNumber) } } } } // If spell details not found or DB not available, just use name and bonus from character spells = append(spells, vm) } return spells } // mapEquipment converts character equipment to EquipmentViewModel func mapEquipment(char *models.Char) []EquipmentViewModel { equipment := make([]EquipmentViewModel, 0, len(char.Ausruestung)+len(char.Behaeltnisse)) // Add regular equipment for _, item := range char.Ausruestung { equipment = append(equipment, EquipmentViewModel{ Name: item.Name, Quantity: item.Anzahl, Weight: item.Gewicht, TotalWeight: item.Gewicht * float64(item.Anzahl), Value: int(item.Wert), Location: item.BeinhaltetIn, Container: item.BeinhaltetIn, IsWorn: item.BeinhaltetIn == "Am Körper", IsContainer: false, }) } // Add containers for _, container := range char.Behaeltnisse { equipment = append(equipment, EquipmentViewModel{ Name: container.Name, Quantity: 1, Weight: container.Gewicht, TotalWeight: container.Gewicht, Value: int(container.Wert), IsContainer: true, }) } return equipment } // calculateAttributeBonus calculates attribute bonus based on value // Same logic as in character/derived_values_calculator.go func calculateAttributeBonus(value int) int { if value >= 1 && value <= 5 { return -2 } else if value >= 6 && value <= 20 { return -1 } else if value >= 21 && value <= 80 { return 0 } else if value >= 81 && value <= 95 { return 1 } else if value >= 96 && value <= 100 { return 2 } return 0 } // calculateWeaponDamageWithBase calculates the total damage string using an already-loaded weapon // Format: BaseDamage+TotalBonus, e.g., "1W6+3" // TotalBonus = Character's SchadenBonus + Weapon's Schadensbonus (Schb) func calculateWeaponDamageWithBase(baseWeapon *models.Weapon, schadenBonus int, weaponSchb int) string { baseDamage := baseWeapon.Damage if baseDamage == "" { return "" } // Calculate total damage bonus totalBonus := schadenBonus + weaponSchb // Format the damage string if totalBonus > 0 { return fmt.Sprintf("%s+%d", baseDamage, totalBonus) } else if totalBonus < 0 { return fmt.Sprintf("%s%d", baseDamage, totalBonus) } return baseDamage } // calculateWeaponDamage calculates the total damage string for a weapon // Format: BaseDamage+TotalBonus, e.g., "1W6+3" // TotalBonus = Character's SchadenBonus + Weapon's Schadensbonus (Schb) func calculateWeaponDamage(weaponName string, schadenBonus int, weaponSchb int) string { // Try to load weapon details from gsmaster to get base damage var baseDamage string if database.DB != nil { masterWeapon := &models.Weapon{} err := masterWeapon.First(weaponName) if err == nil && masterWeapon.ID > 0 && masterWeapon.Damage != "" { return calculateWeaponDamageWithBase(masterWeapon, schadenBonus, weaponSchb) } } return baseDamage } // calculateWeaponRange returns the range string for a weapon and whether it's ranged // Format: "Nah/Mittel/Fern", e.g., "10/30/100" // Returns (rangeString, isRanged) func calculateWeaponRange(weaponName string) (string, bool) { // Try to load weapon details from gsmaster to get ranges if database.DB != nil { masterWeapon := &models.Weapon{} err := masterWeapon.First(weaponName) if err == nil && masterWeapon.ID > 0 { // Check if weapon is ranged (at least one range value > 0) if masterWeapon.IsRanged() { // Format as "Nah/Mittel/Fern" rangeStr := fmt.Sprintf("%d/%d/%d", masterWeapon.RangeNear, masterWeapon.RangeMiddle, masterWeapon.RangeFar) return rangeStr, true } } } return "", false }