1- // Package gmcp handles Combat Cooldown timer updates for GMCP.
2- //
3- // Sends high-frequency timer updates (5Hz) during combat for smooth countdown animations.
4- // Uses a dedicated timer that only runs when players are in combat to minimize CPU usage.
1+ // Cooldown module provides high-frequency (5Hz) combat round timer updates.
2+ // Timer only runs when players are actively in combat to minimize CPU usage.
53package gmcp
64
75import (
@@ -15,7 +13,6 @@ import (
1513 "github.com/GoMudEngine/GoMud/internal/users"
1614)
1715
18- // GMCPCombatCooldownUpdate is sent frequently during combat to update cooldown timers
1916type GMCPCombatCooldownUpdate struct {
2017 UserId int
2118 CooldownSeconds float64
@@ -26,37 +23,32 @@ type GMCPCombatCooldownUpdate struct {
2623
2724func (g GMCPCombatCooldownUpdate ) Type () string { return `GMCPCombatCooldownUpdate` }
2825
29- // CombatCooldownTimer manages the fast 1/10 second cooldown updates
26+ // CombatCooldownTimer sends 5Hz updates for smooth UI countdown animations
3027type CombatCooldownTimer struct {
3128 ticker * time.Ticker
3229 done chan bool
3330 roundStarted time.Time
3431 roundNumber uint64
3532 roundMutex sync.RWMutex
3633 playerMutex sync.RWMutex
37- players map [int ]bool // tracking which players are in combat
34+ players map [int ]bool
3835 running bool
3936 runningMutex sync.Mutex
4037}
4138
4239var cooldownTimer * CombatCooldownTimer
4340
44- // InitCombatCooldownTimer initializes the cooldown timer system
4541func InitCombatCooldownTimer () {
4642 cooldownTimer = & CombatCooldownTimer {
4743 players : make (map [int ]bool ),
4844 done : make (chan bool ),
4945 }
5046
51- // Register for NewRound events to track round timing
5247 events .RegisterListener (events.NewRound {}, cooldownTimer .handleNewRound )
53-
54- // Register the GMCP event handler
5548 events .RegisterListener (GMCPCombatCooldownUpdate {}, handleCombatCooldownUpdate )
5649
5750}
5851
59- // handleNewRound updates round tracking
6052func (ct * CombatCooldownTimer ) handleNewRound (e events.Event ) events.ListenerReturn {
6153 evt , ok := e .(events.NewRound )
6254 if ! ok {
@@ -72,7 +64,6 @@ func (ct *CombatCooldownTimer) handleNewRound(e events.Event) events.ListenerRet
7264 return events .Continue
7365}
7466
75- // AddPlayer adds a player to cooldown tracking
7667func (ct * CombatCooldownTimer ) AddPlayer (userId int ) {
7768 ct .playerMutex .Lock ()
7869 ct .players [userId ] = true
@@ -81,26 +72,22 @@ func (ct *CombatCooldownTimer) AddPlayer(userId int) {
8172
8273 mudlog .Info ("CombatCooldownTimer" , "action" , "AddPlayer" , "userId" , userId , "needsStart" , needsStart )
8374
84- // Start timer if this is the first player
8575 if needsStart {
8676 ct .start ()
8777 }
8878}
8979
90- // RemovePlayer removes a player from cooldown tracking
9180func (ct * CombatCooldownTimer ) RemovePlayer (userId int ) {
9281 ct .playerMutex .Lock ()
9382 delete (ct .players , userId )
9483 shouldStop := len (ct .players ) == 0
9584 ct .playerMutex .Unlock ()
9685
97- // Stop timer if no more players
9886 if shouldStop {
9987 ct .stop ()
10088 }
10189}
10290
103- // start begins the cooldown timer
10491func (ct * CombatCooldownTimer ) start () {
10592 ct .runningMutex .Lock ()
10693 defer ct .runningMutex .Unlock ()
@@ -127,7 +114,6 @@ func (ct *CombatCooldownTimer) start() {
127114 mudlog .Info ("CombatCooldownTimer" , "action" , "started" )
128115}
129116
130- // stop halts the cooldown timer
131117func (ct * CombatCooldownTimer ) stop () {
132118 ct .runningMutex .Lock ()
133119 defer ct .runningMutex .Unlock ()
@@ -139,7 +125,7 @@ func (ct *CombatCooldownTimer) stop() {
139125 ct .running = false
140126 ct .ticker .Stop ()
141127
142- // Non-blocking send to avoid deadlock
128+ // Non-blocking send prevents deadlock if channel is full
143129 select {
144130 case ct .done <- true :
145131 default :
@@ -148,22 +134,18 @@ func (ct *CombatCooldownTimer) stop() {
148134 mudlog .Info ("CombatCooldownTimer" , "action" , "stopped" )
149135}
150136
151- // sendUpdates sends cooldown updates to all tracked players
152137func (ct * CombatCooldownTimer ) sendUpdates () {
153138 ct .roundMutex .RLock ()
154139 roundStarted := ct .roundStarted
155140 ct .roundMutex .RUnlock ()
156141
157- // If we haven't received a NewRound event yet, use current time
158142 if roundStarted .IsZero () {
159143 roundStarted = time .Now ()
160144 }
161145
162- // Get round duration from config
163146 timingConfig := configs .GetTimingConfig ()
164147 roundDuration := time .Duration (timingConfig .RoundSeconds ) * time .Second
165148
166- // Calculate remaining time
167149 elapsed := time .Since (roundStarted )
168150 remainingMs := roundDuration - elapsed
169151 if remainingMs < 0 {
@@ -173,7 +155,6 @@ func (ct *CombatCooldownTimer) sendUpdates() {
173155 remainingSeconds := float64 (remainingMs ) / float64 (time .Second )
174156 maxSeconds := float64 (roundDuration ) / float64 (time .Second )
175157
176- // Get current players to update
177158 ct .playerMutex .RLock ()
178159 playerIds := make ([]int , 0 , len (ct .players ))
179160 for userId := range ct .players {
@@ -184,27 +165,21 @@ func (ct *CombatCooldownTimer) sendUpdates() {
184165 if len (playerIds ) > 0 {
185166 }
186167
187- // Send updates
188168 for _ , userId := range playerIds {
189169 user := users .GetByUserId (userId )
190170 if user == nil {
191- // User no longer exists, clean up stale entry
192- ct .playerMutex .Lock ()
171+ ct .playerMutex .Lock ()
193172 delete (ct .players , userId )
194173 ct .playerMutex .Unlock ()
195174 mudlog .Warn ("CombatCooldownTimer" , "action" , "sendUpdates" , "issue" , "user not found, cleaning up stale entry" , "userId" , userId )
196175 continue
197176 }
198177
199- // Check if player is actively fighting (has aggro)
200- // Cooldown only matters when the player is attacking, not when just being attacked
178+ // Cooldown only shows when player is attacking (has aggro set)
201179 if user .Character .Aggro == nil {
202- // Skip players not actively fighting
203- // They will be removed by the CombatStatus module
204180 continue
205181 }
206182
207- // Queue cooldown update event
208183 events .AddToQueue (GMCPCombatCooldownUpdate {
209184 UserId : userId ,
210185 CooldownSeconds : remainingSeconds ,
@@ -215,9 +190,7 @@ func (ct *CombatCooldownTimer) sendUpdates() {
215190 }
216191}
217192
218- // TrackCombatPlayer starts tracking cooldown for a player entering combat
219193func TrackCombatPlayer (userId int ) {
220- // Validate user exists before tracking
221194 user := users .GetByUserId (userId )
222195 if user == nil {
223196 mudlog .Warn ("CombatCooldownTimer" , "action" , "TrackCombatPlayer" , "issue" , "attempted to track non-existent user" , "userId" , userId )
@@ -229,17 +202,15 @@ func TrackCombatPlayer(userId int) {
229202 }
230203}
231204
232- // UntrackCombatPlayer stops tracking cooldown for a player leaving combat
233205func UntrackCombatPlayer (userId int ) {
234206 if cooldownTimer != nil {
235- // Check if user still exists before sending final update
236207 user := users .GetByUserId (userId )
237208 if user != nil {
238209 // Send final 0.0 update before removing
239210 timingConfig := configs .GetTimingConfig ()
240211 maxSeconds := float64 (timingConfig .RoundSeconds )
241212
242- // Send final update synchronously to avoid race condition
213+ // Send final 0.0 update before removing to signal combat end
243214 handleCombatCooldownUpdate (GMCPCombatCooldownUpdate {
244215 UserId : userId ,
245216 CooldownSeconds : 0.0 ,
@@ -253,7 +224,6 @@ func UntrackCombatPlayer(userId int) {
253224 }
254225}
255226
256- // handleCombatCooldownUpdate sends GMCP cooldown updates
257227func handleCombatCooldownUpdate (e events.Event ) events.ListenerReturn {
258228 evt , typeOk := e .(GMCPCombatCooldownUpdate )
259229 if ! typeOk {
@@ -266,7 +236,6 @@ func handleCombatCooldownUpdate(e events.Event) events.ListenerReturn {
266236 return events .Continue
267237 }
268238
269- // Build the payload
270239 payload := map [string ]interface {}{
271240 "cooldown" : fmt .Sprintf ("%.1f" , evt .CooldownSeconds ),
272241 "max_cooldown" : fmt .Sprintf ("%.1f" , evt .MaxSeconds ),
0 commit comments