package spells import ( "fmt" "dk/internal/database" "dk/internal/helpers/scanner" "zombiezen.com/go/sqlite" ) // Spell represents a spell in the database type Spell struct { database.BaseModel ID int `db:"id" json:"id"` Name string `db:"name" json:"name"` MP int `db:"mp" json:"mp"` Attribute int `db:"attribute" json:"attribute"` Type int `db:"type" json:"type"` } func (s *Spell) GetTableName() string { return "spells" } func (s *Spell) GetID() int { return s.ID } func (s *Spell) SetID(id int) { s.ID = id } func (s *Spell) Set(field string, value any) error { return database.Set(s, field, value) } func (s *Spell) Save() error { return database.Save(s) } func (s *Spell) Delete() error { return database.Delete(s) } // Creates a new Spell with sensible defaults func New() *Spell { return &Spell{ Name: "", MP: 5, // Default MP cost Attribute: 10, // Default attribute value Type: TypeHealing, // Default to healing spell } } var spellScanner = scanner.New[Spell]() // Returns the column list for spell queries func spellColumns() string { return spellScanner.Columns() } // Populates a Spell struct using the fast scanner func scanSpell(stmt *sqlite.Stmt) *Spell { spell := &Spell{} spellScanner.Scan(stmt, spell) return spell } // SpellType constants for spell types const ( TypeHealing = 1 TypeHurt = 2 TypeSleep = 3 TypeAttackBoost = 4 TypeDefenseBoost = 5 ) // Retrieves a spell by ID func Find(id int) (*Spell, error) { var spell *Spell query := `SELECT ` + spellColumns() + ` FROM spells WHERE id = ?` err := database.Query(query, func(stmt *sqlite.Stmt) error { spell = scanSpell(stmt) return nil }, id) if err != nil { return nil, fmt.Errorf("failed to find spell: %w", err) } if spell == nil { return nil, fmt.Errorf("spell with ID %d not found", id) } return spell, nil } // Retrieves all spells func All() ([]*Spell, error) { var spells []*Spell query := `SELECT ` + spellColumns() + ` FROM spells ORDER BY type, mp, id` err := database.Query(query, func(stmt *sqlite.Stmt) error { spell := scanSpell(stmt) spells = append(spells, spell) return nil }) if err != nil { return nil, fmt.Errorf("failed to retrieve all spells: %w", err) } return spells, nil } // Retrieves spells by type func ByType(spellType int) ([]*Spell, error) { var spells []*Spell query := `SELECT ` + spellColumns() + ` FROM spells WHERE type = ? ORDER BY mp, id` err := database.Query(query, func(stmt *sqlite.Stmt) error { spell := scanSpell(stmt) 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 } // Retrieves spells that cost at most the specified MP func ByMaxMP(maxMP int) ([]*Spell, error) { var spells []*Spell query := `SELECT ` + spellColumns() + ` FROM spells WHERE mp <= ? ORDER BY type, mp, id` err := database.Query(query, func(stmt *sqlite.Stmt) error { spell := scanSpell(stmt) 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 } // Retrieves spells of a specific type that cost at most the specified MP func ByTypeAndMaxMP(spellType, maxMP int) ([]*Spell, error) { var spells []*Spell query := `SELECT ` + spellColumns() + ` FROM spells WHERE type = ? AND mp <= ? ORDER BY mp, id` err := database.Query(query, func(stmt *sqlite.Stmt) error { spell := scanSpell(stmt) 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 } // Retrieves a spell by name (case-insensitive) func ByName(name string) (*Spell, error) { var spell *Spell query := `SELECT ` + spellColumns() + ` FROM spells WHERE LOWER(name) = LOWER(?) LIMIT 1` err := database.Query(query, func(stmt *sqlite.Stmt) error { spell = scanSpell(stmt) return nil }, name) if err != nil { return nil, fmt.Errorf("failed to find spell by name: %w", err) } if spell == nil { return nil, fmt.Errorf("spell with name '%s' not found", name) } return spell, nil } // Saves a new spell to the database and sets the ID func (s *Spell) Insert() error { columns := `name, mp, attribute, type` values := []any{s.Name, s.MP, s.Attribute, s.Type} return database.Insert(s, columns, values...) } // Returns true if the spell is a healing spell func (s *Spell) IsHealing() bool { return s.Type == TypeHealing } // Returns true if the spell is a hurt spell func (s *Spell) IsHurt() bool { return s.Type == TypeHurt } // Returns true if the spell is a sleep spell func (s *Spell) IsSleep() bool { return s.Type == TypeSleep } // Returns true if the spell boosts attack func (s *Spell) IsAttackBoost() bool { return s.Type == TypeAttackBoost } // Returns true if the spell boosts defense func (s *Spell) IsDefenseBoost() bool { return s.Type == TypeDefenseBoost } // 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" } } // Returns true if the spell can be cast with the given MP func (s *Spell) CanCast(availableMP int) bool { return availableMP >= s.MP } // 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) } // Returns true if the spell is used for attacking func (s *Spell) IsOffensive() bool { return s.Type == TypeHurt || s.Type == TypeSleep } // 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 }