-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathMSBTTriggers.lua
More file actions
902 lines (732 loc) · 38.9 KB
/
MSBTTriggers.lua
File metadata and controls
902 lines (732 loc) · 38.9 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
-------------------------------------------------------------------------------
-- Title: Mik's Scrolling Battle Text Triggers
-- Author: Mikord
-------------------------------------------------------------------------------
-- Create module and set its name.
local module = {}
local moduleName = "Triggers"
MikSBT[moduleName] = module
-------------------------------------------------------------------------------
-- Imports.
-------------------------------------------------------------------------------
-- Local references to various modules for faster access.
local MSBTProfiles = MikSBT.Profiles
local MSBTParser = MikSBT.Parser
-- Get local references to various functions for faster access.
local string_find = string.find
local string_gsub = string.gsub
local string_format = string.format
local string_gmatch = string.gmatch
local FormatLargeNumber = FormatLargeNumber
local GetSpellInfo = GetSpellInfo
local Print = MikSBT.Print
local EraseTable = MikSBT.EraseTable
local DisplayEvent = MikSBT.Animations.DisplayEvent
local TestFlagsAny = MSBTParser.TestFlagsAny
local ShortenNumber = MikSBT.ShortenNumber
--local SeparateNumber = MikSBT.SeparateNumber
-- Local reference to various variables for faster access.
local REACTION_HOSTILE = MSBTParser.REACTION_HOSTILE
local unitMap = MSBTParser.unitMap
local classMap = MSBTParser.classMap
-------------------------------------------------------------------------------
-- Constants.
-------------------------------------------------------------------------------
-- Special flag to indicate the player.
local FLAG_YOU = 0xF0000000
-- Max group sizes.
local MAX_PARTY_MEMBERS = 5
local MAX_RAID_MEMBERS = 40
-------------------------------------------------------------------------------
-- Private variables.
-------------------------------------------------------------------------------
-- Prevent tainting global _.
local _
-- Holds dynamically created frame for receiving events.
local eventFrame
-- Holds the player's name, GUID, and class.
local playerName, playerGUID, playerClass
-- Events the triggers use.
local listenEvents = {}
-- Functions to handle combat log events and conditions.
local captureFuncs
local testFuncs
local eventConditionFuncs
local exceptionConditionFuncs
-- Holds triggers in a format optimized for searching.
local categorizedTriggers = {}
local triggerExceptions = {}
local parserEvent = {}
local lookupTable = {}
-- Information about triggers used for condition checking.
local lastPercentages = {}
local lastPowerTypes = {}
local firedTimes = {}
local triggersToFire = {}
-- Hold buffs and debuffs that should be suppressed since there is a trigger for them.
local triggerSuppressions = {}
-- Supported power types given a power token.
local powerTypes = {}
-------------------------------------------------------------------------------
-- Trigger utility functions.
-------------------------------------------------------------------------------
-- ****************************************************************************
-- Returns whether or not the passed spell name is unavailable.
-- ****************************************************************************
local function IsSkillUnavailable(skillName)
-- Pass if there is no skill to check.
if (not skillName or skillName == "") then return true end
-- Pass if the skill isn't known.
if (not GetSpellInfo(skillName)) then return true end
-- Pass check if the skillName is cooling down (but ignore the global cooldown).
local start, duration = GetSpellCooldown(skillName)
if (start > 0 and duration > 1.5) then return true end
end
-- ****************************************************************************
-- Creates a map of test functions for supported test types.
-- ****************************************************************************
local function CreateTestFuncs()
testFuncs = {
eq = function(l, r) return l == r end,
ne = function(l, r) return l ~= r end,
like = function(l, r) return type(l)=="string" and type(r)=="string" and string_find(l, r) end,
unlike = function(l, r) return type(l)=="string" and type(r)=="string" and not string_find(l, r) end,
lt = function(l, r) return type(l)=="number" and type(r)=="number" and l < r end,
gt = function(l, r) return type(l)=="number" and type(r)=="number" and l > r end,
}
end
-- ****************************************************************************
-- Creates a map of capture functions for supported combat log events.
-- Also makes use of the ones already defined in the parser module.
-- ****************************************************************************
local function CreateCaptureFuncs()
captureFuncs = {
-- Leave out eventType because we really don't care about it for triggers.
SPELL_AURA_BROKEN_SPELL = function (p, ...) p.skillID, p.skillName, p.skillSchool, p.extraSkillID, p.extraSkillName, p.extraSkillSchool, p.auraType = ... end,
SPELL_AURA_REFRESH = function (p, ...) p.skillID, p.skillName, p.skillSchool, p.auraType = ... end,
SPELL_CAST_SUCCESS = function (p, ...) p.skillID, p.skillName, p.skillSchool = ... end,
SPELL_CAST_FAILED = function (p, ...) p.skillID, p.skillName, p.skillSchool, p.missType = ... end,
SPELL_SUMMON = function (p, ...) p.skillID, p.skillName, p.skillSchool = ... end,
SPELL_CREATE = function (p, ...) p.skillID, p.skillName, p.skillSchool = ... end,
UNIT_DIED = function (p, ...) end,
UNIT_DESTROYED = function (p, ...) end,
}
-- Make use of the parser module capture functions instead of redefining them.
captureFuncs.__index = MSBTParser.captureFuncs
setmetatable(captureFuncs, captureFuncs)
end
-- ****************************************************************************
-- Creates maps of functions for supported conditions.
-- ****************************************************************************
local function CreateConditionFuncs()
-- Event conditions.
eventConditionFuncs = {
-- Source unit.
sourceName = function (f, t, v) return f(t.sourceName, v) end,
sourceAffiliation = function (f, t, v) if (v == FLAG_YOU) then return f(t.sourceUnit, "player") else return f(TestFlagsAny(t.sourceFlags, v), true) end end,
sourceReaction = function (f, t, v) return f(TestFlagsAny(t.sourceFlags, v), true) end,
sourceControl = function (f, t, v) return f(TestFlagsAny(t.sourceFlags, v), true) end,
sourceUnitType = function (f, t, v) return f(TestFlagsAny(t.sourceFlags, v), true) end, -- player, NPC, pet, guardian, object
-- Recipient unit.
recipientName = function (f, t, v) return f(t.recipientName, v) end,
recipientAffiliation = function (f, t, v) if (v == FLAG_YOU) then return f(t.recipientUnit, "player") else return f(TestFlagsAny(t.recipientFlags, v), true) end end,
recipientReaction = function (f, t, v) return f(TestFlagsAny(t.recipientFlags, v), true) end,
recipientControl = function (f, t, v) return f(TestFlagsAny(t.recipientFlags, v), true) end,
recipientUnitType = function (f, t, v) return f(TestFlagsAny(t.recipientFlags, v), true) end,
-- Skill.
skillID = function (f, t, v) return f(t.skillID, v) end,
skillName = function (f, t, v) return f(t.skillName, v) end,
skillSchool = function (f, t, v) return f(t.skillSchool, v) end,
-- Extra skill.
extraSkillID = function (f, t, v) return f(t.extraSkillID, v) end,
extraSkillName = function (f, t, v) return f(t.extraSkillName, v) end,
extraSkillSchool = function (f, t, v) return f(t.extraSkillSchool, v) end,
-- Damage/heal.
amount = function (f, t, v) return f(t.amount, v) end,
overkillAmount = function (f, t, v) return f(t.overkillAmount, v) end,
damageType = function (f, t, v) return f(t.damageType, v) end,
resistAmount = function (f, t, v) return f(t.resistAmount, v) end,
blockAmount = function (f, t, v) return f(t.blockAmount, v) end,
absorbAmount = function (f, t, v) return f(t.absorbAmount, v) end,
isCrit = function (f, t, v) return f(t.isCrit and true or false, v) end,
isGlancing = function (f, t, v) return f(t.isGlancing and true or false, v) end,
isCrushing = function (f, t, v) return f(t.isCrushing and true or false, v) end,
-- Miss.
missType = function (f, t, v) return f(t.missType, v) end,
-- Environmental.
hazardType = function (f, t, v) return f(t.hazardType, v) end,
-- Power.
powerType = function (f, t, v) return f(t.powerType, v) end,
extraAmount = function (f, t, v) return f(t.extraAmount, v) end,
-- Aura.
auraType = function (f, t, v) return f(t.auraType, v) end,
-- Health/power changes.
threshold = function (f, t, v) if (type(v)=="number") then return f(t.currentPercentage, v/100) and not f(t.lastPercentage, v/100) end end,
unitID = function (f, t, v) if ((v == "party" and string_find(t.unitID, "party%d+")) or (v == "raid" and (string_find(t.unitID, "raid%d+") or string_find(t.unitID, "party%d+")))) then v = t.unitID end return f(t.unitID, v) end,
unitReaction = function (f, t, v) if (v == REACTION_HOSTILE) then return f(UnitIsFriend(t.unitID, "player"), false) else return f(UnitIsFriend(t.unitID, "player"), true) end end,
}
-- Exception conditions.
exceptionConditionFuncs = {
activeTalents = function (f, t, v) return f(GetActiveSpecGroup(), v) end,
buffActive = function (f, t, v) return UnitBuff("player", v) and true or false end,
buffInactive = function (f, t, v) return not UnitBuff("player", v) and true or false end,
currentCP = function (f, t, v) return f(GetComboPoints("player"), v) end,
currentPower = function (f, t, v) return f(UnitPower("player"), v) end,
inCombat = function (f, t, v) return f(UnitAffectingCombat("player") == true and true or false, v) end,
recentlyFired = function (f, t, v) return f(GetTime() - firedTimes[t], v) end,
trivialTarget = function (f, t, v) return f(UnitIsTrivial("target") == true and true or false, v) end,
unavailableSkill = function (f, t, v) return IsSkillUnavailable(v) and true or false end,
warriorStance = function (f, t, v) if (playerClass == "WARRIOR") then return f(GetShapeshiftForm(true), v) end end,
zoneName = function (f, t, v) return f(GetZoneText(), v) end,
zoneType = function (f, t, v) local _, zoneType = IsInInstance() return f(zoneType, v) end,
}
end
-- ****************************************************************************
-- Converts a string representation of a number, boolean, or nil to its
-- corresponding type.
-- ****************************************************************************
local function ConvertType(value)
if (type(value) == "string") then
if (value == "true") then return true end
if (value == "false") then return false end
if (tonumber(value)) then return tonumber(value) end
if (value == "nil") then return nil end
end
return value
end
-- ****************************************************************************
-- Categorizes the passed trigger if it is not disabled and it applies to the
-- current player's class. Also tracks the events the trigger uses so the
-- only events that are received are those needed by the active triggers.
-- ****************************************************************************
local function CategorizeTrigger(triggerSettings)
-- Don't register the trigger if it is disabled, not for the current class,
-- or there aren't any main events.
if (triggerSettings.disabled) then return end
if (triggerSettings.classes and not string_find(triggerSettings.classes, playerClass, nil, 1)) then return end
if (not triggerSettings.mainEvents) then return end
-- Loop through the main events for the trigger.
local eventConditions, conditions
for mainEvent, conditionsString in string_gmatch(triggerSettings.mainEvents .. "&&", "(.-)%{(.-)%}&&") do
-- Loop through the conditions for the event and populate the settings into a conditions table.
conditions = {triggerSettings = triggerSettings}
if (conditionsString and conditionsString ~= "") then
for conditionEntry in string_gmatch(conditionsString .. ";;", "(.-);;") do
conditions[#conditions+1] = ConvertType(conditionEntry)
end
end
-- Check for special consolidated miss events.
if (mainEvent == "GENERIC_MISSED") then
listenEvents["COMBAT_LOG_EVENT_UNFILTERED"] = true
-- Create a table to hold an array of the triggers for the main events if there isn't already one for it.
if (not categorizedTriggers["SWING_MISSED"]) then categorizedTriggers["SWING_MISSED"] = {} end
if (not categorizedTriggers["RANGE_MISSED"]) then categorizedTriggers["RANGE_MISSED"] = {} end
if (not categorizedTriggers["SPELL_MISSED"]) then categorizedTriggers["SPELL_MISSED"] = {} end
-- Add the conditions table categorized by main events.
categorizedTriggers["SWING_MISSED"][#categorizedTriggers["SWING_MISSED"]+1] = conditions
categorizedTriggers["RANGE_MISSED"][#categorizedTriggers["RANGE_MISSED"]+1] = conditions
categorizedTriggers["SPELL_MISSED"][#categorizedTriggers["SPELL_MISSED"]+1] = conditions
-- Consolidated damage.
elseif (mainEvent == "GENERIC_DAMAGE") then
listenEvents["COMBAT_LOG_EVENT_UNFILTERED"] = true
-- Create a table to hold an array of the triggers for the main events if there isn't already one for it.
if (not categorizedTriggers["SWING_DAMAGE"]) then categorizedTriggers["SWING_DAMAGE"] = {} end
if (not categorizedTriggers["RANGE_DAMAGE"]) then categorizedTriggers["RANGE_DAMAGE"] = {} end
if (not categorizedTriggers["SPELL_DAMAGE"]) then categorizedTriggers["SPELL_DAMAGE"] = {} end
-- Add the conditions table categorized by main events.
categorizedTriggers["SWING_DAMAGE"][#categorizedTriggers["SWING_DAMAGE"]+1] = conditions
categorizedTriggers["RANGE_DAMAGE"][#categorizedTriggers["RANGE_DAMAGE"]+1] = conditions
categorizedTriggers["SPELL_DAMAGE"][#categorizedTriggers["SPELL_DAMAGE"]+1] = conditions
-- Consolidated aura application.
elseif (mainEvent == "SPELL_AURA_APPLIED") then
listenEvents["COMBAT_LOG_EVENT_UNFILTERED"] = true
-- Create a table to hold an array of the triggers for the main events if there isn't already one for it.
if (not categorizedTriggers["SPELL_AURA_APPLIED"]) then categorizedTriggers["SPELL_AURA_APPLIED"] = {} end
if (not categorizedTriggers["SPELL_AURA_APPLIED_DOSE"]) then categorizedTriggers["SPELL_AURA_APPLIED_DOSE"] = {} end
-- Add the conditions table categorized by main events.
categorizedTriggers["SPELL_AURA_APPLIED"][#categorizedTriggers["SPELL_AURA_APPLIED"]+1] = conditions
categorizedTriggers["SPELL_AURA_APPLIED_DOSE"][#categorizedTriggers["SPELL_AURA_APPLIED_DOSE"]+1] = conditions
-- Add aura gains to the trigger suppression so the normal buff gain/fade events are ignored.
local skillName, recipientAffiliation
for x = 1, #conditions, 3 do
if (conditions[x] == "skillName" and conditions[x+1] == "eq" and conditions[x+2]) then skillName = conditions[x+2] end
if (conditions[x] == "recipientAffiliation" and conditions[x+1] == "eq" and conditions[x+2] == FLAG_YOU) then recipientAffiliation = FLAG_YOU end
if (conditions[x] == "skillID" and conditions[x+1] == "eq" and conditions[x+2]) then skillName = GetSpellInfo(conditions[x+2]) or UNKNOWN end
end
if (skillName and recipientAffiliation) then triggerSuppressions[skillName] = true end
-- Consolidated aura removal.
elseif (mainEvent == "SPELL_AURA_REMOVED") then
listenEvents["COMBAT_LOG_EVENT_UNFILTERED"] = true
-- Create a table to hold an array of the triggers for the main events if there isn't already one for it.
if (not categorizedTriggers["SPELL_AURA_REMOVED"]) then categorizedTriggers["SPELL_AURA_REMOVED"] = {} end
if (not categorizedTriggers["SPELL_AURA_REMOVED_DOSE"]) then categorizedTriggers["SPELL_AURA_REMOVED_DOSE"] = {} end
-- Add the conditions table categorized by main events.
categorizedTriggers["SPELL_AURA_REMOVED"][#categorizedTriggers["SPELL_AURA_REMOVED"]+1] = conditions
categorizedTriggers["SPELL_AURA_REMOVED_DOSE"][#categorizedTriggers["SPELL_AURA_REMOVED_DOSE"]+1] = conditions
-- Other events.
else
-- Create a table to hold an array of the triggers for the main event if there isn't already one for it.
if (not categorizedTriggers[mainEvent]) then categorizedTriggers[mainEvent] = {} end
eventConditions = categorizedTriggers[mainEvent]
-- Health events.
if (mainEvent == "UNIT_HEALTH") then
listenEvents[mainEvent] = true
lastPercentages[mainEvent] = {}
-- Categorize the change by used units for better performance. The unitID condition is required for
-- health triggers.
for x = 1, #conditions, 3 do
if (conditions[x] == "unitID") then
-- Expand the consolidated party unit id to individual ones.
local conditionValue = conditions[x+2]
if (conditionValue == "party") then
for partyMember = 1, MAX_PARTY_MEMBERS do
local unitID = "party" .. partyMember
if (not eventConditions[unitID]) then eventConditions[unitID] = {} end
eventConditions[unitID][#eventConditions[unitID]+1] = conditions
end
elseif (conditionValue == "raid") then
for raidMember = 1, MAX_RAID_MEMBERS do
local unitID = "raid" .. raidMember
if (not eventConditions[unitID]) then eventConditions[unitID] = {} end
eventConditions[unitID][#eventConditions[unitID]+1] = conditions
end
-- Specific unit.
else
if (not eventConditions[conditionValue]) then eventConditions[conditionValue] = {} end
eventConditions[conditionValue][#eventConditions[conditionValue]+1] = conditions
end
end -- unitID?
end -- Loop through conditions.
-- Power events.
elseif (mainEvent == "UNIT_POWER_UPDATE") then
listenEvents[mainEvent] = true
-- Detect power type. The powerType and unitID conditions are required for power triggers.
local powerType
for x = 1, #conditions, 3 do
if (conditions[x] == "powerType") then powerType = conditions[x+2] break end
end
-- Ensure the power type is defined.
if (powerType) then
lastPercentages[powerType] = {}
-- Categorize the change by used power types and units for better performance.
-- The powerType and unitID conditions are required for power triggers.
for x = 1, #conditions, 3 do
if (conditions[x] == "unitID") then
if (not eventConditions[powerType]) then eventConditions[powerType] = {} end
local powerConditions = eventConditions[powerType]
-- Expand the consolidated party unit id to individual ones.
local conditionValue = conditions[x+2]
if (conditionValue == "party") then
for partyMember = 1, MAX_PARTY_MEMBERS do
local unitID = "party" .. partyMember
if (not powerConditions[unitID]) then powerConditions[unitID] = {} end
powerConditions[unitID][#powerConditions[unitID]+1] = conditions
end
elseif (conditionValue == "raid") then
for raidMember = 1, MAX_RAID_MEMBERS do
local unitID = "raid" .. raidMember
if (not powerConditions[unitID]) then powerConditions[unitID] = {} end
powerConditions[unitID][#powerConditions[unitID]+1] = conditions
end
-- Specific unit.
else
if (not powerConditions[conditionValue]) then powerConditions[conditionValue] = {} end
powerConditions[conditionValue][#powerConditions[conditionValue]+1] = conditions
end
end -- unitID?
end -- Loop through conditions.
end -- Power type?
-- Skill cooldown events.
elseif (mainEvent == "SKILL_COOLDOWN") then
eventConditions[#eventConditions+1] = conditions
MikSBT.Cooldowns.UpdateRegisteredEvents()
-- Pet cooldown events.
elseif (mainEvent == "PET_COOLDOWN") then
eventConditions[#eventConditions+1] = conditions
MikSBT.Cooldowns.UpdateRegisteredEvents()
-- Item cooldown events.
elseif (mainEvent == "ITEM_COOLDOWN") then
eventConditions[#eventConditions+1] = conditions
MikSBT.Cooldowns.UpdateRegisteredEvents()
-- Combat log event.
elseif (captureFuncs[mainEvent]) then
listenEvents["COMBAT_LOG_EVENT_UNFILTERED"] = true
eventConditions[#eventConditions+1] = conditions
end
end -- Specific events check.
end -- Loop through conditions.
-- Leave the function if there are no exceptions for the trigger.
if (not triggerSettings.exceptions or triggerSettings.exceptions == "") then return end
-- Loop through the conditions for the exceptions for the trigger.
local exceptionConditions = {}
for exceptionValue in string_gmatch(triggerSettings.exceptions .. ";;", "(.-);;") do
exceptionConditions[#exceptionConditions+1] = ConvertType(exceptionValue)
end
-- Create an entry to track fired times for the trigger.
for x = 1, #exceptionConditions, 3 do
if (exceptionConditions[x] == "recentlyFired") then firedTimes[triggerSettings] = 0 end
end
-- Set the exceptions for the trigger.
triggerExceptions[triggerSettings] = exceptionConditions
end
-- ****************************************************************************
-- Update the categorized triggers table that is used for optimized searching.
-- ****************************************************************************
local function UpdateTriggers()
-- Unregister all of the events from the event frame.
eventFrame:UnregisterAllEvents()
-- Erase the listen events table.
EraseTable(listenEvents)
-- Loop through all of the categorized trigger arrays and erase them.
for mainEvent in pairs(categorizedTriggers) do
EraseTable(categorizedTriggers[mainEvent])
end
-- Update the registered cooldown event.
MikSBT.Cooldowns.UpdateRegisteredEvents()
-- Erase the trigger exceptions array.
EraseTable(triggerExceptions)
-- Categorize triggers from the current profile.
local currentProfileTriggers = rawget(MSBTProfiles.currentProfile, "triggers")
if (currentProfileTriggers) then
for triggerKey, triggerSettings in pairs(currentProfileTriggers) do
if (triggerSettings) then CategorizeTrigger(triggerSettings) end
end
end
-- Categorize triggers available in the master profile that aren't in the current profile.
for triggerKey, triggerSettings in pairs(MSBTProfiles.masterProfile.triggers) do
if (not currentProfileTriggers or rawget(currentProfileTriggers, triggerKey) == nil) then
CategorizeTrigger(triggerSettings)
end
end
-- Register all of the events the triggers use.
for event in pairs(listenEvents) do
eventFrame:RegisterEvent(event)
end
end
-- ****************************************************************************
-- Displays the passed trigger settings.
-- ****************************************************************************
local function DisplayTrigger(triggerSettings, sourceName, sourceClass, recipientName, recipientClass, skillName, extraSkillName, amount, effectTexture)
-- Get a local reference to the current profile.
local currentProfile = MSBTProfiles.currentProfile
-- Get the trigger message and icon skill.
local message = triggerSettings.message
local iconSkill = triggerSettings.iconSkill
-- Substitute source name.
if (sourceName and string_find(message, "%n", 1, true)) then
-- Strip realm from names.
if (string_find(sourceName, "-", 1, true)) then sourceName = string_gsub(sourceName, "(.-)%-.*", "%1") end
-- Color the name according to the class if there is one and it's enabled.
if (sourceClass and not currentProfile.classColoringDisabled) then
local classSettings = currentProfile[sourceClass]
if (classSettings and not classSettings.disabled) then sourceName = string_format("|cFF%02x%02x%02x%s|r", classSettings.colorR * 255, classSettings.colorG * 255, classSettings.colorB * 255, sourceName) end
end
-- Substitute all %n event codes with the source name.
message = string_gsub(message, "%%n", sourceName)
end
-- Substitute recipient name.
if (recipientName and string_find(message, "%r", 1, true)) then
-- Strip realm from names.
if (string_find(recipientName, "-", 1, true)) then recipientName = string_gsub(recipientName, "(.-)%-.*", "%1") end
-- Color the name according to the class if there is one and it's enabled.
if (recipientClass and not currentProfile.classColoringDisabled) then
local classSettings = currentProfile[recipientClass]
if (classSettings and not classSettings.disabled) then recipientName = string_format("|cFF%02x%02x%02x%s|r", classSettings.colorR * 255, classSettings.colorG * 255, classSettings.colorB * 255, recipientName) end
end
-- Substitute all %r event codes with the recipient name.
message = string_gsub(message, "%%r", recipientName)
end
-- Substitute skill name.
if (skillName and string_find(message, "%s", 1, true)) then message = string_gsub(message, "%%s", skillName) end
-- Substitute extra skill name.
if (extraSkillName and string_find(message, "%e", 1, true)) then message = string_gsub(message, "%%e", extraSkillName) end
-- Substitute amount.
if (amount and string_find(message, "%a", 1, true)) then
-- Shorten amount with SI suffixes or separate into digit groups depending on options.
local formattedAmount = amount
if (currentProfile.shortenNumbers) then
formattedAmount = ShortenNumber(formattedAmount, currentProfile.shortenNumberPrecision)
elseif (currentProfile.groupNumbers) then
formattedAmount = FormatLargeNumber(formattedAmount)
end
message = string_gsub(message, "%%a", formattedAmount)
end
-- Override the texture if there is an icon skill for the trigger.
if (iconSkill) then
if (skillName and string_find(iconSkill, "%s", 1, true)) then iconSkill = string_gsub(iconSkill, "%%s", skillName) end
if (extraSkillName and string_find(iconSkill, "%e", 1, true)) then iconSkill = string_gsub(iconSkill, "%%e", extraSkillName) end
_, _, effectTexture = GetSpellInfo(iconSkill)
end
-- Display the trigger event.
DisplayEvent(triggerSettings, message, effectTexture)
end
-------------------------------------------------------------------------------
-- Trigger handler functions.
-------------------------------------------------------------------------------
-- ****************************************************************************
-- Tests if any of the exceptions for the passed trigger settings are true.
-- ****************************************************************************
local function TestExceptions(triggerSettings)
-- Trigger is not excluded if there are no exceptions.
if (not triggerExceptions[triggerSettings]) then return end
-- Loop through each exception triplet.
local exceptionConditions = triggerExceptions[triggerSettings]
for position = 1, #exceptionConditions, 3 do
-- Test the exception and if it passes, don't waste time checking others.
local conditionFunc = exceptionConditionFuncs[exceptionConditions[position]]
local testFunc = testFuncs[exceptionConditions[position+1]]
if (conditionFunc and testFunc and conditionFunc(testFunc, triggerSettings, exceptionConditions[position+2])) then return true end
end -- Exceptions loop.
-- Set the current time as the last time the trigger was fired if the the trigger
-- has a recently fired exception.
if (firedTimes[triggerSettings]) then firedTimes[triggerSettings] = GetTime() end
end
-- ****************************************************************************
-- Handles triggers for health and power events.
-- ****************************************************************************
local function HandleHealthAndPowerTriggers(unit, event, currentAmount, maxAmount, powerType)
-- Ignore the event if there are no triggers to search for it.
local eventTriggers = categorizedTriggers[event]
if (powerType and eventTriggers) then eventTriggers = eventTriggers[powerType] end
if (not eventTriggers or not eventTriggers[unit]) then return end
-- Calculate current last percentages.
local currentPercentage = currentAmount / maxAmount
local lastEventPercentages = lastPercentages[powerType or event]
local lastPercentage = lastEventPercentages[unit]
-- Ignore thresholds on death.
if (not lastPercentage) then lastEventPercentages[unit] = currentPercentage return end
if (UnitIsDeadOrGhost(unit)) then lastEventPercentages[unit] = nil return end
-- Populate the lookup table for conditions checking.
lookupTable.amount = currentAmount
lookupTable.currentPercentage = currentPercentage
lookupTable.lastPercentage = lastPercentage
lookupTable.unitID = unit
lookupTable.powerType = powerType
-- Erase the list of triggers to fire.
for k in pairs(triggersToFire) do triggersToFire[k] = nil end
-- Loop through the conditions list for the main event.
for _, eventConditions in ipairs(eventTriggers[unit]) do
-- Trigger fires by default.
local doFire = true
-- Don't bother checking conditions for a trigger that has already been fired.
if (not triggersToFire[eventConditions.triggerSettings]) then
-- Loop through each condition triplet.
for position = 1, #eventConditions, 3 do
-- Test the condition and if it fails, don't waste time checking other conditions.
local conditionFunc = eventConditionFuncs[eventConditions[position]]
local testFunc = testFuncs[eventConditions[position+1]]
if (conditionFunc and testFunc and not conditionFunc(testFunc, lookupTable, eventConditions[position+2])) then doFire = false break end
end -- Conditions loop.
-- Set the trigger to be fired if none of the conditions failed.
if (doFire) then triggersToFire[eventConditions.triggerSettings] = true end
end
end
-- Get the texture for the event and display triggers that aren't excepted.
if (next(triggersToFire)) then
-- Display the fired triggers if none of the exceptions are true.
local recipientName = UnitName(unit)
local _, recipientClass = UnitClass(unit)
local amount = currentAmount
for triggerSettings in pairs(triggersToFire) do
if (not TestExceptions(triggerSettings)) then DisplayTrigger(triggerSettings, nil, nil, recipientName, recipientClass, nil, nil, amount) end
end
end -- Triggers to fire?
-- Update the last percentage for the unit.
lastEventPercentages[unit] = currentPercentage
end
-- ****************************************************************************
-- Handles triggers for skill cooldowns.
-- ****************************************************************************
local function HandleCooldowns(cooldownType, cooldownID, cooldownName, effectTexture)
-- Choose the correct cooldown event based on the cooldown type.
local event = "SKILL_COOLDOWN"
if (cooldownType == "pet") then
event = "PET_COOLDOWN"
elseif (cooldownType == "item") then
event = "ITEM_COOLDOWN"
end
-- Ignore the event if there are no triggers to search for it.
local eventTriggers = categorizedTriggers[event]
if (not eventTriggers) then return end
-- Populate the lookup table for conditions checking.
if (cooldownType == "item") then
lookupTable.itemID = cooldownID
lookupTable.itemName = cooldownName
else
lookupTable.skillID = cooldownID
lookupTable.skillName = cooldownName
end
-- Erase the list of triggers to fire.
for k in pairs(triggersToFire) do triggersToFire[k] = nil end
-- Loop through the conditions list for the main event.
for _, eventConditions in ipairs(eventTriggers) do
-- Trigger fires by default.
local doFire = true
-- Don't bother checking conditions for a trigger that has already been fired.
if (not triggersToFire[eventConditions.triggerSettings]) then
-- Loop through each condition triplet.
for position = 1, #eventConditions, 3 do
-- Test the condition and if it fails, don't waste time checking other conditions.
local conditionFunc = eventConditionFuncs[eventConditions[position]]
local testFunc = testFuncs[eventConditions[position+1]]
if (conditionFunc and testFunc and not conditionFunc(testFunc, lookupTable, eventConditions[position+2])) then doFire = false break end
end -- Conditions loop.
-- Set the trigger to be fired if none of the conditions failed.
if (doFire) then triggersToFire[eventConditions.triggerSettings] = true end
end
end
-- Get the texture for the event and display triggers that aren't excepted.
if (next(triggersToFire)) then
-- Display the fired triggers if none of the exceptions are true.
local recipientName = playerName
for triggerSettings in pairs(triggersToFire) do
if (not TestExceptions(triggerSettings)) then DisplayTrigger(triggerSettings, nil, nil, recipientName, playerClass, cooldownName, nil, nil, effectTexture) end
end
end -- Triggers to fire?
end
-- ****************************************************************************
-- Handles triggers for combat log events.
-- ****************************************************************************
local function HandleCombatLogTriggers(timestamp, event, hideCaster, sourceGUID, sourceName, sourceFlags, sourceRaidFlags, recipientGUID, recipientName, recipientFlags, recipientRaidFlags, ...)
-- Ignore the event if there are no triggers to search for it.
if (not categorizedTriggers[event]) then return end
-- Make sure the capture function for the event exists.
local captureFunc = captureFuncs[event]
if (not captureFunc) then return end
-- Erase the parser event table.
for k in pairs(parserEvent) do parserEvent[k] = nil end
-- Populate fields that exist for all events.
parserEvent.sourceGUID = sourceGUID
parserEvent.sourceName = sourceName
parserEvent.sourceFlags = sourceFlags
parserEvent.recipientGUID = recipientGUID
parserEvent.recipientName = recipientName
parserEvent.recipientFlags = recipientFlags
parserEvent.sourceUnit = unitMap[sourceGUID]
parserEvent.recipientUnit = unitMap[recipientGUID]
-- Map the local arguments into the parser event table.
captureFunc(parserEvent, ...)
-- Erase the list of triggers to fire.
for k in pairs(triggersToFire) do triggersToFire[k] = nil end
-- Loop through the conditions list for the main event.
for _, eventConditions in ipairs(categorizedTriggers[event]) do
-- Trigger fires by default.
local doFire = true
-- Don't bother checking conditions for a trigger that has already been fired.
if (not triggersToFire[eventConditions.triggerSettings]) then
-- Loop through each condition triplet.
for position = 1, #eventConditions, 3 do
-- Test the condition and if it fails, don't waste time checking other conditions.
local conditionFunc = eventConditionFuncs[eventConditions[position]]
local testFunc = testFuncs[eventConditions[position+1]]
if (conditionFunc and testFunc and not conditionFunc(testFunc, parserEvent, eventConditions[position+2])) then doFire = false break end
end -- Conditions loop.
-- Set the trigger to be fired if none of the conditions failed.
if (doFire) then triggersToFire[eventConditions.triggerSettings] = true end
end
end
-- Get the texture for the event and display triggers that aren't excepted.
if (next(triggersToFire)) then
local effectTexture
if (parserEvent.skillID or parserEvent.extraSkillID) then _, _, effectTexture = GetSpellInfo(parserEvent.extraSkillID or parserEvent.skillID) end
-- Display the fired triggers if none of the exceptions are true.
local sourceName = parserEvent.sourceName
local recipientName = parserEvent.recipientName
local sourceClass = classMap[sourceGUID]
local recipientClass = classMap[recipientGUID]
local skillName = parserEvent.skillName
local extraSkillName = parserEvent.extraSkillName
local amount = parserEvent.amount
for triggerSettings in pairs(triggersToFire) do
if (not TestExceptions(triggerSettings)) then DisplayTrigger(triggerSettings, sourceName, sourceClass, recipientName, recipientClass, skillName, extraSkillName, amount, effectTexture) end
end
end -- Triggers to fire?
end
-------------------------------------------------------------------------------
-- Initialization and event handlers.
-------------------------------------------------------------------------------
-- ****************************************************************************
-- Called when the registered events occur.
-- ****************************************************************************
local function OnEvent(this, event, arg1, arg2, ...)
-- Health.
if (event == "UNIT_HEALTH") then
-- Ignore the event if there are no triggers to search for it.
if (not categorizedTriggers[event] or not categorizedTriggers[event][arg1]) then return end
HandleHealthAndPowerTriggers(arg1, event, UnitHealth(arg1), UnitHealthMax(arg1))
-- Power.
elseif (event == "UNIT_POWER_UPDATE") then
-- Ignore the event if there are no triggers to search for it.
if (not categorizedTriggers[event]) then return end
local powerType = powerTypes[arg2]
if (not powerType) then return end
if (not categorizedTriggers[event][powerType] or not categorizedTriggers[event][powerType][arg1]) then return end
HandleHealthAndPowerTriggers(arg1, event, UnitPower(arg1, powerType), UnitPowerMax(arg1, powerType), powerType)
-- Combat log event.
elseif (event == "COMBAT_LOG_EVENT_UNFILTERED") then
HandleCombatLogTriggers(CombatLogGetCurrentEventInfo())
end -- Event types.
end
-- ****************************************************************************
-- Enables the trigger parsing.
-- ****************************************************************************
local function Enable()
-- Register events the triggers use.
for event in pairs(listenEvents) do
eventFrame:RegisterEvent(event)
end
end
-- ****************************************************************************
-- Disables the trigger parsing.
-- ****************************************************************************
local function Disable()
-- Unregister all of the events from the event frame.
eventFrame:UnregisterAllEvents()
end
-------------------------------------------------------------------------------
-- Initialization.
-------------------------------------------------------------------------------
-- Get the player's name and class.
playerName = UnitName("player")
playerGUID = UnitGUID("player")
_, playerClass = UnitClass("player")
-- Create a frame to receive events.
eventFrame = CreateFrame("Frame")
eventFrame:Hide()
eventFrame:SetScript("OnEvent", OnEvent)
-- Create function maps.
CreateCaptureFuncs()
CreateTestFuncs()
CreateConditionFuncs()
-- Create the power types lookup map.
powerTypes["MANA"] = Enum.PowerType.Mana
powerTypes["RAGE"] = Enum.PowerType.Rage
powerTypes["FOCUS"] = Enum.PowerType.Focus
powerTypes["ENERGY"] = Enum.PowerType.Energy
powerTypes["COMBO_POINTS"] = Enum.PowerType.ComboPoints
powerTypes["RUNES"] = Enum.PowerType.Runes
powerTypes["RUNIC_POWER"] = Enum.PowerType.RunicPower
powerTypes["SOUL_SHARDS"] = Enum.PowerType.SoulShards
powerTypes["LUNAR_POWER"] = Enum.PowerType.LunarPower
powerTypes["HOLY_POWER"] = Enum.PowerType.HolyPower
powerTypes["ALTERNATE_POWER"] = Enum.PowerType.Alternate
powerTypes["MAELSTROM"] = Enum.PowerType.Maelstrom
powerTypes["CHI"] = Enum.PowerType.Chi
powerTypes["INSANITY"] = Enum.PowerType.Insanity
powerTypes["ARCANE_CHARGES"] = Enum.PowerType.ArcaneCharges
powerTypes["FURY"] = Enum.PowerType.Fury
powerTypes["PAIN"] = Enum.PowerType.Pain
-------------------------------------------------------------------------------
-- Module interface.
-------------------------------------------------------------------------------
-- Protected Variables.
module.triggerSuppressions = triggerSuppressions
module.categorizedTriggers = categorizedTriggers
module.powerTypes = powerTypes
-- Protected Functions.
module.HandleCooldowns = HandleCooldowns
module.HandleCombatLogTriggers = HandleCombatLogTriggers
module.ConvertType = ConvertType
module.UpdateTriggers = UpdateTriggers
module.Enable = Enable
module.Disable = Disable