finish model migration back to SQLite

This commit is contained in:
Sky Johnson 2025-08-21 22:35:41 -05:00
parent 412baeb46d
commit 09574294a4
11 changed files with 644 additions and 934 deletions

View File

@ -1,16 +1,16 @@
package control package control
import ( import (
"encoding/json"
"fmt" "fmt"
"os"
"sync" "sync"
nigiri "git.sharkk.net/Sharkk/Nigiri"
) )
var ( var (
store *nigiri.BaseStore[Control]
global *Control global *Control
mu sync.RWMutex mu sync.RWMutex
filename string
) )
// Control represents the game control settings // Control represents the game control settings
@ -24,39 +24,72 @@ type Control struct {
Class3Name string `json:"class_3_name"` Class3Name string `json:"class_3_name"`
} }
// Init sets up the Nigiri store for control settings // Init loads control settings from the specified JSON file
func Init(collection *nigiri.Collection) { func Init(jsonFile string) error {
store = nigiri.NewBaseStore[Control]() mu.Lock()
defer mu.Unlock()
// Load or create the singleton control instance filename = jsonFile
all := store.GetAll()
if len(all) == 0 { // Try to load from file
// Create default control settings if data, err := os.ReadFile(filename); err == nil {
global = New() var ctrl Control
global.ID = 1 if err := json.Unmarshal(data, &ctrl); err != nil {
store.Add(1, global) return fmt.Errorf("failed to parse JSON: %w", err)
} else {
// Use the first (and only) control entry
for _, ctrl := range all {
global = ctrl
break
} }
// Apply defaults for any missing fields // Apply defaults for any missing fields
defaults := New() defaults := New()
if global.WorldSize == 0 { if ctrl.WorldSize == 0 {
global.WorldSize = defaults.WorldSize ctrl.WorldSize = defaults.WorldSize
} }
if global.Class1Name == "" { if ctrl.Class1Name == "" {
global.Class1Name = defaults.Class1Name ctrl.Class1Name = defaults.Class1Name
} }
if global.Class2Name == "" { if ctrl.Class2Name == "" {
global.Class2Name = defaults.Class2Name ctrl.Class2Name = defaults.Class2Name
} }
if global.Class3Name == "" { if ctrl.Class3Name == "" {
global.Class3Name = defaults.Class3Name ctrl.Class3Name = defaults.Class3Name
} }
store.Update(global.ID, global)
ctrl.ID = 1 // Ensure singleton ID
global = &ctrl
} else {
// Create default control settings if file doesn't exist
global = New()
if err := save(); err != nil {
return fmt.Errorf("failed to create default config file: %w", err)
} }
}
return global.Validate()
}
// save writes the current control settings to the JSON file (internal use)
func save() error {
if filename == "" {
return fmt.Errorf("no filename set")
}
data, err := json.MarshalIndent(global, "", " ")
if err != nil {
return fmt.Errorf("failed to marshal JSON: %w", err)
}
return os.WriteFile(filename, data, 0644)
}
// Save writes the current control settings to the JSON file (public)
func Save() error {
mu.RLock()
defer mu.RUnlock()
if global == nil {
return fmt.Errorf("control not initialized")
}
return save()
} }
// New creates a new Control with sensible defaults // New creates a new Control with sensible defaults
@ -77,12 +110,12 @@ func Get() *Control {
mu.RLock() mu.RLock()
defer mu.RUnlock() defer mu.RUnlock()
if global == nil { if global == nil {
panic("control not initialized - call Initialize first") panic("control not initialized - call Init first")
} }
return global return global
} }
// Set updates the global control instance (thread-safe) // Set updates the global control instance and saves to file (thread-safe)
func Set(control *Control) error { func Set(control *Control) error {
mu.Lock() mu.Lock()
defer mu.Unlock() defer mu.Unlock()
@ -92,15 +125,11 @@ func Set(control *Control) error {
return err return err
} }
if err := store.Update(1, control); err != nil {
return err
}
global = control global = control
return nil return save()
} }
// Update updates specific fields of the control settings // Update updates specific fields of the control settings and saves to file
func Update(updater func(*Control)) error { func Update(updater func(*Control)) error {
mu.Lock() mu.Lock()
defer mu.Unlock() defer mu.Unlock()
@ -113,7 +142,20 @@ func Update(updater func(*Control)) error {
return err return err
} }
if err := store.Update(1, &updated); err != nil { global = &updated
return save()
}
// UpdateNoSave updates specific fields without saving (useful for batch updates)
func UpdateNoSave(updater func(*Control)) error {
mu.Lock()
defer mu.Unlock()
// Create a copy to work with
updated := *global
updater(&updated)
if err := updated.Validate(); err != nil {
return err return err
} }
@ -234,14 +276,3 @@ func (c *Control) GetWorldBounds() (minX, minY, maxX, maxY int) {
radius := c.GetWorldRadius() radius := c.GetWorldRadius()
return -radius, -radius, radius, radius return -radius, -radius, radius, radius
} }
// Legacy compatibility functions (will be removed later)
func Load(filename string) error {
// No longer needed - Nigiri handles this
return nil
}
func Save() error {
// No longer needed - Nigiri handles this
return nil
}

View File

