forked from Dicebar/Raven
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathMain.lua
More file actions
3168 lines (2926 loc) · 109 KB
/
Main.lua
File metadata and controls
3168 lines (2926 loc) · 109 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
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
-- Raven is an addon to monitor auras and cooldowns, providing timer bars and icons plus helpful notifications.
-- Author: Tomber
-- Copyright 2010-2019, All Rights Reserved
-- Main.lua contains initialization and update routines supporting Raven's core capability of tracking active auras and cooldowns.
-- It includes special cases for weapon buffs, stances, and trinkets.
-- It works primarily by tracking events that indicate when auras and spell casts occur. It maintains internal
-- tables of active auras to facilitate seamless tracking of auras, including casts that refresh ongoing auras.
-- In addition, it tracks combat log events in order to detect auras that the player has cast on multiple targets.
-- And, for cooldowns, it monitors events related to spells going onto cooldown.
-- Exported functions:
-- Raven:CheckAura(unit, name, isBuff) checks if an aura is active on a unit, returning detailed info if found
-- Raven:IterateAuras(unit, func, isBuff, p1, p2, p3) calls func for each active aura, parameters include a table with detailed aura info
-- Raven:CheckCooldown(name) checks if cooldown with the specified name is active, returning detailed info if found
-- Raven:IterateCooldowns(func, p1, p2, p3) calls func for each active cooldown, parameters include a table with detailed cooldown info
-- Raven:UnitHasBuff(unit, type) returns true and table with detailed info if unit has an active buff of the specified type (e.g., "Mainhand")
-- Raven:UnitHasDebuff(unit, type) returns true and table with detailed info if unit has an active debuff of the specified type (e.g., "Poison")
Raven = LibStub("AceAddon-3.0"):NewAddon("Raven", "AceConsole-3.0", "AceEvent-3.0")
local L = LibStub("AceLocale-3.0"):GetLocale("Raven")
local media = LibStub("LibSharedMedia-3.0")
local MOD = Raven
local MOD_Options = "Raven_Options"
local SHIM = {}
MOD.SHIM = SHIM
local _
local addonInitialized = false -- set when the addon is initialized
local addonEnabled = false -- set when the addon is enabled
local optionsLoaded = false -- set when the load-on-demand options panel module has been loaded
local optionsFailed = false -- set if loading the option panel module failed
MOD.isVanilla = LE_EXPANSION_LEVEL_CURRENT == 0
MOD.isWrath = LE_EXPANSION_LEVEL_CURRENT == LE_EXPANSION_WRATH_OF_THE_LICH_KING
MOD.isCata = LE_EXPANSION_LEVEL_CURRENT == LE_EXPANSION_CATACLYSM
MOD.isMists = LE_EXPANSION_LEVEL_CURRENT == LE_EXPANSION_MISTS_OF_PANDARIA
MOD.isClassic = MOD.isWrath or MOD.isVanilla or MOD.isCata or MOD.isMists
MOD.isModernUI = LE_EXPANSION_LEVEL_CURRENT >= LE_EXPANSION_DRAGONFLIGHT
function MOD.ExpansionIsOrAbove(exp)
if exp == nil then
return false
end --This is Vanilla
return LE_EXPANSION_LEVEL_CURRENT >= exp
end
function MOD.ExpansionIsOrBelow(exp)
if exp == nil then
return true
end --This is Vanilla
return LE_EXPANSION_LEVEL_CURRENT <= exp
end
MOD.updateOptions = false -- set this to cause the options panel to update (checked every half second)
MOD.LocalSpellNames = {} -- must be defined in first module loaded
local LSPELL = MOD.LocalSpellNames
MOD.frame = nil
MOD.db = nil
MOD.ldb = nil
MOD.ldbi = nil -- set when using DBIcon library
MOD.LibLDB = nil
MOD.myClass = nil
MOD.localClass = nil
MOD.myRace = nil
MOD.localRace = nil
MOD.lockoutSpells = {} -- spells for testing lock out of each school of magic for current player
MOD.classConditions = {} -- stores info about pre-defined conditions for each class
MOD.talents = {} -- table containing names and talent table location for each talent
MOD.talentList = {} -- table with list of talent names
MOD.runeSlots = {} -- cache information about each rune slot for DKs
MOD.runeCount = 0 -- current number of available runes
MOD.updateActions = true -- action bar changed
MOD.updateDispels = true -- need to update dispel types
MOD.knownBrokers = {} -- table of registered data brokers
MOD.brokerList = {} -- table of brokers suitable for a selection list
MOD.cooldownSpells = {} -- table of spell ids that have a cooldown to track, updated when spellbook changes
MOD.spellOverrides = {} -- table of overriding spell ids
MOD.chargeSpells = {} -- table of spell ids with max charges
MOD.petSpells = {} -- table of pet spell ids with a cooldown to track
MOD.professionSpells = {} -- table of profession spell ids with a cooldown to track
MOD.bookSpells = {} -- table of spells currently available in the spell book
MOD.suppress = true -- this is set when certain special effects are to be disabled (e.g., at start up)
MOD.combatTimer = 0 -- if not 0 then this is set to the time when the player last entered combat
MOD.status = {} -- global status info cached by conditions module on every update
local doUpdate = true -- set by any event that can change bars (used to throttle major updates)
local forceUpdate = false -- set to cause immediate update (reserved for critical changes like to player's target or focus)
local suppressTime = nil -- set when addon code is loaded
local updateCooldowns = false -- set when actionbar or inventory slot cooldown starts or stops
local units = {} -- list of units to track
local mainUnits = { "player", "pet", "target", "focus", "targettarget", "focustarget", "pettarget", "mouseover" } -- ordered list of main units
local bossUnits = { "boss1", "boss2", "boss3", "boss4", "boss5" } -- optional boss units
local arenaUnits = { "arena1", "arena2", "arena3", "arena4", "arena5" } -- optional arena units
local partyUnits, partyUnitPets, partyUnitTargets = {}, {}, {} -- optional party units
local raidUnits, raidUnitPets, raidUnitTargets = {}, {}, {}
do -- precache unit tokens
for i = 1, MAX_PARTY_MEMBERS do
partyUnits[i] = "party" .. i
partyUnitPets[i] = "partypet" .. i
partyUnitTargets[i] = "party" .. i .. "target"
end
for i = 1, MAX_RAID_MEMBERS do
raidUnits[i] = "raid" .. i
raidUnitPets[i] = "raidpet" .. i
raidUnitTargets[i] = "raid" .. i .. "target"
end
end
local nameplateUnits = {} -- cache of 40 nameplate unit ids
local eventUnits = { "targettarget", "focustarget", "pettarget", "mouseover" } -- can't count on events for these units
local tagUnits = { player = true, target = true, focus = true, pet = true, targettarget = true, focustarget = true, pettarget = true, mouseover = true } -- for hash tag generation
local unitUpdate = {} -- boolean for each unit that indicates need to update auras
local unitStatus = {} -- status of each unit set on every update (0 = no unit, 1 = unit exists, "unit" = unit is other unit)
local unitBuffs = {} -- indexed by GUID for tracking buffs cast by player
local unitDebuffs = {} -- indexed by GUID for tracking debuffs cast by player
local activeBuffs = {} -- active buffs for each unit
local activeDebuffs = {} -- active debuffs for each unit
local tagBuffs = {} -- cache of buff tags for each unit
local tagDebuffs = {} -- cache of debuff tags for each unit
local cacheBuffs = {} -- cache of active buff names
local cacheDebuffs = {} -- cache of active debuff names
local cacheUnits = {} -- cache of unit IDs, indexed by GUID
local refreshUnits = {} -- unit id cache used to optimize refresh
local tablePool = {} -- pool of available tables
local activeCooldowns = {} -- spells/items that are currently on cooldown
local internalCooldowns = {} -- tracking entries for internal cooldowns
local spellEffects = {} -- tracking entries for spell effects
local spellAlerts = {} -- tracking entries for spell alerts
local spellAlertCounter = 0 -- incremented with each spell alert
local spellAlertClassColors = nil -- set on first reference to table of class color hex strings
local lastTime = 0 -- time when last update happened
local lastTrackers = 0 -- time when last looked at trackers on major units
local lastWeapons = 0 -- time when last looked at weapon buffs
local elapsedTime = 0 -- time in seconds since last update
local updateCounter = 0 -- update counter included for testing
local refreshTime = 0 -- time since last animation refresh
local refreshCounter = 0 -- refresh counter included for testing
local throttleTime = 0 -- secondary throttle that resets once per second
local throttleCounter = 0 -- throttle counter included for testing
local throttleTracker = 0 -- throttle max count seen included for testing
local now = 0 -- refresh time value set at combat log and update events
local scanningTooltip = nil -- used to store tooltip for scanning
local mainHandLastBuff = nil -- saves name of most recent main hand weapon buff
local offHandLastBuff = nil -- saves name of most recent off hand weapon buff
local rangedLastBuff = nil -- saves name of most recent ranged weapon buff
local iconGCD = nil -- icon for global cooldown
local iconPotion = nil -- icon for shared potions cooldown
local iconElixir = nil -- icon for shared elixirs cooldown
local iconRune = nil -- icon for death knight runes
local lastTotems = {} -- cache last totems in each slot to see if changed
local lockedOut = false -- true if currently locked out of at least one spell school
local lockouts = {} -- schools of magic that we are currently locked out of
local lockstarts = {} -- start times for current school lockouts
local talentsInitialized = false -- set once talents have been initialized
local matchTable = {} -- passed from MOD:CheckAura with list of active auras
local startGCD, durationGCD = nil -- detect global cooldowns
local raidTargets = {} -- raid target to GUID
local petGUID = nil -- cache pet GUID so can properly remove trackers for them when dismissed
local enteredWorld = nil -- set by PLAYER_ENTERING_WORLD event
local trackerMarker = 0 -- used for mark/sweep in AddTrackers
local professions = {} -- temporary table for profession indices
local summonedCreatures = {} -- table of guids to expire time pairs used for tracking warlock creatures so they despawn properly
local minionTypes = {} -- temporary table for sorting minions by type
local minionCounts = {} -- temporary table for counting minions by type
local activeBrokers = {} -- table of brokers that trigger update events
local hiding = {} -- used to track elements of the UI so don't keep trying to show them
local bagCooldowns = {} -- table containing all the bag items with cooldowns
local inventoryCooldowns = {} -- table containing all the inventory items with cooldowns
local nullFunction = function() end -- used to disable Blizzard frames
local updateUIScale = false
local alertColors = { -- default colors for spell alerts
EnemySpellCastAlerts = { r = 1, g = 0, b = 0, a = 1 },
FriendSpellCastAlerts = { r = 0, g = 1, b = 0, a = 1 },
EnemyBuffAlerts = { r = 1, g = 1, b = 0, a = 1 },
FriendDebuffAlerts = { r = 1, g = 0, b = 1, a = 1 },
}
local UnitAura = UnitAura
MOD.LCD = nil
if MOD.ExpansionIsOrBelow(LE_EXPANSION_WRATH_OF_THE_LICH_KING) then
if C_AddOns.LoadAddOn == nil then
C_AddOns.LoadAddOn = LoadAddOn
end
MOD.LCD = LibStub("LibClassicDurations", true)
if MOD.LCD then
MOD.LCD:Register(Raven) -- tell library it's being used and should start working
-- UnitAura = MOD.LCD.UnitAuraWrapper
UnitAura = MOD.LCD.UnitAuraWithBuffs -- support buffs on enemy targets
end
end
local band = bit.band -- shortcut for common bit logic operator
-- Functions for reading Auras
function MOD:GetAuraData(unitToken, index, filter)
-- Rely on the LCD shim when reading target buffs and debuffs.
if MOD.isClassic and not MOD.isCata and unitToken == "target" then
return UnitAura(unitToken, index, filter)
end
if C_UnitAuras and C_UnitAuras.GetAuraDataByIndex and AuraUtil.UnpackAuraData then
return AuraUtil.UnpackAuraData(C_UnitAuras.GetAuraDataByIndex(unitToken, index, filter))
end
return UnitAura(unitToken, index, filter)
end
-- UnitAura no longer works with spell names in xxBfAxx so this function searches for them by scanning
-- While not the most efficient way to do this, it is generally used with a filter that should limit the depth of the search
-- This is only called for combat log events related to spell auras
function MOD.UnitAuraSpellName(unit, spellName, filter)
local name, icon, count, btype, duration, expire, caster, isStealable, nameplateShowSelf, spellID, apply, boss
if type(spellName) == "string" then -- sanity check only being called with a spell name
local i = 1
repeat
name, icon, count, btype, duration, expire, caster, isStealable, nameplateShowSelf, spellID, apply, boss = MOD:GetAuraData(unit, i, filter)
if name == spellName then
break
end
i = i + 1
until not name
end
return name, icon, count, btype, duration, expire, caster, isStealable, nameplateShowSelf, spellID, boss, apply
end
-- This table is used to fix the "not cast by player" bug for Jade Spirit, River's Song, and Dancing Steel introduced in 5.1
-- and the legendary meta gem procs Tempus Repit, Fortitude, Capacitance, and Lucidity added in 5.2
local fixEnchants = {
[104993] = true,
[120032] = true,
[118334] = true,
[118335] = true,
[116660] = true,
[137590] = true,
[137593] = true,
[137331] = true,
[137323] = true,
[137247] = true,
[137596] = true,
}
-- Initialization called when addon is loaded
function MOD:OnInitialize()
if addonInitialized then
return
end -- only run this code once
addonInitialized = true
MOD.localClass, MOD.myClass = UnitClass("player") -- cache the player's class
MOD.localRace, MOD.myRace = UnitRace("player") -- cache the player's race
C_AddOns.LoadAddOn("LibDataBroker-1.1")
C_AddOns.LoadAddOn("LibDBIcon-1.0")
C_AddOns.LoadAddOn("LibBossIDs-1.0", true)
MOD.MSQ = LibStub("Masque", true)
SHIM = MOD.SHIM
now = GetTime() -- start tracking time
suppressTime = now -- start suppression period for certain special effects
end
-- Print debug messages with variable number of arguments in a useful format
function MOD.Debug(a, ...)
if type(a) == "table" then
for k, v in pairs(a) do
print(tostring(k) .. " = " .. tostring(v))
end -- if first parameter is a table, print out its fields
else
local s = tostring(a) -- otherwise first argument is a string but just make sure
local parm = { ... }
for i = 1, #parm do
s = s .. " " .. tostring(parm[i])
end -- append remaining arguments converted to strings
print(s)
end
end
-- Hide or show a frame after checking settings
local function HideShow(key, frame, check, options)
if not frame then
return
end -- added because not supported in classic but okay regardless
local hideBlizz = MOD.db.profile.hideBlizz
local hide, show = false, false
local visible = frame:IsShown()
if visible then
if hideBlizz then
hide = check
end -- only hide if option for this frame is checked
else
if hideBlizz then
show = not check and hiding[key]
else
show = hiding[key]
end -- only show if Raven hid the frame
end
-- MOD.Debug("hide/show", key, "hide:", hide, "show:", show, "vis: ", visible)
if not options then
if hide then
frame:Hide()
frame.Show = nullFunction
hiding[key] = true
elseif show then
frame.Show = nil
frame:Show()
hiding[key] = false
end
elseif options == "noshow" then
if hide then
frame:Hide()
frame.Show = nullFunction
hiding[key] = true
elseif show then
frame.Show = nil
hiding[key] = false
end
elseif options == "unreg" then
if hide then
frame:Hide()
frame.Show = nullFunction, frame:UnregisterAllEvents()
hiding[key] = true
elseif show then
frame.Show = nil
frame:RegisterAllEvents()
hiding[key] = false
end
elseif options == "buffs" then
if hide then
BuffFrame:Hide()
if TemporaryEnchantFrame then
TemporaryEnchantFrame:Hide()
end
BuffFrame:UnregisterAllEvents()
hiding[key] = true
elseif show then
BuffFrame:Show()
if TemporaryEnchantFrame then
TemporaryEnchantFrame:Show()
end
BuffFrame:RegisterEvent("UNIT_AURA")
hiding[key] = false
end
elseif options == "debuffs" then
if hide then
DebuffFrame:Hide()
hiding[key] = true
elseif show then
DebuffFrame:Show()
hiding[key] = false
end
end
end
-- Show or hide the blizzard frames, called during update so synched with other changes
local function CheckBlizzFrames()
if MOD.ExpansionIsOrAbove(LE_EXPANSION_MISTS_OF_PANDARIA) and C_PetBattles.IsInBattle() then
return
end -- don't change visibility of any frame during pet battles
local p = MOD.db.profile
HideShow("buffs", _G.BuffFrame, p.hideBlizzBuffs, "buffs")
if MOD.ExpansionIsOrAbove(LE_EXPANSION_DRAGONFLIGHT) then
HideShow("debuffs", _G.DebuffFrame, p.hideBlizzDebuffs, "debuffs")
end
if _G.TemporaryEnchantFrame then
HideShow("enchants", _G.TemporaryEnchantFrame, p.hideBlizzBuffs, "enchants")
end
HideShow("player", _G.PlayerFrame, p.hideBlizzPlayer)
HideShow("castbar", _G.PlayerCastingBarFrame, p.hideBlizzPlayerCastBar, "noshow")
HideShow("mirror1", _G.MirrorTimer1, p.hideBlizzMirrors, "unreg")
HideShow("mirror2", _G.MirrorTimer2, p.hideBlizzMirrors, "unreg")
HideShow("mirror3", _G.MirrorTimer3, p.hideBlizzMirrors, "unreg")
if MOD.myClass == "DEATHKNIGHT" then
HideShow("runes", _G.RuneFrame, p.hideRunes)
end
local isDruid = (MOD.myClass == "DRUID")
local isCat = isDruid and (GetShapeshiftForm(2) == 2)
if isCat or (MOD.myClass == "ROGUE") then
HideShow("combo", _G.ComboPointPlayerFrame, p.hideBlizzComboPoints)
end
if isDruid and not isCat then
HideShow("combo", _G.ComboPointPlayerFrame, p.hideBlizzComboPoints, "noshow")
end
if MOD.myClass == "MONK" then
HideShow("chi", _G.MonkHarmonyBarFrame, p.hideBlizzChi)
if MOD.ExpansionIsOrAbove(LE_EXPANSION_MISTS_OF_PANDARIA) and GetSpecializationInfoByID(268) then
HideShow("stagger", _G.MonkStaggerBar, p.hideBlizzStagger)
end
end
if (MOD.myClass == "PRIEST") and (MOD.ExpansionIsOrAbove(LE_EXPANSION_MISTS_OF_PANDARIA) and GetSpecializationInfoByID(258)) then
HideShow("insanity", _G.InsanityBarFrame, p.hideBlizzInsanity)
end
if MOD.myClass == "WARLOCK" then
HideShow("shards", _G.WarlockPowerFrame, p.hideBlizzShards)
end
if MOD.myClass == "MAGE" then
HideShow("arcane", _G.MageArcaneChargesFrame, p.hideBlizzArcane)
end
if MOD.myClass == "PALADIN" then
HideShow("holy", _G.PaladinPowerBarFrame, p.hideBlizzHoly)
end
if MOD.myClass == "EVOKER" then
HideShow("essence", _G.EvokerPowerBarFrame, p.hideBlizzEssence)
end
local totems = false
for i = 1, MAX_TOTEMS do
if GetTotemInfo(i) then
totems = true
end
end
if totems then
HideShow("totems", _G.TotemFrame, p.hideBlizzTotems)
end
end
local function CheckCastBar(event, unit)
if unit == "player" then
HideShow("castbar", _G.PlayerCastingBarFrame, MOD.db.profile.hideBlizzPlayerCastBar, "noshow")
end
end
local function CheckMirrorFrames()
local p = MOD.db.profile
HideShow("mirror1", _G.MirrorTimer1, p.hideBlizzMirrors, "unreg")
HideShow("mirror2", _G.MirrorTimer2, p.hideBlizzMirrors, "unreg")
HideShow("mirror23", _G.MirrorTimer3, p.hideBlizzMirrors, "unreg")
end
-- Functions called to trigger updates
local function TriggerPlayerUpdate()
unitUpdate.player = true
updateCooldowns = true
doUpdate = true
end
local function TriggerCooldownUpdate()
updateCooldowns = true
doUpdate = true
end
local function TriggerActionsUpdate()
MOD.updateActions = true
doUpdate = true
end
function MOD:ForceUpdate()
doUpdate = true
forceUpdate = true
end
-- Event called when the player changes talents or specialization
local function CheckTalentSpecialization()
talentsInitialized = false
unitUpdate.player = true
doUpdate = true
end
-- Function called to detect global cooldowns
local function CheckGCD(event, unit, spell)
if unit == "player" and spell then
local name = SHIM:GetSpellInfo(spell) -- added verification of spell argument due to error seen while testing 1/1/2019
if name and (name ~= "") then
local start, duration = SHIM:GetSpellCooldown(spell)
if start and duration and (duration > 0) and (duration <= 1.5) then
startGCD = start
durationGCD = duration
TriggerCooldownUpdate()
end
end
end
if event == "UNIT_SPELLCAST_START" then
CheckCastBar(event, unit)
end
end
-- Function called for successful spell cast
local function CheckSpellCasts(event, unit, lineID, spellID)
CheckGCD(event, unit, spellID)
local name = SHIM:GetSpellInfo(spellID)
if name and (name ~= "") and MOD.db.global.DetectSpellEffects then
MOD:DetectSpellEffect(name, unit)
end -- check if spell triggers a spell effect
end
-- Create and delete routines for managing tables, using a recycling pool to minimize garbage collection
local function AllocateTable()
local b = next(tablePool)
if b then
tablePool[b] = nil
else
b = {}
end
return b
end
local function ReleaseTable(b)
table.wipe(b)
tablePool[b] = true
return nil
end
-- Compare unit and global ids, updating cache with latest info
local function CheckUnitIDs(uid, guid)
local id = UnitGUID(uid)
if id == guid then
return uid
end
if id then
cacheUnits[id] = uid
end
return nil
end
-- Add or update a tracker entry, including an optional marker useful for mark/sweep type garbage collection
local function AddTracker(dstGUID, dstName, isBuff, name, icon, count, btype, duration, expire, caster, isStealable, spellID, boss, apply, marker)
doUpdate = true
local tracker = isBuff and unitBuffs[dstGUID] or unitDebuffs[dstGUID] -- get or create the aura tracking table
if not tracker then
tracker = AllocateTable()
if isBuff then
unitBuffs[dstGUID] = tracker
else
unitDebuffs[dstGUID] = tracker
end
end
local id = name .. tostring(spellID or "") -- append spellID if known to the tracker so can track multiple with same name (e.g., sacred shield)
local t = tracker[id] -- get or create a tracker entry for the spell
if not t then
t = AllocateTable()
tracker[id] = t
end -- create the tracker if necessary
local vehicle = MOD.ExpansionIsOrAbove(LE_EXPANSION_CATACLYSM) and UnitHasVehicleUI("player")
local tag = isBuff and "T-Buff:" or "T-Debuff:" -- build a unique tag for this aura (this is a bit simpler than the AddAura version)
local guid = UnitGUID(caster)
if guid then
tag = tag .. guid .. ":"
elseif caster then
tag = tag .. caster .. ":"
end -- include caster in unique tag, prefer guid when it is known
if not tagUnits[caster or "unknown"] and expire and expire > 0 then
tag = tag .. tostring(math.floor((expire * 100) + 0.5)) .. ":"
end -- add expire time with 1/100s precision
if spellID then
tag = tag .. tostring(spellID) .. ":"
end
t[1], t[2], t[3], t[4], t[5], t[6], t[7], t[8], t[9], t[10], t[11], t[12], t[13], t[14], t[15], t[16], t[17], t[18], t[19], t[20], t[21], t[22] =
true,
0,
count,
btype,
duration,
caster,
isStealable,
icon,
tag,
expire,
"spell id",
spellID,
name,
spellID,
boss,
UnitName("player"),
apply,
nil,
vehicle,
dstGUID,
dstName,
marker
end
-- Remove tracker entries for a unit, if marker is specified then only remove if tracker tag not equal
function MOD:RemoveTrackers(dstGUID, marker)
doUpdate = true
local tracker = unitBuffs[dstGUID] -- table of buffs currently applied to this GUID
if tracker then
for id, t in pairs(tracker) do
if not marker or t[22] ~= marker then
tracker[id] = ReleaseTable(t)
end
end
if not next(tracker) then
unitBuffs[dstGUID] = ReleaseTable(tracker)
end -- release the debuffs associated with the GUID
end
local tracker = unitDebuffs[dstGUID] -- table of auras currently applied to this GUID
if tracker then
for id, t in pairs(tracker) do
if not marker or t[22] ~= marker then
tracker[id] = ReleaseTable(t)
end
end
if not next(tracker) then
unitDebuffs[dstGUID] = ReleaseTable(tracker)
end -- release the table associated with the GUID
end
end
-- Remove trackers for all units that match the name of the designated unit
function MOD:RemoveMatchingTrackers(dstGUID)
local name = nil
local tracker = unitBuffs[dstGUID] -- find name by looking at active trackers
if tracker then
for id, t in pairs(tracker) do
name = t[21]
if name then
break
end
end
end
if not name then
tracker = unitDebuffs[dstGUID]
if tracker then
for id, t in pairs(tracker) do
name = t[21]
if name then
break
end
end
end
end
MOD:RemoveTrackers(dstGUID) -- start by removing the trackers for the unit passed in
if name then
local guids = {} -- build list of guids to remove
for id, tracker in pairs(unitBuffs) do
if tracker then
for _, t in pairs(tracker) do
if t[21] == name then
guids[id] = true
break
end
end
end
end
for id, tracker in pairs(unitDebuffs) do
if tracker then
for _, t in pairs(tracker) do
if t[21] == name then
guids[id] = true
break
end
end
end
end
for id in pairs(guids) do
MOD:RemoveTrackers(id)
end
end
end
-- Check tracker entries for a unit to see if one already exists for a spell
local function CheckTrackers(isBuff, dstGUID, name, spellID)
local tracker = isBuff and unitBuffs[dstGUID] or unitDebuffs[dstGUID] -- get the aura tracking table
if tracker then
local id = name .. tostring(spellID or "") -- append spellID if known
local t = tracker[id]
if t then
if t[13] == name then
return t
end
end
end
return nil
end
-- Add trackers for a unit
function MOD:AddTrackers(unit)
local dstGUID, dstName = UnitGUID(unit), UnitName(unit)
if dstGUID and dstName and not refreshUnits[dstGUID] then
refreshUnits[dstGUID] = true
local name, icon, count, btype, duration, expire, caster, isStealable, _, spellID, boss, apply
trackerMarker = trackerMarker + 1 -- unique tag for this pass
local i = 1
repeat
name, icon, count, btype, duration, expire, caster, isStealable, _, spellID, apply = MOD:GetAuraData(unit, i, "HELPFUL|PLAYER")
if name and caster == "player" then
AddTracker(dstGUID, dstName, true, name, icon, count, btype, duration, expire, caster, isStealable, spellID, nil, apply, trackerMarker)
MOD.SetDuration(name, spellID, duration)
MOD.SetSpellType(spellID, btype)
end
i = i + 1
until not name
i = 1
repeat
name, icon, count, btype, duration, expire, caster, isStealable, _, spellID, apply, boss = MOD:GetAuraData(unit, i, "HARMFUL|PLAYER")
if name and caster == "player" then
if spellID ~= 146739 or duration ~= 0 or InCombatLockdown() then -- don't add Corruption if out-of-combat
AddTracker(dstGUID, dstName, false, name, icon, count, btype, duration, expire, caster, isStealable, spellID, boss, apply, trackerMarker)
MOD.SetDuration(name, spellID, duration)
MOD.SetSpellType(spellID, btype)
end
end
i = i + 1
until not name
MOD:RemoveTrackers(dstGUID, trackerMarker) -- takes advantage of side-effect of saving current trackerMarker with each tracker
end
end
-- Check if currently tracking a unit
local function IsBeingTracked(dstGUID)
return unitBuffs[dstGUID] and unitDebuffs[dstGUID]
end
-- Validate cached ids, garbage collect any that are out-of-date
local function ValidateUnitIDs()
for guid, uid in pairs(cacheUnits) do
if UnitGUID(uid) ~= guid then
cacheUnits[guid] = nil
end
end
end
-- Get a unit id suitable for calling UnitAura from a GUID
local function GetUnitIDFromGUID(guid)
if not guid then
return nil
end
local uid = cacheUnits[guid] -- look up the guid in the cache and if it is there make sure it is still valid and then return it
if uid then
if guid == UnitGUID(uid) then
return uid
else
uid = nil
end
end
for _, unit in ipairs(units) do
uid = CheckUnitIDs(unit, guid)
if uid then
break
end
end -- first check primary units
local inRaid, inGroup = IsInRaid(), IsInGroup()
if not uid then
if inRaid then
for i = 1, MAX_RAID_MEMBERS do
uid = CheckUnitIDs(raidUnits[i], guid)
if uid then
break
end
uid = CheckUnitIDs(raidUnitPets[i], guid)
if uid then
break
end
uid = CheckUnitIDs(raidUnitTargets[i], guid)
if uid then
break
end
end
elseif inGroup then
for i = 1, MAX_PARTY_MEMBERS do
uid = CheckUnitIDs(partyUnits[i], guid)
if uid then
break
end
uid = CheckUnitIDs(partyUnitPets[i], guid)
if uid then
break
end
uid = CheckUnitIDs(partyUnitTargets[i], guid)
if uid then
break
end
end
end
for i = 1, 40 do
local np = nameplateUnits[i]
local id = UnitGUID(np)
if not id then
break
end
if id == guid then
uid = np
break
end
end
end
cacheUnits[guid] = uid
return uid
end
-- Parse a guid into fields and return them in a table
local parseTable = {}
local function ParseGUID(guid)
table.wipe(parseTable) -- reused this since never nest calls to the function
local start = 1
local s = guid .. "-"
local length = string.len(s)
repeat
local nextdash = string.find(s, "-", start)
table.insert(parseTable, string.sub(s, start, nextdash - 1))
start = nextdash + 1
until start > length
return parseTable
end
local function SpellAlertFilter(alerts, spellName, spellID, srcFlags, dstGUID)
local spellNum = spellID and ("#" .. tostring(spellID)) -- string to look up the spell id in lists
local list = alerts.spellList and MOD.db.global.SpellLists[alerts.spellList]
local listed = list and (list[spellName] or (spellNum and list[spellNum])) -- check to see if spell is in the spell list
if (alerts.blackList and listed) or (not alerts.blackList and not listed) then
return false
end
local controlledBy = band(srcFlags, COMBATLOG_OBJECT_CONTROL_MASK)
local byPlayer = (controlledBy == COMBATLOG_OBJECT_CONTROL_PLAYER)
local byNPC = (controlledBy == COMBATLOG_OBJECT_CONTROL_NPC)
local srcTarget = (band(srcFlags, COMBATLOG_OBJECT_TARGET) == COMBATLOG_OBJECT_TARGET)
local srcFocus = (band(srcFlags, COMBATLOG_OBJECT_FOCUS) == COMBATLOG_OBJECT_FOCUS)
local dstTarget, dstFocus, dstPlayer = false, false, false
if dstGUID ~= "" then
dstTarget = (dstGUID == UnitGUID("target"))
dstFocus = (dstGUID == UnitGUID("focus"))
dstPlayer = (dstGUID == UnitGUID("player"))
end
-- MOD.Debug("alert!", spellName, dstGUID, byPlayer, byNPC, srcTarget, srcFocus, dstTarget, dstFocus, dstPlayer)
if alerts.include then
local found = (alerts.isTarget and srcTarget)
or (alerts.isFocus and srcFocus)
or (alerts.isPlayer and byPlayer)
or (alerts.isNPC and byNPC)
or (alerts.includeTarget and dstTarget)
or (alerts.includeFocus and dstFocus)
or (alerts.includePlayer and dstPlayer)
if not found then
return false
end
end
if alerts.exclude then
local found = (alerts.notTarget and srcTarget)
or (alerts.notFocus and srcFocus)
or (alerts.notPlayer and byPlayer)
or (alerts.notNPC and byNPC)
or (alerts.excludeTarget and dstTarget)
or (alerts.excludeFocus and dstFocus)
or (alerts.excludePlayer and dstPlayer)
if found then
return false
end
end
return true
end
local function AddSpellAlert(alertType, event, spellName, spellID, srcName, srcGUID, dstName, dstGUID)
local alert = AllocateTable()
alert.alertType = alertType
alert.event = event
alert.start = now
alert.duration = MOD.db.global.SpellAlerts.duration or 3
alert.expire = now + alert.duration
alert.spellName = spellName
alert.spellID = spellID
alert.icon = MOD:GetIcon(spellName, spellID)
alert.srcName = srcName
alert.srcGUID = srcGUID
alert.srcUnit = GetUnitIDFromGUID(srcGUID)
alert.dstName = dstName
alert.dstGUID = dstGUID
alert.dstUnit = GetUnitIDFromGUID(dstGUID)
spellAlertCounter = spellAlertCounter + 1
spellAlerts[spellAlertCounter] = alert
-- MOD.Debug("alert", spellAlertCounter, alert.alertType, alert.event, alert.spellName, alert.icon, alert.srcName, alert.dstName)
TriggerPlayerUpdate()
end
-- Remove any spell cast alerts for the guid
local function EndCastAlert(guid)
for id, alert in pairs(spellAlerts) do
if (alert.srcGUID == guid) and (alert.event == "SPELL_CAST_START") then
spellAlerts[id] = ReleaseTable(alert)
TriggerPlayerUpdate()
end
end
end
-- Remove any spell alert entries that have expired
local function CheckSpellAlerts()
for id, alert in pairs(spellAlerts) do
if now >= alert.expire then
spellAlerts[id] = ReleaseTable(alert)
TriggerPlayerUpdate()
end
end
end
-- Get label and color info for the spell alert
local function GetSpellAlertInfo(alert)
local opts = MOD.db.global.SpellAlerts
local label, spacer, showTarget, color = "", "", opts.labelTarget, alert.color
local caster = alert.srcName
local target = alert.dstName
if not opts.showRealm then
if caster then
local i = string.find(caster, "-", 1, true)
if i and (i > 1) then
caster = string.sub(caster, 1, i - 1)
end
end
if target then
local i = string.find(target, "-", 1, true)
if i and (i > 1) then
target = string.sub(target, 1, i - 1)
end
end
end
if not spellAlertClassColors then
spellAlertClassColors = {} -- generate table of class colors
for class, c in pairs(RAID_CLASS_COLORS) do
spellAlertClassColors[class] = string.format("%02x%02x%02x", c.r * 255, c.g * 255, c.b * 255)
end
end
if opts.labelSpells then
label = alert.spellName
spacer = " : "
end
if opts.labelCaster and caster then
if alert.srcUnit then
if opts.nameUnit then
caster = alert.srcUnit
end
local _, class = UnitClass(alert.srcUnit)
if class then
local s = spellAlertClassColors[class]
if s then
caster = "|cff" .. s .. caster .. "|r"
end
end
end
label = label .. spacer .. caster
spacer = " > "
if opts.casterMatch and (alert.srcGUID == alert.dstGUID) then
label = label .. " <<"
showTarget = false
end
end
if opts.ignoreTargets and opts.ignoreList then
local list = MOD.db.global.SpellLists[opts.ignoreList]
local listed = list and (list[alert.spellName] or list[alert.spellID]) -- check to see if spell is in the ignore list
if listed then
showTarget = false
end
end
if showTarget and target then
if alert.dstUnit then
if opts.nameUnit then
target = alert.dstUnit
end
local _, class = UnitClass(alert.dstUnit)
if class then
local s = spellAlertClassColors[class]
if s then
target = "|cff" .. s .. target .. "|r"
end
end
end
label = label .. spacer .. target
end
if not color then
color = alertColors[alert.alertType]
end
return color, label
end
local eventKill = { UNIT_DIED = true, UNIT_DESTROYED = true, UNIT_DISSIPATES = true, PARTY_KILL = true, SPELL_INSTAKILL = true }
local eventAura = { SPELL_AURA_APPLIED = true, SPELL_AURA_APPLIED_DOSE = true, SPELL_AURA_REMOVED_DOSE = true, SPELL_AURA_REFRESH = true }
local eventInternal = { SPELL_AURA_APPLIED = true, SPELL_AURA_APPLIED_DOSE = true, SPELL_AURA_REFRESH = true, SPELL_ENERGIZE = true, SPELL_HEAL = true }
local eventEndCast = { SPELL_CAST_START = true, SPELL_CAST_SUCCESS = true, SPELL_CAST_FAILED = true, SPELL_MISSED = true }
-- Function called for combat log events to track hots and dots
local function CombatLogTracker() -- no longer passes in arguments with the event
local timeStamp, e, hc, srcGUID, srcName, sf1, sf2, dstGUID, dstName, df1, df2, spellID, spellName, spellSchool, auraType, amount = CombatLogGetCurrentEventInfo()
local isMine = band(sf1, COMBATLOG_OBJECT_AFFILIATION_MASK) == COMBATLOG_OBJECT_AFFILIATION_MINE
if isMine then -- make sure event controlled by the player