246 lines
6.6 KiB
Go
246 lines
6.6 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"`
|
|
}
|
|
|
|
// 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(id int) (*Monster, error) {
|
|
monster := &Monster{}
|
|
|
|
query := "SELECT id, name, max_hp, max_dmg, armor, level, max_exp, max_gold, immune FROM monsters WHERE id = ?"
|
|
err := database.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() ([]*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 := database.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),
|
|
}
|
|
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(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 := database.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),
|
|
}
|
|
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(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 := database.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),
|
|
}
|
|
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(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 := database.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),
|
|
}
|
|
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
|
|
}
|
|
|
|
// 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 database.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 database.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)
|
|
}
|