@ -2,19 +2,27 @@ package babble
import ( import (
"fmt" "fmt"
"sort"
"strings" "strings"
"time" "time"
nigiri "git.sharkk.net/Sharkk/Nigiri" "dk/internal/database"
) )
// Babble represents a global chat message in the game // Babble represents a global chat message in the game
type Babble struct { type Babble struct {
ID int `json:"id"` ID int
Posted int64 `json:"posted"` Posted int64
Author string `json:"author" db:"index"` Author string
Babble string `json:"babble"` Babble string
}
// New creates a new Babble with sensible defaults
func New() *Babble {
return &Babble{
Posted: time.Now().Unix(),
Author: "",
Babble: "",
}
} }
// Validate checks if babble has valid values // Validate checks if babble has valid values
@ -31,136 +39,71 @@ func (b *Babble) Validate() error {
return nil return nil
} }
// Global store with singleton pattern
var store *nigiri.BaseStore[Babble]
// Init sets up the Nigiri store and indices
func Init(collection *nigiri.Collection) {
store = nigiri.NewBaseStore[Babble]()
// Register custom indices
store.RegisterIndex("byAuthor", nigiri.BuildStringGroupIndex(func(b *Babble) string {
return strings.ToLower(b.Author)
}))
store.RegisterIndex("allByPosted", nigiri.BuildSortedListIndex(func(a, b *Babble) bool {
if a.Posted != b.Posted {
return a.Posted > b.Posted // DESC
}
return a.ID > b.ID // DESC
}))
store.RebuildIndices()
}
// GetStore returns the babble store
func GetStore() *nigiri.BaseStore[Babble] {
if store == nil {
panic("babble store not initialized - call Initialize first")
}
return store
}
// Creates a new Babble with sensible defaults
func New() *Babble {
return &Babble{
Posted: time.Now().Unix(),
Author: "",
Babble: "",
}
}
// CRUD operations // CRUD operations
func (b *Babble) Save() error {
if b.ID == 0 {
id, err := store.Create(b)
if err != nil {
return err
}
b.ID = id
return nil
}
return store.Update(b.ID, b)
}
func (b *Babble) Delete() error { func (b *Babble) Delete() error {
store.Remove(b.ID) return database.Exec("DELETE FROM babble WHERE id = %d", b.ID)
return nil
} }
// Insert with ID assignment
func (b *Babble) Insert() error { func (b *Babble) Insert() error {
id, err := store.Create(b) id, err := database.Insert("babble", b, "ID")
if err != nil { if err != nil {
return err return err
} }
b.ID = id b.ID = int(id)
return nil return nil
} }
// Query functions // Query functions
func Find(id int) (*Babble, error) { func Find(id int) (*Babble, error) {
babble, exists := store.Find(id) var babble Babble
if !exists { err := database.Get(&babble, "SELECT * FROM babble WHERE id = %d", id)
if err != nil {
return nil, fmt.Errorf("babble with ID %d not found", id) return nil, fmt.Errorf("babble with ID %d not found", id)
} }
return babble, nil return &babble, nil
} }
func All() ([]*Babble, error) { func All() ([]*Babble, error) {
return store.AllSorted("allByPosted"), nil var babbles []*Babble
err := database.Select(&babbles, "SELECT * FROM babble ORDER BY posted DESC, id DESC")
return babbles, err
} }
func ByAuthor(author string) ([]*Babble, error) { func ByAuthor(author string) ([]*Babble, error) {
messages := store.GroupByIndex("byAuthor", strings.ToLower(author)) var babbles []*Babble
err := database.Select(&babbles, "SELECT * FROM babble WHERE author = %s COLLATE NOCASE ORDER BY posted DESC, id DESC", author)
// Sort by posted DESC, then ID DESC return babbles, err
sort.Slice(messages, func(i, j int) bool {
if messages[i].Posted != messages[j].Posted {
return messages[i].Posted > messages[j].Posted // DESC
}
return messages[i].ID > messages[j].ID // DESC
})
return messages, nil
} }
func Recent(limit int) ([]*Babble, error) { func Recent(limit int) ([]*Babble, error) {
all := store.AllSorted("allByPosted") var babbles []*Babble
if limit > len(all) { err := database.Select(&babbles, "SELECT * FROM babble ORDER BY posted DESC, id DESC LIMIT %d", limit)
limit = len(all) return babbles, err
}
return all[:limit], nil
} }
func Since(since int64) ([]*Babble, error) { func Since(since int64) ([]*Babble, error) {
return store.FilterByIndex("allByPosted", func(b *Babble) bool { var babbles []*Babble
return b.Posted >= since err := database.Select(&babbles, "SELECT * FROM babble WHERE posted >= %d ORDER BY posted DESC, id DESC", since)
}), nil return babbles, err
} }
func Between(start, end int64) ([]*Babble, error) { func Between(start, end int64) ([]*Babble, error) {
return store.FilterByIndex("allByPosted", func(b *Babble) bool { var babbles []*Babble
return b.Posted >= start && b.Posted <= end err := database.Select(&babbles, "SELECT * FROM babble WHERE posted >= %d AND posted <= %d ORDER BY posted DESC, id DESC", start, end)
}), nil return babbles, err
} }
func Search(term string) ([]*Babble, error) { func Search(term string) ([]*Babble, error) {
lowerTerm := strings.ToLower(term) var babbles []*Babble
return store.FilterByIndex("allByPosted", func(b *Babble) bool { searchTerm := "%" + term + "%"
return strings.Contains(strings.ToLower(b.Babble), lowerTerm) err := database.Select(&babbles, "SELECT * FROM babble WHERE babble LIKE %s ORDER BY posted DESC, id DESC", searchTerm)
}), nil return babbles, err
} }
func RecentByAuthor(author string, limit int) ([]*Babble, error) { func RecentByAuthor(author string, limit int) ([]*Babble, error) {
messages, err := ByAuthor(author) var babbles []*Babble
if err != nil { err := database.Select(&babbles, "SELECT * FROM babble WHERE author = %s COLLATE NOCASE ORDER BY posted DESC, id DESC LIMIT %d", author, limit)
return nil, err return babbles, err
}
if limit > len(messages) {
limit = len(messages)
}
return messages[:limit], nil
} }
// Helper methods // Helper methods
@ -265,14 +208,3 @@ func (b *Babble) HasMention(username string) bool {
} }
return false return false
} }
// Legacy compatibility functions (will be removed later)
func LoadData(dataPath string) error {
// No longer needed - Nigiri handles this
return nil
}
func SaveData(dataPath string) error {
// No longer needed - Nigiri handles this
return nil
}

View File

@ -3,16 +3,16 @@ package drops
import ( import (
"fmt" "fmt"
nigiri "git.sharkk.net/Sharkk/Nigiri" "dk/internal/database"
) )
// Drop represents a drop item in the game // Drop represents a drop item in the game
type Drop struct { type Drop struct {
ID int `json:"id"` ID int
Name string `json:"name" db:"required"` Name string
Level int `json:"level" db:"index"` Level int
Type int `json:"type" db:"index"` Type int
Att string `json:"att"` Att string
} }
// DropType constants for drop types // DropType constants for drop types
@ -20,43 +20,12 @@ const (
TypeConsumable = 1 TypeConsumable = 1
) )
// Global store // New creates a new Drop with sensible defaults
var store *nigiri.BaseStore[Drop]
// Init sets up the Nigiri store and indices
func Init(collection *nigiri.Collection) {
store = nigiri.NewBaseStore[Drop]()
// Register custom indices
store.RegisterIndex("byLevel", nigiri.BuildIntGroupIndex(func(d *Drop) int {
return d.Level
}))
store.RegisterIndex("byType", nigiri.BuildIntGroupIndex(func(d *Drop) int {
return d.Type
}))
store.RegisterIndex("allByID", nigiri.BuildSortedListIndex(func(a, b *Drop) bool {
return a.ID < b.ID
}))
store.RebuildIndices()
}
// GetStore returns the drops store
func GetStore() *nigiri.BaseStore[Drop] {
if store == nil {
panic("drops store not initialized - call Initialize first")
}
return store
}
// Creates a new Drop with sensible defaults
func New() *Drop { func New() *Drop {
return &Drop{ return &Drop{
Name: "", Name: "",
Level: 1, // Default minimum level Level: 1,
Type: TypeConsumable, // Default to consumable Type: TypeConsumable,
Att: "", Att: "",
} }
} }
@ -76,54 +45,45 @@ func (d *Drop) Validate() error {
} }
// CRUD operations // CRUD operations
func (d *Drop) Save() error {
if d.ID == 0 {
id, err := store.Create(d)
if err != nil {
return err
}
d.ID = id
return nil
}
return store.Update(d.ID, d)
}
func (d *Drop) Delete() error { func (d *Drop) Delete() error {
store.Remove(d.ID) return database.Exec("DELETE FROM drops WHERE id = %d", d.ID)
return nil
} }
// Insert with ID assignment
func (d *Drop) Insert() error { func (d *Drop) Insert() error {
id, err := store.Create(d) id, err := database.Insert("drops", d, "ID")
if err != nil { if err != nil {
return err return err
} }
d.ID = id d.ID = int(id)
return nil return nil
} }
// Query functions // Query functions
func Find(id int) (*Drop, error) { func Find(id int) (*Drop, error) {
drop, exists := store.Find(id) var drop Drop
if !exists { err := database.Get(&drop, "SELECT * FROM drops WHERE id = %d", id)
if err != nil {
return nil, fmt.Errorf("drop with ID %d not found", id) return nil, fmt.Errorf("drop with ID %d not found", id)
} }
return drop, nil return &drop, nil
} }
func All() ([]*Drop, error) { func All() ([]*Drop, error) {
return store.AllSorted("allByID"), nil var drops []*Drop
err := database.Select(&drops, "SELECT * FROM drops ORDER BY id ASC")
return drops, err
} }
func ByLevel(minLevel int) ([]*Drop, error) { func ByLevel(minLevel int) ([]*Drop, error) {
return store.FilterByIndex("allByID", func(d *Drop) bool { var drops []*Drop
return d.Level <= minLevel err := database.Select(&drops, "SELECT * FROM drops WHERE level <= %d ORDER BY id ASC", minLevel)
}), nil return drops, err
} }
func ByType(dropType int) ([]*Drop, error) { func ByType(dropType int) ([]*Drop, error) {
return store.GroupByIndex("byType", dropType), nil var drops []*Drop
err := database.Select(&drops, "SELECT * FROM drops WHERE type = %d ORDER BY id ASC", dropType)
return drops, err
} }
// Helper methods // Helper methods
@ -139,14 +99,3 @@ func (d *Drop) TypeName() string {
return "Unknown" return "Unknown"
} }
} }
// Legacy compatibility functions (will be removed later)
func LoadData(dataPath string) error {
// No longer needed - Nigiri handles this
return nil
}
func SaveData(dataPath string) error {
// No longer needed - Nigiri handles this
return nil
}

