package spells import ( "fmt" "dk/internal/database" "zombiezen.com/go/sqlite" ) // Spell represents a spell in the database type Spell struct { ID int `json:"id"` Name string `json:"name"` MP int `json:"mp"` Attribute int `json:"attribute"` Type int `json:"type"` db *database.DB } // SpellType constants for spell types const ( TypeHealing = 1 TypeHurt = 2 TypeSleep = 3 TypeAttackBoost = 4 TypeDefenseBoost = 5 ) // Find retrieves a spell by ID func Find(db *database.DB, id int) (*Spell, error) { spell := &Spell{db: db} query := "SELECT id, name, mp, attribute, type FROM spells WHERE id = ?" err := db.Query(query, func(stmt *sqlite.Stmt) error { spell.ID = stmt.ColumnInt(0) spell.Name = stmt.ColumnText(1) spell.MP = stmt.ColumnInt(2) spell.Attribute = stmt.ColumnInt(3) spell.Type = stmt.ColumnInt(4) return nil }, id) if err != nil { return nil, fmt.Errorf("failed to find spell: %w", err) } if spell.ID == 0 { return nil, fmt.Errorf("spell with ID %d not found", id) } return spell, nil } // All retrieves all spells func All(db *database.DB) ([]*Spell, error) { var spells []*Spell query := "SELECT id, name, mp, attribute, type FROM spells ORDER BY type, mp, id" err := db.Query(query, func(stmt *sqlite.Stmt) error { spell := &Spell{ ID: stmt.ColumnInt(0), Name: stmt.ColumnText(1), MP: stmt.ColumnInt(2), Attribute: stmt.ColumnInt(3), Type: stmt.ColumnInt(4), db: db, } spells = append(spells, spell) return nil }) if err != nil { return nil, fmt.Errorf("failed to retrieve all spells: %w", err) } return spells, nil } // ByType retrieves spells by type func ByType(db *database.DB, spellType int) ([]*Spell, error) { var spells []*Spell query := "SELECT id, name, mp, attribute, type FROM spells WHERE type = ? ORDER BY mp, id" err := db.Query(query, func(stmt *sqlite.Stmt) error { spell := &Spell{ ID: stmt.ColumnInt(0), Name: stmt.ColumnText(1), MP: stmt.ColumnInt(2), Attribute: stmt.ColumnInt(3), Type: stmt.ColumnInt(4), db: db, } spells = append(spells, spell) return nil }, spellType) if err != nil { return nil, fmt.Errorf("failed to retrieve spells by type: %w", err) } return spells, nil } // ByMaxMP retrieves spells that cost at most the specified MP func ByMaxMP(db *database.DB, maxMP int) ([]*Spell, error) { var spells []*Spell query := "SELECT id, name, mp, attribute, type FROM spells WHERE mp <= ? ORDER BY type, mp, id" err := db.Query(query, func(stmt *sqlite.Stmt) error { spell := &Spell{ ID: stmt.ColumnInt(0), Name: stmt.ColumnText(1), MP: stmt.ColumnInt(2), Attribute: stmt.ColumnInt(3), Type: stmt.ColumnInt(4), db: db, } spells = append(spells, spell) return nil }, maxMP) if err != nil { return nil, fmt.Errorf("failed to retrieve spells by max MP: %w", err) } return spells, nil } // ByTypeAndMaxMP retrieves spells of a specific type that cost at most the specified MP func ByTypeAndMaxMP(db *database.DB, spellType, maxMP int) ([]*Spell, error) { var spells []*Spell query := "SELECT id, name, mp, attribute, type FROM spells WHERE type = ? AND mp <= ? ORDER BY mp, id" err := db.Query(query, func(stmt *sqlite.Stmt) error { spell := &Spell{ ID: stmt.ColumnInt(0), Name: stmt.ColumnText(1), MP: stmt.ColumnInt(2), Attribute: stmt.ColumnInt(3), Type: stmt.ColumnInt(4), db: db, } spells = append(spells, spell) return nil }, spellType, maxMP) if err != nil { return nil, fmt.Errorf("failed to retrieve spells by type and max MP: %w", err) } return spells, nil } // ByName retrieves a spell by name (case-insensitive) func ByName(db *database.DB, name string) (*Spell, error) { spell := &Spell{db: db} query := "SELECT id, name, mp, attribute, type FROM spells WHERE LOWER(name) = LOWER(?) LIMIT 1" err := db.Query(query, func(stmt *sqlite.Stmt) error { spell.ID = stmt.ColumnInt(0) spell.Name = stmt.ColumnText(1) spell.MP = stmt.ColumnInt(2) spell.Attribute = stmt.ColumnInt(3) spell.Type = stmt.ColumnInt(4) return nil }, name) if err != nil { return nil, fmt.Errorf("failed to find spell by name: %w", err) } if spell.ID == 0 { return nil, fmt.Errorf("spell with name '%s' not found", name) } return spell, nil } // Save updates an existing spell in the database func (s *Spell) Save() error { if s.ID == 0 { return fmt.Errorf("cannot save spell without ID") } query := `UPDATE spells SET name = ?, mp = ?, attribute = ?, type = ? WHERE id = ?` return s.db.Exec(query, s.Name, s.MP, s.Attribute, s.Type, s.ID) } // Delete removes the spell from the database func (s *Spell) Delete() error { if s.ID == 0 { return fmt.Errorf("cannot delete spell without ID") } query := "DELETE FROM spells WHERE id = ?" return s.db.Exec(query, s.ID) } // IsHealing returns true if the spell is a healing spell func (s *Spell) IsHealing() bool { return s.Type == TypeHealing } // IsHurt returns true if the spell is a hurt spell func (s *Spell) IsHurt() bool { return s.Type == TypeHurt } // IsSleep returns true if the spell is a sleep spell func (s *Spell) IsSleep() bool { return s.Type == TypeSleep } // IsAttackBoost returns true if the spell boosts attack func (s *Spell) IsAttackBoost() bool { return s.Type == TypeAttackBoost } // IsDefenseBoost returns true if the spell boosts defense func (s *Spell) IsDefenseBoost() bool { return s.Type == TypeDefenseBoost } // TypeName returns the string representation of the spell type func (s *Spell) TypeName() string { switch s.Type { case TypeHealing: return "Healing" case TypeHurt: return "Hurt" case TypeSleep: return "Sleep" case TypeAttackBoost: return "Attack Boost" case TypeDefenseBoost: return "Defense Boost" default: return "Unknown" } } // CanCast returns true if the spell can be cast with the given MP func (s *Spell) CanCast(availableMP int) bool { return availableMP >= s.MP } // Efficiency returns the attribute per MP ratio (higher is more efficient) func (s *Spell) Efficiency() float64 { if s.MP == 0 { return 0 } return float64(s.Attribute) / float64(s.MP) } // IsOffensive returns true if the spell is used for attacking func (s *Spell) IsOffensive() bool { return s.Type == TypeHurt || s.Type == TypeSleep } // IsSupport returns true if the spell is used for support/buffs func (s *Spell) IsSupport() bool { return s.Type == TypeHealing || s.Type == TypeAttackBoost || s.Type == TypeDefenseBoost }