You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
89 lines
3.4 KiB
Go
89 lines
3.4 KiB
Go
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"` // "heal", "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"`
|
|
}
|