View File

@ -0,0 +1,263 @@
package fightlogs
import (
"fmt"
"strings"
"time"
"dk/internal/database"
)
// FightLog represents a single action in a fight
type FightLog struct {
ID int
FightID int
Type int
Data int
Name string
Created int64
}
// Action type constants
const (
ActionAttackHit = 1
ActionAttackMiss = 2
ActionSpellHeal = 3
ActionSpellHurt = 4
ActionRunSuccess = 5
ActionRunFail = 6
ActionGeneric = 7
ActionMonsterAttack = 8
ActionMonsterMiss = 9
ActionMonsterSpell = 10
ActionMonsterDeath = 11
)
// New creates a new FightLog with sensible defaults
func New(fightID int) *FightLog {
return &FightLog{
FightID: fightID,
Type: ActionGeneric,
Data: 0,
Name: "",
Created: time.Now().Unix(),
}
}
// Validate checks if fight log has valid values
func (fl *FightLog) Validate() error {
if fl.FightID <= 0 {
return fmt.Errorf("fight log FightID must be positive")
}
if fl.Created <= 0 {
return fmt.Errorf("fight log Created timestamp must be positive")
}
return nil
}
// CRUD operations
func (fl *FightLog) Delete() error {
return database.Exec("DELETE FROM fight_logs WHERE id = %d", fl.ID)
}
func (fl *FightLog) Insert() error {
id, err := database.Insert("fight_logs", fl, "ID")
if err != nil {
return err
}
fl.ID = int(id)
return nil
}
// Query functions
func Find(id int) (*FightLog, error) {
var log FightLog
err := database.Get(&log, "SELECT * FROM fight_logs WHERE id = %d", id)
if err != nil {
return nil, fmt.Errorf("fight log with ID %d not found", id)
}
return &log, nil
}
func ByFightID(fightID int) ([]*FightLog, error) {
var logs []*FightLog
err := database.Select(&logs, "SELECT * FROM fight_logs WHERE fight_id = %d ORDER BY created ASC, id ASC", fightID)
return logs, err
}
func DeleteByFightID(fightID int) error {
return database.Exec("DELETE FROM fight_logs WHERE fight_id = %d", fightID)
}
// Helper functions for adding different types of actions
func AddAction(fightID int, action string) error {
log := &FightLog{
FightID: fightID,
Type: ActionGeneric,
Name: action,
Created: time.Now().Unix(),
}
return log.Insert()
}
func AddAttackHit(fightID, damage int) error {
log := &FightLog{
FightID: fightID,
Type: ActionAttackHit,
Data: damage,
Created: time.Now().Unix(),
}
return log.Insert()
}
func AddAttackMiss(fightID int) error {
log := &FightLog{
FightID: fightID,
Type: ActionAttackMiss,
Created: time.Now().Unix(),
}
return log.Insert()
}
func AddSpellHeal(fightID int, spellName string, healAmount int) error {
log := &FightLog{
FightID: fightID,
Type: ActionSpellHeal,
Data: healAmount,
Name: spellName,
Created: time.Now().Unix(),
}
return log.Insert()
}
func AddSpellHurt(fightID int, spellName string, damage int) error {
log := &FightLog{
FightID: fightID,
Type: ActionSpellHurt,
Data: damage,
Name: spellName,
Created: time.Now().Unix(),
}
return log.Insert()
}
func AddRunSuccess(fightID int) error {
log := &FightLog{
FightID: fightID,
Type: ActionRunSuccess,
Created: time.Now().Unix(),
}
return log.Insert()
}
func AddRunFail(fightID int) error {
log := &FightLog{
FightID: fightID,
Type: ActionRunFail,
Created: time.Now().Unix(),
}
return log.Insert()
}
func AddMonsterAttack(fightID int, monsterName string, damage int) error {
log := &FightLog{
FightID: fightID,
Type: ActionMonsterAttack,
Data: damage,
Name: monsterName,
Created: time.Now().Unix(),
}
return log.Insert()
}
func AddMonsterMiss(fightID int, monsterName string) error {
log := &FightLog{
FightID: fightID,
Type: ActionMonsterMiss,
Name: monsterName,
Created: time.Now().Unix(),
}
return log.Insert()
}
func AddMonsterSpell(fightID int, monsterName, spellName string, damage int) error {
log := &FightLog{
FightID: fightID,
Type: ActionMonsterSpell,
Data: damage,
Name: monsterName + "|" + spellName,
Created: time.Now().Unix(),
}
return log.Insert()
}
func AddMonsterDeath(fightID int, monsterName string) error {
log := &FightLog{
FightID: fightID,
Type: ActionMonsterDeath,
Name: monsterName,
Created: time.Now().Unix(),
}
return log.Insert()
}
// Convert logs to human-readable strings
func GetActions(fightID int) ([]string, error) {
logs, err := ByFightID(fightID)
if err != nil {
return nil, err
}
result := make([]string, len(logs))
for i, log := range logs {
result[i] = log.ToString()
}
return result, nil
}
func GetLastAction(fightID int) (string, error) {
var log FightLog
err := database.Get(&log, "SELECT * FROM fight_logs WHERE fight_id = %d ORDER BY created DESC, id DESC LIMIT 1", fightID)
if err != nil {
return "", nil // No logs found
}
return log.ToString(), nil
}
// Helper methods
func (fl *FightLog) CreatedTime() time.Time {
return time.Unix(fl.Created, 0)
}
func (fl *FightLog) ToString() string {
switch fl.Type {
case ActionAttackHit:
return fmt.Sprintf("You attacked for %d damage!", fl.Data)
case ActionAttackMiss:
return "You missed your attack!"
case ActionSpellHeal:
return fmt.Sprintf("You cast %s and healed %d HP!", fl.Name, fl.Data)
case ActionSpellHurt:
return fmt.Sprintf("You cast %s and dealt %d damage!", fl.Name, fl.Data)
case ActionRunSuccess:
return "You successfully ran away!"
case ActionRunFail:
return "You failed to run away!"
case ActionGeneric:
return fl.Name
case ActionMonsterAttack:
return fmt.Sprintf("%s attacks for %d damage!", fl.Name, fl.Data)
case ActionMonsterMiss:
return fmt.Sprintf("%s missed its attack!", fl.Name)
case ActionMonsterSpell:
parts := strings.Split(fl.Name, "|")
if len(parts) == 2 {
return fmt.Sprintf("%s casts %s for %d damage!", parts[0], parts[1], fl.Data)
}
return fmt.Sprintf("%s casts a spell for %d damage!", fl.Name, fl.Data)
case ActionMonsterDeath:
return fmt.Sprintf("%s has been defeated!", fl.Name)
default:
return "Unknown action"
}
}

View File

