forked from Tojaso/Raven
-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathMain.lua
2314 lines (2068 loc) · 107 KB
/
Main.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
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.isClassic = MOD.isWrath or MOD.isVanilla or MOD.isCata
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 partyUnits = { "party1", "party2", "party3", "party4" } -- optional party units
local bossUnits = { "boss1", "boss2", "boss3", "boss4", "boss5" } -- optional boss units
local arenaUnits = { "arena1", "arena2", "arena3", "arena4", "arena5" } -- optional arena units
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 buffTooltip = nil -- used to store tooltip for scanning weapon buffs
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
for i = 1, 100 do
name, icon, count, btype, duration, expire, caster, isStealable, nameplateShowSelf, spellID, apply, boss = MOD:GetAuraData(unit, i, filter)
if name == spellName then break end
end
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 = IsInRaid()
if not uid and not inRaid then -- check party, party pet, and party target units
for i = 1, GetNumGroupMembers() do
uid = CheckUnitIDs("party"..i, guid); if uid then break end
uid = CheckUnitIDs("partypet"..i, guid); if uid then break end
uid = CheckUnitIDs("party"..i.."target", guid); if uid then break end
end
end
if not uid and inRaid then -- check raid, raid pet, and raid target units
for i = 1, GetNumGroupMembers() do
uid = CheckUnitIDs("raid"..i, guid); if uid then break end
uid = CheckUnitIDs("raidpet"..i, guid); if uid then break end
uid = CheckUnitIDs("raid"..i.."target", guid); if uid then break end
end
end
if not uid then -- check nameplates as last resort
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
-- MOD.Debug(e, srcGUID, srcName, sf1, sf2, dstGUID, dstName, df1, df2, spellID, spellName, spellSchool, auraType, tostring(amount)) -- display all events
doUpdate = true
now = GetTime()
if e == "SPELL_CAST_SUCCESS" or e == "SPELL_CAST_FAILED" then -- check for special cases involving spell casts
if spellID == 104318 then
local tyrant = false
for guid, gt in pairs(summonedCreatures) do if gt.spell == 265187 then tyrant = true end end
if not tyrant then -- if tyrant is not active then all imps reduce their energy by 1, if they reach 0 then remove them
local gt = summonedCreatures[srcGUID]
if gt and gt.energy then -- only imps have energy limit field defined
gt.energy = gt.energy - 1
if gt.energy <= 0 then summonedCreatures[srcGUID] = ReleaseTable(gt) end -- delete entry for this imp
end
end
end
if e == "SPELL_CAST_SUCCESS" then
if spellID == 33763 then
e = "SPELL_AURA_APPLIED"; auraType = "BUFF" -- Lifebloom refreshes don't always generate aura applied events
elseif spellID == 265187 then -- summon demonic tyrant extends duration of all warlock minions
for guid, gt in pairs(summonedCreatures) do
gt.expire = gt.expire + 15; gt.duration = gt.duration + 15
end
elseif spellID == 196277 then -- implosion destroys all current warlock wild imps
for guid, gt in pairs(summonedCreatures) do
local pt = ParseGUID(guid)
if pt[1] == "Creature" and ((pt[6] == "55659") or (pt[6] == "143622")) then -- found a wild imp!
summonedCreatures[guid] = ReleaseTable(gt)
end
end
elseif spellID == 980 then -- Agony refresh does not always generate aura refresh event, even if debuff just expired
local t = CheckTrackers(false, dstGUID, spellName, spellID)
if t then
t[10] = now + t[5] -- extend the time on current tracker (preserves the dose amount)
else
e = "SPELL_AURA_REFRESH" -- event not generated automatically by Agony
end
end
end
elseif eventAura[e] then
local name, icon, count, btype, duration, expire, caster, isStealable, boss, sid, apply, _
local isBuff, dst = true, GetUnitIDFromGUID(dstGUID)
if dst and UnitExists(dst) then
name, icon, count, btype, duration, expire, caster, isStealable, _, sid, apply = MOD.UnitAuraSpellName(dst, spellName, "HELPFUL|PLAYER")
if not name and (srcGUID ~= dstGUID) then -- don't get debuffs cast by player on self (e.g., Sated)
isBuff = false
name, icon, count, btype, duration, expire, caster, isStealable, _, sid, apply, boss = MOD.UnitAuraSpellName(dst, spellName, "HARMFUL|PLAYER")
end
if sid and spellID and spellID ~= sid then name = nil end -- not a match so must be a duplicate name
if name then MOD.SetDuration(name, spellID, duration); MOD.SetSpellType(spellID, btype) end
end
if not spellID then spellID = MOD:GetSpellID(spellName) end
if spellID and not icon then icon = MOD:GetIcon(spellName, spellID) end
if not name then
name = spellName; count = 1; btype = MOD.GetSpellType(spellID); duration = MOD.GetDuration(name, spellID); isBuff = (auraType == "BUFF")
if duration > 0 then expire = now + duration else duration = 0; expire = 0 end
if e == "SPELL_AURA_APPLIED_DOSE" or e == "SPELL_AURA_REMOVED_DOSE" then -- may be refresh of existing spell's stack count (e.g., Agony)
count = amount
local t = CheckTrackers(isBuff, dstGUID, name, spellID)
if t then duration = t[5]; expire = t[10]; btype = t[4] end
end
caster = "player"; isStealable = nil; boss = nil; apply = nil
end
if name and caster == "player" and (isBuff or (srcGUID ~= dstGUID)) then
AddTracker(dstGUID, dstName, isBuff, name, icon, count, btype, duration, expire, caster, isStealable, spellID, boss, apply, nil)
end
if dstGUID == UnitGUID("target") and not IsBeingTracked(dstGUID) then ValidateUnitIDs() end -- refresh all auras when target changes
if MOD.db.global.DetectInternalCooldowns then MOD:DetectInternalCooldown(spellName, false) end -- check internal cooldowns
elseif e == "SPELL_ENERGIZE" or e == "SPELL_HEAL" then
if MOD.db.global.DetectInternalCooldowns then MOD:DetectInternalCooldown(spellName, false) end -- check internal cooldowns
elseif e == "SPELL_AURA_REMOVED" then
local tracker = unitBuffs[dstGUID] -- table of buffs currently applied to this GUID
if tracker then
local id = spellName .. tostring(spellID or "")
local t = tracker[id] -- get tracker entry for the spell, if one exists
if t then tracker[id] = ReleaseTable(t) end -- release the tracker entry
if not next(tracker) then unitBuffs[dstGUID] = ReleaseTable(tracker) end -- release table when no more entries for this GUID
end
tracker = unitDebuffs[dstGUID] -- table of debuffs currently applied to this GUID
if tracker then
local id = spellName .. tostring(spellID or "")
local t = tracker[id] -- get tracker entry for the spell, if one exists
if t then tracker[id] = ReleaseTable(t) end -- release the tracker entry
if not next(tracker) then unitDebuffs[dstGUID] = ReleaseTable(tracker) end -- release table when no more entries for this GUID
end
elseif e == "SPELL_SUMMON" then
if MOD.myClass == "MAGE" and spellID == 99063 then -- special case for mage T12 2-piece
local name = SHIM:GetSpellInfo(99061) -- T12 bonus spell name
if name and name ~= "" then
if MOD.db.global.DetectInternalCooldowns then MOD:DetectInternalCooldown(name, false) end
if MOD.db.global.DetectSpellEffects then MOD:DetectSpellEffect(name, "player") end
end
elseif MOD.myClass == "WARLOCK" and dstGUID and spellID then
local duration = MOD.warlockCreatures[spellID]
if duration then
local gt = AllocateTable() -- use table pool for minion tracking
gt.expire = duration + now; gt.duration = duration; gt.name = dstName; gt.icon = SHIM:GetSpellTexture(spellID); gt.spell = spellID
if duration == 22 then gt.energy = 5 end -- imps have 22 second duration and also are subject to energy limit for 5 casts
summonedCreatures[dstGUID] = gt -- summoned creature table contains expire time, duration, name and icon
end
end
end
elseif dstGUID == UnitGUID("player") then
if eventInternal[e] then
if MOD.db.global.DetectInternalCooldowns then MOD:DetectInternalCooldown(spellName, true) end -- check aura triggers or cancels an internal cooldown
end
end
if eventKill[e] then
MOD:RemoveTrackers(dstGUID) -- remove the trackers currently associated with this GUID
cacheUnits[dstGUID] = nil -- release the unit cache entry for this GUID
local gt = summonedCreatures[dstGUID] -- remove GUID if on minion list for warlocks (probably only fires if someone kills a minion)
if gt then summonedCreatures[dstGUID] = ReleaseTable(gt) end -- only release table when entry found
end
if MOD.db.global.DetectSpellAlerts and spellID and not isMine then -- check for spell alerts only if have a spell id and non-player event
local stat, opts, pst = MOD.status, MOD.db.global.SpellAlerts, "solo"
if GetNumGroupMembers() > 0 then if IsInRaid() then pst = "raid" else pst = "party" end end
if ((stat.inArena and opts.showArena) or ((pst == "solo") and opts.showSolo) or ((pst == "party") and opts.showParty) or ((pst == "raid") and opts.showRaid)) and
(stat.inInstance or opts.showNotInstance) then -- check if spell alerts are enabled given player's current status
if eventEndCast[e] then EndCastAlert(srcGUID) elseif eventKill[e] then EndCastAlert(dstGUID) end -- end spell cast alerts when complete or interrupted
if (e == "SPELL_CAST_SUCCESS") or ((e == "SPELL_CAST_START") and not MOD.db.global.SpellAlerts.hideCasting) then
local reaction = band(sf1, COMBATLOG_OBJECT_REACTION_MASK)
if MOD.db.global.EnemySpellCastAlerts.enabled and (reaction == COMBATLOG_OBJECT_REACTION_HOSTILE) then -- check for enemy spell casts
if SpellAlertFilter(MOD.db.global.EnemySpellCastAlerts, spellName, spellID, sf1, dstGUID) then
AddSpellAlert("EnemySpellCastAlerts", e, spellName, spellID, srcName, srcGUID, dstName, dstGUID)
end
end
if MOD.db.global.FriendSpellCastAlerts.enabled and (reaction == COMBATLOG_OBJECT_REACTION_FRIENDLY) then -- check for friend spell casts
if SpellAlertFilter(MOD.db.global.FriendSpellCastAlerts, spellName, spellID, sf1, dstGUID) then
AddSpellAlert("FriendSpellCastAlerts", e, spellName, spellID, srcName, srcGUID, dstName, dstGUID)
end
end
elseif e == "SPELL_AURA_APPLIED" then
local reaction = band(df1, COMBATLOG_OBJECT_REACTION_MASK)
if (auraType == "BUFF") and MOD.db.global.EnemyBuffAlerts.enabled and (reaction == COMBATLOG_OBJECT_REACTION_HOSTILE) then -- check for buffs on enemies
if SpellAlertFilter(MOD.db.global.EnemyBuffAlerts, spellName, spellID, sf1, dstGUID) then
AddSpellAlert("EnemyBuffAlerts", e, spellName, spellID, srcName, srcGUID, dstName, dstGUID)
end
end
if (auraType == "DEBUFF") and MOD.db.global.FriendDebuffAlerts.enabled and (reaction == COMBATLOG_OBJECT_REACTION_FRIENDLY) then -- check for debuffs on friends
if SpellAlertFilter(MOD.db.global.FriendDebuffAlerts, spellName, spellID, sf1, dstGUID) then
AddSpellAlert("FriendDebuffAlerts", e, spellName, spellID, srcName, srcGUID, dstName, dstGUID)
end
end
end
end
end
end
-- Check if there is a raid target on a unit
local function CheckRaidTarget(unit)
local id = UnitGUID(unit)
if id then
local index = GetRaidTargetIndex(unit)
for k, v in pairs(raidTargets) do if (v == id) and (k ~= index) then raidTargets[k] = nil end end
if index then raidTargets[index] = id end
end
end
-- Check raid targets on all addressable units
local function CheckRaidTargets()
doUpdate = true
for _, unit in pairs(units) do CheckRaidTarget(unit) end -- first check primary units
if IsInRaid() then
for i = 1, GetNumGroupMembers() do CheckRaidTarget("raid"..i); CheckRaidTarget("raidpet"..i); CheckRaidTarget("raid"..i.."target") end
else
for i = 1, GetNumGroupMembers() do CheckRaidTarget("party"..i); CheckRaidTarget("partypet"..i); CheckRaidTarget("party"..i.."target") end
end
end
-- Check raid target on mouseover unit
local function CheckMouseoverRaidTarget() CheckRaidTarget("mouseover"); CheckRaidTarget("mouseovertarget"); doUpdate = true end
-- Return the raid target index for a GUID
function MOD:GetRaidTarget(id) for k, v in pairs(raidTargets) do if v == id then return k end end return nil end
-- When UI Scale changes need to recalculate pixel perfect settings and force a complete update
function UIScaleChanged() updateUIScale = true end
-- Event called when addon is enabled
function MOD:OnEnable()
if addonEnabled then return end -- only run this code once
addonEnabled = true
MOD:InitializeProfile() -- initialize the profile database
MOD:InitializeLDB() -- initialize the data broker
MOD:RegisterChatCommand("raven", function() MOD:OptionsPanel() end)
MOD.Nest_Initialize() -- initialize the graphics module
MOD:InitializeConditions() -- initialize condition evaluation module
MOD:InitializeValues() -- initialize functions used for value bars
MOD:BAG_UPDATE("OnEnable") -- initialize bag cooldowns
MOD:UNIT_INVENTORY_CHANGED("OnEnable", "player") -- initialize inventory cooldowns
-- Create a frame so that updates can be registered
MOD.frame = CreateFrame("Frame")
-- Set frame level high so visible above other addons
MOD.frame:SetFrameLevel(MOD.frame:GetFrameLevel() + 8)
-- Register events called prior to starting play
self:RegisterEvent("PLAYER_ENTERING_WORLD")
self:RegisterEvent("UNIT_AURA")
self:RegisterEvent("UNIT_POWER_UPDATE")
self:RegisterEvent("UNIT_PET")
self:RegisterEvent("UNIT_TARGET")
self:RegisterEvent("PLAYER_TARGET_CHANGED")
self:RegisterEvent("SPELLS_CHANGED")
self:RegisterEvent("BAG_UPDATE")
self:RegisterEvent("UNIT_INVENTORY_CHANGED")
self:RegisterEvent("RAID_TARGET_UPDATE", CheckRaidTargets)
self:RegisterEvent("UPDATE_MOUSEOVER_UNIT", CheckMouseoverRaidTarget)
self:RegisterEvent("UPDATE_SHAPESHIFT_FORM", TriggerPlayerUpdate)
self:RegisterEvent("MINIMAP_UPDATE_TRACKING", TriggerPlayerUpdate)
self:RegisterEvent("SPELL_UPDATE_COOLDOWN", TriggerCooldownUpdate)
self:RegisterEvent("ACTIONBAR_UPDATE_COOLDOWN", TriggerCooldownUpdate)
self:RegisterEvent("BAG_UPDATE_COOLDOWN", TriggerCooldownUpdate)
self:RegisterEvent("PET_BAR_UPDATE_COOLDOWN", TriggerCooldownUpdate)
self:RegisterEvent("ACTIONBAR_SLOT_CHANGED", TriggerActionsUpdate)
self:RegisterEvent("ACTIONBAR_PAGE_CHANGED", TriggerActionsUpdate)
self:RegisterEvent("PLAYER_TOTEM_UPDATE", TriggerPlayerUpdate)
self:RegisterEvent("MIRROR_TIMER_START", CheckMirrorFrames)
self:RegisterEvent("UNIT_SPELLCAST_START", CheckGCD)
self:RegisterEvent("UNIT_SPELLCAST_SUCCEEDED", CheckSpellCasts)
self:RegisterEvent("UNIT_SPELLCAST_CHANNEL_START", CheckCastBar)
self:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED", CombatLogTracker)
self:RegisterEvent("UI_SCALE_CHANGED", UIScaleChanged)
if MOD.ExpansionIsOrBelow(LE_EXPANSION_WRATH_OF_THE_LICH_KING) then -- register events specific to classic
if MOD.LCD then -- in classic, add library callback so target auras are handled correctly
MOD.LCD.RegisterCallback(Raven, "UNIT_BUFF", function(e, unit)
if unit ~= "target" then return end
MOD:UNIT_AURA(e, unit)
end)
end
else -- register events that are not implemented in classic
self:RegisterEvent("PLAYER_FOCUS_CHANGED")
self:RegisterEvent("PLAYER_TALENT_UPDATE", CheckTalentSpecialization)
self:RegisterEvent("TRAIT_CONFIG_UPDATED", CheckTalentSpecialization)
self:RegisterEvent("PLAYER_SPECIALIZATION_CHANGED", CheckTalentSpecialization)
self:RegisterEvent("VEHICLE_UPDATE")
self:RegisterEvent("RUNE_POWER_UPDATE", TriggerCooldownUpdate)
end
MOD:InitializeBars() -- initialize routine that manages the bar library
MOD:InitializeMedia(media) -- add sounds to LibSharedMedia
MOD.LibBossIDs = LibStub("LibBossIDs-1.0", true)
MOD.db.global.Version = "7" -- version number for database validation
end
-- Event called when addon is disabled but this is probably never called
function MOD:OnDisable() end
-- Cache icons for special purposes such as shared cooldowns
local function InitializeIcons()
iconGCD = SHIM:GetSpellTexture(28730) -- cached for global cooldown (using same icon as Arcane Torrent, must be valid)
iconPotion = SHIM:GetItemIconByID(31677) -- icon for shared potions cooldown
iconElixir = SHIM:GetItemIconByID(28104) -- icon for shared elixirs cooldown
MOD:SetIcon(L["Rune"], SHIM:GetSpellTexture(48266)) -- cached for death knight runes (this is for Frost Presence)
end
-- Updates will be driven by the new timer function, compute elapsed time since last update
local function UpdateHandler()
now = GetTime()
local elapsed = now - lastTime -- seconds since last call to update
if elapsed > 1.0 then elapsed = 1.0 end -- should only happen during initialization
MOD:Update(elapsed)
lastTime = now
C_Timer.After(0.001, UpdateHandler) -- register to be called for next frame
end
-- Initialize list of units that are tracked
function MOD:InitializeUnits()
table.wipe(units)
for i, k in pairs(mainUnits) do units[i] = k end
if MOD.db.global.IncludePartyUnits then for _, k in pairs(partyUnits) do table.insert(units, k) end end
if MOD.db.global.IncludeBossUnits then for _, k in pairs(bossUnits) do table.insert(units, k) end end
if MOD.db.global.IncludeArenaUnits then for _, k in pairs(arenaUnits) do table.insert(units, k) end end
for i = 1, 40 do nameplateUnits[i] = "nameplate"..i end
end
-- Initialize when play starts, deferred to allow system initialization to complete
function MOD:PLAYER_ENTERING_WORLD()
if not enteredWorld then
MOD:InitializeUnits() -- initialize list of units to track (this requires /reload to update)
for _, k in pairs(units) do -- initialize tables used to track each unit's status and auras
unitUpdate[k] = true; activeBuffs[k] = {} activeDebuffs[k] = {}
tagBuffs[k] = {}; tagDebuffs[k] = {}; cacheBuffs[k] = {}; cacheDebuffs[k] = {}
end
updateCooldowns = true -- start tracking cooldowns
MOD:InitializeBuffTooltip() -- initialize tooltip used to monitor weapon buffs
InitializeIcons() -- cache special purpose icons
MOD:InitializeOverlays() -- initialize overlays used to cancel player buffs
MOD:InitializeInCombatBar() -- initialize special bar for cancelling buffs in combat
MOD:UpdateAllBarGroups() -- final update before starting event-based updates
CheckBlizzFrames() -- check blizz frames and hide the ones selected on the Defaults tab
enteredWorld = true; doUpdate = true
UpdateHandler() -- register for calls on every frame
end
if not InCombatLockdown() then collectgarbage("collect") end -- recover deleted preset data but not if in combat
end
-- Event called when an aura changes on a unit, returns the unit name
function MOD:UNIT_AURA(e, unit)
if unit and (unitUpdate[unit] ~= nil) then
if unit == "vehicle" then unitUpdate.player = true end -- any time vehicle updates, also update player
unitUpdate[unit] = true; doUpdate = true
end
end
-- Event called when a unit's power changes
function MOD:UNIT_POWER_UPDATE(e, unit) if unit == "player" then unitUpdate[unit] = true; doUpdate = true end end