-
Notifications
You must be signed in to change notification settings - Fork 3
/
MSBTCooldowns.lua
484 lines (383 loc) · 19.6 KB
/
MSBTCooldowns.lua
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
-------------------------------------------------------------------------------
-- Title: Mik's Scrolling Battle Text Cooldowns
-- Author: Mikord
-------------------------------------------------------------------------------
-- Create module and set its name.
local module = {}
local moduleName = "Cooldowns"
MikSBT[moduleName] = module
-------------------------------------------------------------------------------
-- Imports.
-------------------------------------------------------------------------------
-- Local references to various modules for faster access.
local MSBTProfiles = MikSBT.Profiles
local MSBTTriggers = MikSBT.Triggers
-- Local references to various functions for faster access.
local string_gsub = string.gsub
local string_find = string.find
local string_format = string.format
local string_match = string.match
local GetSpellCooldown = GetSpellCooldown
local EraseTable = MikSBT.EraseTable
local GetSkillName = MikSBT.GetSkillName
local DisplayEvent = MikSBT.Animations.DisplayEvent
local HandleCooldowns = MSBTTriggers.HandleCooldowns
-------------------------------------------------------------------------------
-- Constants.
-------------------------------------------------------------------------------
-- The minimum and maximum amount of time to delay between checking cooldowns.
local MIN_COOLDOWN_UPDATE_DELAY = 0.1
local MAX_COOLDOWN_UPDATE_DELAY = 1
-- Spell names.
local SPELLID_COLD_SNAP = 11958
local SPELLID_MIND_FREEZE = 47528
local SPELLID_PREPARATION = 14185
local SPELLID_READINESS = 23989
-- Death knight rune cooldown.
local RUNE_COOLDOWN = 10
-- Parameter locations.
local ITEM_INFO_TEXTURE_POSITION = 10
-------------------------------------------------------------------------------
-- Private variables.
-------------------------------------------------------------------------------
-- Prevent tainting global _.
local _
-- Dynamically created frame for receiving events.
local eventFrame = CreateFrame("Frame")
-- Player's class.
local playerClass
-- Cooldown information.
local activeCooldowns = {player={}, pet={}, item={}}
local delayedCooldowns = {player={}, pet={}, item={}}
local resetAbilities = {}
local runeCooldownAbilities = {}
local lastCooldownIDs = {}
local watchItemIDs = {}
-- Used for timing between updates.
local updateDelay = MIN_COOLDOWN_UPDATE_DELAY
local lastUpdate = 0
-- Flag for whether item cooldowns are enabled.
local itemCooldownsEnabled = true
-------------------------------------------------------------------------------
-- Utility functions.
-------------------------------------------------------------------------------
-- ****************************************************************************
-- Attempts to return a texture for a given cooldown type and id.
-- ****************************************************************************
local function GetCooldownTexture(cooldownType, cooldownID)
if (cooldownType == "item") then
return select(ITEM_INFO_TEXTURE_POSITION, GetItemInfo(cooldownID))
else
return GetSpellTexture(cooldownID)
end
end
-------------------------------------------------------------------------------
-- Event handlers.
-------------------------------------------------------------------------------
-- ****************************************************************************
-- Called when the player successfully casts a spell.
-- ****************************************************************************
local function OnSpellCast(unitID, spellID)
-- Ignore the cast if the spell name is excluded.
local spellName = GetSpellInfo(spellID) or UNKNOWN
local cooldownExclusions = MSBTProfiles.currentProfile.cooldownExclusions
if (cooldownExclusions[spellName] or cooldownExclusions[spellID]) then return end
-- An ability that resets cooldowns was cast.
if (resetAbilities[spellID] and unitID == "player") then
-- Remove active cooldowns that the game is now reporting inactive.
for spellID, remainingDuration in pairs(activeCooldowns[unitID]) do
local startTime, duration = GetSpellCooldown(spellID)
if (duration <= 1.5 and remainingDuration > 1.5) then activeCooldowns[unitID][spellID] = nil end
-- Force an update.
updateDelay = MIN_COOLDOWN_UPDATE_DELAY
end
end
-- Set the cooldown spell id to be checked on the next cooldown update event.
lastCooldownIDs[unitID] = spellID
end
-- ****************************************************************************
-- Called when the player uses an item.
-- ****************************************************************************
local function OnItemUse(itemID)
-- Ignore if the item name is excluded.
local itemName = GetItemInfo(itemID)
local cooldownExclusions = MSBTProfiles.currentProfile.cooldownExclusions
if (cooldownExclusions[itemName] or cooldownExclusions[itemID]) then return end
-- Add the item to a lsit to be checked for cooldowns in a a couple of seconds.
-- There doesn't appear to be a realible event that can be used for items like
-- there is for spells, so this allows a fairly efficient method of only checking
-- the cooldowns on used items versus scanning all of the slots in inventory
-- and bages every update.
watchItemIDs[itemID] = GetTime()
-- Force an update.
updateDelay = MIN_COOLDOWN_UPDATE_DELAY
-- Check if the event frame is not visible and make it visible so the OnUpdate events start firing.
-- This is done to keep the number of OnUpdate events down to a minimum for better performance.
if (not eventFrame:IsVisible()) then eventFrame:Show() end
end
-- ****************************************************************************
-- Called when a spell cooldown is started.
-- ****************************************************************************
local function OnUpdateCooldown(cooldownType, cooldownFunc)
if (not delayedCooldowns[cooldownType] or not activeCooldowns[cooldownType]) then return end
-- Start delayed cooldowns once they have been used.
for cooldownID in pairs(delayedCooldowns[cooldownType]) do
-- Check if the cooldown is enabled yet.
local _, duration, enabled = cooldownFunc(cooldownID)
if (enabled == 1) then
-- Add the cooldown to the active cooldowns list if the cooldown is longer than the cooldown threshold or it's required to show.
local cooldownName = GetSpellInfo(cooldownID)
local ignoreCooldownThreshold = MSBTProfiles.currentProfile.ignoreCooldownThreshold
if (duration >= MSBTProfiles.currentProfile.cooldownThreshold or ignoreCooldownThreshold[cooldownName] or ignoreCooldownThreshold[cooldownID]) then
activeCooldowns[cooldownType][cooldownID] = duration
-- Force an update.
updateDelay = MIN_COOLDOWN_UPDATE_DELAY
-- Check if the event frame is not visible and make it visible so the OnUpdate events start firing.
-- This is done to keep the number of OnUpdate events down to a minimum for better performance.
if (not eventFrame:IsVisible()) then eventFrame:Show() end
end
-- Remove the cooldown from the delayed cooldowns list.
delayedCooldowns[cooldownType][cooldownID] = nil
end
end
-- Add the last successful spell to the active cooldowns if necessary.
local cooldownID = lastCooldownIDs[cooldownType]
if (cooldownID) then
-- Make sure the spell cooldown is enabled.
local _, duration, enabled = cooldownFunc(cooldownID)
if (enabled == 1) then
-- XXX This is a hack to compensate for Blizzard's API reporting incorrect cooldown information for death knights.
-- XXX Ignore cooldowns that are the same duration as a rune cooldown except for the abilities that truly have the same cooldown.
if (playerClass == "DEATHKNIGHT" and duration == RUNE_COOLDOWN and cooldownType == "player" and not runeCooldownAbilities[cooldownID]) then duration = -1 end
-- Add the cooldown to the active cooldowns list if the cooldown is longer than the cooldown threshold or it's required to show.
local cooldownName = GetSpellInfo(cooldownID)
local ignoreCooldownThreshold = MSBTProfiles.currentProfile.ignoreCooldownThreshold
if (duration >= MSBTProfiles.currentProfile.cooldownThreshold or ignoreCooldownThreshold[cooldownName] or ignoreCooldownThreshold[cooldownID]) then
activeCooldowns[cooldownType][cooldownID] = duration
-- Force an update.
updateDelay = MIN_COOLDOWN_UPDATE_DELAY
-- Check if the event frame is not visible and make it visible so the OnUpdate events start firing.
-- This is done to keep the number of OnUpdate events down to a minimum for better performance.
if (not eventFrame:IsVisible()) then eventFrame:Show() end
end
-- Cooldown is NOT enabled so add it to the delayed cooldowns list.
else
delayedCooldowns[cooldownType][cooldownID] = true
end
lastCooldownIDs[cooldownType] = nil
end -- cooldownID?
end
-- ****************************************************************************
-- Called when the OnUpdate event occurs.
-- ****************************************************************************
local function OnUpdate(frame, elapsed)
-- Increment the amount of time passed since the last update.
lastUpdate = lastUpdate + elapsed
-- Check if it's time for an update.
if (lastUpdate >= updateDelay) then
-- Reset the update delay to the max value.
updateDelay = MAX_COOLDOWN_UPDATE_DELAY
-- Loop through the item ids being watched for cooldowns.
local currentTime = GetTime()
for cooldownID, usedTime in pairs(watchItemIDs) do
if (currentTime >= (usedTime + 1)) then
lastCooldownIDs["item"] = cooldownID
OnUpdateCooldown("item", C_Container.GetItemCooldown)
watchItemIDs[cooldownID] = nil
break
end
end
-- Loop through all of the active cooldowns.
local currentTime = GetTime()
for cooldownType, cooldowns in pairs(activeCooldowns) do
local cooldownFunc = (cooldownType == "item") and C_Container.GetItemCooldown or GetSpellCooldown
local infoFunc = (cooldownType == "item") and GetItemInfo or GetSpellInfo
for cooldownID, remainingDuration in pairs(cooldowns) do
-- Ensure the cooldown is still valid.
local startTime, duration, enabled = cooldownFunc(cooldownID)
if (startTime ~= nil) then
-- Calculate the remaining cooldown.
local cooldownRemaining = startTime + duration - currentTime
-- XXX This is to compensate for Blizzard's API reporting incorrect cooldown information for pets that have been dismissed.
-- XXX Use an internal timer for pet skills.
-- XXX This will have to be updated if any skills are added that dynamically adjust pet cooldowns.
if (cooldownType == "pet") then cooldownRemaining = remainingDuration - lastUpdate end
-- Cooldown completed.
if (cooldownRemaining <= 0) then
local cooldownName = infoFunc(cooldownID) or UNKNOWN
local texture = GetCooldownTexture(cooldownType, cooldownID)
HandleCooldowns(cooldownType, cooldownID, cooldownName, texture)
local eventSettings = MSBTProfiles.currentProfile.events.NOTIFICATION_COOLDOWN
if (cooldownType == "pet") then
eventSettings = MSBTProfiles.currentProfile.events.NOTIFICATION_PET_COOLDOWN
elseif (cooldownType == "item") then
eventSettings = MSBTProfiles.currentProfile.events.NOTIFICATION_ITEM_COOLDOWN
end
if (eventSettings and not eventSettings.disabled) then
local message = eventSettings.message
local formattedSkillName = string_format("|cFF%02x%02x%02x%s|r", eventSettings.skillColorR * 255, eventSettings.skillColorG * 255, eventSettings.skillColorB * 255, string_gsub(cooldownName, "%(.+%)%(%)$", ""))
message = string_gsub(message, "%%e", formattedSkillName)
DisplayEvent(eventSettings, message, texture)
end
-- Remove the cooldown from active cooldowns.
cooldowns[cooldownID] = nil
-- Cooldown NOT completed.
else
cooldowns[cooldownID] = cooldownRemaining
if (cooldownRemaining < updateDelay) then updateDelay = cooldownRemaining end
end
-- Cooldown is no longer valid.
else
cooldowns[cooldownID] = nil
end
end -- cooldowns
end -- units
-- Ensure the update delay isn't less than the min value.
if (updateDelay < MIN_COOLDOWN_UPDATE_DELAY) then updateDelay = MIN_COOLDOWN_UPDATE_DELAY end
-- Hide the event frame if there are no active cooldowns so the OnUpdate events stop firing.
-- This is done to keep the number of OnUpdate events down to a minimum for better performance.
local allInactive = true
for cooldownType, cooldowns in pairs(activeCooldowns) do
if (next(cooldowns)) then allInactive = false end
end
if (allInactive and not next(watchItemIDs)) then eventFrame:Hide() end
-- Reset the time since last update.
lastUpdate = 0
end
end
-- ****************************************************************************
-- Successful spell casts.
-- ****************************************************************************
function eventFrame:UNIT_SPELLCAST_SUCCEEDED(unitID, lineID, skillID)
if (unitID == "player") then OnSpellCast("player", skillID) end
end
-- ****************************************************************************
-- Combat log event for detecting pet casts.
-- ****************************************************************************
function eventFrame:COMBAT_LOG_EVENT_UNFILTERED()
eventFrame:CombatLogEvent(CombatLogGetCurrentEventInfo())
end
function eventFrame:CombatLogEvent(timestamp, event, hideCaster, sourceGUID, sourceName, sourceFlags, sourceRaidFlags, recipientGUID, recipientName, recipientFlags, recipientRaidFlags, skillID)
if (event ~= "SPELL_CAST_SUCCESS") then return end
if (sourceGUID == UnitGUID("pet")) then OnSpellCast("pet", skillID) end
end
-- ****************************************************************************
-- Called when spell cooldowns begin.
-- ****************************************************************************
function eventFrame:SPELL_UPDATE_COOLDOWN()
OnUpdateCooldown("player", GetSpellCooldown)
end
-- ****************************************************************************
-- Called when pet cooldowns begin.
-- ****************************************************************************
function eventFrame:PET_BAR_UPDATE_COOLDOWN()
OnUpdateCooldown("pet", GetSpellCooldown)
end
-- ****************************************************************************
-- Updates the registered events depending on active options.
-- ****************************************************************************
local function UpdateRegisteredEvents()
-- Toggle the registered events for player cooldowns depending on enabled notifications and triggers.
local doEnable = false
if (not MSBTProfiles.currentProfile.events.NOTIFICATION_COOLDOWN.disabled or MSBTTriggers.categorizedTriggers["SKILL_COOLDOWN"]) then doEnable = true end
local registerFunc = doEnable and "RegisterEvent" or "UnregisterEvent"
eventFrame[registerFunc](eventFrame, "UNIT_SPELLCAST_SUCCEEDED")
eventFrame[registerFunc](eventFrame, "SPELL_UPDATE_COOLDOWN")
-- Toggle the registered events for pet cooldowns depending on enabled notifications and triggers.
local doEnable = false
if (not MSBTProfiles.currentProfile.events.NOTIFICATION_PET_COOLDOWN.disabled or MSBTTriggers.categorizedTriggers["PET_COOLDOWN"]) then doEnable = true end
local registerFunc = doEnable and "RegisterEvent" or "UnregisterEvent"
eventFrame[registerFunc](eventFrame, "COMBAT_LOG_EVENT_UNFILTERED")
eventFrame[registerFunc](eventFrame, "PET_BAR_UPDATE_COOLDOWN")
-- Toggle the flag for tracking item cooldowns depending on enabled notifications and triggers.
local doEnable = false
if (not MSBTProfiles.currentProfile.events.NOTIFICATION_ITEM_COOLDOWN.disabled or MSBTTriggers.categorizedTriggers["ITEM_COOLDOWN"]) then doEnable = true end
itemCooldownsEnabled = doEnable
end
-- ****************************************************************************
-- Enables the module.
-- ****************************************************************************
local function Enable()
UpdateRegisteredEvents()
end
-- ****************************************************************************
-- Disables the module.
-- ****************************************************************************
local function Disable()
-- Stop receiving updates.
eventFrame:Hide()
eventFrame:UnregisterAllEvents()
-- Clear the cooldown tracking tables.
for cooldownType, cooldowns in pairs(activeCooldowns) do EraseTable(cooldowns) end
for cooldownType, cooldowns in pairs(delayedCooldowns) do EraseTable(cooldowns) end
EraseTable(watchItemIDs)
end
-------------------------------------------------------------------------------
-- Item usage hooks.
-------------------------------------------------------------------------------
-- ****************************************************************************
-- Called when an action button is used.
-- ****************************************************************************
local function UseActionHook(slot)
if (not itemCooldownsEnabled) then return end
-- Get item id for the action if the action was using and item.
local actionType, itemID = GetActionInfo(slot)
if (actionType == "item") then OnItemUse(itemID) end
end
-- ****************************************************************************
-- Called when an inventory item is used.
-- ****************************************************************************
local function UseInventoryItemHook(slot)
if (not itemCooldownsEnabled) then return end
-- Get item id for the used inventory item.
local itemID = GetInventoryItemID("player", slot);
if (itemID) then OnItemUse(itemID) end
end
-- ****************************************************************************
-- Called when a container item is used.
-- ****************************************************************************
local function UseContainerItemHook(bag, slot)
if (not itemCooldownsEnabled) then return end
-- Get item id for the used bag and slot.
local itemID = C_Container.GetContainerItemID(bag, slot)
if (itemID) then OnItemUse(itemID) end
end
-- ****************************************************************************
-- Called when an item is used by name.
-- ****************************************************************************
local function UseItemByNameHook(itemName)
if (not itemCooldownsEnabled) then return end
-- Get item link for the name and extract item id from item link.
if (not itemName) then return end
local _, itemLink = GetItemInfo(itemName)
local itemID
if (itemLink) then itemID = string_match(itemLink, "item:(%d+)") end
if (itemID) then OnItemUse(itemID) end
end
-------------------------------------------------------------------------------
-- Initialization.
-------------------------------------------------------------------------------
-- Setup event frame.
eventFrame:Hide()
eventFrame:SetScript("OnEvent", function (self, event, ...) if (self[event]) then self[event](self, ...) end end)
eventFrame:SetScript("OnUpdate", OnUpdate)
-- Get the player's class.
_, playerClass = UnitClass("player")
-- Setup hooks.
hooksecurefunc("UseAction", UseActionHook)
hooksecurefunc("UseInventoryItem", UseInventoryItemHook)
hooksecurefunc(C_Container, "UseContainerItem", UseContainerItemHook)
hooksecurefunc("UseItemByName", UseItemByNameHook)
-- Specify the abilities that reset cooldowns.
resetAbilities[SPELLID_COLD_SNAP] = true
resetAbilities[SPELLID_PREPARATION] = true
resetAbilities[SPELLID_READINESS] = true
-- Set the death knight abilities that are the same as the rune cooldown.
runeCooldownAbilities[SPELLID_MIND_FREEZE] = true
-------------------------------------------------------------------------------
-- Module interface.
-------------------------------------------------------------------------------
-- Protected Functions.
module.Enable = Enable
module.Disable = Disable
module.UpdateRegisteredEvents = UpdateRegisteredEvents