@ -1,140 +0,0 @@
package fights
import (
"fmt"
"strings"
"time"
)
// ActionEntry represents a compacted fight action log. This allows us to store more logs
// in the same space as a single string.
type ActionEntry struct {
Type int `json:"t"`
Data int `json:"d,omitempty"`
Name string `json:"n,omitempty"` // For spell names
}
// Action type constants
const (
ActionAttackHit = 1
ActionAttackMiss = 2
ActionSpellHeal = 3
ActionSpellHurt = 4
ActionRunSuccess = 5
ActionRunFail = 6
ActionGeneric = 7
ActionMonsterAttack = 8
ActionMonsterMiss = 9
ActionMonsterSpell = 10
ActionMonsterDeath = 11
)
func (f *Fight) AddAction(action string) {
f.Actions = append(f.Actions, ActionEntry{Type: ActionGeneric, Name: action})
f.Updated = time.Now().Unix()
}
func (f *Fight) AddActionAttackHit(damage int) {
f.Actions = append(f.Actions, ActionEntry{Type: ActionAttackHit, Data: damage})
f.Updated = time.Now().Unix()
}
func (f *Fight) AddActionAttackMiss() {
f.Actions = append(f.Actions, ActionEntry{Type: ActionAttackMiss})
f.Updated = time.Now().Unix()
}
func (f *Fight) AddActionSpellHeal(spellName string, healAmount int) {
f.Actions = append(f.Actions, ActionEntry{Type: ActionSpellHeal, Data: healAmount, Name: spellName})
f.Updated = time.Now().Unix()
}
func (f *Fight) AddActionSpellHurt(spellName string, damage int) {
f.Actions = append(f.Actions, ActionEntry{Type: ActionSpellHurt, Data: damage, Name: spellName})
f.Updated = time.Now().Unix()
}
func (f *Fight) AddActionRunSuccess() {
f.Actions = append(f.Actions, ActionEntry{Type: ActionRunSuccess})
f.Updated = time.Now().Unix()
}
func (f *Fight) AddActionRunFail() {
f.Actions = append(f.Actions, ActionEntry{Type: ActionRunFail})
f.Updated = time.Now().Unix()
}
// Convert actions to human-readable strings
func (f *Fight) GetActions() []string {
result := make([]string, len(f.Actions))
for i, action := range f.Actions {
result[i] = f.actionToString(action)
}
return result
}
func (f *Fight) GetLastAction() string {
if len(f.Actions) == 0 {
return ""
}
return f.actionToString(f.Actions[len(f.Actions)-1])
}
func (f *Fight) ClearActions() {
f.Actions = make([]ActionEntry, 0)
f.Updated = time.Now().Unix()
}
func (f *Fight) AddActionMonsterAttack(monsterName string, damage int) {
f.Actions = append(f.Actions, ActionEntry{Type: ActionMonsterAttack, Data: damage, Name: monsterName})
f.Updated = time.Now().Unix()
}
func (f *Fight) AddActionMonsterMiss(monsterName string) {
f.Actions = append(f.Actions, ActionEntry{Type: ActionMonsterMiss, Name: monsterName})
f.Updated = time.Now().Unix()
}
func (f *Fight) AddActionMonsterSpell(monsterName, spellName string, damage int) {
f.Actions = append(f.Actions, ActionEntry{Type: ActionMonsterSpell, Data: damage, Name: monsterName + "|" + spellName})
f.Updated = time.Now().Unix()
}
func (f *Fight) AddActionMonsterDeath(monsterName string) {
f.Actions = append(f.Actions, ActionEntry{Type: ActionMonsterDeath, Name: monsterName})
f.Updated = time.Now().Unix()
}
// Update actionToString method - add these cases
func (f *Fight) actionToString(action ActionEntry) string {
switch action.Type {
case ActionAttackHit:
return fmt.Sprintf("You attacked for %d damage!", action.Data)
case ActionAttackMiss:
return "You missed your attack!"
case ActionSpellHeal:
return fmt.Sprintf("You cast %s and healed %d HP!", action.Name, action.Data)
case ActionSpellHurt:
return fmt.Sprintf("You cast %s and dealt %d damage!", action.Name, action.Data)
case ActionRunSuccess:
return "You successfully ran away!"
case ActionRunFail:
return "You failed to run away!"
case ActionGeneric:
return action.Name
case ActionMonsterAttack:
return fmt.Sprintf("%s attacks for %d damage!", action.Name, action.Data)
case ActionMonsterMiss:
return fmt.Sprintf("%s missed its attack!", action.Name)
case ActionMonsterSpell:
parts := strings.Split(action.Name, "|")
if len(parts) == 2 {
return fmt.Sprintf("%s casts %s for %d damage!", parts[0], parts[1], action.Data)
}
return fmt.Sprintf("%s casts a spell for %d damage!", action.Name, action.Data)
case ActionMonsterDeath:
return fmt.Sprintf("%s has been defeated!", action.Name)
default:
return "Unknown action"
}
}

View File

@ -4,80 +4,29 @@ import (
"fmt" "fmt"
"time" "time"
nigiri "git.sharkk.net/Sharkk/Nigiri" "dk/internal/database"
) )
// Fight represents a fight, past or present // Fight represents a fight, past or present
type Fight struct { type Fight struct {
ID int `json:"id"` ID int
UserID int `json:"user_id" db:"index"` UserID int
MonsterID int `json:"monster_id" db:"index"` MonsterID int
MonsterHP int `json:"monster_hp"` MonsterHP int
MonsterMaxHP int `json:"monster_max_hp"` MonsterMaxHP int
MonsterSleep int `json:"monster_sleep"` MonsterSleep int
MonsterImmune int `json:"monster_immune"` MonsterImmune int
UberDamage int `json:"uber_damage"` UberDamage int
UberDefense int `json:"uber_defense"` UberDefense int
FirstStrike bool `json:"first_strike"` FirstStrike bool
Turn int `json:"turn"` Turn int
RanAway bool `json:"ran_away"` RanAway bool
Victory bool `json:"victory"` Victory bool
Won bool `json:"won"` Won bool
RewardGold int `json:"reward_gold"` RewardGold int
RewardExp int `json:"reward_exp"` RewardExp int
Actions []ActionEntry `json:"actions"` Created int64
Created int64 `json:"created"` Updated int64
Updated int64 `json:"updated"`
}
// Global store
var store *nigiri.BaseStore[Fight]
// Init sets up the Nigiri store and indices
func Init(collection *nigiri.Collection) {
store = nigiri.NewBaseStore[Fight]()
// Register custom indices
store.RegisterIndex("byUserID", nigiri.BuildIntGroupIndex(func(f *Fight) int {
return f.UserID
}))
store.RegisterIndex("byMonsterID", nigiri.BuildIntGroupIndex(func(f *Fight) int {
return f.MonsterID
}))
store.RegisterIndex("activeFights", nigiri.BuildFilteredIntGroupIndex(
func(f *Fight) bool {
return !f.RanAway && !f.Victory
},
func(f *Fight) int {
return f.UserID
},
))
store.RegisterIndex("allByCreated", nigiri.BuildSortedListIndex(func(a, b *Fight) bool {
if a.Created != b.Created {
return a.Created > b.Created // DESC
}
return a.ID > b.ID // DESC
}))
store.RegisterIndex("allByUpdated", nigiri.BuildSortedListIndex(func(a, b *Fight) bool {
if a.Updated != b.Updated {
return a.Updated > b.Updated // DESC
}
return a.ID > b.ID // DESC
}))
store.RebuildIndices()
}
// GetStore returns the fights store
func GetStore() *nigiri.BaseStore[Fight] {
if store == nil {
panic("fights store not initialized - call Initialize first")
}
return store
} }
// New creates a new Fight with sensible defaults // New creates a new Fight with sensible defaults
@ -99,7 +48,6 @@ func New(userID, monsterID int) *Fight {
Won: false, Won: false,
RewardGold: 0, RewardGold: 0,
RewardExp: 0, RewardExp: 0,
Actions: make([]ActionEntry, 0),
Created: now, Created: now,
Updated: now, Updated: now,
} }
@ -129,75 +77,88 @@ func (f *Fight) Validate() error {
} }
// CRUD operations // CRUD operations
func (f *Fight) Save() error {
f.Updated = time.Now().Unix()
if f.ID == 0 {
id, err := store.Create(f)
if err != nil {
return err
}
f.ID = id
return nil
}
return store.Update(f.ID, f)
}
func (f *Fight) Delete() error { func (f *Fight) Delete() error {
store.Remove(f.ID) return database.Exec("DELETE FROM fights WHERE id = %d", f.ID)
return nil
} }
// Insert with ID assignment
func (f *Fight) Insert() error { func (f *Fight) Insert() error {
f.Updated = time.Now().Unix() f.Updated = time.Now().Unix()
id, err := store.Create(f) id, err := database.Insert("fights", f, "ID")
if err != nil { if err != nil {
return err return err
} }
f.ID = id f.ID = int(id)
return nil return nil
} }
func (f *Fight) Update() error {
f.Updated = time.Now().Unix()
return database.Update("fights", map[string]any{
"user_id": f.UserID,
"monster_id": f.MonsterID,
"monster_hp": f.MonsterHP,
"monster_max_hp": f.MonsterMaxHP,
"monster_sleep": f.MonsterSleep,
"monster_immune": f.MonsterImmune,
"uber_damage": f.UberDamage,
"uber_defense": f.UberDefense,
"first_strike": f.FirstStrike,
"turn": f.Turn,
"ran_away": f.RanAway,
"victory": f.Victory,
"won": f.Won,
"reward_gold": f.RewardGold,
"reward_exp": f.RewardExp,
"created": f.Created,
"updated": f.Updated,
}, "id", f.ID)
}
// Query functions // Query functions
func Find(id int) (*Fight, error) { func Find(id int) (*Fight, error) {
fight, exists := store.Find(id) var fight Fight
if !exists { err := database.Get(&fight, "SELECT * FROM fights WHERE id = %d", id)
if err != nil {
return nil, fmt.Errorf("fight with ID %d not found", id) return nil, fmt.Errorf("fight with ID %d not found", id)
} }
return fight, nil return &fight, nil
} }
func All() ([]*Fight, error) { func All() ([]*Fight, error) {
return store.AllSorted("allByCreated"), nil var fights []*Fight
err := database.Select(&fights, "SELECT * FROM fights ORDER BY created DESC, id DESC")
return fights, err
} }
func ByUserID(userID int) ([]*Fight, error) { func ByUserID(userID int) ([]*Fight, error) {
return store.GroupByIndex("byUserID", userID), nil var fights []*Fight
err := database.Select(&fights, "SELECT * FROM fights WHERE user_id = %d ORDER BY created DESC, id DESC", userID)
return fights, err
} }
func ByMonsterID(monsterID int) ([]*Fight, error) { func ByMonsterID(monsterID int) ([]*Fight, error) {
return store.GroupByIndex("byMonsterID", monsterID), nil var fights []*Fight
err := database.Select(&fights, "SELECT * FROM fights WHERE monster_id = %d ORDER BY created DESC, id DESC", monsterID)
return fights, err
} }
func ActiveByUserID(userID int) ([]*Fight, error) { func ActiveByUserID(userID int) ([]*Fight, error) {
return store.GroupByIndex("activeFights", userID), nil var fights []*Fight
err := database.Select(&fights, "SELECT * FROM fights WHERE user_id = %d AND ran_away = 0 AND victory = 0 ORDER BY created DESC, id DESC", userID)
return fights, err
} }
func Active() ([]*Fight, error) { func Active() ([]*Fight, error) {
result := store.FilterByIndex("allByCreated", func(f *Fight) bool { var fights []*Fight
return !f.RanAway && !f.Victory err := database.Select(&fights, "SELECT * FROM fights WHERE ran_away = 0 AND victory = 0 ORDER BY created DESC, id DESC")
}) return fights, err
return result, nil
} }
func Recent(within time.Duration) ([]*Fight, error) { func Recent(within time.Duration) ([]*Fight, error) {
cutoff := time.Now().Add(-within).Unix() cutoff := time.Now().Add(-within).Unix()
var fights []*Fight
result := store.FilterByIndex("allByCreated", func(f *Fight) bool { err := database.Select(&fights, "SELECT * FROM fights WHERE created >= %d ORDER BY created DESC, id DESC", cutoff)
return f.Created >= cutoff return fights, err
})
return result, nil
} }
// Helper methods // Helper methods

