410 lines
9.5 KiB
Go

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()
}