359 lines
9.5 KiB
Go

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)
}