View File

@ -2,67 +2,32 @@ package forum
import ( import (
"fmt" "fmt"
"sort"
"strings" "strings"
"time" "time"
nigiri "git.sharkk.net/Sharkk/Nigiri" "dk/internal/database"
) )
// Forum represents a forum post or thread in the game // Forum represents a forum post or thread in the game
type Forum struct { type Forum struct {
ID int `json:"id"` ID int
Posted int64 `json:"posted"` Posted int64
LastPost int64 `json:"last_post"` LastPost int64
Author int `json:"author" db:"index"` Author int
Parent int `json:"parent" db:"index"` Parent int
Replies int `json:"replies"` Replies int
Title string `json:"title" db:"required"` Title string
Content string `json:"content" db:"required"` Content string
} }
// Global store // New creates a new Forum with sensible defaults
var store *nigiri.BaseStore[Forum]
// Init sets up the Nigiri store and indices
func Init(collection *nigiri.Collection) {
store = nigiri.NewBaseStore[Forum]()
// Register custom indices
store.RegisterIndex("byParent", nigiri.BuildIntGroupIndex(func(f *Forum) int {
return f.Parent
}))
store.RegisterIndex("byAuthor", nigiri.BuildIntGroupIndex(func(f *Forum) int {
return f.Author
}))
store.RegisterIndex("allByLastPost", nigiri.BuildSortedListIndex(func(a, b *Forum) bool {
if a.LastPost != b.LastPost {
return a.LastPost > b.LastPost // DESC
}
return a.ID > b.ID // DESC
}))
store.RebuildIndices()
}
// GetStore returns the forum store
func GetStore() *nigiri.BaseStore[Forum] {
if store == nil {
panic("forum store not initialized - call Initialize first")
}
return store
}
// Creates a new Forum with sensible defaults
func New() *Forum { func New() *Forum {
now := time.Now().Unix() now := time.Now().Unix()
return &Forum{ return &Forum{
Posted: now, Posted: now,
LastPost: now, LastPost: now,
Author: 0, Author: 0,
Parent: 0, // Default to thread (not reply) Parent: 0,
Replies: 0, Replies: 0,
Title: "", Title: "",
Content: "", Content: "",
@ -93,102 +58,77 @@ func (f *Forum) Validate() error {
} }
// CRUD operations // CRUD operations
func (f *Forum) Save() error {
if f.ID == 0 {
id, err := store.Create(f)
if err != nil {
return err
}
f.ID = id
return nil
}
return store.Update(f.ID, f)
}
func (f *Forum) Delete() error { func (f *Forum) Delete() error {
store.Remove(f.ID) return database.Exec("DELETE FROM forum WHERE id = %d", f.ID)
return nil
} }
// Insert with ID assignment
func (f *Forum) Insert() error { func (f *Forum) Insert() error {
id, err := store.Create(f) id, err := database.Insert("forum", f, "ID")
if err != nil { if err != nil {
return err return err
} }
f.ID = id f.ID = int(id)
return nil return nil
} }
// Query functions // Query functions
func Find(id int) (*Forum, error) { func Find(id int) (*Forum, error) {
forum, exists := store.Find(id) var forum Forum
if !exists { err := database.Get(&forum, "SELECT * FROM forum WHERE id = %d", id)
if err != nil {
return nil, fmt.Errorf("forum post with ID %d not found", id) return nil, fmt.Errorf("forum post with ID %d not found", id)
} }
return forum, nil return &forum, nil
} }
func All() ([]*Forum, error) { func All() ([]*Forum, error) {
return store.AllSorted("allByLastPost"), nil var forums []*Forum
err := database.Select(&forums, "SELECT * FROM forum ORDER BY last_post DESC, id DESC")
return forums, err
} }
func Threads() ([]*Forum, error) { func Threads() ([]*Forum, error) {
return store.FilterByIndex("allByLastPost", func(f *Forum) bool { var forums []*Forum
return f.Parent == 0 err := database.Select(&forums, "SELECT * FROM forum WHERE parent = 0 ORDER BY last_post DESC, id DESC")
}), nil return forums, err
} }
func ByParent(parentID int) ([]*Forum, error) { func ByParent(parentID int) ([]*Forum, error) {
replies := store.GroupByIndex("byParent", parentID) var forums []*Forum
if parentID > 0 {
// Sort replies chronologically (posted ASC, then ID ASC) // Replies sorted chronologically
if parentID > 0 && len(replies) > 1 { err := database.Select(&forums, "SELECT * FROM forum WHERE parent = %d ORDER BY posted ASC, id ASC", parentID)
sort.Slice(replies, func(i, j int) bool { return forums, err
if replies[i].Posted != replies[j].Posted { } else {
return replies[i].Posted < replies[j].Posted // ASC // Threads sorted by last activity
err := database.Select(&forums, "SELECT * FROM forum WHERE parent = %d ORDER BY last_post DESC, id DESC", parentID)
return forums, err
} }
return replies[i].ID < replies[j].ID // ASC
})
}
return replies, nil
} }
func ByAuthor(authorID int) ([]*Forum, error) { func ByAuthor(authorID int) ([]*Forum, error) {
posts := store.GroupByIndex("byAuthor", authorID) var forums []*Forum
err := database.Select(&forums, "SELECT * FROM forum WHERE author = %d ORDER BY posted DESC, id DESC", authorID)
// Sort by posted DESC, then ID DESC return forums, err
sort.Slice(posts, func(i, j int) bool {
if posts[i].Posted != posts[j].Posted {
return posts[i].Posted > posts[j].Posted // DESC
}
return posts[i].ID > posts[j].ID // DESC
})
return posts, nil
} }
func Recent(limit int) ([]*Forum, error) { func Recent(limit int) ([]*Forum, error) {
all := store.AllSorted("allByLastPost") var forums []*Forum
if limit > len(all) { err := database.Select(&forums, "SELECT * FROM forum ORDER BY last_post DESC, id DESC LIMIT %d", limit)
limit = len(all) return forums, err
}
return all[:limit], nil
} }
func Search(term string) ([]*Forum, error) { func Search(term string) ([]*Forum, error) {
lowerTerm := strings.ToLower(term) var forums []*Forum
return store.FilterByIndex("allByLastPost", func(f *Forum) bool { searchTerm := "%" + term + "%"
return strings.Contains(strings.ToLower(f.Title), lowerTerm) || err := database.Select(&forums, "SELECT * FROM forum WHERE title LIKE %s OR content LIKE %s ORDER BY last_post DESC, id DESC", searchTerm, searchTerm)
strings.Contains(strings.ToLower(f.Content), lowerTerm) return forums, err
}), nil
} }
func Since(since int64) ([]*Forum, error) { func Since(since int64) ([]*Forum, error) {
return store.FilterByIndex("allByLastPost", func(f *Forum) bool { var forums []*Forum
return f.LastPost >= since err := database.Select(&forums, "SELECT * FROM forum WHERE last_post >= %d ORDER BY last_post DESC, id DESC", since)
}), nil return forums, err
} }
// Helper methods // Helper methods
@ -309,14 +249,3 @@ func (f *Forum) GetThread() (*Forum, error) {
} }
return Find(f.Parent) return Find(f.Parent)
} }
// Legacy compatibility functions (will be removed later)
func LoadData(dataPath string) error {
// No longer needed - Nigiri handles this
return nil
}
func SaveData(dataPath string) error {
// No longer needed - Nigiri handles this
return nil
}

