package model import "time" // GameState represents the hero's current activity state. type GameState string const ( StateWalking GameState = "walking" StateFighting GameState = "fighting" StateDead GameState = "dead" StateResting GameState = "resting" // in town, resting StateInTown GameState = "in_town" // in town, interacting with NPCs ) // EncounterCombatStatsSnapshot is MaxHP/Attack/Defense for admin/debug: compare base vs multiplier stages. type EncounterCombatStatsSnapshot struct { MaxHP int `json:"maxHp"` Attack int `json:"attack"` Defense int `json:"defense"` } // CombatState holds the state of an active combat encounter. type CombatState struct { HeroID int64 `json:"heroId"` Hero *Hero `json:"-"` // hero reference, not serialised to avoid circular refs Enemy Enemy `json:"enemy"` // EnemyStatsBasePreEncounterMult: level-scaled stats only (no encounter multipliers). EnemyStatsBasePreEncounterMult *EncounterCombatStatsSnapshot `json:"enemyStatsBasePreEncounterMult,omitempty"` // EnemyStatsAfterGlobalEncounterMult: after tuning EnemyEncounterStatMultiplier only (before unequipped-hero scaling). EnemyStatsAfterGlobalEncounterMult *EncounterCombatStatsSnapshot `json:"enemyStatsAfterGlobalEncounterMult,omitempty"` HeroNextAttack time.Time `json:"heroNextAttack"` EnemyNextAttack time.Time `json:"enemyNextAttack"` StartedAt time.Time `json:"startedAt"` LastTickAt time.Time `json:"-"` // tracks previous tick for periodic effects // Fractional regen carry between ticks to avoid rounding to 1 HP each tick. EnemyRegenRemainder float64 `json:"-"` } // AttackEvent is a min-heap entry for scheduling attacks by next_attack_at. type AttackEvent struct { NextAttackAt time.Time `json:"nextAttackAt"` IsHero bool `json:"isHero"` // true = hero attacks, false = enemy attacks CombatID int64 `json:"combatId"` // reference to the combat session } // AttackQueue implements container/heap.Interface for scheduling attacks. type AttackQueue []*AttackEvent func (q AttackQueue) Len() int { return len(q) } func (q AttackQueue) Less(i, j int) bool { a, b := q[i], q[j] if a.NextAttackAt.Equal(b.NextAttackAt) { if a.IsHero != b.IsHero { return a.IsHero // hero before enemy when simultaneous } return a.CombatID < b.CombatID } return a.NextAttackAt.Before(b.NextAttackAt) } func (q AttackQueue) Swap(i, j int) { q[i], q[j] = q[j], q[i] } func (q *AttackQueue) Push(x any) { *q = append(*q, x.(*AttackEvent)) } func (q *AttackQueue) Pop() any { old := *q n := len(old) item := old[n-1] old[n-1] = nil // avoid memory leak *q = old[:n-1] return item } // CombatEvent is broadcast over WebSocket to clients observing combat. type CombatEvent struct { Type string `json:"type"` // "attack", "death", "buff_applied", "debuff_applied", "combat_start", "combat_end" HeroID int64 `json:"heroId"` Damage int `json:"damage,omitempty"` Source string `json:"source"` // "hero" or "enemy" Outcome string `json:"outcome,omitempty"` // "hit", "dodge", "block", "stun" IsCrit bool `json:"isCrit,omitempty"` DebuffApplied string `json:"debuffApplied,omitempty"` // debuff type applied this event, if any HeroHP int `json:"heroHp"` EnemyHP int `json:"enemyHp"` Timestamp time.Time `json:"timestamp"` }