package combat import "sync" // State captures a single player's active combat engagement. type State struct { PlayerName string MobID string Active bool DamageWarningShown bool } // Tracker holds all in-progress combat engagements. One player fights one mob // at a time; one mob is fought by one player at a time (the IsMobInCombat lock // is reused by task/labor mobs too). Safe for concurrent use. type Tracker struct { mu sync.Mutex combatants map[string]*State // player name -> state mobTargets map[string]string // mob instance ID -> player name } // NewTracker returns an empty combat tracker. func NewTracker() *Tracker { return &Tracker{ combatants: make(map[string]*State), mobTargets: make(map[string]string), } } // Enter records that playerName is now fighting mobID. func (t *Tracker) Enter(playerName, mobID string) { t.mu.Lock() defer t.mu.Unlock() t.combatants[playerName] = &State{ PlayerName: playerName, MobID: mobID, Active: true, } t.mobTargets[mobID] = playerName } // Leave ends playerName's combat, freeing their target mob. func (t *Tracker) Leave(playerName string) { t.mu.Lock() defer t.mu.Unlock() state, ok := t.combatants[playerName] if !ok { return } delete(t.mobTargets, state.MobID) delete(t.combatants, playerName) } // Get returns the player's combat state, or nil if not in combat. func (t *Tracker) Get(playerName string) *State { t.mu.Lock() defer t.mu.Unlock() return t.combatants[playerName] } // IsMobInCombat reports whether the mob instance is currently engaged. func (t *Tracker) IsMobInCombat(mobID string) bool { t.mu.Lock() defer t.mu.Unlock() _, ok := t.mobTargets[mobID] return ok } // MobTarget returns the name of the player fighting the mob, or "". func (t *Tracker) MobTarget(mobID string) string { t.mu.Lock() defer t.mu.Unlock() return t.mobTargets[mobID] }