View File

@ -3,17 +3,17 @@ package items
import ( import (
"fmt" "fmt"
nigiri "git.sharkk.net/Sharkk/Nigiri" "dk/internal/database"
) )
// Item represents an item in the game // Item represents an item in the game
type Item struct { type Item struct {
ID int `json:"id"` ID int
Type int `json:"type" db:"index"` Type int
Name string `json:"name" db:"required"` Name string
Value int `json:"value"` Value int
Att int `json:"att"` Att int
Special string `json:"special"` Special string
} }
// ItemType constants for item types // ItemType constants for item types
@ -23,37 +23,10 @@ const (
TypeShield = 3 TypeShield = 3
) )
// Global store // New creates a new Item with sensible defaults
var store *nigiri.BaseStore[Item]
// Init sets up the Nigiri store and indices
func Init(collection *nigiri.Collection) {
store = nigiri.NewBaseStore[Item]()
// Register custom indices
store.RegisterIndex("byType", nigiri.BuildIntGroupIndex(func(i *Item) int {
return i.Type
}))
store.RegisterIndex("allByID", nigiri.BuildSortedListIndex(func(a, b *Item) bool {
return a.ID < b.ID
}))
store.RebuildIndices()
}
// GetStore returns the items store
func GetStore() *nigiri.BaseStore[Item] {
if store == nil {
panic("items store not initialized - call Initialize first")
}
return store
}
// Creates a new Item with sensible defaults
func New() *Item { func New() *Item {
return &Item{ return &Item{
Type: TypeWeapon, // Default to weapon Type: TypeWeapon,
Name: "", Name: "",
Value: 0, Value: 0,
Att: 0, Att: 0,
@ -79,48 +52,39 @@ func (i *Item) Validate() error {
} }
// CRUD operations // CRUD operations
func (i *Item) Save() error {
if i.ID == 0 {
id, err := store.Create(i)
if err != nil {
return err
}
i.ID = id
return nil
}
return store.Update(i.ID, i)
}
func (i *Item) Delete() error { func (i *Item) Delete() error {
store.Remove(i.ID) return database.Exec("DELETE FROM items WHERE id = %d", i.ID)
return nil
} }
// Insert with ID assignment
func (i *Item) Insert() error { func (i *Item) Insert() error {
id, err := store.Create(i) id, err := database.Insert("items", i, "ID")
if err != nil { if err != nil {
return err return err
} }
i.ID = id i.ID = int(id)
return nil return nil
} }
// Query functions // Query functions
func Find(id int) (*Item, error) { func Find(id int) (*Item, error) {
item, exists := store.Find(id) var item Item
if !exists { err := database.Get(&item, "SELECT * FROM items WHERE id = %d", id)
if err != nil {
return nil, fmt.Errorf("item with ID %d not found", id) return nil, fmt.Errorf("item with ID %d not found", id)
} }
return item, nil return &item, nil
} }
func All() ([]*Item, error) { func All() ([]*Item, error) {
return store.AllSorted("allByID"), nil var items []*Item
err := database.Select(&items, "SELECT * FROM items ORDER BY id ASC")
return items, err
} }
func ByType(itemType int) ([]*Item, error) { func ByType(itemType int) ([]*Item, error) {
return store.GroupByIndex("byType", itemType), nil var items []*Item
err := database.Select(&items, "SELECT * FROM items WHERE type = %d ORDER BY id ASC", itemType)
return items, err
} }
// Helper methods // Helper methods
@ -156,14 +120,3 @@ func (i *Item) HasSpecial() bool {
func (i *Item) IsEquippable() bool { func (i *Item) IsEquippable() bool {
return i.Type == TypeWeapon || i.Type == TypeArmor || i.Type == TypeShield return i.Type == TypeWeapon || i.Type == TypeArmor || i.Type == TypeShield
} }
// Legacy compatibility functions (will be removed later)
func LoadData(dataPath string) error {
// No longer needed - Nigiri handles this
return nil
}
func SaveData(dataPath string) error {
// No longer needed - Nigiri handles this
return nil
}

View File

@ -3,20 +3,20 @@ package monsters
import ( import (
"fmt" "fmt"
nigiri "git.sharkk.net/Sharkk/Nigiri" "dk/internal/database"
) )
// Monster represents a monster in the game // Monster represents a monster in the game
type Monster struct { type Monster struct {
ID int `json:"id"` ID int
Name string `json:"name" db:"required"` Name string
MaxHP int `json:"max_hp"` MaxHP int
MaxDmg int `json:"max_dmg"` MaxDmg int
Armor int `json:"armor"` Armor int
Level int `json:"level" db:"index"` Level int
MaxExp int `json:"max_exp"` MaxExp int
MaxGold int `json:"max_gold"` MaxGold int
Immune int `json:"immune" db:"index"` Immune int
} }
// Immunity constants // Immunity constants
@ -26,41 +26,7 @@ const (
ImmuneSleep = 2 ImmuneSleep = 2
) )
// Global store // New creates a new Monster with sensible defaults
var store *nigiri.BaseStore[Monster]
// Init sets up the Nigiri store and indices
func Init(collection *nigiri.Collection) {
store = nigiri.NewBaseStore[Monster]()
// Register custom indices
store.RegisterIndex("byLevel", nigiri.BuildIntGroupIndex(func(m *Monster) int {
return m.Level
}))
store.RegisterIndex("byImmunity", nigiri.BuildIntGroupIndex(func(m *Monster) int {
return m.Immune
}))
store.RegisterIndex("allByLevel", nigiri.BuildSortedListIndex(func(a, b *Monster) bool {
if a.Level == b.Level {
return a.ID < b.ID
}
return a.Level < b.Level
}))
store.RebuildIndices()
}
// GetStore returns the monsters store
func GetStore() *nigiri.BaseStore[Monster] {
if store == nil {
panic("monsters store not initialized - call Initialize first")
}
return store
}
// Creates a new Monster with sensible defaults
func New() *Monster { func New() *Monster {
return &Monster{ return &Monster{
Name: "", Name: "",
@ -92,61 +58,51 @@ func (m *Monster) Validate() error {
} }
// CRUD operations // CRUD operations
func (m *Monster) Save() error {
if m.ID == 0 {
id, err := store.Create(m)
if err != nil {
return err
}
m.ID = id
return nil
}
return store.Update(m.ID, m)
}
func (m *Monster) Delete() error { func (m *Monster) Delete() error {
store.Remove(m.ID) return database.Exec("DELETE FROM monsters WHERE id = %d", m.ID)
return nil
} }
// Insert with ID assignment
func (m *Monster) Insert() error { func (m *Monster) Insert() error {
id, err := store.Create(m) id, err := database.Insert("monsters", m, "ID")
if err != nil { if err != nil {
return err return err
} }
m.ID = id m.ID = int(id)
return nil return nil
} }
// Query functions // Query functions
func Find(id int) (*Monster, error) { func Find(id int) (*Monster, error) {
monster, exists := store.Find(id) var monster Monster
if !exists { err := database.Get(&monster, "SELECT * FROM monsters WHERE id = %d", id)
if err != nil {
return nil, fmt.Errorf("monster with ID %d not found", id) return nil, fmt.Errorf("monster with ID %d not found", id)
} }
return monster, nil return &monster, nil
} }
func All() ([]*Monster, error) { func All() ([]*Monster, error) {
return store.AllSorted("allByLevel"), nil var monsters []*Monster
err := database.Select(&monsters, "SELECT * FROM monsters ORDER BY level ASC, id ASC")
return monsters, err
} }
func ByLevel(level int) ([]*Monster, error) { func ByLevel(level int) ([]*Monster, error) {
return store.GroupByIndex("byLevel", level), nil var monsters []*Monster
err := database.Select(&monsters, "SELECT * FROM monsters WHERE level = %d ORDER BY id ASC", level)
return monsters, err
} }
func ByLevelRange(minLevel, maxLevel int) ([]*Monster, error) { func ByLevelRange(minLevel, maxLevel int) ([]*Monster, error) {
var result []*Monster var monsters []*Monster
for level := minLevel; level <= maxLevel; level++ { err := database.Select(&monsters, "SELECT * FROM monsters WHERE level >= %d AND level <= %d ORDER BY level ASC, id ASC", minLevel, maxLevel)
monsters := store.GroupByIndex("byLevel", level) return monsters, err
result = append(result, monsters...)
}
return result, nil
} }
func ByImmunity(immunityType int) ([]*Monster, error) { func ByImmunity(immunityType int) ([]*Monster, error) {
return store.GroupByIndex("byImmunity", immunityType), nil var monsters []*Monster
err := database.Select(&monsters, "SELECT * FROM monsters WHERE immune = %d ORDER BY level ASC, id ASC", immunityType)
return monsters, err
} }
// Helper methods // Helper methods
@ -195,14 +151,3 @@ func (m *Monster) GoldPerHP() float64 {
} }
return float64(m.MaxGold) / float64(m.MaxHP) return float64(m.MaxGold) / float64(m.MaxHP)
} }
// Legacy compatibility functions (will be removed later)
func LoadData(dataPath string) error {
// No longer needed - Nigiri handles this
return nil
}
func SaveData(dataPath string) error {
// No longer needed - Nigiri handles this
return nil
}

View File

@ -5,53 +5,23 @@ import (
"strings" "strings"
"time" "time"
nigiri "git.sharkk.net/Sharkk/Nigiri" "dk/internal/database"
) )
// News represents a news post in the game // News represents a news post in the game
type News struct { type News struct {
ID int `json:"id"` ID int
Author int `json:"author" db:"index"` Author int
Posted int64 `json:"posted"` Posted int64
Content string `json:"content" db:"required"` Content string
} }
// Global store // New creates a new News with sensible defaults
var store *nigiri.BaseStore[News]
// Init sets up the Nigiri store and indices
func Init(collection *nigiri.Collection) {
store = nigiri.NewBaseStore[News]()
// Register custom indices
store.RegisterIndex("byAuthor", nigiri.BuildIntGroupIndex(func(n *News) int {
return n.Author
}))
store.RegisterIndex("allByPosted", nigiri.BuildSortedListIndex(func(a, b *News) bool {
if a.Posted != b.Posted {
return a.Posted > b.Posted // DESC
}
return a.ID > b.ID // DESC
}))
store.RebuildIndices()
}
// GetStore returns the news store
func GetStore() *nigiri.BaseStore[News] {
if store == nil {
panic("news store not initialized - call Init first")
}
return store
}
// Creates a new News with sensible defaults
func New() *News { func New() *News {
return &News{ return &News{
Author: 0, // No author by default Author: 0,
Posted: time.Now().Unix(), // Current time Posted: time.Now().Unix(),
Content: "", // Empty content Content: "",
} }
} }
@ -67,75 +37,64 @@ func (n *News) Validate() error {
} }
// CRUD operations // CRUD operations
func (n *News) Save() error {
if n.ID == 0 {
id, err := store.Create(n)
if err != nil {
return err
}
n.ID = id
return nil
}
return store.Update(n.ID, n)
}
func (n *News) Delete() error { func (n *News) Delete() error {
store.Remove(n.ID) return database.Exec("DELETE FROM news WHERE id = %d", n.ID)
return nil
} }
// Insert with ID assignment
func (n *News) Insert() error { func (n *News) Insert() error {
id, err := store.Create(n) id, err := database.Insert("news", n, "ID")
if err != nil { if err != nil {
return err return err
} }
n.ID = id n.ID = int(id)
return nil return nil
} }
// Query functions // Query functions
func Find(id int) (*News, error) { func Find(id int) (*News, error) {
news, exists := store.Find(id) var news News
if !exists { err := database.Get(&news, "SELECT * FROM news WHERE id = %d", id)
if err != nil {
return nil, fmt.Errorf("news with ID %d not found", id) return nil, fmt.Errorf("news with ID %d not found", id)
} }
return news, nil return &news, nil
} }
func All() ([]*News, error) { func All() ([]*News, error) {
return store.AllSorted("allByPosted"), nil var news []*News
err := database.Select(&news, "SELECT * FROM news ORDER BY posted DESC, id DESC")
return news, err
} }
func ByAuthor(authorID int) ([]*News, error) { func ByAuthor(authorID int) ([]*News, error) {
return store.GroupByIndex("byAuthor", authorID), nil var news []*News
err := database.Select(&news, "SELECT * FROM news WHERE author = %d ORDER BY posted DESC, id DESC", authorID)
return news, err
} }
func Recent(limit int) ([]*News, error) { func Recent(limit int) ([]*News, error) {
all := store.AllSorted("allByPosted") var news []*News
if limit > len(all) { err := database.Select(&news, "SELECT * FROM news ORDER BY posted DESC, id DESC LIMIT %d", limit)
limit = len(all) return news, err
}
return all[:limit], nil
} }
func Since(since int64) ([]*News, error) { func Since(since int64) ([]*News, error) {
return store.FilterByIndex("allByPosted", func(n *News) bool { var news []*News
return n.Posted >= since err := database.Select(&news, "SELECT * FROM news WHERE posted >= %d ORDER BY posted DESC, id DESC", since)
}), nil return news, err
} }
func Between(start, end int64) ([]*News, error) { func Between(start, end int64) ([]*News, error) {
return store.FilterByIndex("allByPosted", func(n *News) bool { var news []*News
return n.Posted >= start && n.Posted <= end err := database.Select(&news, "SELECT * FROM news WHERE posted >= %d AND posted <= %d ORDER BY posted DESC, id DESC", start, end)
}), nil return news, err
} }
func Search(term string) ([]*News, error) { func Search(term string) ([]*News, error) {
lowerTerm := strings.ToLower(term) var news []*News
return store.FilterByIndex("allByPosted", func(n *News) bool { searchTerm := "%" + term + "%"
return strings.Contains(strings.ToLower(n.Content), lowerTerm) err := database.Select(&news, "SELECT * FROM news WHERE content LIKE %s ORDER BY posted DESC, id DESC", searchTerm)
}), nil return news, err
} }
// Helper methods // Helper methods
@ -213,14 +172,3 @@ func (n *News) Contains(term string) bool {
func (n *News) IsEmpty() bool { func (n *News) IsEmpty() bool {
return strings.TrimSpace(n.Content) == "" return strings.TrimSpace(n.Content) == ""
} }
// Legacy compatibility functions (will be removed later)
func LoadData(dataPath string) error {
// No longer needed - Nigiri handles this
return nil
}
func SaveData(dataPath string) error {
// No longer needed - Nigiri handles this
return nil
}

View File

@ -2,18 +2,17 @@ package spells
import ( import (
"fmt" "fmt"
"strings"
nigiri "git.sharkk.net/Sharkk/Nigiri" "dk/internal/database"
) )
// Spell represents a spell in the game // Spell represents a spell in the game
type Spell struct { type Spell struct {
ID int `json:"id"` ID int
Name string `json:"name" db:"required,unique"` Name string
MP int `json:"mp" db:"index"` MP int
Attribute int `json:"attribute"` Attribute int
Type int `json:"type" db:"index"` Type int
} }
// SpellType constants for spell types // SpellType constants for spell types
@ -25,54 +24,13 @@ const (
TypeDefenseBoost = 5 TypeDefenseBoost = 5
) )
// Global store // New creates a new Spell with sensible defaults
var store *nigiri.BaseStore[Spell]
// Init sets up the Nigiri store and indices
func Init(collection *nigiri.Collection) {
store = nigiri.NewBaseStore[Spell]()
// Register custom indices
store.RegisterIndex("byType", nigiri.BuildIntGroupIndex(func(s *Spell) int {
return s.Type
}))
store.RegisterIndex("byName", nigiri.BuildCaseInsensitiveLookupIndex(func(s *Spell) string {
return s.Name
}))
store.RegisterIndex("byMP", nigiri.BuildIntGroupIndex(func(s *Spell) int {
return s.MP
}))
store.RegisterIndex("allByTypeMP", nigiri.BuildSortedListIndex(func(a, b *Spell) bool {
if a.Type != b.Type {
return a.Type < b.Type
}
if a.MP != b.MP {
return a.MP < b.MP
}
return a.ID < b.ID
}))
store.RebuildIndices()
}
// GetStore returns the spells store
func GetStore() *nigiri.BaseStore[Spell] {
if store == nil {
panic("spells store not initialized - call Initialize first")
}
return store
}
// Creates a new Spell with sensible defaults
func New() *Spell { func New() *Spell {
return &Spell{ return &Spell{
Name: "", Name: "",
MP: 5, // Default MP cost MP: 5,
Attribute: 10, // Default attribute value Attribute: 10,
Type: TypeHealing, // Default to healing spell Type: TypeHealing,
} }
} }
@ -94,68 +52,60 @@ func (s *Spell) Validate() error {
} }
// CRUD operations // CRUD operations
func (s *Spell) Save() error {
if s.ID == 0 {
id, err := store.Create(s)
if err != nil {
return err
}
s.ID = id
return nil
}
return store.Update(s.ID, s)
}
func (s *Spell) Delete() error { func (s *Spell) Delete() error {
store.Remove(s.ID) return database.Exec("DELETE FROM spells WHERE id = %d", s.ID)
return nil
} }
// Insert with ID assignment
func (s *Spell) Insert() error { func (s *Spell) Insert() error {
id, err := store.Create(s) id, err := database.Insert("spells", s, "ID")
if err != nil { if err != nil {
return err return err
} }
s.ID = id s.ID = int(id)
return nil return nil
} }
// Query functions // Query functions
func Find(id int) (*Spell, error) { func Find(id int) (*Spell, error) {
spell, exists := store.Find(id) var spell Spell
if !exists { err := database.Get(&spell, "SELECT * FROM spells WHERE id = %d", id)
if err != nil {
return nil, fmt.Errorf("spell with ID %d not found", id) return nil, fmt.Errorf("spell with ID %d not found", id)
} }
return spell, nil return &spell, nil
} }
func All() ([]*Spell, error) { func All() ([]*Spell, error) {
return store.AllSorted("allByTypeMP"), nil var spells []*Spell
err := database.Select(&spells, "SELECT * FROM spells ORDER BY type ASC, mp ASC, id ASC")
return spells, err
} }
func ByType(spellType int) ([]*Spell, error) { func ByType(spellType int) ([]*Spell, error) {
return store.GroupByIndex("byType", spellType), nil var spells []*Spell
err := database.Select(&spells, "SELECT * FROM spells WHERE type = %d ORDER BY mp ASC, id ASC", spellType)
return spells, err
} }
func ByMaxMP(maxMP int) ([]*Spell, error) { func ByMaxMP(maxMP int) ([]*Spell, error) {
return store.FilterByIndex("allByTypeMP", func(s *Spell) bool { var spells []*Spell
return s.MP <= maxMP err := database.Select(&spells, "SELECT * FROM spells WHERE mp <= %d ORDER BY type ASC, mp ASC, id ASC", maxMP)
}), nil return spells, err
} }
func ByTypeAndMaxMP(spellType, maxMP int) ([]*Spell, error) { func ByTypeAndMaxMP(spellType, maxMP int) ([]*Spell, error) {
return store.FilterByIndex("allByTypeMP", func(s *Spell) bool { var spells []*Spell
return s.Type == spellType && s.MP <= maxMP err := database.Select(&spells, "SELECT * FROM spells WHERE type = %d AND mp <= %d ORDER BY mp ASC, id ASC", spellType, maxMP)
}), nil return spells, err
} }
func ByName(name string) (*Spell, error) { func ByName(name string) (*Spell, error) {
spell, exists := store.LookupByIndex("byName", strings.ToLower(name)) var spell Spell
if !exists { err := database.Get(&spell, "SELECT * FROM spells WHERE name = %s COLLATE NOCASE", name)
if err != nil {
return nil, fmt.Errorf("spell with name '%s' not found", name) return nil, fmt.Errorf("spell with name '%s' not found", name)
} }
return spell, nil return &spell, nil
} }
// Helper methods // Helper methods
@ -214,14 +164,3 @@ func (s *Spell) IsOffensive() bool {
func (s *Spell) IsSupport() bool { func (s *Spell) IsSupport() bool {
return s.Type == TypeHealing || s.Type == TypeAttackBoost || s.Type == TypeDefenseBoost return s.Type == TypeHealing || s.Type == TypeAttackBoost || s.Type == TypeDefenseBoost
} }
// Legacy compatibility functions (will be removed later)
func LoadData(dataPath string) error {
// No longer needed - Nigiri handles this
return nil
}
func SaveData(dataPath string) error {
// No longer needed - Nigiri handles this
return nil
}