312 lines
6.6 KiB
Go
312 lines
6.6 KiB
Go
package fights
|
|
|
|
import (
|
|
"dk/internal/store"
|
|
"fmt"
|
|
"time"
|
|
)
|
|
|
|
// Fight represents a fight, past or present
|
|
type Fight struct {
|
|
ID int `json:"id"`
|
|
UserID int `json:"user_id"`
|
|
MonsterID int `json:"monster_id"`
|
|
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"`
|
|
Created int64 `json:"created"`
|
|
Updated int64 `json:"updated"`
|
|
}
|
|
|
|
func (f *Fight) Save() error {
|
|
f.Updated = time.Now().Unix()
|
|
return GetStore().UpdateWithRebuild(f.ID, f)
|
|
}
|
|
|
|
func (f *Fight) Delete() error {
|
|
GetStore().RemoveWithRebuild(f.ID)
|
|
return nil
|
|
}
|
|
|
|
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: 1,
|
|
RanAway: false,
|
|
Victory: false,
|
|
Won: false,
|
|
RewardGold: 0,
|
|
RewardExp: 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 < 1 {
|
|
return fmt.Errorf("fight Turn must be at least 1")
|
|
}
|
|
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
|
|
}
|
|
|
|
// FightStore with enhanced BaseStore
|
|
type FightStore struct {
|
|
*store.BaseStore[Fight]
|
|
}
|
|
|
|
// Global store with singleton pattern
|
|
var GetStore = store.NewSingleton(func() *FightStore {
|
|
fs := &FightStore{BaseStore: store.NewBaseStore[Fight]()}
|
|
|
|
// Register indices
|
|
fs.RegisterIndex("byUserID", store.BuildIntGroupIndex(func(f *Fight) int {
|
|
return f.UserID
|
|
}))
|
|
|
|
fs.RegisterIndex("byMonsterID", store.BuildIntGroupIndex(func(f *Fight) int {
|
|
return f.MonsterID
|
|
}))
|
|
|
|
fs.RegisterIndex("activeFights", store.BuildFilteredIntGroupIndex(
|
|
func(f *Fight) bool {
|
|
return !f.RanAway && !f.Victory
|
|
},
|
|
func(f *Fight) int {
|
|
return f.UserID
|
|
},
|
|
))
|
|
|
|
fs.RegisterIndex("allByCreated", store.BuildSortedListIndex(func(a, b *Fight) bool {
|
|
if a.Created != b.Created {
|
|
return a.Created > b.Created // DESC
|
|
}
|
|
return a.ID > b.ID // DESC
|
|
}))
|
|
|
|
fs.RegisterIndex("allByUpdated", store.BuildSortedListIndex(func(a, b *Fight) bool {
|
|
if a.Updated != b.Updated {
|
|
return a.Updated > b.Updated // DESC
|
|
}
|
|
return a.ID > b.ID // DESC
|
|
}))
|
|
|
|
return fs
|
|
})
|
|
|
|
// Enhanced CRUD operations
|
|
func (fs *FightStore) AddFight(fight *Fight) error {
|
|
return fs.AddWithRebuild(fight.ID, fight)
|
|
}
|
|
|
|
func (fs *FightStore) RemoveFight(id int) {
|
|
fs.RemoveWithRebuild(id)
|
|
}
|
|
|
|
func (fs *FightStore) UpdateFight(fight *Fight) error {
|
|
return fs.UpdateWithRebuild(fight.ID, fight)
|
|
}
|
|
|
|
// Data persistence
|
|
func LoadData(dataPath string) error {
|
|
fs := GetStore()
|
|
return fs.BaseStore.LoadData(dataPath)
|
|
}
|
|
|
|
func SaveData(dataPath string) error {
|
|
fs := GetStore()
|
|
return fs.BaseStore.SaveData(dataPath)
|
|
}
|
|
|
|
// Query functions using enhanced store
|
|
func Find(id int) (*Fight, error) {
|
|
fs := GetStore()
|
|
fight, exists := fs.Find(id)
|
|
if !exists {
|
|
return nil, fmt.Errorf("fight with ID %d not found", id)
|
|
}
|
|
return fight, nil
|
|
}
|
|
|
|
func All() ([]*Fight, error) {
|
|
fs := GetStore()
|
|
return fs.AllSorted("allByCreated"), nil
|
|
}
|
|
|
|
func ByUserID(userID int) ([]*Fight, error) {
|
|
fs := GetStore()
|
|
return fs.GroupByIndex("byUserID", userID), nil
|
|
}
|
|
|
|
func ByMonsterID(monsterID int) ([]*Fight, error) {
|
|
fs := GetStore()
|
|
return fs.GroupByIndex("byMonsterID", monsterID), nil
|
|
}
|
|
|
|
func ActiveByUserID(userID int) ([]*Fight, error) {
|
|
fs := GetStore()
|
|
return fs.GroupByIndex("activeFights", userID), nil
|
|
}
|
|
|
|
func Active() ([]*Fight, error) {
|
|
fs := GetStore()
|
|
result := fs.FilterByIndex("allByCreated", func(f *Fight) bool {
|
|
return !f.RanAway && !f.Victory
|
|
})
|
|
return result, nil
|
|
}
|
|
|
|
func Recent(within time.Duration) ([]*Fight, error) {
|
|
fs := GetStore()
|
|
cutoff := time.Now().Add(-within).Unix()
|
|
|
|
result := fs.FilterByIndex("allByCreated", func(f *Fight) bool {
|
|
return f.Created >= cutoff
|
|
})
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// Insert with ID assignment
|
|
func (f *Fight) Insert() error {
|
|
fs := GetStore()
|
|
if f.ID == 0 {
|
|
f.ID = fs.GetNextID()
|
|
}
|
|
f.Updated = time.Now().Unix()
|
|
return fs.AddFight(f)
|
|
}
|
|
|
|
// 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()
|
|
}
|