303 lines
6.6 KiB
Go

package fights
import (
"fmt"
"time"
nigiri "git.sharkk.net/Sharkk/Nigiri"
)
// Fight represents a fight, past or present
type Fight struct {
ID int `json:"id"`
UserID int `json:"user_id" db:"index"`
MonsterID int `json:"monster_id" db:"index"`
MonsterHP int `json:"monster_hp"`
MonsterMaxHP int `json:"monster_max_hp"`
MonsterSleep int `json:"monster_sleep"`
MonsterImmune int `json:"monster_immune"`
UberDamage int `json:"uber_damage"`
UberDefense int `json:"uber_defense"`
FirstStrike bool `json:"first_strike"`
Turn int `json:"turn"`
RanAway bool `json:"ran_away"`
Victory bool `json:"victory"`
Won bool `json:"won"`
RewardGold int `json:"reward_gold"`
RewardExp int `json:"reward_exp"`
Actions []ActionEntry `json:"actions"`
Created int64 `json:"created"`
Updated int64 `json:"updated"`
}
// Global store
var store *nigiri.BaseStore[Fight]
var db *nigiri.Collection
// Init sets up the Nigiri store and indices
func Init(collection *nigiri.Collection) {
db = 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
func New(userID, monsterID int) *Fight {
now := time.Now().Unix()
return &Fight{
UserID: userID,
MonsterID: monsterID,
MonsterHP: 0,
MonsterMaxHP: 0,
MonsterSleep: 0,
MonsterImmune: 0,
UberDamage: 0,
UberDefense: 0,
FirstStrike: false,
Turn: 0,
RanAway: false,
Victory: false,
Won: false,
RewardGold: 0,
RewardExp: 0,
Actions: make([]ActionEntry, 0),
Created: now,
Updated: now,
}
}
// Validate checks if fight has valid values
func (f *Fight) Validate() error {
if f.UserID <= 0 {
return fmt.Errorf("fight UserID must be positive")
}
if f.MonsterID <= 0 {
return fmt.Errorf("fight MonsterID must be positive")
}
if f.Turn < 0 {
return fmt.Errorf("fight Turn cannot be negative")
}
if f.MonsterHP < 0 {
return fmt.Errorf("fight MonsterHP cannot be negative")
}
if f.Created <= 0 {
return fmt.Errorf("fight Created timestamp must be positive")
}
if f.Updated <= 0 {
return fmt.Errorf("fight Updated timestamp must be positive")
}
return nil
}
// 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 {
store.Remove(f.ID)
return nil
}
// Insert with ID assignment
func (f *Fight) Insert() error {
f.Updated = time.Now().Unix()
id, err := store.Create(f)
if err != nil {
return err
}
f.ID = id
return nil
}
// Query functions
func Find(id int) (*Fight, error) {
fight, exists := store.Find(id)
if !exists {
return nil, fmt.Errorf("fight with ID %d not found", id)
}
return fight, nil
}
func All() ([]*Fight, error) {
return store.AllSorted("allByCreated"), nil
}
func ByUserID(userID int) ([]*Fight, error) {
return store.GroupByIndex("byUserID", userID), nil
}
func ByMonsterID(monsterID int) ([]*Fight, error) {
return store.GroupByIndex("byMonsterID", monsterID), nil
}
func ActiveByUserID(userID int) ([]*Fight, error) {
return store.GroupByIndex("activeFights", userID), nil
}
func Active() ([]*Fight, error) {
result := store.FilterByIndex("allByCreated", func(f *Fight) bool {
return !f.RanAway && !f.Victory
})
return result, nil
}
func Recent(within time.Duration) ([]*Fight, error) {
cutoff := time.Now().Add(-within).Unix()
result := store.FilterByIndex("allByCreated", func(f *Fight) bool {
return f.Created >= cutoff
})
return result, nil
}
// Helper methods
func (f *Fight) CreatedTime() time.Time {
return time.Unix(f.Created, 0)
}
func (f *Fight) UpdatedTime() time.Time {
return time.Unix(f.Updated, 0)
}
func (f *Fight) IsActive() bool {
return !f.RanAway && !f.Victory
}
func (f *Fight) IsComplete() bool {
return f.RanAway || f.Victory
}
func (f *Fight) GetStatus() string {
if f.Won {
return "Won"
}
if f.Victory && !f.Won {
return "Lost"
}
if f.RanAway {
return "Ran Away"
}
return "Active"
}
func (f *Fight) GetMonsterHealthPercent() float64 {
if f.MonsterMaxHP <= 0 {
return 0.0
}
return float64(f.MonsterHP) / float64(f.MonsterMaxHP) * 100.0
}
func (f *Fight) IsMonsterSleeping() bool {
return f.MonsterSleep > 0
}
func (f *Fight) IsMonsterImmune() bool {
return f.MonsterImmune > 0
}
func (f *Fight) HasUberBonus() bool {
return f.UberDamage > 0 || f.UberDefense > 0
}
func (f *Fight) GetDuration() time.Duration {
return time.Unix(f.Updated, 0).Sub(time.Unix(f.Created, 0))
}
func (f *Fight) EndFight(victory bool) {
f.Victory = victory
f.RanAway = !victory
f.Updated = time.Now().Unix()
}
func (f *Fight) WinFight(goldReward, expReward int) {
f.Victory = true
f.Won = true
f.RanAway = false
f.RewardGold = goldReward
f.RewardExp = expReward
f.Updated = time.Now().Unix()
}
func (f *Fight) LoseFight() {
f.Victory = true
f.Won = false
f.RanAway = false
f.Updated = time.Now().Unix()
}
func (f *Fight) RunAway() {
f.RanAway = true
f.Victory = false
f.Updated = time.Now().Unix()
}
func (f *Fight) IncrementTurn() {
f.Turn++
f.Updated = time.Now().Unix()
}
func (f *Fight) SetMonsterHP(hp int) {
f.MonsterHP = hp
f.Updated = time.Now().Unix()
}
func (f *Fight) DamageMonster(damage int) {
f.MonsterHP -= damage
if f.MonsterHP < 0 {
f.MonsterHP = 0
}
f.Updated = time.Now().Unix()
}