-
Notifications
You must be signed in to change notification settings - Fork 8
Expand file tree
/
Copy pathDecursive.lua
More file actions
999 lines (768 loc) · 35.4 KB
/
Decursive.lua
File metadata and controls
999 lines (768 loc) · 35.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
--[[
This file is part of Decursive.
Decursive (v @project-version@) add-on for World of Warcraft UI
Copyright (C) 2006-2025 John Wellesz (Decursive AT 2072productions.com) ( http://www.2072productions.com/to/decursive.php )
Decursive is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Decursive is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Decursive. If not, see <https://www.gnu.org/licenses/>.
Decursive is inspired from the original "Decursive v1.9.4" by Patrick Bohnet (Quu).
The original "Decursive 1.9.4" is in public domain ( www.quutar.com )
Decursive is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
This file was last updated on @file-date-iso@
--]]
-------------------------------------------------------------------------------
local addonName, T = ...;
-- big ugly scary fatal error message display function {{{
if not T._FatalError then
-- the beautiful error popup : {{{ -
StaticPopupDialogs["DECURSIVE_ERROR_FRAME"] = {
text = "|cFFFF0000Decursive Error:|r\n%s",
button1 = "OK",
OnAccept = function()
return false;
end,
timeout = 0,
whileDead = 1,
hideOnEscape = 1,
showAlert = 1,
preferredIndex = 3,
}; -- }}}
T._FatalError = function (TheError) StaticPopup_Show ("DECURSIVE_ERROR_FRAME", TheError); end
end
-- }}}
if not T._LoadedFiles or not T._LoadedFiles["Dcr_Raid.lua"] then
if not DecursiveInstallCorrupted then T._FatalError("Decursive installation is corrupted! (Dcr_Raid.lua not loaded)"); end;
DecursiveInstallCorrupted = true;
return;
end
T._LoadedFiles["Decursive.lua"] = false;
local D = T.Dcr;
local L = D.L;
local LC = D.LC;
local DC = T._C;
-------------------------------------------------------------------------------
local _G = _G;
local pairs = _G.pairs;
local ipairs = _G.ipairs;
local type = _G.type;
local table = _G.table;
local t_sort = _G.table.sort;
local t_wipe = _G.table.wipe;
local UnitName = _G.UnitName;
local UnitDebuff = _G.UnitDebuff;
local UnitBuff = _G.UnitBuff;
local UnitIsCharmed = _G.UnitIsCharmed;
local UnitCanAttack = _G.UnitCanAttack;
local UnitClass = _G.UnitClass;
local UnitExists = _G.UnitExists;
local GetNetStats = _G.GetNetStats;
local canaccessvalue = _G.canaccessvalue or function(_) return true; end
local _;
-------------------------------------------------------------------------------
-- The UI functions {{{
-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
-- The printing functions {{{
-------------------------------------------------------------------------------
function D:Show_Cure_Order() --{{{
self:Println("printing cure order:");
for index, unit in ipairs(self.Status.Unit_Array) do
self:Println( unit, " - ", self:MakePlayerName((self:UnitName(unit))) , " Index: ", index);
end
end --}}}
-- }}}
-------------------------------------------------------------------------------
-- Show Hide FUNCTIONS -- {{{
function D:ShowHideLiveList(hide) --{{{
if not D.DcrFullyInitialized then
return;
end
-- if hide is requested or if hide is not set and the live-list is shown
if (hide==1 or (not hide and DcrLiveList:IsVisible())) then
D.profile.HideLiveList = true;
DcrLiveList:Hide();
D:CancelDelayedCall("Dcr_LLupdate");
else
D.profile.HideLiveList = false;
DcrLiveList:ClearAllPoints();
DcrLiveList:SetPoint("TOPLEFT", "DecursiveMainBar", "BOTTOMLEFT");
DcrLiveList:Show();
D:ScheduleRepeatedCall("Dcr_LLupdate", D.LiveList.Update_Display, D.profile.ScanTime, D.LiveList);
end
end --}}}
-- This functions hides or shows the "Decursive" bar depending on its current
-- state, it's also able hide/show the live-list if the "tie live-list" option is active
function D:HideBar(hide) --{{{
if not D.DcrFullyInitialized then
return;
end
if (hide==1 or (not hide and DecursiveMainBar:IsVisible())) then
if (D.profile.LiveListTied) then
D:ShowHideLiveList(1);
end
D.profile.BarHidden = true;
DecursiveMainBar:Hide();
else
if (D.profile.LiveListTied) then
D:ShowHideLiveList(0);
end
D.profile.BarHidden = false;
DecursiveMainBar:Show();
end
if DecursiveMainBar:IsVisible() and DcrLiveList:IsVisible() then
DcrLiveList:ClearAllPoints();
DcrLiveList:SetPoint("TOPLEFT", "DecursiveMainBar", "BOTTOMLEFT");
else
D:ColorPrint(0.3, 0.5, 1, L["SHOW_MSG"]);
end
LibStub("AceConfigRegistry-3.0"):NotifyChange(D.name);
end --}}}
function D:ShowHidePriorityListUI() --{{{
if not D.DcrFullyInitialized then
return;
end
if (DecursivePriorityListFrame:IsVisible()) then
DecursivePriorityListFrame:Hide();
else
DecursivePriorityListFrame:Show();
end
end --}}}
function D:ShowHideSkipListUI() --{{{
if not D.DcrFullyInitialized then
return;
end
if (DecursiveSkipListFrame:IsVisible()) then
DecursiveSkipListFrame:Hide();
else
DecursiveSkipListFrame:Show();
end
end --}}}
-- This shows/hides the buttons near the "Decursive" bar
function D:ShowHideButtons(UseCurrentValue) --{{{
if not D.DcrFullyInitialized then
return;
end
if not D.profile then
return;
end
local DcrFrame = "DecursiveMainBar";
local buttons = {
DcrFrame .. "Priority",
DcrFrame .. "Skip",
DcrFrame .. "Hide",
}
local DCRframeObject = _G[DcrFrame];
if (not UseCurrentValue) then
D.profile.HideButtons = (not D.profile.HideButtons);
end
for _, ButtonName in pairs(buttons) do
local Button = _G[ButtonName];
if (D.profile.HideButtons) then
Button:Hide();
DCRframeObject.isLocked = 1;
else
Button:Show();
DCRframeObject.isLocked = 0;
end
end
end --}}}
-- }}}
-- this resets the location of the windows
function D:ResetWindow() --{{{
DecursiveMainBar:ClearAllPoints();
DecursiveMainBar:SetPoint("CENTER", UIParent);
DecursiveMainBar:Show();
DcrLiveList:ClearAllPoints();
DcrLiveList:SetPoint("TOPLEFT", DecursiveMainBar, "BOTTOMLEFT");
DcrLiveList:Show();
DecursivePriorityListFrame:ClearAllPoints();
DecursivePriorityListFrame:SetPoint("CENTER", UIParent);
DecursiveSkipListFrame:ClearAllPoints();
DecursiveSkipListFrame:SetPoint("CENTER", UIParent);
DecursivePopulateListFrame:ClearAllPoints();
DecursivePopulateListFrame:SetPoint("CENTER", UIParent);
D.MFContainer:ClearAllPoints();
D.MFContainer:SetPoint("CENTER", UIParent, "CENTER", 0, 0);
DecursiveAnchor:ClearAllPoints();
DecursiveAnchor:SetPoint("TOP", UIErrorsFrame, "BOTTOM", 0, 0);
end --}}}
function D:PlaySound (UnitID, Caller) --{{{
if self.profile.PlaySound and not self.Status.SoundPlayed then
local Debuffs, IsCharmed = self:UnitCurableDebuffs(UnitID, true);
if Debuffs[1] or IsCharmed then
-- since WoW 8.2, one has to use ids found at https://wow.tools/files/
self:SafePlaySoundFile(self.profile.SoundFile);
self.Status.SoundPlayed = true;
if self.debug then
self:Debug("Sound scheduled by", Caller);
end
end
end
end --}}}
-- LIVE-LIST DISPLAY functions {{{
-- Those set the scalling of the LIVELIST container
-- SACALING FUNCTIONS {{{
-- Place the LIVELIST container according to its scale
function D:PlaceLL () -- {{{
local UIScale = UIParent:GetEffectiveScale()
local FrameScale = DecursiveMainBar:GetEffectiveScale();
local x, y = D.profile.MainBarX, D.profile.MainBarY;
-- check if the coordinates are correct
if x and y and (x + 10 > UIParent:GetWidth() * UIScale or x < 0 or (-1 * y + 10) > UIParent:GetHeight() * UIScale or y > 0) then
x = false; -- reset to default position
T._FatalError("Decursive's bar position reset to default");
end
-- Executed for the very first time, then put it in the top right corner of the screen
if (not x or not y) then
x = (UIParent:GetWidth() * UIScale) / 2;
y = - (UIParent:GetHeight() * UIScale) / 8;
D.profile.MainBarX = x;
D.profile.MainBarY = y;
end
-- set to the scaled position
DecursiveMainBar:ClearAllPoints();
DecursiveMainBar:SetPoint("TOPLEFT", UIParent, "TOPLEFT", x/FrameScale , y/FrameScale);
DcrLiveList:ClearAllPoints();
DcrLiveList:SetPoint("TOPLEFT", DecursiveMainBar, "BOTTOMLEFT");
end -- }}}
-- Save the position of the frame without its scale
function D:SaveLLPos () -- {{{
if self.profile and DecursiveMainBar:IsVisible() then
-- We save the unscalled position (no problem if the sacale is changed behind our back)
self.profile.MainBarX = DecursiveMainBar:GetEffectiveScale() * DecursiveMainBar:GetLeft();
self.profile.MainBarY = DecursiveMainBar:GetEffectiveScale() * DecursiveMainBar:GetTop() - UIParent:GetHeight() * UIParent:GetEffectiveScale();
if self.profile.MainBarX < 0 then
self.profile.MainBarX = 0;
end
if self.profile.MainBarY > 0 then
self.profile.MainBarY = 0;
end
D:Debug("LL pos Saved:", self.profile.MainBarX, self.profile.MainBarY);
end
end -- }}}
-- set the scaling of the LIVELIST container according to the user settings
function D:SetLLScale (NewScale) -- {{{
-- save the current position without any scaling
D:SaveLLPos ();
-- Set the new scale
DecursiveMainBar:SetScale(NewScale);
DcrLiveList:SetScale(NewScale);
-- Place the frame adapting its position to the news cale
D:PlaceLL ();
end -- }}}
-- }}}
-- }}}
-- // }}}
-------------------------------------------------------------------------------
do
local iterator = 1;
local DebuffHistHashTable = {};
function D:Debuff_History_Add( DebuffName, DebuffType, spellID)
if not canaccessvalue(DebuffName) then -- do not store secret value
return;
end
if not DebuffHistHashTable[DebuffName] then
-- reset iterator if out of boundaries
if iterator > DC.DebuffHistoryLength then
iterator = 1;
end
-- clean hastable if necessary before adding a new entry
if D.DebuffHistory[iterator] and DebuffHistHashTable[D.DebuffHistory[iterator][1]] then
DebuffHistHashTable[D.DebuffHistory[iterator][1]] = nil;
end
-- Register the name in the HashTable using the debuff type
DebuffHistHashTable[DebuffName] = (DebuffType and DC.NameToTypes[DebuffType] or DC.NOTYPE);
--D:Debug(DebuffName, DebuffHistHashTable[DebuffName]);
-- Put this debuff in our history
D.DebuffHistory[iterator] = {DebuffName, spellID};
-- This is a useless comment
iterator = iterator + 1;
end
end
function D:Debuff_History_Get (Index, Colored)
local HumanIndex = iterator - Index;
if HumanIndex < 1 then
HumanIndex = HumanIndex + DC.DebuffHistoryLength;
end
if not D.DebuffHistory[HumanIndex] then
return "|cFF777777Empty|r", false;
end
if Colored then
--D:Debug(D.DebuffHistory[HumanIndex], DebuffHistHashTable[D.DebuffHistory[HumanIndex]]);
return D:ColorText(D.DebuffHistory[HumanIndex][1], D.profile.TypeColors[DebuffHistHashTable[D.DebuffHistory[HumanIndex][1]]]), D.DebuffHistory[HumanIndex][2], true;
else
return D.DebuffHistory[HumanIndex][1], D.DebuffHistory[HumanIndex][2], true;
end
end
end
-- Scanning functionalities {{{
-------------------------------------------------------------------------------
do
local D = D;
local C_UnitAuras = _G.C_UnitAuras
local filter = DC.MN and "RAID_PLAYER_DISPELLABLE" or nil
local UnitDebuff = _G.UnitDebuff or function (unitToken, i)
local auraData = C_UnitAuras.GetDebuffDataByIndex(unitToken, i, filter);
if not auraData then
return nil;
end
return auraData.name,
auraData.icon,
auraData.applications,
auraData.dispelName,
auraData.duration,
auraData.expirationTime,
nil,
nil,
nil,
auraData.spellId,
DC.MN and auraData.auraInstanceID or nil;
end
local UnitIsCharmed = _G.UnitIsCharmed;
local UnitCanAttack = _G.UnitCanAttack;
local GetTime = _G.GetTime;
local GetSpellDescription = _G.C_Spell and _G.C_Spell.GetSpellDescription or _G.GetSpellDescription;
local IsSpellDataCached = _G.C_Spell.IsSpellDataCached
local RequestLoadSpellData = _G.C_Spell.RequestLoadSpellData
local UnTrustedUnitIDs = {
['mouseover'] = true,
['target'] = true,
};
-- This local function only sets interesting values of UnitDebuff()
local Name, Texture, Applications, TypeName, Duration, ExpirationTime, _, SpellID, secretMode, auraInstanceID;
local function GetUnitDebuff (Unit, i) --{{{
if D.LiveList.TestItemDisplayed and UnitExists(Unit) then -- and not UnTrustedUnitIDs[Unit] then
if i == 1 then
Name, Texture, Applications, TypeName, Duration, ExpirationTime, SpellID = "Test item", "Interface\\AddOns\\Decursive\\iconON.tga", 2, DC.TypeNames[D.Status.ReversedCureOrder[1]], 70, (D.LiveList.TestItemDisplayed + 70), 0;
-- D:Debug("|cFFFF0000Setting test debuff for ", Unit, " (debuff ", i, ")|r");--, Name, Texture, Applications, TypeName, Duration, ExpirationTime);
return true;
else
i = i - 1;
end
end
Name, Texture, Applications, TypeName, Duration, ExpirationTime, _, _, _, SpellID, auraInstanceID = UnitDebuff (Unit, i);
secretMode = not canaccessvalue(TypeName)
if Name then
return true;
else
return false;
end
end --}}}
-- there is a known maximum number of unit and a known maximum debuffs per unit so lets allocate the memory needed only once. Memory will be allocated when needed and re-used...
local DebuffUnitCache = {};
-- Variables are declared outside so that Lua doesn't initialize them at each call
local Type, i, StoredDebuffIndex, CharmFound, IsCharmed;
local DcrC = T._C; -- for faster access
local function checkSpellIDForBleed()
-- it appears that sometime SpellID can be nil...
if not SpellID or D.Status.t_CheckBleedDebuffsActiveIDs[SpellID] ~= nil
or not D.db.global.BleedAutoDetection then
return
end
if not IsSpellDataCached(SpellID) then
RequestLoadSpellData(SpellID);
elseif D.Status.P_BleedEffectsKeywords_noCase ~= false then
if D:hasDescBleedEffectkeyword(GetSpellDescription(SpellID)) then
D.Status.t_CheckBleedDebuffsActiveIDs[SpellID] = true;
D.db.global.t_BleedEffectsIDCheck[SpellID] = true;
else
D.Status.t_CheckBleedDebuffsActiveIDs[SpellID] = false;
end
end
end
-- This is the core debuff scanning function of Decursive
-- This function does more than just reporting Debuffs. it also detects charmed units
function D:GetUnitDebuffAll (Unit) --{{{
-- create a Debuff table for this unit if there is not already one
if not DebuffUnitCache[Unit] then
DebuffUnitCache[Unit] = {};
end
-- This is just a shortcut for easier readability
local ThisUnitDebuffs = DebuffUnitCache[Unit];
i = 1; -- => to index all debuffs
StoredDebuffIndex = 1; -- => this index only debuffs with a type
CharmFound = false; -- => avoid to find that the unit is charmed again and again...
-- test if the unit is mind controlled once
-- The unit is not mouseover or target and it's attackable ---> it's charmed! (A new game's mechanic as been introduced where a player can become hostile but remain in control...)
if not UnTrustedUnitIDs[Unit] and UnitCanAttack("player", Unit) then
IsCharmed = true;
else
IsCharmed = false;
end
if self.LiveList.TestItemDisplayed and not UnTrustedUnitIDs[Unit] and (D.Status.ReversedCureOrder[1] == DC.CHARMED or D.Status.ReversedCureOrder[1] == DC.ENEMYMAGIC) then
IsCharmed = true;
end
-- iterate all available debuffs
while true do
if not GetUnitDebuff(Unit, i) then
if not IsCharmed or CharmFound then
break;
else
Name = "*Charm effect*";
Texture = "Interface\\AddOns\\Decursive\\iconON.tga";
ExpirationTime = false;
Duration = false;
Applications = 0;
--D:AddDebugText("Charm effect without debuff", i);
end
end
local isSpellIDScret = not canaccessvalue(SpellID)
--@debug@
if isSpellIDScret then
D:Debug("spell ids are secret, aura id: ", auraInstanceID)
end
if secretMode then
D:Debug("Debuff type is secret")
end
--@end-debug@
local s_color = DC.MN and auraInstanceID and C_UnitAuras.GetAuraDispelTypeColor(Unit, auraInstanceID, D.Status.dsCurve)
-- test for a type
if not secretMode then
if TypeName and TypeName ~= "" then
Type = DC.NameToTypes[TypeName];
elseif not isSpellIDScret and DC.IS_OMNI_DEBUFF[SpellID] then -- it's a special debuff for which any dispel will work
TypeName = DC.TypeNames[self.Status.ReversedCureOrder[1]];
Type = DC.NameToTypes[TypeName]
elseif not isSpellIDScret and self.Status.CuringSpells[DC.BLEED] then
checkSpellIDForBleed();
if D.Status.t_CheckBleedDebuffsActiveIDs[SpellID] then
Type = DC.NameToTypes["Bleed"]
TypeName = DC.TypeNames[DC.BLEED];
else
Type = false;
end
else
Type = false;
end
elseif s_color then --
-- just affect the first spell we know, it is mormally used to detect the range or button miss clicks
-- but in MN it's no longer possible so just default to the first spell as it's better than nothing...
TypeName = DC.TypeNames[self.Status.ReversedCureOrder[1]];
Type = DC.NameToTypes[TypeName]
else
Type = false;
end
-- if the unit is charmed and we didn't took care of this information yet
if IsCharmed and (not CharmFound or Type == DC.MAGIC) then
-- If the unit has a magical debuff and we can cure it
-- (note that the target is not friendly in that case)
if (Type == DC.MAGIC and self.Status.CuringSpells[DC.ENEMYMAGIC]) then
Type = DC.ENEMYMAGIC;
-- NOTE: if a unit is charmed and has another magical debuff
-- this block will be executed...
else -- the unit doesn't have a magical debuff or we can't remove magical debuffs
Type = DC.CHARMED; -- The player can't remove it anyway so just say the unit is afflicted by a charming effect
TypeName = DC.TypeNames[DC.CHARMED];
end
CharmFound = true;
end
-- If we found a type, register the Debuff
if Type then
-- Create a Debuff index entry if necessary
if (not ThisUnitDebuffs[StoredDebuffIndex]) then
ThisUnitDebuffs[StoredDebuffIndex] = {};
end
ThisUnitDebuffs[StoredDebuffIndex].Duration = Duration;
ThisUnitDebuffs[StoredDebuffIndex].ExpirationTime = ExpirationTime;
ThisUnitDebuffs[StoredDebuffIndex].Texture = Texture;
ThisUnitDebuffs[StoredDebuffIndex].Applications = Applications;
ThisUnitDebuffs[StoredDebuffIndex].TypeName = TypeName;
ThisUnitDebuffs[StoredDebuffIndex].Type = Type;
ThisUnitDebuffs[StoredDebuffIndex].Name = Name;
ThisUnitDebuffs[StoredDebuffIndex].SpellID = SpellID;
ThisUnitDebuffs[StoredDebuffIndex].auraInstanceID = auraInstanceID;
ThisUnitDebuffs[StoredDebuffIndex].secretMode = secretMode;
ThisUnitDebuffs[StoredDebuffIndex].s_color = s_color;
ThisUnitDebuffs[StoredDebuffIndex].index = i;
-- we can't use i, else we wouldn't have contiguous indexes in the table
StoredDebuffIndex = StoredDebuffIndex + 1;
end
i = i + 1;
-- if a deadly debuff has been found, just forget everything...
if not isSpellIDScret and DC.IS_DEADLY_DEBUFF[SpellID] then
StoredDebuffIndex = 1;
break;
end
end
-- erase remaining unused entries without freeing the memory (less garbage)
while (ThisUnitDebuffs[StoredDebuffIndex]) do
ThisUnitDebuffs[StoredDebuffIndex].Type = false;
StoredDebuffIndex = StoredDebuffIndex + 1;
end
-- if no debuff on the unit then it can't be charmed... or is it?
-- if i == 1 then
-- IsCharmed = false;
-- end
return ThisUnitDebuffs, IsCharmed;
end --}}}
end
do
-- see the comment about DebuffUnitCache
local ManagedDebuffUnitCache = D.ManagedDebuffUnitCache;
local continue_; -- if we have to ignore a debuff, this will become false
local D = D;
local _;
local CureOrder;
local sorting = function (a, b)
local aApps = canaccessvalue(a.Applications) and a.Applications or 0
local bApps = canaccessvalue(b.Applications) and b.Applications or 0
return CureOrder[a.Type] * 10000 - aApps < CureOrder[b.Type] * 10000 - bApps
end
local NotRaidOrParty = {
["player"] = true,
["target"] = true,
["focus"] = true,
["mouseover"] = true,
};
local HostileHolders = {
["target"] = true,
["focus"] = true,
["mouseover"] = true,
};
local function UnitFilteringTest(unit, filterValue)
--D:Debug("UnitFilteringTest:", unit, filterValue);
if not filterValue then
return nil;
end
if filterValue==1 and unit ~= 'player' then -- for personal spells
return true;
elseif filterValue==2 and NotRaidOrParty[unit] then -- for spells that can't be used on oneself
return true;
end
end
D.UnitFilteringTest = UnitFilteringTest; -- I need this function elsewhere
-- This function will return a table containing only the Debuffs we can cure excepts the one we have to ignore
-- in different conditions.
function D:UnitCurableDebuffs (Unit, JustOne) -- {{{
if not Unit then
D:AddDebugText("No unit supplied to UnitCurableDebuffs()");
return DC.EMPTY_TABLE, false;
end
CureOrder = D:GetCureOrderTable();
if not ManagedDebuffUnitCache[Unit] then
ManagedDebuffUnitCache[Unit] = {};
end
local ManagedDebuffs = ManagedDebuffUnitCache[Unit]; -- shortcut for readability
if ManagedDebuffs[1] then
t_wipe(ManagedDebuffs);
end
local AllUnitDebuffs, IsCharmed = self:GetUnitDebuffAll(Unit); -- always return a table, may be empty though
local Spells = self.Status.CuringSpells; -- shortcut to available spells by debuff type
for _, Debuff in ipairs(AllUnitDebuffs) do
continue_ = true;
if not Debuff.Type then
continue_ = false;
break
end
local nameAccessible = canaccessvalue(Debuff.Name)
-- test if we have to ignore this debuff {{{ --
if UnitFilteringTest(Unit, self.Status.UnitFilteringTypes[Debuff.Type]) then
continue_ = false; -- == skip this debuff
end
if nameAccessible and self.profile.DebuffsToIgnore[Debuff.Name] then
-- these are the BAD ones... the ones that make the target immune... abort this unit
--D:Debug("UnitCurableDebuffs(): %s is ignored", Debuff.Name);
break; -- exit here
end
if nameAccessible and self.profile.BuffDebuff[Debuff.Name] then
-- these are just ones you don't care about (sleepless deam etc...)
continue_ = false; -- == skip this debuff
--D:Debug("UnitCurableDebuffs(): %s is not a real debuff", Debuff.Name);
end
if self.Status.Combat or nameAccessible and self.profile.DebuffAlwaysSkipList[Debuff.Name] then
local _, EnUClass = UnitClass(Unit);
if self.profile.skipByClass[EnUClass] then
if nameAccessible and self.profile.skipByClass[EnUClass][Debuff.Name] then
-- these are just ones you don't care about by class while in combat
-- This lead to a problem because once the fight is finished there are no event to trigger
-- a rescan of this unit, so the debuff does not appear...
-- solution to the above problem:
if not self.profile.DebuffAlwaysSkipList[Debuff.Name] then
self:AddDelayedFunctionCall("ReScan"..Unit, D.MicroUnitF.UpdateMUFUnit, D.MicroUnitF, Unit);
end
D:Debug("UnitCurableDebuffs(): %s is configured to be skipped", Debuff.Name);
continue_ = false;
end
end
end
-- }}}
if continue_ then
-- self:Debug("Debuffs matters");
-- If we are still here it means that this Debuff is something not to be ignored...
-- We have an active curing spell for that type and we want to use it
if Spells[Debuff.Type] and CureOrder[Debuff.Type] then
-- self:Debug("we can cure it");
-- if we do have a spell to cure
if Spells[Debuff.Type] then
-- self:Debug("It's managed");
ManagedDebuffs[#ManagedDebuffs + 1] = Debuff;
-- the live-list only reports the first debuff found and set JustOne to true
if JustOne then
break;
end
end
end
end
end -- for END
if ManagedDebuffs[1] then
-- sort the table only if it contains more than 1 debuff
if #ManagedDebuffs > 1 then
t_sort(ManagedDebuffs, sorting);
end
if not D.UnitDebuffed[Unit] then
D.UnitDebuffed[Unit] = true;
D.ForLLDebuffedUnitsNum = D.ForLLDebuffedUnitsNum + 1;
end
else
if D.UnitDebuffed[Unit] then
D.UnitDebuffed[Unit] = false;
D.ForLLDebuffedUnitsNum = D.ForLLDebuffedUnitsNum - 1;
end
return DC.EMPTY_TABLE, false; -- avoid race conditions
end
return ManagedDebuffs, IsCharmed;
end -- // }}}
local GetTime = _G.GetTime;
local Debuffs = DC.EMPTY_TABLE; local IsCharmed = false; local Unit; local MUF; local IsDebuffed = false; local IsMUFDebuffed = false; local CheckStealth = false;
local NoScanStatuses = false;
local band = _G.bit.band;
--@debug@
--local debugprofilestop = _G.debugprofilestop;
--@end-debug@
function D:ScanEveryBody()
if not NoScanStatuses then
NoScanStatuses = {[DC.ABSENT] = true, [DC.FAR] = true, [DC.BLACKLISTED] = true};
end
local UnitArray = self.Status.Unit_Array; local i = 1;
local CheckStealth = self.profile.Show_Stealthed_Status;
--@debug@
--local start = debugprofilestop();
--D:Debug("Scanning everybody...", self.Status.delayedDebuffReportDisabled, self.db.global.MFScanEverybodyReport)
--@end-debug@
while UnitArray[i] do
Unit = UnitArray[i];
MUF = self.MicroUnitF.UnitToMUF[Unit];
if MUF and not NoScanStatuses[MUF.UnitStatus] then
IsMUFDebuffed = MUF.Debuffs[1] and true or band(MUF.UnitStatus, DC.CHARMED_STATUS) == DC.CHARMED_STATUS;
local MUFDebuffName = MUF.Debuffs[1] and MUF.Debuffs[1].Name
Debuffs, IsCharmed = self:UnitCurableDebuffs(Unit, true); -- leaks memory in 10.2.5
if CheckStealth then
self.Stealthed_Units[Unit] = self:CheckUnitStealth(Unit); -- update stealth status
end
IsDebuffed = (Debuffs[1] and true) or IsCharmed;
-- If MUF disagrees
if (IsDebuffed ~= IsMUFDebuffed) and not D:DelayedCallExixts("Dcr_Update" .. Unit) then
-- add counters here independently of the option so that I can monitor things using the reports sent to me.
-- a counter saved in the db, and a local session counter
if IsDebuffed then
self.Status.delayedDebuffOccurences = self.Status.delayedDebuffOccurences + 1;
self.db.global.delayedDebuffOccurences = self.db.global.delayedDebuffOccurences + 1;
else
self.Status.delayedUnDebuffOccurences = self.Status.delayedUnDebuffOccurences + 1;
self.db.global.delayedUnDebuffOccurences = self.db.global.delayedUnDebuffOccurences + 1;
end
if (not self.Status.delayedDebuffReportDisabled) and self.db.global.MFScanEverybodyReport then
if IsDebuffed then
self:AddDebugText("delayed debuff found by scaneveryone (you can disable this error by unchecking the `Periodic scan debug reporting` option in the MUFs performance options - see Decursive 2.7.16 release notes)", Unit, Debuffs[1].Name);
--D:ScheduleDelayedCall("Dcr_lateanalysis" .. Unit, self.MicroUnitF.LateAnalysis, 1, self.MicroUnitF, "ScanEveryone", Debuffs, MUF, MUF.UnitStatus);
else
self:AddDebugText("delayed UNdebuff found by scaneveryone (you can disable this error by unchecking the `Periodic scan debug reporting` option in the MUFs performance options - see Decursive 2.7.16 release notes)", Unit, MUFDebuffName, IsDebuffed, IsMUFDebuffed, MUF.UnitStatus);
end
else
self:Debug("delayed buff found but no-report is set")
end
self.MicroUnitF:UpdateMUFUnit(Unit, true);
--@debug@
--D:Println("HAAAAAAA!!!!!");
--@end-debug@
end
end
i = i + 1;
end
self.Status.delayedDebuffReportDisabled = false; -- set to true after a reconfiguration, reset only here.
--@debug@
--D:Debug("|cFF777777Scanning everybody...", i - 1, "units scanned in ", debugprofilestop() - start, "miliseconds|r");
--@end-debug@
end
-- a little test... the ".." way wins (6x faster than the format solution) when both sides are strings
function D:tests()
local test = "test1";
local start = GetTime();
local strings = {"string1", "string2", "strring3"};
local teststring = "unitraid5"
for i =1, 1000000 do
teststring = strings[i%3 + 1];
test = "test_"..teststring;
end
D:Debug("pass (\"\".. completed in:", GetTime() - start, test);
start = GetTime();
for i =1, 1000000 do
local t = strings[i%3 + 1];
test = ("test_%s"):format(teststring);
end
D:Debug("pass format completed in:", GetTime() - start, test);
end
end
--local UnitBuffsCache = {};
do
local G_UnitBuff = _G.UnitBuff; -- In 10.2.5 UnitBuff and acolytes were deprecated and are falling back to calling C_UnitAuras functions which create a new table each time and thus leak garbage each time they return debuff info... (if only we could provide those functions with a table to use...)
local GetAuraDataBySpellName = C_UnitAuras and C_UnitAuras.GetAuraDataBySpellName or nil;
local buffName;
local GetCVarBool = _G.GetCVarBool
local function auraAccessRestricted()
return DC.MN and (InCombatLockdown() or GetCVarBool("secretAurasForced"))
end
local function UnitBuff(unit, BuffNameToCheck)
local restricted = auraAccessRestricted()
--@debug@
--D:Debug("UnitBuff", unit, BuffNameToCheck)
--@end-debug@
if not restricted and GetAuraDataBySpellName and GetAuraDataBySpellName(unit, BuffNameToCheck) then
--@debug@
D:Debug("used C_UnitAuras")
--@end-debug@
-- return the aura instance id instead of true so that we can check when it's removed
return GetAuraDataBySpellName(unit, BuffNameToCheck).auraInstanceID
elseif not restricted and not GetAuraDataBySpellName then
--@debug@
D:Debug("used old buff scan method")
--@end-debug@
for i = 1, 40 do
buffName = G_UnitBuff(unit, i)
if not buffName then
return false
else
if BuffNameToCheck == buffName then
return (G_UnitBuff(unit, i)) and true or false
end
end
end
end
return false
end
-- this function returns true if one of the debuff(s) passed to it is found on the specified unit
function D:CheckUnitForBuffs(unit, BuffNamesToCheck) --{{{
if type(BuffNamesToCheck) == "string" then
return UnitBuff(unit, BuffNamesToCheck)
else
for buff in pairs(BuffNamesToCheck) do
return UnitBuff(unit, buff)
end
end
return false;
end --}}}
end
function D:CheckUnitStealth(unit)
return self:CheckUnitForBuffs(unit, DC.IS_STEALTH_BUFF)
end
-- }}}
T._LoadedFiles["Decursive.lua"] = "@project-version@";
-- Sin