package fights import ( "dk/internal/store" "fmt" "time" ) // ActionEntry represents a compact fight action 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 ) // 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"` Actions []ActionEntry `json:"actions"` 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: 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 } // Action methods for backward compatibility 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) 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 default: return "Unknown action" } } 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() } // 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() }