-
Notifications
You must be signed in to change notification settings - Fork 3
/
MSBTMain.lua
1544 lines (1228 loc) · 62.4 KB
/
MSBTMain.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
-------------------------------------------------------------------------------
-- Title: Mik's Scrolling Battle Text Main
-- Author: Mikord
-------------------------------------------------------------------------------
-- Create module and set its name.
local module = {}
local moduleName = "Main"
MikSBT[moduleName] = module
-------------------------------------------------------------------------------
-- Imports.
-------------------------------------------------------------------------------
-- Local references to various modules for faster access.
local MSBTAnimations = MikSBT.Animations
local MSBTMedia = MikSBT.Media
local MSBTParser = MikSBT.Parser
local MSBTTriggers = MikSBT.Triggers
local MSBTProfiles = MikSBT.Profiles
local L = MikSBT.translations
-- Local references to various functions for faster access.
local table_remove = table.remove
local string_find = string.find
local string_gsub = string.gsub
local string_format = string.format
local math_abs = math.abs
local bit_bor = bit.bor
local FormatLargeNumber = FormatLargeNumber
local GetTime = GetTime
local GetSpellInfo = GetSpellInfo
local EraseTable = MikSBT.EraseTable
local GetSkillName = MikSBT.GetSkillName
local ShortenNumber = MikSBT.ShortenNumber
--local SeparateNumber = MikSBT.SeparateNumber
local DisplayEvent = MSBTAnimations.DisplayEvent
local IsScrollAreaActive = MSBTAnimations.IsScrollAreaActive
local IsScrollAreaIconShown = MSBTAnimations.IsScrollAreaIconShown
local TestFlagsAll = MSBTParser.TestFlagsAll
-- Local references to various variables for faster access.
local triggerSuppressions = MSBTTriggers.triggerSuppressions
local powerTypes = MSBTTriggers.powerTypes
local classMap = MSBTParser.classMap
-------------------------------------------------------------------------------
-- Constants.
-------------------------------------------------------------------------------
-- How long to wait before showing events so that merges may happen.
local MERGE_DELAY_TIME = 0.3
-- How long to wait between throttle window checking.
local THROTTLE_UPDATE_TIME = 0.5
-- Amount of time to hold recent monster emotes and enemy buffs in cache.
local EMOTE_HOLD_TIME = 1
local ENEMY_BUFF_HOLD_TIME = 5
-- Damage types.
local DAMAGETYPE_PHYSICAL = 0x1
local DAMAGETYPE_HOLY = 0x2
local DAMAGETYPE_FIRE = 0x4
local DAMAGETYPE_NATURE = 0x8
local DAMAGETYPE_FROST = 0x10
local DAMAGETYPE_SHADOW = 0x20
local DAMAGETYPE_ARCANE = 0x40
-- Physical + Magic Damage types.
local DAMAGETYPE_HOLYSTRIKE = DAMAGETYPE_PHYSICAL + DAMAGETYPE_HOLY
local DAMAGETYPE_FLAMESTRIKE = DAMAGETYPE_PHYSICAL + DAMAGETYPE_FIRE
local DAMAGETYPE_STORMSTRIKE = DAMAGETYPE_PHYSICAL + DAMAGETYPE_NATURE
local DAMAGETYPE_FROSTSTRIKE = DAMAGETYPE_PHYSICAL + DAMAGETYPE_FROST
local DAMAGETYPE_SHADOWSTRIKE = DAMAGETYPE_PHYSICAL + DAMAGETYPE_SHADOW
local DAMAGETYPE_SPELLSTRIKE = DAMAGETYPE_PHYSICAL + DAMAGETYPE_ARCANE
-- Two magic damage types.
local DAMAGETYPE_HOLYFIRE = DAMAGETYPE_HOLY + DAMAGETYPE_FIRE
local DAMAGETYPE_HOLYSTORM = DAMAGETYPE_HOLY + DAMAGETYPE_NATURE
local DAMAGETYPE_HOLYFROST = DAMAGETYPE_HOLY + DAMAGETYPE_FROST
local DAMAGETYPE_SHADOWLIGHT = DAMAGETYPE_HOLY + DAMAGETYPE_SHADOW
local DAMAGETYPE_DIVINE = DAMAGETYPE_HOLY + DAMAGETYPE_ARCANE
local DAMAGETYPE_FIRESTORM = DAMAGETYPE_FIRE + DAMAGETYPE_NATURE
local DAMAGETYPE_FROSTFIRE = DAMAGETYPE_FIRE + DAMAGETYPE_FROST
local DAMAGETYPE_SHADOWFLAME = DAMAGETYPE_FIRE + DAMAGETYPE_SHADOW
local DAMAGETYPE_SPELLFIRE = DAMAGETYPE_FIRE + DAMAGETYPE_ARCANE
local DAMAGETYPE_FROSTSTORM = DAMAGETYPE_NATURE + DAMAGETYPE_FROST
local DAMAGETYPE_SHADOWSTORM = DAMAGETYPE_NATURE + DAMAGETYPE_SHADOW
local DAMAGETYPE_SPELLSTORM = DAMAGETYPE_NATURE + DAMAGETYPE_ARCANE
local DAMAGETYPE_SHADOWFROST = DAMAGETYPE_FROST + DAMAGETYPE_SHADOW
local DAMAGETYPE_SPELLFROST = DAMAGETYPE_FROST + DAMAGETYPE_ARCANE
local DAMAGETYPE_SPELLSHADOW = DAMAGETYPE_SHADOW + DAMAGETYPE_ARCANE
-- Three or more damage types.
local DAMAGETYPE_ELEMENTAL = DAMAGETYPE_FIRE + DAMAGETYPE_NATURE + DAMAGETYPE_FROST
local DAMAGETYPE_CHROMATIC = DAMAGETYPE_FIRE + DAMAGETYPE_NATURE + DAMAGETYPE_FROST + DAMAGETYPE_SHADOW + DAMAGETYPE_ARCANE
local DAMAGETYPE_MAGIC = DAMAGETYPE_HOLY + DAMAGETYPE_FIRE + DAMAGETYPE_NATURE + DAMAGETYPE_FROST + DAMAGETYPE_SHADOW + DAMAGETYPE_ARCANE
local DAMAGETYPE_CHAOS = DAMAGETYPE_PHYSICAL + DAMAGETYPE_HOLY + DAMAGETYPE_FIRE + DAMAGETYPE_NATURE + DAMAGETYPE_FROST + DAMAGETYPE_SHADOW + DAMAGETYPE_ARCANE
-- Spell IDs.
local SPELLID_AUTOSHOT = 75
-- Spell names.
local SPELL_BLINK = GetSkillName(1953)
--local SPELL_BLIZZARD = GetSkillName(10)
local SPELL_BLOOD_STRIKE = WOW_PROJECT_ID < WOW_PROJECT_CLASSIC and GetSkillName(60945)
--local SPELL_BLOOD_STRIKE_OFF_HAND = GetSkillName(66215)
--local SPELL_HELLFIRE = GetSkillName(1949)
--local SPELL_HURRICANE = GetSkillName(16914)
local SPELL_RAIN_OF_FIRE = GetSkillName(5740)
-------------------------------------------------------------------------------
-- Private variables.
-------------------------------------------------------------------------------
-- Prevent tainting global _.
local _
-- Dynamically created frames for receiving events.
local eventFrame = CreateFrame("Frame")
local throttleFrame = CreateFrame("Frame")
-- Player's class.
local playerClass
-- Pool of dynamically created combat events that are reused.
local combatEventCache = {}
-- Lookup tables.
local eventHandlers = {}
local damageTypeMap = {}
local damageColorProfileEntries = {}
local powerTokens = {}
local uniquePowerTypes = {}
-- Throttled ability info.
local throttledAbilities = {}
-- Holds unmerged and merged combat events.
local unmergedEvents = {}
local mergedEvents = {}
-- Used for timing between updates.
local lastMergeUpdate = 0
local lastThrottleUpdate = 0
-- Spam control info.
local isEnglish
local lastPowerAmounts = {}
local finisherShown
local recentEmotes = {}
local recentEnemyBuffs = {}
local ignoreAuras = {}
-- Localized off-hand info to allow merging of off-hand strikes.
local offHandTrailer
local offHandPattern
-------------------------------------------------------------------------------
-- Utility functions.
-------------------------------------------------------------------------------
-- ****************************************************************************
-- Creates damage type and damage color profile maps.
-- ****************************************************************************
local function CreateDamageMaps()
-- Create the damage type lookup map.
damageTypeMap[DAMAGETYPE_PHYSICAL] = STRING_SCHOOL_PHYSICAL
damageTypeMap[DAMAGETYPE_HOLY] = STRING_SCHOOL_HOLY
damageTypeMap[DAMAGETYPE_FIRE] = STRING_SCHOOL_FIRE
damageTypeMap[DAMAGETYPE_NATURE] = STRING_SCHOOL_NATURE
damageTypeMap[DAMAGETYPE_FROST] = STRING_SCHOOL_FROST
damageTypeMap[DAMAGETYPE_SHADOW] = STRING_SCHOOL_SHADOW
damageTypeMap[DAMAGETYPE_ARCANE] = STRING_SCHOOL_ARCANE
damageTypeMap[DAMAGETYPE_HOLYSTRIKE] = STRING_SCHOOL_HOLYSTRIKE
damageTypeMap[DAMAGETYPE_FLAMESTRIKE] = STRING_SCHOOL_FLAMESTRIKE
damageTypeMap[DAMAGETYPE_STORMSTRIKE] = STRING_SCHOOL_STORMSTRIKE
damageTypeMap[DAMAGETYPE_FLAMESTRIKE] = STRING_SCHOOL_FLAMESTRIKE
damageTypeMap[DAMAGETYPE_SHADOWSTRIKE] = STRING_SCHOOL_SHADOWSTRIKE
damageTypeMap[DAMAGETYPE_SPELLSTRIKE] = STRING_SCHOOL_SPELLSTRIKE
damageTypeMap[DAMAGETYPE_HOLYFIRE] = STRING_SCHOOL_HOLYFIRE
damageTypeMap[DAMAGETYPE_HOLYSTORM] = STRING_SCHOOL_HOLYSTORM
damageTypeMap[DAMAGETYPE_HOLYFROST] = STRING_SCHOOL_HOLYFROST
damageTypeMap[DAMAGETYPE_SHADOWLIGHT] = STRING_SCHOOL_SHADOWLIGHT
damageTypeMap[DAMAGETYPE_DIVINE] = STRING_SCHOOL_DIVINE
damageTypeMap[DAMAGETYPE_FIRESTORM] = STRING_SCHOOL_FIRESTORM
damageTypeMap[DAMAGETYPE_FROSTFIRE] = STRING_SCHOOL_FROSTFIRE
damageTypeMap[DAMAGETYPE_SHADOWFLAME] = STRING_SCHOOL_SHADOWFLAME
damageTypeMap[DAMAGETYPE_SPELLFIRE] = STRING_SCHOOL_SPELLFIRE
damageTypeMap[DAMAGETYPE_FROSTSTORM] = STRING_SCHOOL_FROSTSTORM
damageTypeMap[DAMAGETYPE_SHADOWSTORM] = STRING_SCHOOL_SHADOWSTORM
damageTypeMap[DAMAGETYPE_SPELLSTORM] = STRING_SCHOOL_SPELLSTORM
damageTypeMap[DAMAGETYPE_SHADOWFROST] = STRING_SCHOOL_SHADOWFROST
damageTypeMap[DAMAGETYPE_SPELLFROST] = STRING_SCHOOL_SPELLFROST
damageTypeMap[DAMAGETYPE_SPELLSHADOW] = STRING_SCHOOL_SPELLSHADOW
damageTypeMap[DAMAGETYPE_ELEMENTAL] = STRING_SCHOOL_ELEMENTAL
damageTypeMap[DAMAGETYPE_CHROMATIC] = STRING_SCHOOL_CHROMATIC
damageTypeMap[DAMAGETYPE_MAGIC] = STRING_SCHOOL_MAGIC
damageTypeMap[DAMAGETYPE_CHAOS] = STRING_SCHOOL_CHAOS
-- Create the damage color profile entries lookup map.
damageColorProfileEntries[DAMAGETYPE_PHYSICAL] = "physical"
damageColorProfileEntries[DAMAGETYPE_HOLY] = "holy"
damageColorProfileEntries[DAMAGETYPE_FIRE] = "fire"
damageColorProfileEntries[DAMAGETYPE_NATURE] = "nature"
damageColorProfileEntries[DAMAGETYPE_FROST] = "frost"
damageColorProfileEntries[DAMAGETYPE_SHADOW] = "shadow"
damageColorProfileEntries[DAMAGETYPE_ARCANE] = "arcane"
damageColorProfileEntries[DAMAGETYPE_FROSTFIRE] = "frostfire"
damageColorProfileEntries[DAMAGETYPE_SHADOWFLAME] = "shadowflame"
end
-- ****************************************************************************
-- Returns an abbreviated form of the passed skill name.
-- ****************************************************************************
local function AbbreviateSkillName(skillName)
if (string_find(skillName, "[%s%-]")) then
skillName = string_gsub(skillName, "(%a)[%l%p]*[%s%-]*", "%1")
end
return skillName
end
-- ****************************************************************************
-- Returns a formatted partial effects trailer using the passed parameters.
-- ****************************************************************************
local function FormatPartialEffects(absorbAmount, blockAmount, resistAmount, isGlancing, isCrushing)
-- Get a local reference to the current profile.
local currentProfile = MSBTProfiles.currentProfile
local effectSettings, amount
local partialEffectText = ""
-- Partial Absorb
if (absorbAmount) then
effectSettings = currentProfile.absorb
amount = absorbAmount
-- Partial Block
elseif (blockAmount) then
effectSettings = currentProfile.block
amount = blockAmount
-- Partial Resist
elseif (resistAmount) then
effectSettings = currentProfile.resist
amount = resistAmount
end
-- Set the partial effect text if there are settings for it, it's enabled, and it's valid.
local trailer = effectSettings and effectSettings.trailer
if (trailer and not effectSettings.disabled) 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
-- Substitute the amount into the trailer.
trailer = string_gsub(trailer, "%%a", formattedAmount)
-- Color the text if coloring isn't disabled.
if (not currentProfile.partialColoringDisabled) then
partialEffectText = string_format("|cFF%02x%02x%02x%s|r", effectSettings.colorR * 255, effectSettings.colorG * 255, effectSettings.colorB * 255, trailer)
else
partialEffectText = trailer
end
end
-- Clear the effect settings and trailer.
effectSettings = nil
trailer = nil
-- Glancing hit
if (isGlancing) then
effectSettings = currentProfile.glancing
-- Crushing blow
elseif (isCrushing) then
effectSettings = currentProfile.crushing
end
-- Append the crushing/glancing text if there are settings for it, it's enabled, and it's valid.
trailer = effectSettings and effectSettings.trailer
if (trailer and not effectSettings.disabled) then
-- Color the text if coloring isn't disabled.
if (not currentProfile.partialColoringDisabled) then
partialEffectText = partialEffectText .. string_format("|cFF%02x%02x%02x%s|r", effectSettings.colorR * 255, effectSettings.colorG * 255, effectSettings.colorB * 255, trailer)
else
partialEffectText = partialEffectText .. trailer
end
end
return partialEffectText
end
-- ****************************************************************************
-- Formats an event with the parameters.
-- ****************************************************************************
local function FormatEvent(message, amount, damageType, overhealAmount, overkillAmount, powerType, name, class, effectName, partialEffects, mergeTrailer, ignoreDamageColoring, hideSkills, hideNames)
-- Get a local reference to the current profile.
local currentProfile = MSBTProfiles.currentProfile
local checkParens
-- Substitute amount.
if (amount and string_find(message, "%a", 1, true)) then
-- Check if there is overheal information and displaying it is enabled.
local partialAmount = ""
if (overhealAmount and overhealAmount > 0 and not currentProfile.overheal.disabled) then
-- Deduct the overheal amount from the total amount healed.
amount = amount - overhealAmount
-- Shorten overheal amount with SI suffixes or separate into digit groups depending on options.
partialAmount = overhealAmount
if (currentProfile.shortenNumbers) then
partialAmount = ShortenNumber(partialAmount, currentProfile.shortenNumberPrecision)
elseif (currentProfile.groupNumbers) then
partialAmount = FormatLargeNumber(partialAmount)
end
-- Color it with the correct color if coloring is enabled.
local overhealSettings = currentProfile.overheal
partialAmount = string_gsub(overhealSettings.trailer, "%%a", partialAmount)
if (not currentProfile.partialColoringDisabled) then
partialAmount = string_format("|cFF%02x%02x%02x%s|r", overhealSettings.colorR * 255, overhealSettings.colorG * 255, overhealSettings.colorB * 255, partialAmount)
end
-- No overheal so check if there is overkill information and displaying it is enabled.
elseif (overkillAmount and overkillAmount > 0 and not currentProfile.overkill.disabled) then
-- Deduct the overkill amount from the total amount of damage done.
amount = amount - overkillAmount
-- Shorten overkill amount with SI suffixes or separate into digit groups depending on options.
partialAmount = overkillAmount
if (currentProfile.shortenNumbers) then
partialAmount = ShortenNumber(partialAmount, currentProfile.shortenNumberPrecision)
elseif (currentProfile.groupNumbers) then
partialAmount = FormatLargeNumber(partialAmount)
end
-- Color it with the correct color if coloring is enabled.
local overkillSettings = currentProfile.overkill
partialAmount = string_gsub(overkillSettings.trailer, "%%a", partialAmount)
if (not currentProfile.partialColoringDisabled) then
partialAmount = string_format("|cFF%02x%02x%02x%s|r", overkillSettings.colorR * 255, overkillSettings.colorG * 255, overkillSettings.colorB * 255, partialAmount)
end
end
-- Make sure to show positive amounts for eclipse energy since it can be negative.
local formattedAmount = amount
--if (powerType == powerTypes["ECLIPSE"]) then formattedAmount = math_abs(amount) end
-- Shorten amount with SI suffixes or separate into digit groups depending on options.
if (currentProfile.shortenNumbers) then
formattedAmount = ShortenNumber(formattedAmount, currentProfile.shortenNumberPrecision)
elseif (currentProfile.groupNumbers) then
formattedAmount = FormatLargeNumber(formattedAmount)
end
-- Get the hex color for the damage type if there is one and coloring is enabled.
if (damageType and not ignoreDamageColoring and not currentProfile.damageColoringDisabled) then
-- Color the amount according to the damage type if there is one and it's enabled.
local damageSettings = currentProfile[damageColorProfileEntries[damageType]]
if (damageSettings and not damageSettings.disabled) then
formattedAmount = string_format("|cFF%02x%02x%02x%s|r", damageSettings.colorR * 255, damageSettings.colorG * 255, damageSettings.colorB * 255, formattedAmount)
end
end -- Damage type and damage coloring is enabled.
-- Substitute all %a event codes with the amount.
message = string_gsub(message, "%%a", formattedAmount .. partialAmount)
end -- Substitute amount.
-- Substitute power type.
if (powerType and string_find(message, "%p", 1, true)) then
local powerString = _G[powerTokens[powerType] or "UNKNOWN"]
--if (powerType == powerTypes["ECLIPSE"]) then powerString = amount and (amount > 0 and BALANCE_POSITIVE_ENERGY or BALANCE_NEGATIVE_ENERGY) or UNKNOWN end
message = string_gsub(message, "%%p", powerString or UNKNOWN)
end
-- Substitute names.
if (name and string_find(message, "%n", 1, true)) then
if (hideNames) then
message = string_gsub(message, "%s?%-?%s?%%n", "")
checkParens = true
else
-- Strip realm from names.
if (string_find(name, "-", 1, true)) then name = string_gsub(name, "(.-)%-.*", "%1") end
-- Color the name according to the class if there is one and it's enabled.
if (class and not currentProfile.classColoringDisabled) then
local classSettings = currentProfile[class]
if (classSettings and not classSettings.disabled) then name = string_format("|cFF%02x%02x%02x%s|r", classSettings.colorR * 255, classSettings.colorG * 255, classSettings.colorB * 255, name) end
end
-- Substitute all %n event codes with the name.
message = string_gsub(message, "%%n", name)
end
else
message = string_gsub(message, "%%n", "")
checkParens = true
end
-- Substitute effect names.
if (effectName and string_find(message, "%e", 1, true)) then message = string_gsub(message, "%%e", effectName) end
-- Substitute skill names.
if (effectName) then
if (string_find(message, "%s", 1, true)) then
-- Hide skill names if there is an icon for it and the option is set.
if (hideSkills) then
message = string_gsub(message, "%s?%-?%s?%%sl?%s?%-?%s?", "")
checkParens = true
else
-- Use the user defined substitution for the ability if there is one.
local isChanged
if (currentProfile.abilitySubstitutions[effectName]) then
effectName = currentProfile.abilitySubstitutions[effectName]
isChanged = true
end
-- Do long substitutions.
if (string_find(message, "%sl", 1, true)) then message = string_gsub(message, "%%sl", effectName) end
-- Abbreviate skill for English if it wasn't user substituted and abbreviation is enabled.
if (isEnglish and not isChanged and currentProfile.abbreviateAbilities) then
effectName = AbbreviateSkillName(effectName)
end
-- Do remaining substitutions.
message = string_gsub(message, "%%s", effectName)
end
end
end
-- Remove empty parenthesis left frame ignoring event codes.
if (checkParens) then
message = string_gsub(message, "%(%)", "")
message = string_gsub(message, "%[%]", "")
message = string_gsub(message, "%{%}", "")
message = string_gsub(message, "%<%>", "")
end
-- Substitute damage types.
if (damageType and string_find(message, "%t", 1, true)) then message = string_gsub(message, "%%t", damageTypeMap[damageType] or STRING_SCHOOL_UNKNOWN) end
-- Append partial effects if there are any.
if (partialEffects) then message = message .. partialEffects end
-- Append the merge trailer if there is one.
if (mergeTrailer) then message = message .. mergeTrailer end
-- Return the formatted message.
return message
end
-- ****************************************************************************
-- Returns the event type prefix and affected unit name for events that
-- can be incoming or outgoing.
-- ****************************************************************************
local function GetInOutEventData(parserEvent)
local eventTypeString, affectedUnitName, affectedUnitClass
-- Get the information for whether the event is incoming or outgoing.
if (parserEvent.recipientUnit == "player") then
affectedUnitName = parserEvent.sourceName
eventTypeString = "INCOMING"
affectedUnitClass = classMap[parserEvent.sourceGUID]
elseif (parserEvent.sourceUnit == "player") then
affectedUnitName = parserEvent.recipientName
eventTypeString = "OUTGOING"
affectedUnitClass = classMap[parserEvent.recipientGUID]
elseif (parserEvent.recipientUnit == "pet") then
affectedUnitName = parserEvent.sourceName
eventTypeString = "PET_INCOMING"
affectedUnitClass = classMap[parserEvent.sourceGUID]
elseif (parserEvent.sourceUnit == "pet") then
affectedUnitName = parserEvent.recipientName
eventTypeString = "PET_OUTGOING"
affectedUnitClass = classMap[parserEvent.recipientGUID]
end
return eventTypeString, affectedUnitName, affectedUnitClass
end
-- ****************************************************************************
-- Detect all power gains.
-- ****************************************************************************
local function DetectPowerGain(powerAmount, powerType)
-- Get the event settings.
local eventSettings = MSBTProfiles.currentProfile.events.NOTIFICATION_POWER_GAIN
-- Don't do anything if the event is disabled or the power type is invalid.
if (eventSettings.disabled or not powerType) then return end
-- Display the power change if it is a gain.
local lastPowerAmount = lastPowerAmounts[powerType] or 65535
if (powerAmount > lastPowerAmount) then
DisplayEvent(eventSettings, FormatEvent(eventSettings.message, powerAmount - lastPowerAmount, nil, nil, nil, powerType, nil, nil, UNKNOWN))
end
end
-- ****************************************************************************
-- Handle combo point changes.
-- ****************************************************************************
local function HandleComboPoints(amount, powerType)
-- Get the correct event settings.
local eventSettings = MSBTProfiles.currentProfile.events.NOTIFICATION_CP_GAIN
local maxAmount = UnitPowerMax("player", powerType)
if (amount == maxAmount) then eventSettings = MSBTProfiles.currentProfile.events.NOTIFICATION_CP_FULL end
-- Don't do anything if the event is disabled.
if (eventSettings.disabled) then return end
-- Don't do anything if 0 CP
if (amount == 0) then return end
-- Display the event.
if amount <= 0 then
return
end
DisplayEvent(eventSettings, FormatEvent(eventSettings.message, amount))
end
-- ****************************************************************************
-- Handle light force changes.
-- ****************************************************************************
local function HandleChi(amount, powerType)
-- Get the correct event settings.
local eventSettings = MSBTProfiles.currentProfile.events.NOTIFICATION_CHI_CHANGE
local maxAmount = UnitPowerMax("player", powerType)
if (amount == maxAmount) then eventSettings = MSBTProfiles.currentProfile.events.NOTIFICATION_CHI_FULL end
-- Don't do anything if the event is disabled.
if (eventSettings.disabled) then return end
-- Display the event.
if amount <= 0 then
return
end
DisplayEvent(eventSettings, FormatEvent(eventSettings.message, amount))
end
-- ****************************************************************************
-- Handle arcane power changes.
-- ****************************************************************************
local function HandleArcanePower(amount, powerType)
-- Get the correct event settings.
local eventSettings = MSBTProfiles.currentProfile.events.NOTIFICATION_AC_CHANGE
local maxAmount = UnitPowerMax("player", powerType)
if (amount == maxAmount) then eventSettings = MSBTProfiles.currentProfile.events.NOTIFICATION_AC_FULL end
-- Don't do anything if the event is disabled.
if (eventSettings.disabled) then return end
-- Display the event.
if amount <= 0 then
return
end
DisplayEvent(eventSettings, FormatEvent(eventSettings.message, amount))
end
-- ****************************************************************************
-- Handle holy power changes.
-- ****************************************************************************
local function HandleHolyPower(amount, powerType)
-- Get the correct event settings.
local eventSettings = MSBTProfiles.currentProfile.events.NOTIFICATION_HOLY_POWER_CHANGE
local maxAmount = UnitPowerMax("player", powerType)
if (amount == maxAmount) then eventSettings = MSBTProfiles.currentProfile.events.NOTIFICATION_HOLY_POWER_FULL end
-- Don't do anything if the event is disabled.
if (eventSettings.disabled) then return end
-- Don't do anything if 0 Holy Power
if (amount == 0) then return end
-- Display the event.
if amount <= 0 then
return
end
DisplayEvent(eventSettings, FormatEvent(eventSettings.message, amount))
end
-- ****************************************************************************
-- Handle monster emotes.
-- ****************************************************************************
local function HandleMonsterEmotes(emoteString)
-- Get the event settings.
local eventSettings = MSBTProfiles.currentProfile.events.NOTIFICATION_MONSTER_EMOTE
-- Don't do anything if the event is disabled.
if (eventSettings.disabled) then return end
-- Loop through all of the recent emotes and remove the old ones.
local now = GetTime()
for emote, cleanupTime in pairs(recentEmotes) do
if (now >= cleanupTime) then recentEmotes[emote] = nil end
end
-- Don't do anything if the emote has already been shown within the specified time frame.
if (recentEmotes[emoteString]) then return end
-- Display the event and add it to the recent emotes list.
DisplayEvent(eventSettings, FormatEvent(eventSettings.message, nil, nil, nil, nil, nil, nil, nil, emoteString))
recentEmotes[emoteString] = now + EMOTE_HOLD_TIME
end
-- ****************************************************************************
-- Merges like combat events.
-- ****************************************************************************
local function MergeEvents(numEvents, currentProfile)
-- Holds an unmerged event and whether or not to merge it.
local unmergedEvent
local doMerge = false
-- Don't attempt to merge any more events than were available when the function was called since
-- more events may get added while the merge is taking place.
for i = 1, numEvents do
-- Get the unmerged event.
unmergedEvent = unmergedEvents[i]
-- Loop through all of the events in the merged events array.
for _, mergedEvent in ipairs(mergedEvents) do
-- Check if the event types match.
if (unmergedEvent.eventType == mergedEvent.eventType) then
-- Check if there is no skill name.
if (not unmergedEvent.effectName) then
-- Set the merge flag if the affected unit name is the same.
if ((unmergedEvent.name == mergedEvent.name) and unmergedEvent.name) then doMerge = true end
-- The skill names match.
elseif (unmergedEvent.effectName == mergedEvent.effectName) then
-- Change the name to the multiple targets string if the names don't match.
if (unmergedEvent.name ~= mergedEvent.name) then mergedEvent.name = L.MSG_MULTIPLE_TARGETS end
-- Clear the class if they don't match.
if (unmergedEvent.class ~= mergedEvent.class) then mergedEvent.class = nil end
-- Set the merge flag.
doMerge = true
end
end -- Event types match.
-- Check if the event should be merged.
if (doMerge) then
-- Clear partial effects.
mergedEvent.partialEffects = nil
-- Set the event merged flag for the event being merged.
unmergedEvent.eventMerged = true
-- Total the amount if there is one.
if (unmergedEvent.amount) then mergedEvent.amount = (mergedEvent.amount or 0) + unmergedEvent.amount end
-- Total the overheal amount if there is one.
if (unmergedEvent.overhealAmount) then mergedEvent.overhealAmount = (mergedEvent.overhealAmount or 0) + unmergedEvent.overhealAmount end
-- Increment the number of merged events.
mergedEvent.numMerged = mergedEvent.numMerged + 1
-- Increment the number of crits if the event being merged is a crit. Clear the crit flag for the merged event if it isn't.
if (unmergedEvent.isCrit) then mergedEvent.numCrits = mergedEvent.numCrits + 1 else mergedEvent.isCrit = false end
-- Break out of the merged events loop since the event has been merged.
break
end -- Do Merge.
end -- Loop through merged events.
-- Add the event to the end of the merged events array if it wasn't merged.
if (not doMerge) then
unmergedEvent.numMerged = 0
-- Set the number of crits depending on if the event is a crit or not.
if (unmergedEvent.isCrit) then unmergedEvent.numCrits = 1 else unmergedEvent.numCrits = 0 end
-- Add the event to the end of the merged events array.
mergedEvents[#mergedEvents+1] = unmergedEvent
end
-- Reset the event merge flag.
doMerge = false
end -- Loop through unmerged events.
-- Append merge trailer information to the merged events if enabled.
if not (currentProfile.hideMergeTrailer) then
for _, mergedEvent in ipairs(mergedEvents) do
-- Check if there were any events merged.
if (mergedEvent.numMerged > 0) then
-- Create the crit trailer text if there were any crits.
local critTrailer = ""
if (mergedEvent.numCrits > 0) then
critTrailer = string_format(", %d %s", mergedEvent.numCrits, mergedEvent.numCrits == 1 and L.MSG_CRIT or L.MSG_CRITS)
end
-- Set the event's merge trailer field.
mergedEvent.mergeTrailer = string_format(" [%d %s%s]", mergedEvent.numMerged + 1, L.MSG_HITS, critTrailer)
end -- Events were merged.
end -- Loop through merged events.
end
-- Remove the processed events from unmerged events queue.
for i = 1, numEvents do
-- Recycle the unmerged event if it was merged.
if (unmergedEvents[1].eventMerged) then
EraseTable(unmergedEvents[1])
combatEventCache[#combatEventCache+1] = unmergedEvents[1]
end
-- Remove the event from the unmerged events array.
table_remove(unmergedEvents, 1)
end
end
-------------------------------------------------------------------------------
-- Event handlers.
-------------------------------------------------------------------------------
-- ****************************************************************************
-- Handles damage parser events.
-- ****************************************************************************
local function DamageHandler(parserEvent, currentProfile)
-- Setup info for whether the event is incoming or outgoing.
local eventTypeString, affectedUnitName, affectedUnitClass = GetInOutEventData(parserEvent)
-- Ignore the event if it doesn't pertain to the player or their pet.
if (not eventTypeString) then return end
-- Ignore the event if the damage amount is under the damage threshold to be shown.
if (parserEvent.amount and parserEvent.amount < currentProfile.damageThreshold) then return end
-- Recharacterize hunter auto shots to melee damage.
local skillID = parserEvent.skillID
if (skillID == SPELLID_AUTOSHOT) then skillID = nil end
-- Append the spell prefix if there is a skill.
if (skillID) then eventTypeString = eventTypeString .. "_SPELL" end
-- Append correct damage suffix.
eventTypeString = eventTypeString .. (parserEvent.isDoT and "_DOT" or parserEvent.isDamageShield and "_DAMAGE_SHIELD" or "_DAMAGE")
return eventTypeString, parserEvent.skillName, affectedUnitName, affectedUnitClass, true
end
-- ****************************************************************************
-- Handles miss parser events.
-- ****************************************************************************
local function MissHandler(parserEvent, currentProfile)
-- Setup info for whether the event is incoming or outgoing.
local eventTypeString, affectedUnitName, affectedUnitClass = GetInOutEventData(parserEvent)
-- Ignore the event if it doesn't pertain to the player or their pet.
if (not eventTypeString) then return end
-- Recharacterize hunter auto shots to melee damage.
local skillID = parserEvent.skillID
if (skillID == SPELLID_AUTOSHOT) then skillID = nil end
-- Append the spell prefix if there is a skill.
if (skillID) then eventTypeString = eventTypeString .. "_SPELL" end
-- Append the miss type.
eventTypeString = eventTypeString .. "_" .. parserEvent.missType
return eventTypeString, parserEvent.skillName, affectedUnitName, affectedUnitClass, true
end
-- ****************************************************************************
-- Handles heal parser events.
-- ****************************************************************************
local function HealHandler(parserEvent, currentProfile)
-- Setup info for whether the event is incoming or outgoing.
local eventTypeString, affectedUnitName, affectedUnitClass = GetInOutEventData(parserEvent)
-- Ignore the event if it doesn't pertain to the player or their pet.
if (not eventTypeString) then return end
local isHoT = parserEvent.isHoT
local amount = parserEvent.amount
if (amount) then
-- Ignore the event if the heal amount is under the healing threshold to be shown.
if (amount < currentProfile.healThreshold) then return end
-- Calculate the effective heal amount.
local overhealAmount = parserEvent.overhealAmount
local effectiveHealAmount = overhealAmount and (amount - overhealAmount) or amount
-- Ignore the event if the effective heal amount is zero and the appropriate
-- hide full overheals option is set.
if (effectiveHealAmount == 0) then
if (not isHoT and currentProfile.hideFullOverheals) then return end
if (isHoT and currentProfile.hideFullHoTOverheals) then return end
end
end
-- Slicing out self-cast heals into their own thing.
if (parserEvent.sourceName == parserEvent.recipientName) then
eventTypeString = "SELF"
end
-- Append hot suffix if it's a hot.
eventTypeString = eventTypeString .. (isHoT and "_HOT" or "_HEAL")
return eventTypeString, parserEvent.skillName, affectedUnitName, affectedUnitClass, true
end
-- ****************************************************************************
-- Handles interrupt parser events.
-- ****************************************************************************
local function InterruptHandler(parserEvent, currentProfile)
-- Setup info for whether the event is incoming or outgoing.
local eventTypeString, affectedUnitName, affectedUnitClass = GetInOutEventData(parserEvent)
-- Ignore the event if it doesn't pertain to the player or their pet.
if (not eventTypeString) then return end
-- Append interrupt suffix.
eventTypeString = eventTypeString .. "_SPELL_INTERRUPT"
return eventTypeString, parserEvent.extraSkillName, affectedUnitName, affectedUnitClass
end
-- ****************************************************************************
-- Handles environmental parser events.
-- ****************************************************************************
local function EnvironmentalHandler(parserEvent, currentProfile)
-- Ignore the event if it isn't the player.
if (parserEvent.recipientUnit ~= "player") then return end
return "INCOMING_ENVIRONMENTAL", parserEvent.hazardType
end
-- ****************************************************************************
-- Handles aura parser events.
-- ****************************************************************************
local function AuraHandler(parserEvent, currentProfile)
local eventTypeString, affectedUnitName, affectedUnitClass
local effectName = parserEvent.skillName
-- Aura is pertaining to the player.
if (parserEvent.recipientUnit == "player") then
-- Ignore auras that don't provide useful information.
if (ignoreAuras[parserEvent.skillName] and parserEvent.sourceUnit == "player") then return end
-- Ignore the event if it's suppressed due to a trigger.
if (triggerSuppressions[effectName]) then return end
-- Set notification buff/debuff.
eventTypeString = "NOTIFICATION_" .. parserEvent.auraType
-- Append stack or fade prefix if needed.
if (not parserEvent.isFade) then
if (parserEvent.isDose) then eventTypeString = eventTypeString .. "_STACK" end
else
eventTypeString = eventTypeString .. "_FADE"
end
-- Aura is pertaining to another unit.
else
-- Ignore the event if it's suppressed due to a trigger.
if (triggerSuppressions[effectName]) then return end
-- Ignore the event if it isn't for the current target.
if (not TestFlagsAll(parserEvent.recipientFlags, MSBTParser.TARGET_TARGET)) then return end
-- Ignore the event if it's a friendly unit.
if (not UnitIsEnemy("player", "target")) then return end
-- Ignore the event if it's not a buff gain.
if (parserEvent.auraType ~= "BUFF" or parserEvent.isFade == true) then return end
-- Loop through all of the recent enemy buff gains and remove the old ones.
local now = GetTime()
for buff, cleanupTime in pairs(recentEnemyBuffs) do
if (now >= cleanupTime) then recentEnemyBuffs[buff] = nil end
end
-- Ignore the event if it has already been shown within the specified time frame.
if (recentEnemyBuffs[effectName]) then return end
-- Add the event to the recent enemy buffs list.
recentEnemyBuffs[effectName] = now + ENEMY_BUFF_HOLD_TIME
eventTypeString = "NOTIFICATION_ENEMY_BUFF"
affectedUnitName = parserEvent.recipientName
affectedUnitClass = classMap[parserEvent.recipientGUID]
end -- Player or enemy check.
return eventTypeString, effectName, affectedUnitName, affectedUnitClass
end
-- ****************************************************************************
-- Handles enchant parser events.
-- ****************************************************************************
local function EnchantHandler(parserEvent, currentProfile)
-- Ignore the event if it isn't the player.
if (parserEvent.recipientUnit ~= "player") then return end
-- Set the item buff event type and append fade suffix if the enchant is fading.
local eventTypeString = "NOTIFICATION_ITEM_BUFF"
if (parserEvent.isFade) then eventTypeString = eventTypeString .. "_FADE" end
return eventTypeString, parserEvent.skillName
end
-- ****************************************************************************
-- Handles dispel parser events.
-- ****************************************************************************
local function DispelHandler(parserEvent, currentProfile)
-- Get the correct dispel event.
local eventTypeString
if (parserEvent.sourceUnit == "player") then
eventTypeString = "OUTGOING_DISPEL"
elseif (parserEvent.sourceUnit == "pet") then
eventTypeString = "PET_OUTGOING_DISPEL"
else
-- Ignore the event if it isn't from the player or their pet.
return
end
return eventTypeString, parserEvent.extraSkillName, parserEvent.recipientName, classMap[parserEvent.recipientGUID]
end
-- ****************************************************************************
-- Handles power parser events.
-- ****************************************************************************
local function PowerHandler(parserEvent, currentProfile)
-- Handle certain power types such as holy power, shadow orbs, and chi uniquely.
if (uniquePowerTypes[parserEvent.powerType] ~= nil) then return end
-- Ignore the event if all power gains are being shown.
if (currentProfile.showAllPowerGains) then return end
-- Ignore the event it doesn't affect the player and set the correct amount.
local amount
if (parserEvent.isLeech) then
if (parserEvent.sourceUnit ~= "player") then return end
amount = parserEvent.extraAmount
else
if (parserEvent.recipientUnit ~= "player") then return end
amount = parserEvent.amount
end
if amount == 0 then
return
end
-- Ignore the event if the power change is under the threshold to be shown.
-- Take the absolute value to ensure negative power amounts such as Lunar Energy are handled correctly.
if (amount and math_abs(amount) < currentProfile.powerThreshold) then return end
-- Use a different event for alternate power.
local eventTypePrefix = "NOTIFICATION_POWER_"
if (parserEvent.powerType == powerTypes["ALTERNATE_POWER"]) then eventTypePrefix = "NOTIFICATION_ALT_POWER_" end
-- Append gain or loss suffix.
local eventTypeString = eventTypePrefix .. (parserEvent.isDrain and "LOSS" or "GAIN")
return eventTypeString, parserEvent.skillName, nil, nil, true
end
-- ****************************************************************************
-- Handles kill parser events.
-- ****************************************************************************
local function KillHandler(parserEvent, currentProfile)
-- Ignore the event if it isn't a kill from the player.