package storage import ( "context" "fmt" "strings" "github.com/jackc/pgx/v5/pgxpool" "github.com/denisovdennis/autohero/internal/model" ) type ContentStore struct { pool *pgxpool.Pool } func NewContentStore(pool *pgxpool.Pool) *ContentStore { return &ContentStore{pool: pool} } func (s *ContentStore) LoadEnemyTemplates(ctx context.Context) (map[model.EnemyType]model.Enemy, error) { rows, err := s.pool.Query(ctx, ` SELECT type, name, hp, max_hp, attack, defense, speed, crit_chance, min_level, max_level, xp_reward, gold_reward, special_abilities, is_elite FROM enemies `) if err != nil { return nil, fmt.Errorf("load enemies from db: %w", err) } defer rows.Close() out := make(map[model.EnemyType]model.Enemy) for rows.Next() { var ( t string e model.Enemy specialAbilities []string ) if err := rows.Scan( &t, &e.Name, &e.HP, &e.MaxHP, &e.Attack, &e.Defense, &e.Speed, &e.CritChance, &e.MinLevel, &e.MaxLevel, &e.XPReward, &e.GoldReward, &specialAbilities, &e.IsElite, ); err != nil { return nil, fmt.Errorf("scan enemy row: %w", err) } e.Type = model.EnemyType(t) e.SpecialAbilities = make([]model.SpecialAbility, 0, len(specialAbilities)) for _, a := range specialAbilities { e.SpecialAbilities = append(e.SpecialAbilities, model.SpecialAbility(a)) } out[e.Type] = e } if err := rows.Err(); err != nil { return nil, fmt.Errorf("enemy rows: %w", err) } return out, nil } func normalizeEquipmentSlot(raw string) model.EquipmentSlot { v := strings.TrimSpace(strings.ToLower(raw)) v = strings.TrimPrefix(v, "gear.slot.") switch v { case "weapon", "mainhand", "main_hand": return model.SlotMainHand case "armor", "chest": return model.SlotChest case "head": return model.SlotHead case "feet": return model.SlotFeet case "neck": return model.SlotNeck case "hands": return model.SlotHands case "legs": return model.SlotLegs case "cloak": return model.SlotCloak case "finger", "ring": return model.SlotFinger case "wrist": return model.SlotWrist default: return model.EquipmentSlot(v) } } func (s *ContentStore) LoadGearFamilies(ctx context.Context) ([]model.GearFamily, error) { out := make([]model.GearFamily, 0, 128) weaponRows, err := s.pool.Query(ctx, ` SELECT name, type, damage, speed, crit_chance, special_effect FROM weapons `) if err != nil { return nil, fmt.Errorf("load weapons from db: %w", err) } for weaponRows.Next() { var name, typ, special string var damage int var speed, crit float64 if err := weaponRows.Scan(&name, &typ, &damage, &speed, &crit, &special); err != nil { weaponRows.Close() return nil, fmt.Errorf("scan weapon row: %w", err) } out = append(out, model.GearFamily{ Slot: model.SlotMainHand, FormID: "gear.form.main_hand." + typ, Name: name, Subtype: typ, BasePrimary: damage, StatType: "attack", SpeedModifier: speed, BaseCrit: crit, SpecialEffect: special, }) } weaponRows.Close() armorRows, err := s.pool.Query(ctx, ` SELECT name, type, defense, speed_modifier, agility_bonus, set_name, special_effect FROM armor `) if err != nil { return nil, fmt.Errorf("load armor from db: %w", err) } for armorRows.Next() { var name, typ, setName, special string var defense, agi int var speed float64 if err := armorRows.Scan(&name, &typ, &defense, &speed, &agi, &setName, &special); err != nil { armorRows.Close() return nil, fmt.Errorf("scan armor row: %w", err) } out = append(out, model.GearFamily{ Slot: model.SlotChest, FormID: "gear.form.chest." + typ, Name: name, Subtype: typ, BasePrimary: defense, StatType: "defense", SpeedModifier: speed, AgilityBonus: agi, SetName: setName, SpecialEffect: special, }) } armorRows.Close() eqRows, err := s.pool.Query(ctx, ` SELECT slot, form_id, name, base_primary, stat_type FROM equipment_items `) if err != nil { return nil, fmt.Errorf("load equipment_items from db: %w", err) } for eqRows.Next() { var slot, formID, name, statType string var basePrimary int if err := eqRows.Scan(&slot, &formID, &name, &basePrimary, &statType); err != nil { eqRows.Close() return nil, fmt.Errorf("scan equipment_item row: %w", err) } out = append(out, model.GearFamily{ Slot: normalizeEquipmentSlot(slot), FormID: formID, Name: name, BasePrimary: basePrimary, StatType: statType, SpeedModifier: 1.0, }) } eqRows.Close() return out, nil }