package monsters import ( "fmt" "dk/internal/database" "zombiezen.com/go/sqlite" ) // Monster represents a monster in the database type Monster struct { ID int `json:"id"` Name string `json:"name"` MaxHP int `json:"max_hp"` MaxDmg int `json:"max_dmg"` Armor int `json:"armor"` Level int `json:"level"` MaxExp int `json:"max_exp"` MaxGold int `json:"max_gold"` Immune int `json:"immune"` db *database.DB } // Immunity constants for monster immunity types const ( ImmuneNone = 0 ImmuneHurt = 1 // Immune to Hurt spells ImmuneSleep = 2 // Immune to Sleep spells ) // Find retrieves a monster by ID func Find(db *database.DB, id int) (*Monster, error) { monster := &Monster{db: db} query := "SELECT id, name, max_hp, max_dmg, armor, level, max_exp, max_gold, immune FROM monsters WHERE id = ?" err := db.Query(query, func(stmt *sqlite.Stmt) error { monster.ID = stmt.ColumnInt(0) monster.Name = stmt.ColumnText(1) monster.MaxHP = stmt.ColumnInt(2) monster.MaxDmg = stmt.ColumnInt(3) monster.Armor = stmt.ColumnInt(4) monster.Level = stmt.ColumnInt(5) monster.MaxExp = stmt.ColumnInt(6) monster.MaxGold = stmt.ColumnInt(7) monster.Immune = stmt.ColumnInt(8) return nil }, id) if err != nil { return nil, fmt.Errorf("failed to find monster: %w", err) } if monster.ID == 0 { return nil, fmt.Errorf("monster with ID %d not found", id) } return monster, nil } // All retrieves all monsters func All(db *database.DB) ([]*Monster, error) { var monsters []*Monster query := "SELECT id, name, max_hp, max_dmg, armor, level, max_exp, max_gold, immune FROM monsters ORDER BY level, id" err := db.Query(query, func(stmt *sqlite.Stmt) error { monster := &Monster{ ID: stmt.ColumnInt(0), Name: stmt.ColumnText(1), MaxHP: stmt.ColumnInt(2), MaxDmg: stmt.ColumnInt(3), Armor: stmt.ColumnInt(4), Level: stmt.ColumnInt(5), MaxExp: stmt.ColumnInt(6), MaxGold: stmt.ColumnInt(7), Immune: stmt.ColumnInt(8), db: db, } monsters = append(monsters, monster) return nil }) if err != nil { return nil, fmt.Errorf("failed to retrieve all monsters: %w", err) } return monsters, nil } // ByLevel retrieves monsters by level func ByLevel(db *database.DB, level int) ([]*Monster, error) { var monsters []*Monster query := "SELECT id, name, max_hp, max_dmg, armor, level, max_exp, max_gold, immune FROM monsters WHERE level = ? ORDER BY id" err := db.Query(query, func(stmt *sqlite.Stmt) error { monster := &Monster{ ID: stmt.ColumnInt(0), Name: stmt.ColumnText(1), MaxHP: stmt.ColumnInt(2), MaxDmg: stmt.ColumnInt(3), Armor: stmt.ColumnInt(4), Level: stmt.ColumnInt(5), MaxExp: stmt.ColumnInt(6), MaxGold: stmt.ColumnInt(7), Immune: stmt.ColumnInt(8), db: db, } monsters = append(monsters, monster) return nil }, level) if err != nil { return nil, fmt.Errorf("failed to retrieve monsters by level: %w", err) } return monsters, nil } // ByLevelRange retrieves monsters within a level range (inclusive) func ByLevelRange(db *database.DB, minLevel, maxLevel int) ([]*Monster, error) { var monsters []*Monster query := "SELECT id, name, max_hp, max_dmg, armor, level, max_exp, max_gold, immune FROM monsters WHERE level BETWEEN ? AND ? ORDER BY level, id" err := db.Query(query, func(stmt *sqlite.Stmt) error { monster := &Monster{ ID: stmt.ColumnInt(0), Name: stmt.ColumnText(1), MaxHP: stmt.ColumnInt(2), MaxDmg: stmt.ColumnInt(3), Armor: stmt.ColumnInt(4), Level: stmt.ColumnInt(5), MaxExp: stmt.ColumnInt(6), MaxGold: stmt.ColumnInt(7), Immune: stmt.ColumnInt(8), db: db, } monsters = append(monsters, monster) return nil }, minLevel, maxLevel) if err != nil { return nil, fmt.Errorf("failed to retrieve monsters by level range: %w", err) } return monsters, nil } // ByImmunity retrieves monsters by immunity type func ByImmunity(db *database.DB, immunityType int) ([]*Monster, error) { var monsters []*Monster query := "SELECT id, name, max_hp, max_dmg, armor, level, max_exp, max_gold, immune FROM monsters WHERE immune = ? ORDER BY level, id" err := db.Query(query, func(stmt *sqlite.Stmt) error { monster := &Monster{ ID: stmt.ColumnInt(0), Name: stmt.ColumnText(1), MaxHP: stmt.ColumnInt(2), MaxDmg: stmt.ColumnInt(3), Armor: stmt.ColumnInt(4), Level: stmt.ColumnInt(5), MaxExp: stmt.ColumnInt(6), MaxGold: stmt.ColumnInt(7), Immune: stmt.ColumnInt(8), db: db, } monsters = append(monsters, monster) return nil }, immunityType) if err != nil { return nil, fmt.Errorf("failed to retrieve monsters by immunity: %w", err) } return monsters, nil } // Builder provides a fluent interface for creating monsters type Builder struct { monster *Monster db *database.DB } // NewBuilder creates a new monster builder func NewBuilder(db *database.DB) *Builder { return &Builder{ monster: &Monster{db: db}, db: db, } } // WithName sets the monster name func (b *Builder) WithName(name string) *Builder { b.monster.Name = name return b } // WithMaxHP sets the monster's maximum hit points func (b *Builder) WithMaxHP(maxHP int) *Builder { b.monster.MaxHP = maxHP return b } // WithMaxDmg sets the monster's maximum damage func (b *Builder) WithMaxDmg(maxDmg int) *Builder { b.monster.MaxDmg = maxDmg return b } // WithArmor sets the monster's armor value func (b *Builder) WithArmor(armor int) *Builder { b.monster.Armor = armor return b } // WithLevel sets the monster's level func (b *Builder) WithLevel(level int) *Builder { b.monster.Level = level return b } // WithMaxExp sets the monster's maximum experience reward func (b *Builder) WithMaxExp(maxExp int) *Builder { b.monster.MaxExp = maxExp return b } // WithMaxGold sets the monster's maximum gold reward func (b *Builder) WithMaxGold(maxGold int) *Builder { b.monster.MaxGold = maxGold return b } // WithImmunity sets the monster's immunity type func (b *Builder) WithImmunity(immunity int) *Builder { b.monster.Immune = immunity return b } // Create saves the monster to the database and returns it func (b *Builder) Create() (*Monster, error) { // Use a transaction to ensure we can get the ID var monster *Monster err := b.db.Transaction(func(tx *database.Tx) error { query := `INSERT INTO monsters (name, max_hp, max_dmg, armor, level, max_exp, max_gold, immune) VALUES (?, ?, ?, ?, ?, ?, ?, ?)` if err := tx.Exec(query, b.monster.Name, b.monster.MaxHP, b.monster.MaxDmg, b.monster.Armor, b.monster.Level, b.monster.MaxExp, b.monster.MaxGold, b.monster.Immune); err != nil { return fmt.Errorf("failed to insert monster: %w", err) } // Get the last inserted ID within the same transaction var lastID int err := tx.Query("SELECT last_insert_rowid()", func(stmt *sqlite.Stmt) error { lastID = stmt.ColumnInt(0) return nil }) if err != nil { return fmt.Errorf("failed to get last insert ID: %w", err) } // Create the monster with the ID monster = &Monster{ ID: lastID, Name: b.monster.Name, MaxHP: b.monster.MaxHP, MaxDmg: b.monster.MaxDmg, Armor: b.monster.Armor, Level: b.monster.Level, MaxExp: b.monster.MaxExp, MaxGold: b.monster.MaxGold, Immune: b.monster.Immune, db: b.db, } return nil }) if err != nil { return nil, fmt.Errorf("failed to create monster: %w", err) } return monster, nil } // Save updates an existing monster in the database func (m *Monster) Save() error { if m.ID == 0 { return fmt.Errorf("cannot save monster without ID") } query := `UPDATE monsters SET name = ?, max_hp = ?, max_dmg = ?, armor = ?, level = ?, max_exp = ?, max_gold = ?, immune = ? WHERE id = ?` return m.db.Exec(query, m.Name, m.MaxHP, m.MaxDmg, m.Armor, m.Level, m.MaxExp, m.MaxGold, m.Immune, m.ID) } // Delete removes the monster from the database func (m *Monster) Delete() error { if m.ID == 0 { return fmt.Errorf("cannot delete monster without ID") } query := "DELETE FROM monsters WHERE id = ?" return m.db.Exec(query, m.ID) } // IsHurtImmune returns true if the monster is immune to Hurt spells func (m *Monster) IsHurtImmune() bool { return m.Immune == ImmuneHurt } // IsSleepImmune returns true if the monster is immune to Sleep spells func (m *Monster) IsSleepImmune() bool { return m.Immune == ImmuneSleep } // HasImmunity returns true if the monster has any immunity func (m *Monster) HasImmunity() bool { return m.Immune != ImmuneNone } // ImmunityName returns the string representation of the monster's immunity func (m *Monster) ImmunityName() string { switch m.Immune { case ImmuneNone: return "None" case ImmuneHurt: return "Hurt Spells" case ImmuneSleep: return "Sleep Spells" default: return "Unknown" } } // DifficultyRating calculates a simple difficulty rating based on stats func (m *Monster) DifficultyRating() float64 { // Simple formula: (HP + Damage + Armor) / Level // Higher values indicate tougher monsters relative to their level if m.Level == 0 { return 0 } return float64(m.MaxHP+m.MaxDmg+m.Armor) / float64(m.Level) } // ExpPerHP returns the experience reward per hit point (efficiency metric) func (m *Monster) ExpPerHP() float64 { if m.MaxHP == 0 { return 0 } return float64(m.MaxExp) / float64(m.MaxHP) } // GoldPerHP returns the gold reward per hit point (efficiency metric) func (m *Monster) GoldPerHP() float64 { if m.MaxHP == 0 { return 0 } return float64(m.MaxGold) / float64(m.MaxHP) }