2025-12-18 21:26:28 +01:00
|
|
|
package pdfrender
|
|
|
|
|
|
|
|
|
|
import (
|
2025-12-19 17:04:20 +01:00
|
|
|
"fmt"
|
|
|
|
|
|
|
|
|
|
"bamort/database"
|
2025-12-18 21:26:28 +01:00
|
|
|
"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,
|
2025-12-20 00:19:20 +01:00
|
|
|
Hand: char.Hand,
|
2025-12-18 21:26:28 +01:00
|
|
|
Homeland: char.Herkunft,
|
|
|
|
|
Religion: char.Glaube,
|
|
|
|
|
Stand: char.SocialClass,
|
|
|
|
|
IconBase64: "", // Will be set later if image exists
|
2025-12-19 17:04:20 +01:00
|
|
|
Vermoegen: WealthInfo{
|
|
|
|
|
Goldstuecke: char.Vermoegen.Goldstuecke,
|
|
|
|
|
Silberstuecke: char.Vermoegen.Silberstuecke,
|
|
|
|
|
Kupferstuecke: char.Vermoegen.Kupferstuecke,
|
|
|
|
|
},
|
2025-12-18 21:26:28 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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.Value
|
|
|
|
|
|
|
|
|
|
return attrs
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// mapDerivedValues extracts derived values like LP, AP, etc.
|
|
|
|
|
func mapDerivedValues(char *models.Char) DerivedValueSet {
|
2025-12-19 17:28:40 +01:00
|
|
|
// Get attributes for bonus calculations
|
|
|
|
|
attrs := mapAttributes(char)
|
2025-12-19 17:29:12 +01:00
|
|
|
|
2025-12-18 21:26:28 +01:00
|
|
|
return DerivedValueSet{
|
2025-12-19 17:28:40 +01:00
|
|
|
LPMax: char.Lp.Max,
|
|
|
|
|
LPAktuell: char.Lp.Value,
|
|
|
|
|
APMax: char.Ap.Max,
|
|
|
|
|
APAktuell: char.Ap.Value,
|
|
|
|
|
AngriffBonus: calculateAttributeBonus(attrs.Gs),
|
|
|
|
|
SchadenBonus: (attrs.St / 20) + (attrs.Gs / 30) - 3,
|
2025-12-18 21:26:28 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// mapSkills converts character skills to SkillViewModel
|
|
|
|
|
func mapSkills(char *models.Char) []SkillViewModel {
|
|
|
|
|
skills := make([]SkillViewModel, 0, len(char.Fertigkeiten))
|
|
|
|
|
|
|
|
|
|
for _, skill := range char.Fertigkeiten {
|
2025-12-23 16:17:21 +01:00
|
|
|
skl := SkillViewModel{
|
2025-12-18 21:26:28 +01:00
|
|
|
Name: skill.Name,
|
|
|
|
|
Category: skill.Category,
|
|
|
|
|
Value: skill.Fertigkeitswert,
|
|
|
|
|
Bonus: skill.Bonus,
|
|
|
|
|
PracticePoints: skill.Pp,
|
|
|
|
|
IsLearned: skill.Fertigkeitswert > 0,
|
2025-12-23 16:17:21 +01:00
|
|
|
Bemerkung: skill.Bemerkung,
|
|
|
|
|
}
|
|
|
|
|
if skl.Name == "Schreiben" || skl.Name == "Sprache" {
|
|
|
|
|
skl.Category = "Sprache"
|
|
|
|
|
}
|
|
|
|
|
skills = append(skills, skl)
|
2025-12-18 21:26:28 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return skills
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-20 00:19:20 +01:00
|
|
|
// mapWeapons converts equipped weapons to WeaponViewModel
|
2025-12-19 17:28:40 +01:00
|
|
|
// EW = Waffenfertigkeit.Fertigkeitswert + Character.AngriffBonus + Weapon.Anb
|
2025-12-18 21:26:28 +01:00
|
|
|
func mapWeapons(char *models.Char) []WeaponViewModel {
|
2025-12-20 00:19:20 +01:00
|
|
|
weapons := make([]WeaponViewModel, 0, len(char.Waffen))
|
2025-12-18 21:26:28 +01:00
|
|
|
|
2025-12-20 00:19:20 +01:00
|
|
|
// Calculate character's bonuses once
|
2025-12-19 17:28:40 +01:00
|
|
|
attrs := mapAttributes(char)
|
|
|
|
|
angriffsBonus := calculateAttributeBonus(attrs.Gs)
|
2025-12-20 00:19:20 +01:00
|
|
|
schadenBonus := calculateAttributeBonus(attrs.St)
|
2025-12-19 17:28:40 +01:00
|
|
|
|
2025-12-20 00:19:20 +01:00
|
|
|
// Create a map of weapon skills for quick lookup
|
|
|
|
|
weaponSkills := make(map[string]int)
|
|
|
|
|
for _, skill := range char.Waffenfertigkeiten {
|
|
|
|
|
weaponSkills[skill.Name] = skill.Fertigkeitswert
|
2025-12-19 17:28:40 +01:00
|
|
|
}
|
|
|
|
|
|
2025-12-20 00:19:20 +01:00
|
|
|
// Iterate over equipped weapons
|
|
|
|
|
for _, equippedWeapon := range char.Waffen {
|
2025-12-19 17:28:40 +01:00
|
|
|
vm := WeaponViewModel{
|
2025-12-20 00:19:20 +01:00
|
|
|
Name: equippedWeapon.Name,
|
2025-12-19 17:28:40 +01:00
|
|
|
}
|
|
|
|
|
|
2025-12-20 00:19:20 +01:00
|
|
|
// Load weapon from gsm_weapons to get base stats and required skill
|
|
|
|
|
baseWeapon := &models.Weapon{}
|
|
|
|
|
err := baseWeapon.First(equippedWeapon.Name)
|
2025-12-19 17:29:12 +01:00
|
|
|
|
2025-12-20 00:19:20 +01:00
|
|
|
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 + angriffsBonus + equippedWeapon.Anb
|
|
|
|
|
|
|
|
|
|
// Calculate damage: Base weapon damage + character bonus + weapon damage bonus
|
|
|
|
|
vm.Damage = calculateWeaponDamageWithBase(baseWeapon, schadenBonus, 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
|
|
|
|
|
}
|
2025-12-19 17:28:40 +01:00
|
|
|
} else {
|
2025-12-20 00:19:20 +01:00
|
|
|
// Weapon not found in gsm_weapons, use basic info
|
|
|
|
|
vm.Value = angriffsBonus + equippedWeapon.Anb
|
2025-12-19 17:28:40 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
weapons = append(weapons, vm)
|
2025-12-18 21:26:28 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return weapons
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// mapSpells converts character spells to SpellViewModel
|
|
|
|
|
func mapSpells(char *models.Char) []SpellViewModel {
|
|
|
|
|
spells := make([]SpellViewModel, 0, len(char.Zauber))
|
|
|
|
|
|
2025-12-19 17:04:20 +01:00
|
|
|
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)
|
2025-12-18 21:26:28 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
}
|
2025-12-19 17:28:40 +01:00
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
}
|
2025-12-20 00:19:20 +01:00
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
}
|