You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

259 lines
7.1 KiB
Go

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
}
// EnemyRow is one row from the enemies table (admin / tooling).
type EnemyRow struct {
ID int64 `json:"id"`
Type string `json:"type"`
Name string `json:"name"`
HP int `json:"hp"`
MaxHP int `json:"maxHp"`
Attack int `json:"attack"`
Defense int `json:"defense"`
Speed float64 `json:"speed"`
CritChance float64 `json:"critChance"`
MinLevel int `json:"minLevel"`
MaxLevel int `json:"maxLevel"`
XPReward int64 `json:"xpReward"`
GoldReward int64 `json:"goldReward"`
SpecialAbilities []string `json:"specialAbilities"`
IsElite bool `json:"isElite"`
}
// ListEnemyRows returns all enemy templates ordered by min_level, type.
func (s *ContentStore) ListEnemyRows(ctx context.Context) ([]EnemyRow, error) {
rows, err := s.pool.Query(ctx, `
SELECT id, type, name, hp, max_hp, attack, defense, speed, crit_chance,
min_level, max_level, xp_reward, gold_reward, special_abilities, is_elite
FROM enemies
ORDER BY min_level, type
`)
if err != nil {
return nil, fmt.Errorf("list enemies: %w", err)
}
defer rows.Close()
var out []EnemyRow
for rows.Next() {
var r EnemyRow
if err := rows.Scan(
&r.ID, &r.Type, &r.Name, &r.HP, &r.MaxHP, &r.Attack, &r.Defense, &r.Speed, &r.CritChance,
&r.MinLevel, &r.MaxLevel, &r.XPReward, &r.GoldReward, &r.SpecialAbilities, &r.IsElite,
); err != nil {
return nil, fmt.Errorf("scan enemy row: %w", err)
}
out = append(out, r)
}
if err := rows.Err(); err != nil {
return nil, err
}
return out, nil
}
// UpdateEnemyByType persists one template and sets hp = max_hp = MaxHP from e.
func (s *ContentStore) UpdateEnemyByType(ctx context.Context, typ string, e model.Enemy) error {
abilities := make([]string, 0, len(e.SpecialAbilities))
for _, a := range e.SpecialAbilities {
abilities = append(abilities, string(a))
}
tag, err := s.pool.Exec(ctx, `
UPDATE enemies SET
name = $2,
hp = $3,
max_hp = $4,
attack = $5,
defense = $6,
speed = $7,
crit_chance = $8,
min_level = $9,
max_level = $10,
xp_reward = $11,
gold_reward = $12,
special_abilities = $13::text[],
is_elite = $14
WHERE type = $1
`, typ, e.Name, e.MaxHP, e.MaxHP, e.Attack, e.Defense, e.Speed, e.CritChance,
e.MinLevel, e.MaxLevel, e.XPReward, e.GoldReward, abilities, e.IsElite)
if err != nil {
return fmt.Errorf("update enemy: %w", err)
}
if tag.RowsAffected() == 0 {
return fmt.Errorf("no enemy row with type %q", typ)
}
return 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
}