245 lines
5.8 KiB
Go

package monsters
import (
"fmt"
"dk/internal/database"
"dk/internal/helpers/scanner"
"zombiezen.com/go/sqlite"
)
// Monster represents a monster in the database
type Monster struct {
database.BaseModel
ID int `db:"id" json:"id"`
Name string `db:"name" json:"name"`
MaxHP int `db:"max_hp" json:"max_hp"`
MaxDmg int `db:"max_dmg" json:"max_dmg"`
Armor int `db:"armor" json:"armor"`
Level int `db:"level" json:"level"`
MaxExp int `db:"max_exp" json:"max_exp"`
MaxGold int `db:"max_gold" json:"max_gold"`
Immune int `db:"immune" json:"immune"`
}
func (m *Monster) GetTableName() string {
return "monsters"
}
func (m *Monster) GetID() int {
return m.ID
}
func (m *Monster) SetID(id int) {
m.ID = id
}
func (m *Monster) Set(field string, value any) error {
return database.Set(m, field, value)
}
func (m *Monster) Save() error {
return database.Save(m)
}
func (m *Monster) Delete() error {
return database.Delete(m)
}
// Creates a new Monster with sensible defaults
func New() *Monster {
return &Monster{
Name: "",
MaxHP: 10, // Default HP
MaxDmg: 5, // Default damage
Armor: 0, // Default armor
Level: 1, // Default level
MaxExp: 10, // Default exp reward
MaxGold: 5, // Default gold reward
Immune: ImmuneNone, // No immunity by default
}
}
var monsterScanner = scanner.New[Monster]()
// Returns the column list for monster queries
func monsterColumns() string {
return monsterScanner.Columns()
}
// Populates a Monster struct using the fast scanner
func scanMonster(stmt *sqlite.Stmt) *Monster {
monster := &Monster{}
monsterScanner.Scan(stmt, monster)
return monster
}
// Immunity constants for monster immunity types
const (
ImmuneNone = 0
ImmuneHurt = 1 // Immune to Hurt spells
ImmuneSleep = 2 // Immune to Sleep spells
)
// Retrieves a monster by ID
func Find(id int) (*Monster, error) {
var monster *Monster
query := `SELECT ` + monsterColumns() + ` FROM monsters WHERE id = ?`
err := database.Query(query, func(stmt *sqlite.Stmt) error {
monster = scanMonster(stmt)
return nil
}, id)
if err != nil {
return nil, fmt.Errorf("failed to find monster: %w", err)
}
if monster == nil {
return nil, fmt.Errorf("monster with ID %d not found", id)
}
return monster, nil
}
// Retrieves all monsters
func All() ([]*Monster, error) {
var monsters []*Monster
query := `SELECT ` + monsterColumns() + ` FROM monsters ORDER BY level, id`
err := database.Query(query, func(stmt *sqlite.Stmt) error {
monster := scanMonster(stmt)
monsters = append(monsters, monster)
return nil
})
if err != nil {
return nil, fmt.Errorf("failed to retrieve all monsters: %w", err)
}
return monsters, nil
}
// Retrieves monsters by level
func ByLevel(level int) ([]*Monster, error) {
var monsters []*Monster
query := `SELECT ` + monsterColumns() + ` FROM monsters WHERE level = ? ORDER BY id`
err := database.Query(query, func(stmt *sqlite.Stmt) error {
monster := scanMonster(stmt)
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
}
// Retrieves monsters within a level range (inclusive)
func ByLevelRange(minLevel, maxLevel int) ([]*Monster, error) {
var monsters []*Monster
query := `SELECT ` + monsterColumns() + ` FROM monsters WHERE level BETWEEN ? AND ? ORDER BY level, id`
err := database.Query(query, func(stmt *sqlite.Stmt) error {
monster := scanMonster(stmt)
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
}
// Retrieves monsters by immunity type
func ByImmunity(immunityType int) ([]*Monster, error) {
var monsters []*Monster
query := `SELECT ` + monsterColumns() + ` FROM monsters WHERE immune = ? ORDER BY level, id`
err := database.Query(query, func(stmt *sqlite.Stmt) error {
monster := scanMonster(stmt)
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
}
// Saves a new monster to the database and sets the ID
func (m *Monster) Insert() error {
columns := `name, max_hp, max_dmg, armor, level, max_exp, max_gold, immune`
values := []any{m.Name, m.MaxHP, m.MaxDmg, m.Armor, m.Level, m.MaxExp, m.MaxGold, m.Immune}
return database.Insert(m, columns, values...)
}
// Returns true if the monster is immune to Hurt spells
func (m *Monster) IsHurtImmune() bool {
return m.Immune == ImmuneHurt
}
// Returns true if the monster is immune to Sleep spells
func (m *Monster) IsSleepImmune() bool {
return m.Immune == ImmuneSleep
}
// Returns true if the monster has any immunity
func (m *Monster) HasImmunity() bool {
return m.Immune != ImmuneNone
}
// 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"
}
}
// 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)
}
// 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)
}
// 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)
}