-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathEvents.lua
More file actions
503 lines (446 loc) · 20 KB
/
Events.lua
File metadata and controls
503 lines (446 loc) · 20 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
local ADDON_NAME, ns = ...
local Events = CreateFrame("Frame")
local ONUPDATE_INTERVAL = 0.1
local _elapsed = 0
-- Modular event router: handlers registered per event name.
local _handlers = {}
-- T013: UNIT_AURA coalescing — multiple UNIT_AURA events can fire for the same
-- unit within a single frame (e.g. several auras applied at cast-time). Buffer
-- per-unit updateInfo and flush once per OnUpdate tick so downstream handlers
-- (CombatTracker, TimelineProducer) each process one merged call per unit.
local _pendingAura = {} -- [unitTarget] = { addedAuras={}, removedAuraInstanceIDs={} }
-- Defined before _flushPendingAura so its error path can actually reach it
-- (Lua binds the upvalue at closure-creation time; a later `local function`
-- would leave reportError nil inside _flushPendingAura).
local function reportError(prefix, message)
if ns and ns.Addon and ns.Addon.Warn then
ns.Addon:Warn(string.format("%s: %s", prefix, tostring(message)))
end
end
local function _accumulateUnitAura(unitTarget, updateInfo)
if not unitTarget then return end
local pending = _pendingAura[unitTarget]
if not pending then
pending = { addedAuras = {}, removedAuraInstanceIDs = {} }
_pendingAura[unitTarget] = pending
end
if updateInfo then
if updateInfo.addedAuras then
for _, v in ipairs(updateInfo.addedAuras) do
pending.addedAuras[#pending.addedAuras + 1] = v
end
end
if updateInfo.removedAuraInstanceIDs then
for _, v in ipairs(updateInfo.removedAuraInstanceIDs) do
pending.removedAuraInstanceIDs[#pending.removedAuraInstanceIDs + 1] = v
end
end
end
end
local function _flushPendingAura()
if not next(_pendingAura) then return end
local tracker = ns.Addon:GetModule("CombatTracker")
local tp = ns.Addon:GetModule("TimelineProducer")
for unit, merged in pairs(_pendingAura) do
if tracker and tracker.HandleUnitAura then
local ok, err = xpcall(function()
tracker:HandleUnitAura(unit, merged)
end, debugstack)
if not ok then reportError("UNIT_AURA flush (CombatTracker)", err) end
end
if tp and tp.HandleUnitAura then
local ok, err = xpcall(function()
tp:HandleUnitAura(unit, merged)
end, debugstack)
if not ok then reportError("UNIT_AURA flush (TimelineProducer)", err) end
end
end
wipe(_pendingAura)
end
function Events.RegisterHandler(eventName, handler)
if not _handlers[eventName] then
_handlers[eventName] = {}
end
_handlers[eventName][#_handlers[eventName] + 1] = handler
end
-- Build default handler table from CombatTracker method names.
-- This replaces the old elseif dispatch chain.
local TRACKER_EVENT_MAP = {
PLAYER_ENTERING_WORLD = "HandlePlayerEnteringWorld",
TRAIT_CONFIG_LIST_UPDATED = "HandleTraitConfigListUpdated",
TRAIT_CONFIG_UPDATED = "HandleTraitConfigUpdated",
-- COMBAT_LOG_EVENT_UNFILTERED is not routed here: registering that event
-- via Frame:RegisterEvent() is forbidden in Midnight arena and raises a
-- Lua error. Damage data flows from C_DamageMeter instead.
PLAYER_REGEN_DISABLED = "HandlePlayerRegenDisabled",
PLAYER_REGEN_ENABLED = "HandlePlayerRegenEnabled",
DAMAGE_METER_COMBAT_SESSION_UPDATED = "HandleDamageMeterCombatSessionUpdated",
DAMAGE_METER_CURRENT_SESSION_UPDATED = "HandleDamageMeterCurrentSessionUpdated",
DAMAGE_METER_RESET = "HandleDamageMeterReset",
UNIT_SPELLCAST_SUCCEEDED = "HandleUnitSpellcastSucceeded",
SPELL_DATA_LOAD_RESULT = "HandleSpellDataLoadResult",
-- UNIT_AURA is NOT dispatched via TRACKER_EVENT_MAP; it is coalesced per
-- unit per frame by _flushPendingAura() called from OnUpdate (T013).
-- UNIT_AURA = "HandleUnitAura",
PLAYER_SPECIALIZATION_CHANGED = "HandlePlayerSpecializationChanged",
PLAYER_JOINED_PVP_MATCH = "HandlePlayerJoinedPvpMatch",
PVP_MATCH_ACTIVE = "HandlePvpMatchActive",
PVP_MATCH_COMPLETE = "HandlePvpMatchComplete",
PVP_MATCH_INACTIVE = "HandlePvpMatchInactive",
PVP_MATCH_STATE_CHANGED = "HandlePvpMatchStateChanged",
ARENA_OPPONENT_UPDATE = "HandleArenaOpponentUpdate",
ARENA_PREP_OPPONENT_SPECIALIZATIONS = "HandleArenaPrepOpponentSpecializations",
UPDATE_BATTLEFIELD_STATUS = "HandlePvpMatchStateChanged",
ZONE_CHANGED_NEW_AREA = "HandleZoneChanged",
DUEL_REQUESTED = "HandleDuelRequested",
DUEL_TO_THE_DEATH_REQUESTED = "HandleDuelToTheDeathRequested",
DUEL_INBOUNDS = "HandleDuelInbounds",
DUEL_OUTOFBOUNDS = "HandleDuelOutOfBounds",
DUEL_FINISHED = "HandleDuelFinished",
PLAYER_PVP_TALENT_UPDATE = "HandlePlayerPvpTalentUpdate",
ARENA_CROWD_CONTROL_SPELL_UPDATE = "HandleArenaCrowdControlUpdate",
LOSS_OF_CONTROL_ADDED = "HandleLossOfControlAdded",
LOSS_OF_CONTROL_UPDATE = "HandleLossOfControlUpdate",
PLAYER_CONTROL_LOST = "HandlePlayerControlLost",
PLAYER_CONTROL_GAINED = "HandlePlayerControlGained",
PLAYER_TARGET_CHANGED = "HandlePlayerTargetChanged",
PLAYER_FOCUS_CHANGED = "HandlePlayerFocusChanged",
UNIT_PET = "HandleUnitPet",
NAME_PLATE_UNIT_ADDED = "HandleNamePlateUnitAdded",
ADDON_RESTRICTION_STATE_CHANGED = "HandleRestrictionStateChanged",
INSPECT_READY = nil, -- handled via registered handler below
CHAT_MSG_ADDON = nil, -- handled via registered handler below
}
local function dispatch(tracker, event, ...)
-- Run registered external handlers first.
local registered = _handlers[event]
if registered then
for _, handler in ipairs(registered) do
handler(event, ...)
end
end
-- Run built-in tracker method.
local methodName = TRACKER_EVENT_MAP[event]
if methodName then
if type(tracker[methodName]) == "function" then
tracker[methodName](tracker, ...)
else
-- Negative-space guard: TRACKER_EVENT_MAP maps this event to a
-- method that does not exist (or is not a function) on the tracker.
-- This is a programming error — the map and the module are out of sync.
reportError("Events.dispatch",
string.format("event '%s' mapped to missing method '%s'", event, methodName))
end
end
-- Negative-space guard: event reached dispatch with no registered handler
-- and no TRACKER_EVENT_MAP entry. This is a programming error — the event
-- is registered but has no dispatch target. Check ROUTER_EVENTS against
-- TRACKER_EVENT_MAP and Events.RegisterHandler() calls.
if not registered and not methodName then
if ns and ns.Addon and ns.Addon.Warn then
ns.Addon:Warn(string.format(
"dispatch: event '%s' has no handler — check ROUTER_EVENTS vs TRACKER_EVENT_MAP",
event
))
end
end
end
Events:SetScript("OnEvent", function(_, event, ...)
if event == "ADDON_LOADED" then
local addonName = ...
if addonName ~= ADDON_NAME then
return
end
local ok, err = xpcall(function()
ns.Addon:InitializeCore()
end, debugstack)
if not ok then
reportError("Initialization failed", err)
end
return
end
if event == "PLAYER_LOGIN" then
local ok, err = xpcall(function()
ns.Addon:InitializeRuntime()
end, debugstack)
if not ok then
reportError("Runtime initialization failed", err)
end
return
end
if not ns.Addon.runtimeInitialized then
return
end
local tracker = ns.Addon:GetModule("CombatTracker")
if tracker then
-- Zero-allocation dispatch: forward varargs through xpcall directly
-- instead of materializing a { ... } table on every game event.
local ok, err = xpcall(dispatch, debugstack, tracker, event, ...)
if not ok then
reportError("Event handler failed for " .. event, err)
end
end
end)
Events:SetScript("OnUpdate", function(_, elapsed)
if not ns.Addon.initialized then
return
end
_elapsed = _elapsed + elapsed
if _elapsed < ONUPDATE_INTERVAL then
return
end
local tracker = ns.Addon:GetModule("CombatTracker")
if tracker then
local ok, err = xpcall(function()
tracker:OnUpdate(_elapsed)
end, debugstack)
if not ok then
reportError("OnUpdate failed", err)
end
end
-- T013: Flush coalesced UNIT_AURA events once per tick.
_flushPendingAura()
-- Process inspect queue for PvP talent capture (Task 1.4).
local art = ns.Addon:GetModule("ArenaRoundTracker")
if art and art.ProcessInspectQueue then
art:ProcessInspectQueue(_elapsed)
end
_elapsed = 0
end)
for _, event in ipairs(ns.Constants.ROUTER_EVENTS) do
Events:RegisterEvent(event)
end
-- Register CHAT_MSG_ADDON handler for Party Sync (Task 6.3).
Events.RegisterHandler("CHAT_MSG_ADDON", function(event, prefix, payload, channel, sender)
local sync = ns.Addon:GetModule("PartySyncService")
if sync and sync.HandleAddonMessage then
sync:HandleAddonMessage(prefix, payload, channel, sender)
end
end)
-- Register INSPECT_READY handler for ArenaRoundTracker PvP talent capture.
Events.RegisterHandler("INSPECT_READY", function()
local art = ns.Addon:GetModule("ArenaRoundTracker")
if art and art.HandleInspectReady then
art:HandleInspectReady()
end
end)
-- ── Timeline Producer Wiring ─────────────────────────────────────────────────
-- T027-T030: Route sanctioned events to TimelineProducer for timeline event creation.
-- T028/T016: UNIT_SPELLCAST_SUCCEEDED → VisibleCastProducer (via TimelineProducer shim)
Events.RegisterHandler("UNIT_SPELLCAST_SUCCEEDED", function(event, unitTarget, castGUID, spellID, castBarID)
local tp = ns.Addon:GetModule("TimelineProducer")
if tp and tp.HandleUnitSpellcastSucceeded then
tp:HandleUnitSpellcastSucceeded(unitTarget, castGUID, spellID, castBarID)
end
end)
-- T016: Arena cast lifecycle events → VisibleCastProducer.
-- NOTE: Some of these events may be forbidden in Midnight restricted sessions;
-- the ADDON_ACTION_BLOCKED handler will log the violation if so.
Events.RegisterHandler("UNIT_SPELLCAST_START", function(event, unitTarget, castGUID, spellID, castBarID)
local vcp = ns.Addon:GetModule("VisibleCastProducer")
if vcp and vcp.HandleUnitSpellcastStart then
vcp:HandleUnitSpellcastStart(unitTarget, castGUID, spellID, castBarID)
end
end)
Events.RegisterHandler("UNIT_SPELLCAST_STOP", function(event, unitTarget, castGUID, spellID, castBarID)
local vcp = ns.Addon:GetModule("VisibleCastProducer")
if vcp and vcp.HandleUnitSpellcastStop then
vcp:HandleUnitSpellcastStop(unitTarget, castGUID, spellID, castBarID)
end
end)
Events.RegisterHandler("UNIT_SPELLCAST_INTERRUPTED", function(event, unitTarget, castGUID, spellID, castBarID)
local vcp = ns.Addon:GetModule("VisibleCastProducer")
if vcp and vcp.HandleUnitSpellcastInterrupted then
vcp:HandleUnitSpellcastInterrupted(unitTarget, castGUID, spellID, castBarID)
end
end)
Events.RegisterHandler("UNIT_SPELLCAST_CHANNEL_START", function(event, unitTarget, castGUID, spellID, castBarID)
local vcp = ns.Addon:GetModule("VisibleCastProducer")
if vcp and vcp.HandleUnitSpellcastChannelStart then
vcp:HandleUnitSpellcastChannelStart(unitTarget, castGUID, spellID, castBarID)
end
end)
Events.RegisterHandler("UNIT_SPELLCAST_CHANNEL_STOP", function(event, unitTarget, castGUID, spellID, castBarID)
local vcp = ns.Addon:GetModule("VisibleCastProducer")
if vcp and vcp.HandleUnitSpellcastChannelStop then
vcp:HandleUnitSpellcastChannelStop(unitTarget, castGUID, spellID, castBarID)
end
end)
-- T005: UNIT_SPELLCAST_FAILED — cast failed with a reason (e.g. LoS, range, interrupted).
-- Registered in ROUTER_EVENTS; forwarded to VisibleCastProducer for lifecycle tracking.
-- NOTE: May be forbidden in Midnight restricted sessions; ADDON_ACTION_BLOCKED will surface violations.
Events.RegisterHandler("UNIT_SPELLCAST_FAILED", function(event, unitTarget, castGUID, spellID, castBarID)
local vcp = ns.Addon:GetModule("VisibleCastProducer")
if vcp and vcp.HandleUnitSpellcastFailed then
vcp:HandleUnitSpellcastFailed(unitTarget, castGUID, spellID, castBarID)
end
end)
-- T013/T029: UNIT_AURA → coalescing accumulator.
-- Downstream dispatch (CombatTracker + TimelineProducer) happens in
-- _flushPendingAura() called from OnUpdate, once per unit per frame.
Events.RegisterHandler("UNIT_AURA", function(event, unitTarget, updateInfo)
_accumulateUnitAura(unitTarget, updateInfo)
end)
-- T030: LOSS_OF_CONTROL_ADDED → CCReceivedProducer
Events.RegisterHandler("LOSS_OF_CONTROL_ADDED", function(event, ...)
local tp = ns.Addon:GetModule("TimelineProducer")
if tp and tp.HandleLossOfControlAdded then
tp:HandleLossOfControlAdded(...)
end
end)
-- PLAYER_CONTROL_LOST → CCReceivedProducer
Events.RegisterHandler("PLAYER_CONTROL_LOST", function(event, ...)
local tp = ns.Addon:GetModule("TimelineProducer")
if tp and tp.HandlePlayerControlLost then
tp:HandlePlayerControlLost(...)
end
end)
-- PLAYER_CONTROL_GAINED → CCReceivedProducer (end marker)
Events.RegisterHandler("PLAYER_CONTROL_GAINED", function(event, ...)
local tp = ns.Addon:GetModule("TimelineProducer")
if tp and tp.HandlePlayerControlGained then
tp:HandlePlayerControlGained(...)
end
end)
-- PVP_MATCH_ACTIVE → MatchStateProducer
Events.RegisterHandler("PVP_MATCH_ACTIVE", function(event, ...)
local tp = ns.Addon:GetModule("TimelineProducer")
if tp and tp.HandlePvpMatchActive then
tp:HandlePvpMatchActive(...)
end
end)
-- PVP_MATCH_COMPLETE → MatchStateProducer
Events.RegisterHandler("PVP_MATCH_COMPLETE", function(event, ...)
local tp = ns.Addon:GetModule("TimelineProducer")
if tp and tp.HandlePvpMatchComplete then
tp:HandlePvpMatchComplete(...)
end
end)
-- DUEL_INBOUNDS → MatchStateProducer
Events.RegisterHandler("DUEL_INBOUNDS", function(event, ...)
local tp = ns.Addon:GetModule("TimelineProducer")
if tp and tp.HandleDuelInbounds then
tp:HandleDuelInbounds(...)
end
end)
-- DUEL_FINISHED → MatchStateProducer
Events.RegisterHandler("DUEL_FINISHED", function(event, ...)
local tp = ns.Addon:GetModule("TimelineProducer")
if tp and tp.HandleDuelFinished then
tp:HandleDuelFinished(...)
end
end)
-- DAMAGE_METER_COMBAT_SESSION_UPDATED → DamageMeterCheckpointProducer
Events.RegisterHandler("DAMAGE_METER_COMBAT_SESSION_UPDATED", function(event, ...)
local tp = ns.Addon:GetModule("TimelineProducer")
if tp and tp.HandleDamageMeterCombatSessionUpdated then
tp:HandleDamageMeterCombatSessionUpdated(...)
end
end)
-- DAMAGE_METER_CURRENT_SESSION_UPDATED → DamageMeterCheckpointProducer
Events.RegisterHandler("DAMAGE_METER_CURRENT_SESSION_UPDATED", function(event, ...)
local tp = ns.Addon:GetModule("TimelineProducer")
if tp and tp.HandleDamageMeterCurrentSessionUpdated then
tp:HandleDamageMeterCurrentSessionUpdated(...)
end
end)
-- INSPECT_READY → InspectProducer (timeline marker)
Events.RegisterHandler("INSPECT_READY", function(event, ...)
local tp = ns.Addon:GetModule("TimelineProducer")
if tp and tp.HandleInspectReady then
tp:HandleInspectReady(...)
end
end)
-- UNIT_SPELL_DIMINISH_CATEGORY_STATE_UPDATED → native DR tracking + timeline.
-- Single handler fans out to both consumers to avoid duplicate per-event
-- module lookups and handler-list iterations.
Events.RegisterHandler("UNIT_SPELL_DIMINISH_CATEGORY_STATE_UPDATED", function(event, unitTarget, trackerInfo)
local art = ns.Addon:GetModule("ArenaRoundTracker")
if art and art.HandleDiminishStateUpdated then
art:HandleDiminishStateUpdated(unitTarget, trackerInfo)
end
local tp = ns.Addon:GetModule("TimelineProducer")
if tp and tp.HandleDiminishStateUpdated then
tp:HandleDiminishStateUpdated(unitTarget, trackerInfo)
end
end)
-- Diagnostic: log exactly which function triggered ADDON_ACTION_BLOCKED or
-- ADDON_ACTION_FORBIDDEN so the culprit can be identified in the trace log.
-- These events fire with (addonName, blockedFunctionName).
local function handleAddonActionBlocked(event, addonName, funcName)
if addonName ~= ns.Constants.ADDON_NAME then return end
if ns and ns.Addon and ns.Addon.Warn then
ns.Addon:Warn(string.format("%s: blocked call to '%s'", event, tostring(funcName or "?")))
end
end
Events:RegisterEvent("ADDON_ACTION_BLOCKED")
Events:RegisterEvent("ADDON_ACTION_FORBIDDEN")
Events.RegisterHandler("ADDON_ACTION_BLOCKED", handleAddonActionBlocked)
Events.RegisterHandler("ADDON_ACTION_FORBIDDEN", handleAddonActionBlocked)
-- UnitGraphService handlers — identity graph for GUID↔token resolution.
Events.RegisterHandler("ARENA_OPPONENT_UPDATE", function(event, unitToken)
local ugs = ns.Addon:GetModule("UnitGraphService")
if ugs and ugs.HandleArenaOpponentUpdate then
ugs:HandleArenaOpponentUpdate(unitToken)
end
end)
Events.RegisterHandler("PLAYER_TARGET_CHANGED", function()
local ugs = ns.Addon:GetModule("UnitGraphService")
if ugs and ugs.HandlePlayerTargetChanged then
ugs:HandlePlayerTargetChanged()
end
end)
Events.RegisterHandler("PLAYER_FOCUS_CHANGED", function()
local ugs = ns.Addon:GetModule("UnitGraphService")
if ugs and ugs.HandlePlayerFocusChanged then
ugs:HandlePlayerFocusChanged()
end
end)
Events.RegisterHandler("GROUP_ROSTER_UPDATE", function()
local ugs = ns.Addon:GetModule("UnitGraphService")
if ugs and ugs.HandleGroupRosterUpdate then
ugs:HandleGroupRosterUpdate()
end
end)
Events.RegisterHandler("UNIT_PET", function(event, unitId)
local ugs = ns.Addon:GetModule("UnitGraphService")
if ugs and ugs.HandleUnitPet then
ugs:HandleUnitPet(unitId)
end
end)
Events.RegisterHandler("NAME_PLATE_UNIT_ADDED", function(event, unitToken)
local ugs = ns.Addon:GetModule("UnitGraphService")
if ugs and ugs.HandleNamePlateAdded then
ugs:HandleNamePlateAdded(unitToken)
end
end)
Events.RegisterHandler("NAME_PLATE_UNIT_REMOVED", function(event, unitToken)
local ugs = ns.Addon:GetModule("UnitGraphService")
if ugs and ugs.HandleNamePlateRemoved then
ugs:HandleNamePlateRemoved(unitToken)
end
end)
-- T019: AuraReconciliationService nameplate hooks.
-- NAME_PLATE_UNIT_ADDED → full aura rescan for newly visible unit.
-- NAME_PLATE_UNIT_REMOVED → clean up stale aura records for that unit.
Events.RegisterHandler("NAME_PLATE_UNIT_ADDED", function(event, unitToken)
local ars = ns.Addon:GetModule("AuraReconciliationService")
if ars and ars.HandleFullRescan then
ars:HandleFullRescan(unitToken)
end
end)
Events.RegisterHandler("NAME_PLATE_UNIT_REMOVED", function(event, unitToken)
local ars = ns.Addon:GetModule("AuraReconciliationService")
if ars and ars.HandleUnitRemoved then
ars:HandleUnitRemoved(unitToken)
end
end)
-- ADDON_RESTRICTION_STATE_CHANGED → CombatTracker restriction awareness (12.0.0+)
Events.RegisterHandler("ADDON_RESTRICTION_STATE_CHANGED", function(event, ...)
local tracker = ns.Addon:GetModule("CombatTracker")
if tracker and tracker.HandleRestrictionStateChanged then
tracker:HandleRestrictionStateChanged(...)
end
end)
-- Expose for external handler registration.
ns.Events = Events