-
Notifications
You must be signed in to change notification settings - Fork 2.1k
/
Build.lua
2066 lines (1949 loc) · 101 KB
/
Build.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
-- Path of Building
--
-- Module: Build
-- Loads and manages the current build.
--
local pairs = pairs
local ipairs = ipairs
local next = next
local t_insert = table.insert
local m_min = math.min
local m_max = math.max
local m_floor = math.floor
local m_abs = math.abs
local s_format = string.format
local buildMode = new("ControlHost")
local function InsertIfNew(t, val)
if (not t) then return end
for i,v in ipairs(t) do
if v == val then return end
end
table.insert(t, val)
end
function buildMode:Init(dbFileName, buildName, buildXML, convertBuild, importLink)
self.dbFileName = dbFileName
self.buildName = buildName
self.importLink = importLink
if dbFileName then
self.dbFileSubPath = self.dbFileName:sub(#main.buildPath + 1, -#self.buildName - 5)
else
self.dbFileSubPath = main.modes.LIST.subPath or ""
end
if not buildName then
main:SetMode("LIST")
end
-- Load build file
self.xmlSectionList = { }
self.spectreList = { }
self.timelessData = { jewelType = { }, conquerorType = { }, devotionVariant1 = 1, devotionVariant2 = 1, jewelSocket = { }, fallbackWeightMode = { }, searchList = "", searchListFallback = "", searchResults = { }, sharedResults = { } }
self.viewMode = "TREE"
self.characterLevel = m_min(m_max(main.defaultCharLevel or 1, 1), 100)
self.targetVersion = liveTargetVersion
self.bandit = "None"
self.pantheonMajorGod = "None"
self.pantheonMinorGod = "None"
self.characterLevelAutoMode = main.defaultCharLevel == 1 or main.defaultCharLevel == nil
if buildXML then
if self:LoadDB(buildXML, "Unnamed build") then
self:CloseBuild()
return
end
self.modFlag = true
else
if self:LoadDBFile() then
self:CloseBuild()
return
end
self.modFlag = false
end
if convertBuild then
self.targetVersion = liveTargetVersion
end
if self.targetVersion ~= liveTargetVersion then
self.targetVersion = nil
self:OpenConversionPopup()
return
end
self.abortSave = true
wipeTable(self.controls)
local miscTooltip = new("Tooltip")
-- Controls: top bar, left side
self.anchorTopBarLeft = new("Control", nil, 4, 4, 0, 20)
self.controls.back = new("ButtonControl", {"LEFT",self.anchorTopBarLeft,"RIGHT"}, 0, 0, 60, 20, "<< Back", function()
if self.unsaved then
self:OpenSavePopup("LIST")
else
self:CloseBuild()
end
end)
self.controls.buildName = new("Control", {"LEFT",self.controls.back,"RIGHT"}, 8, 0, 0, 20)
self.controls.buildName.width = function(control)
local limit = self.anchorTopBarRight:GetPos() - 98 - 40 - self.controls.back:GetSize() - self.controls.save:GetSize() - self.controls.saveAs:GetSize()
local bnw = DrawStringWidth(16, "VAR", self.buildName)
self.strWidth = m_min(bnw, limit)
self.strLimited = bnw > limit
return self.strWidth + 98
end
self.controls.buildName.Draw = function(control)
local x, y = control:GetPos()
local width, height = control:GetSize()
SetDrawColor(0.5, 0.5, 0.5)
DrawImage(nil, x + 91, y, self.strWidth + 6, 20)
SetDrawColor(0, 0, 0)
DrawImage(nil, x + 92, y + 1, self.strWidth + 4, 18)
SetDrawColor(1, 1, 1)
SetViewport(x, y + 2, self.strWidth + 94, 16)
DrawString(0, 0, "LEFT", 16, "VAR", "Current build: "..self.buildName)
SetViewport()
if control:IsMouseInBounds() then
SetDrawLayer(nil, 10)
miscTooltip:Clear()
if self.dbFileSubPath and self.dbFileSubPath ~= "" then
miscTooltip:AddLine(16, self.dbFileSubPath..self.buildName)
elseif self.strLimited then
miscTooltip:AddLine(16, self.buildName)
end
miscTooltip:Draw(x, y, width, height, main.viewPort)
SetDrawLayer(nil, 0)
end
end
self.controls.save = new("ButtonControl", {"LEFT",self.controls.buildName,"RIGHT"}, 8, 0, 50, 20, "Save", function()
self:SaveDBFile()
end)
self.controls.save.enabled = function()
return not self.dbFileName or self.unsaved
end
self.controls.saveAs = new("ButtonControl", {"LEFT",self.controls.save,"RIGHT"}, 8, 0, 70, 20, "Save As", function()
self:OpenSaveAsPopup()
end)
self.controls.saveAs.enabled = function()
return self.dbFileName
end
-- Controls: top bar, right side
self.anchorTopBarRight = new("Control", nil, function() return main.screenW / 2 + 6 end, 4, 0, 20)
self.controls.pointDisplay = new("Control", {"LEFT",self.anchorTopBarRight,"RIGHT"}, -12, 0, 0, 20)
self.controls.pointDisplay.x = function(control)
local width, height = control:GetSize()
if self.controls.saveAs:GetPos() + self.controls.saveAs:GetSize() < self.anchorTopBarRight:GetPos() - width - 16 then
return -12 - width
else
return 0
end
end
self.controls.pointDisplay.width = function(control)
control.str, control.req = self:EstimatePlayerProgress()
return DrawStringWidth(16, "FIXED", control.str) + 8
end
self.controls.pointDisplay.Draw = function(control)
local x, y = control:GetPos()
local width, height = control:GetSize()
SetDrawColor(1, 1, 1)
DrawImage(nil, x, y, width, height)
SetDrawColor(0, 0, 0)
DrawImage(nil, x + 1, y + 1, width - 2, height - 2)
SetDrawColor(1, 1, 1)
DrawString(x + 4, y + 2, "LEFT", 16, "FIXED", control.str)
if control:IsMouseInBounds() then
SetDrawLayer(nil, 10)
miscTooltip:Clear()
miscTooltip:AddLine(16, control.req)
miscTooltip:Draw(x, y, width, height, main.viewPort)
SetDrawLayer(nil, 0)
end
end
self.controls.levelScalingButton = new("ButtonControl", {"LEFT",self.controls.pointDisplay,"RIGHT"}, 12, 0, 50, 20, self.characterLevelAutoMode and "Auto" or "Manual", function()
self.characterLevelAutoMode = not self.characterLevelAutoMode
self.controls.levelScalingButton.label = self.characterLevelAutoMode and "Auto" or "Manual"
self.configTab:BuildModList()
self.modFlag = true
self.buildFlag = true
end)
self.controls.characterLevel = new("EditControl", {"LEFT",self.controls.levelScalingButton,"RIGHT"}, 8, 0, 106, 20, "", "Level", "%D", 3, function(buf)
self.characterLevel = m_min(m_max(tonumber(buf) or 1, 1), 100)
self.configTab:BuildModList()
self.modFlag = true
self.buildFlag = true
self.characterLevelAutoMode = false
self.controls.levelScalingButton.label = "Manual"
end)
self.controls.characterLevel:SetText(self.characterLevel)
self.controls.characterLevel.tooltipFunc = function(tooltip)
if tooltip:CheckForUpdate(self.characterLevel) then
tooltip:AddLine(16, "Experience multiplier:")
local playerLevel = self.characterLevel
local safeZone = 3 + m_floor(playerLevel / 16)
for level, expLevel in ipairs(self.data.monsterExperienceLevelMap) do
local diff = m_abs(playerLevel - expLevel) - safeZone
local mult
if diff <= 0 then
mult = 1
else
mult = ((playerLevel + 5) / (playerLevel + 5 + diff ^ 2.5)) ^ 1.5
end
if playerLevel >= 95 then
mult = mult * (1 / (1 + 0.1 * (playerLevel - 94)))
end
if mult > 0.01 then
local line = level
if level >= 68 then
line = line .. string.format(" (Tier %d)", level - 67)
end
line = line .. string.format(": %.1f%%", mult * 100)
tooltip:AddLine(14, line)
end
end
end
end
self.controls.classDrop = new("DropDownControl", {"LEFT",self.controls.characterLevel,"RIGHT"}, 8, 0, 100, 20, nil, function(index, value)
if value.classId ~= self.spec.curClassId then
if self.spec:CountAllocNodes() == 0 or self.spec:IsClassConnected(value.classId) then
self.spec:SelectClass(value.classId)
self.spec:AddUndoState()
self.spec:SetWindowTitleWithBuildClass()
self.buildFlag = true
else
main:OpenConfirmPopup("Class Change", "Changing class to "..value.label.." will reset your passive tree.\nThis can be avoided by connecting one of the "..value.label.." starting nodes to your tree.", "Continue", function()
self.spec:SelectClass(value.classId)
self.spec:AddUndoState()
self.spec:SetWindowTitleWithBuildClass()
self.buildFlag = true
end)
end
end
end)
self.controls.ascendDrop = new("DropDownControl", {"LEFT",self.controls.classDrop,"RIGHT"}, 8, 0, 120, 20, nil, function(index, value)
self.spec:SelectAscendClass(value.ascendClassId)
self.spec:AddUndoState()
self.spec:SetWindowTitleWithBuildClass()
self.buildFlag = true
end)
-- // hiding away until we learn more, this dropdown and the Loadout dropdown conflict for UI space, will need to address if secondaryAscendancies come back
--self.controls.secondaryAscendDrop = new("DropDownControl", {"LEFT",self.controls.ascendDrop,"RIGHT"}, 8, 0, 120, 20, nil, function(index, value)
-- self.spec:SelectSecondaryAscendClass(value.ascendClassId)
-- self.spec:AddUndoState()
-- self.spec:SetWindowTitleWithBuildClass()
-- self.buildFlag = true
--end)
self.controls.buildLoadouts = new("DropDownControl", {"LEFT",self.controls.ascendDrop,"RIGHT"}, 8, 0, 190, 20, {}, function(index, value)
if value == "^7^7Loadouts:" or value == "^7^7-----" then
self.controls.buildLoadouts:SetSel(1)
return
end
if value == "^7^7Sync" then
self:SyncLoadouts()
self.controls.buildLoadouts:SetSel(1)
return
end
if value == "^7^7Help >>" then
main:OpenAboutPopup(7)
self.controls.buildLoadouts:SetSel(1)
return
end
if value == "^7^7New Loadout" then
local controls = { }
controls.label = new("LabelControl", nil, 0, 20, 0, 16, "^7Enter name for this loadout:")
controls.edit = new("EditControl", nil, 0, 40, 350, 20, "New Loadout", nil, nil, 100, function(buf)
controls.save.enabled = buf:match("%S")
end)
controls.save = new("ButtonControl", nil, -45, 70, 80, 20, "Save", function()
local loadout = controls.edit.buf
local newSpec = new("PassiveSpec", self, latestTreeVersion)
newSpec.title = loadout
t_insert(self.treeTab.specList, newSpec)
local itemSet = self.itemsTab:NewItemSet(#self.itemsTab.itemSets + 1)
t_insert(self.itemsTab.itemSetOrderList, itemSet.id)
itemSet.title = loadout
local skillSet = self.skillsTab:NewSkillSet(#self.skillsTab.skillSets + 1)
t_insert(self.skillsTab.skillSetOrderList, skillSet.id)
skillSet.title = loadout
local configSet = self.configTab:NewConfigSet(#self.configTab.configSets + 1)
t_insert(self.configTab.configSetOrderList, configSet.id)
configSet.title = loadout
self:SyncLoadouts()
self.modFlag = true
main:ClosePopup()
end)
controls.save.enabled = false
controls.cancel = new("ButtonControl", nil, 45, 70, 80, 20, "Cancel", function()
main:ClosePopup()
end)
main:OpenPopup(370, 100, "Set Name", controls, "save", "edit", "cancel")
self.controls.buildLoadouts:SetSel(1)
return
end
-- item, skill, and config sets have identical structure
-- return id as soon as it's found
local function findSetId(setOrderList, value, sets, setSpecialLinks)
for _, setOrder in ipairs(setOrderList) do
if value == (sets[setOrder].title or "Default") then
return setOrder
else
local linkMatch = string.match(value, "%{(%w+)%}")
if linkMatch then
return setSpecialLinks[linkMatch]["setId"]
end
end
end
return nil
end
-- trees have a different structure with id/name pairs
-- return id as soon as it's found
local function findNamedSetId(treeList, value, setSpecialLinks)
for id, spec in ipairs(treeList) do
if value == spec then
return id
else
local linkMatch = string.match(value, "%{(%w+)%}")
if linkMatch then
return setSpecialLinks[linkMatch]["setId"]
end
end
end
return nil
end
local oneSkill = self.skillsTab and #self.skillsTab.skillSetOrderList == 1
local oneItem = self.itemsTab and #self.itemsTab.itemSetOrderList == 1
local oneConfig = self.configTab and #self.configTab.configSetOrderList == 1
local newSpecId = findNamedSetId(self.treeTab:GetSpecList(), value, self.treeListSpecialLinks)
local newItemId = oneItem and 1 or findSetId(self.itemsTab.itemSetOrderList, value, self.itemsTab.itemSets, self.itemListSpecialLinks)
local newSkillId = oneSkill and 1 or findSetId(self.skillsTab.skillSetOrderList, value, self.skillsTab.skillSets, self.skillListSpecialLinks)
local newConfigId = oneConfig and 1 or findSetId(self.configTab.configSetOrderList, value, self.configTab.configSets, self.configListSpecialLinks)
-- if exact match nor special grouping cannot find setIds, bail
if newSpecId == nil or newItemId == nil or newSkillId == nil or newConfigId == nil then
return
end
if newSpecId ~= self.treeTab.activeSpec then
self.treeTab:SetActiveSpec(newSpecId)
end
if newItemId ~= self.itemsTab.activeItemSetId then
self.itemsTab:SetActiveItemSet(newItemId)
end
if newSkillId ~= self.skillsTab.activeSkillSetId then
self.skillsTab:SetActiveSkillSet(newSkillId)
end
if newConfigId ~= self.configTab.activeConfigSetId then
self.configTab:SetActiveConfigSet(newConfigId)
end
self.controls.buildLoadouts:SelByValue(value)
end)
--self.controls.similarBuilds = new("ButtonControl", {"LEFT",self.controls.buildLoadouts,"RIGHT"}, 8, 0, 100, 20, "Similar Builds", function()
-- self:OpenSimilarPopup()
--end)
--self.controls.similarBuilds.tooltipFunc = function(tooltip)
-- tooltip:Clear()
-- tooltip:AddLine(16, "Search for builds similar to your current character.")
-- tooltip:AddLine(16, "For best results, make sure to select your main item set, tree, and skills before opening the popup.")
--end
-- List of display stats
-- This defines the stats in the side bar, and also which stats show in node/item comparisons
-- This may be user-customisable in the future
self.displayStats = {
{ stat = "ActiveMinionLimit", label = "Active Minion Limit", fmt = "d" },
{ stat = "AverageHit", label = "Average Hit", fmt = ".1f", compPercent = true },
{ stat = "PvpAverageHit", label = "PvP Average Hit", fmt = ".1f", compPercent = true, flag = "isPvP" },
{ stat = "AverageDamage", label = "Average Damage", fmt = ".1f", compPercent = true, flag = "attack" },
{ stat = "AverageDamage", label = "Average Damage", fmt = ".1f", compPercent = true, flag = "monsterExplode", condFunc = function(v,o) return o.HitChance ~= 100 end },
{ stat = "AverageBurstDamage", label = "Average Burst Damage", fmt = ".1f", compPercent = true, condFunc = function(v,o) return o.AverageBurstHits and o.AverageBurstHits > 1 and v > 0 end },
{ stat = "PvpAverageDamage", label = "PvP Average Damage", fmt = ".1f", compPercent = true, flag = "attackPvP" },
{ stat = "Speed", label = "Attack Rate", fmt = ".2f", compPercent = true, flag = "attack", condFunc = function(v,o) return v > 0 and (o.TriggerTime or 0) == 0 end },
{ stat = "Speed", label = "Cast Rate", fmt = ".2f", compPercent = true, flag = "spell", condFunc = function(v,o) return v > 0 and (o.TriggerTime or 0) == 0 end },
{ stat = "Speed", label = "Effective Trigger Rate", fmt = ".2f", compPercent = true, condFunc = function(v,o) return (o.TriggerTime or 0) ~= 0 end },
{ stat = "WarcryCastTime", label = "Cast Time", fmt = ".2fs", compPercent = true, lowerIsBetter = true, flag = "warcry" },
{ stat = "HitSpeed", label = "Hit Rate", fmt = ".2f", compPercent = true, condFunc = function(v,o) return not o.TriggerTime end },
{ stat = "HitTime", label = "Channel Time", fmt = ".2fs", compPercent = true, flag = "channelRelease", lowerIsBetter = true, condFunc = function(v,o) return not o.TriggerTime end },
{ stat = "ChannelTimeToTrigger", label = "Channel Time", fmt = ".2fs", compPercent = true, lowerIsBetter = true, },
{ stat = "TrapThrowingTime", label = "Trap Throwing Time", fmt = ".2fs", compPercent = true, lowerIsBetter = true, },
{ stat = "TrapCooldown", label = "Trap Cooldown", fmt = ".3fs", lowerIsBetter = true },
{ stat = "MineLayingTime", label = "Mine Throwing Time", fmt = ".2fs", compPercent = true, lowerIsBetter = true, },
{ stat = "TrapThrowCount", label = "Avg. Traps per Throw", fmt = ".2f"},
{ stat = "MineThrowCount", label = "Avg. Mines per Throw", fmt = ".2f"},
{ stat = "TotemPlacementTime", label = "Totem Placement Time", fmt = ".2fs", compPercent = true, lowerIsBetter = true, condFunc = function(v,o) return not o.TriggerTime end },
{ stat = "PreEffectiveCritChance", label = "Crit Chance", fmt = ".2f%%" },
{ stat = "CritChance", label = "Effective Crit Chance", fmt = ".2f%%", condFunc = function(v,o) return v ~= o.PreEffectiveCritChance end },
{ stat = "CritMultiplier", label = "Crit Multiplier", fmt = "d%%", pc = true, condFunc = function(v,o) return (o.CritChance or 0) > 0 end },
{ stat = "HitChance", label = "Hit Chance", fmt = ".0f%%", flag = "attack" },
{ stat = "HitChance", label = "Hit Chance", fmt = ".0f%%", condFunc = function(v,o) return o.enemyHasSpellBlock end },
{ stat = "TotalDPS", label = "Hit DPS", fmt = ".1f", compPercent = true, flag = "notAverage" },
{ stat = "PvpTotalDPS", label = "PvP Hit DPS", fmt = ".1f", compPercent = true, flag = "notAveragePvP" },
{ stat = "TotalDPS", label = "Hit DPS", fmt = ".1f", compPercent = true, flag = "showAverage", condFunc = function(v,o) return (o.TriggerTime or 0) ~= 0 end },
{ stat = "TotalDot", label = "DoT DPS", fmt = ".1f", compPercent = true },
{ stat = "WithDotDPS", label = "Total DPS inc. DoT", fmt = ".1f", compPercent = true, flag = "notAverage", condFunc = function(v,o) return v ~= o.TotalDPS and (o.PoisonDPS or 0) == 0 and (o.IgniteDPS or 0) == 0 and (o.ImpaleDPS or 0) == 0 and (o.BleedDPS or 0) == 0 end },
{ stat = "BleedDPS", label = "Bleed DPS", fmt = ".1f", compPercent = true, warnFunc = function(v) return v >= data.misc.DotDpsCap and "Bleed DPS exceeds in game limit" end },
{ stat = "CorruptingBloodDPS", label = "Corrupting Blood DPS", fmt = ".1f", compPercent = true, warnFunc = function(v,o) return v >= data.misc.DotDpsCap and "Corrupting Blood DPS exceeds in game limit" end },
{ stat = "BleedDamage", label = "Total Damage per Bleed", fmt = ".1f", compPercent = true, flag = "showAverage" },
{ stat = "WithBleedDPS", label = "Total DPS inc. Bleed", fmt = ".1f", compPercent = true, flag = "notAverage", condFunc = function(v,o) return v ~= o.TotalDPS and (o.TotalDot or 0) == 0 and (o.PoisonDPS or 0) == 0 and (o.ImpaleDPS or 0) == 0 and (o.IgniteDPS or 0) == 0 end },
{ stat = "IgniteDPS", label = "Ignite DPS", fmt = ".1f", compPercent = true, warnFunc = function(v) return v >= data.misc.DotDpsCap and "Ignite DPS exceeds in game limit" end },
{ stat = "IgniteDamage", label = "Total Damage per Ignite", fmt = ".1f", compPercent = true, flag = "showAverage" },
{ stat = "BurningGroundDPS", label = "Burning Ground DPS", fmt = ".1f", compPercent = true, warnFunc = function(v,o) return v >= data.misc.DotDpsCap and "Burning Ground DPS exceeds in game limit" end },
{ stat = "MirageBurningGroundDPS", label = "Mirage Burning Ground DPS", fmt = ".1f", compPercent = true, condFunc = function(v,o) return v ~= o.BurningGroundDPS end, warnFunc = function(v,o) return v >= data.misc.DotDpsCap and "Mirage Burning Ground DPS exceeds in game limit" end },
{ stat = "WithIgniteDPS", label = "Total DPS inc. Ignite", fmt = ".1f", compPercent = true, flag = "notAverage", condFunc = function(v,o) return v ~= o.TotalDPS and (o.TotalDot or 0) == 0 and (o.PoisonDPS or 0) == 0 and (o.ImpaleDPS or 0) == 0 and (o.BleedDPS or 0) == 0 end },
{ stat = "WithIgniteAverageDamage", label = "Average Dmg. inc. Ignite", fmt = ".1f", compPercent = true },
{ stat = "PoisonDPS", label = "Poison DPS", fmt = ".1f", compPercent = true, warnFunc = function(v) return v >= data.misc.DotDpsCap and "Poison DPS exceeds in game limit" end },
{ stat = "CausticGroundDPS", label = "Caustic Ground DPS", fmt = ".1f", compPercent = true, warnFunc = function(v,o) return v >= data.misc.DotDpsCap and "Caustic Ground DPS exceeds in game limit" end },
{ stat = "MirageCausticGroundDPS", label = "Mirage Caustic Ground DPS", fmt = ".1f", compPercent = true, condFunc = function(v,o) return v ~= o.CausticGroundDPS end, warnFunc = function(v,o) return v >= data.misc.DotDpsCap and "Mirage Caustic Ground DPS exceeds in game limit" end },
{ stat = "PoisonDamage", label = "Total Damage per Poison", fmt = ".1f", compPercent = true },
{ stat = "WithPoisonDPS", label = "Total DPS inc. Poison", fmt = ".1f", compPercent = true, flag = "poison", flag = "notAverage", condFunc = function(v,o) return v ~= o.TotalDPS and (o.TotalDot or 0) == 0 and (o.IgniteDPS or 0) == 0 and (o.ImpaleDPS or 0) == 0 and (o.BleedDPS or 0) == 0 end },
{ stat = "DecayDPS", label = "Decay DPS", fmt = ".1f", compPercent = true },
{ stat = "TotalDotDPS", label = "Total DoT DPS", fmt = ".1f", compPercent = true, condFunc = function(v,o) return o.showTotalDotDPS or ( v ~= o.TotalDot and v ~= o.TotalPoisonDPS and v ~= o.CausticGroundDPS and v ~= (o.TotalIgniteDPS or o.IgniteDPS) and v ~= o.BurningGroundDPS and v ~= o.BleedDPS and v~= o.CorruptingBloodDPS and v ~= o.MirageCausticGroundDPS and v ~= o.MirageBurningGroundDPS) end, warnFunc = function(v) return v >= data.misc.DotDpsCap and "DoT DPS exceeds in game limit" end },
{ stat = "ImpaleDPS", label = "Impale Damage", fmt = ".1f", compPercent = true, flag = "impale", flag = "showAverage" },
{ stat = "WithImpaleDPS", label = "Damage inc. Impale", fmt = ".1f", compPercent = true, flag = "impale", flag = "showAverage", condFunc = function(v,o) return v ~= o.TotalDPS and (o.TotalDot or 0) == 0 and (o.IgniteDPS or 0) == 0 and (o.PoisonDPS or 0) == 0 and (o.BleedDPS or 0) == 0 end },
{ stat = "ImpaleDPS", label = "Impale DPS", fmt = ".1f", compPercent = true, flag = "impale", flag = "notAverage" },
{ stat = "WithImpaleDPS", label = "Total DPS inc. Impale", fmt = ".1f", compPercent = true, flag = "impale", flag = "notAverage", condFunc = function(v,o) return v ~= o.TotalDPS and (o.TotalDot or 0) == 0 and (o.IgniteDPS or 0) == 0 and (o.PoisonDPS or 0) == 0 and (o.BleedDPS or 0) == 0 end },
{ stat = "MirageDPS", label = "Total Mirage DPS", fmt = ".1f", compPercent = true, flag = "mirageArcher", condFunc = function(v,o) return v > 0 end },
{ stat = "MirageDPS", label = "Total Wisp DPS", fmt = ".1f", compPercent = true, flag = "wisp", condFunc = function(v,o) return v > 0 end },
{ stat = "CullingDPS", label = "Culling DPS", fmt = ".1f", compPercent = true, condFunc = function(v,o) return (o.CullingDPS or 0) > 0 end },
{ stat = "ReservationDPS", label = "Reservation DPS", fmt = ".1f", compPercent = true, condFunc = function(v,o) return (o.ReservationDPS or 0) > 0 end },
{ stat = "CombinedDPS", label = "Combined DPS", fmt = ".1f", compPercent = true, flag = "notAverage", condFunc = function(v,o) return v ~= ((o.TotalDPS or 0) + (o.TotalDot or 0)) and v ~= o.WithImpaleDPS and ( o.showTotalDotDPS or ( v ~= o.WithPoisonDPS and v ~= o.WithIgniteDPS and v ~= o.WithBleedDPS ) ) end },
{ stat = "CombinedAvg", label = "Combined Total Damage", fmt = ".1f", compPercent = true, flag = "showAverage", condFunc = function(v,o) return (v ~= o.AverageDamage and (o.TotalDot or 0) == 0) and (v ~= o.WithPoisonDPS or v ~= o.WithIgniteDPS or v ~= o.WithBleedDPS) end },
{ stat = "ExplodeChance", label = "Total Explode Chance", fmt = ".0f%%" },
{ stat = "CombinedAvgToMonsterLife", label = "Enemy Life Equivalent", fmt = ".1f%%" },
{ stat = "Cooldown", label = "Skill Cooldown", fmt = ".3fs", lowerIsBetter = true },
{ stat = "SealCooldown", label = "Seal Gain Frequency", fmt = ".2fs", lowerIsBetter = true },
{ stat = "SealMax", label = "Max Number of Seals", fmt = "d" },
{ stat = "TimeMaxSeals", label = "Time to Gain Max Seals", fmt = ".2fs", lowerIsBetter = true },
{ stat = "AreaOfEffectRadiusMetres", label = "AoE Radius", fmt = ".1fm" },
{ stat = "BrandAttachmentRangeMetre", label = "Attachment Range", fmt = ".1fm", flag = "brand" },
{ stat = "BrandTicks", label = "Activations per Brand", fmt = "d", flag = "brand" },
{ stat = "ManaCost", label = "Mana Cost", fmt = "d", color = colorCodes.MANA, pool = "ManaUnreserved", compPercent = true, lowerIsBetter = true, condFunc = function(v,o) return o.ManaHasCost end },
{ stat = "ManaPercentCost", label = "Mana Cost", fmt = "d%%", color = colorCodes.MANA, pool = "ManaUnreservedPercent", compPercent = true, lowerIsBetter = true, condFunc = function(v,o) return o.ManaPercentHasCost end },
{ stat = "ManaPerSecondCost", label = "Mana Cost per second", fmt = ".2f", color = colorCodes.MANA, pool = "ManaUnreserved", compPercent = true, lowerIsBetter = true, condFunc = function(v,o) return o.ManaPerSecondHasCost end },
{ stat = "ManaPercentPerSecondCost", label = "Mana Cost per second", fmt = ".2f%%", color = colorCodes.MANA, pool = "ManaUnreservedPercent", compPercent = true, lowerIsBetter = true, condFunc = function(v,o) return o.ManaPercentPerSecondHasCost end },
{ stat = "LifeCost", label = "Life Cost", fmt = "d", color = colorCodes.LIFE, pool = "LifeUnreserved", compPercent = true, lowerIsBetter = true, condFunc = function(v,o) return o.LifeHasCost end },
{ stat = "LifePercentCost", label = "Life Cost", fmt = "d%%", color = colorCodes.LIFE, pool = "LifeUnreservedPercent", compPercent = true, lowerIsBetter = true, condFunc = function(v,o) return o.LifePercentHasCost end },
{ stat = "LifePerSecondCost", label = "Life Cost per second", fmt = ".2f", color = colorCodes.LIFE, pool = "LifeUnreserved", compPercent = true, lowerIsBetter = true, condFunc = function(v,o) return o.LifePerSecondHasCost end },
{ stat = "LifePercentPerSecondCost", label = "Life Cost per second", fmt = ".2f%%", color = colorCodes.LIFE, pool = "LifeUnreservedPercent", compPercent = true, lowerIsBetter = true, condFunc = function(v,o) return o.LifePercentPerSecondHasCost end },
{ stat = "ESCost", label = "Energy Shield Cost", fmt = "d", color = colorCodes.ES, pool = "EnergyShield", compPercent = true, lowerIsBetter = true, condFunc = function(v,o) return o.ESHasCost end },
{ stat = "ESPerSecondCost", label = "ES Cost per second", fmt = ".2f", color = colorCodes.ES, pool = "EnergyShield", compPercent = true, lowerIsBetter = true, condFunc = function(v,o) return o.ESPerSecondHasCost end },
{ stat = "ESPercentPerSecondCost", label = "ES Cost per second", fmt = ".2f%%", color = colorCodes.ES, compPercent = true, lowerIsBetter = true, condFunc = function(v,o) return o.ESPercentPerSecondHasCost end },
{ stat = "RageCost", label = "Rage Cost", fmt = "d", color = colorCodes.RAGE, pool = "Rage", compPercent = true, lowerIsBetter = true, condFunc = function(v,o) return o.RageHasCost end },
{ stat = "RagePerSecondCost", label = "Rage Cost per second", fmt = ".2f", color = colorCodes.RAGE, pool = "Rage", compPercent = true, lowerIsBetter = true, condFunc = function(v,o) return o.RagePerSecondHasCost end },
{ stat = "SoulCost", label = "Soul Cost", fmt = "d", color = colorCodes.RAGE, pool = "Soul", compPercent = true, lowerIsBetter = true, condFunc = function(v,o) return o.SoulHasCost end },
{ },
{ stat = "Str", label = "Strength", color = colorCodes.STRENGTH, fmt = "d" },
{ stat = "ReqStr", label = "Strength Required", color = colorCodes.STRENGTH, fmt = "d", lowerIsBetter = true, condFunc = function(v,o) return v > o.Str end, warnFunc = function(v) return "You do not meet the Strength requirement" end },
{ stat = "Dex", label = "Dexterity", color = colorCodes.DEXTERITY, fmt = "d" },
{ stat = "ReqDex", label = "Dexterity Required", color = colorCodes.DEXTERITY, fmt = "d", lowerIsBetter = true, condFunc = function(v,o) return v > o.Dex end, warnFunc = function(v) return "You do not meet the Dexterity requirement" end },
{ stat = "Int", label = "Intelligence", color = colorCodes.INTELLIGENCE, fmt = "d" },
{ stat = "ReqInt", label = "Intelligence Required", color = colorCodes.INTELLIGENCE, fmt = "d", lowerIsBetter = true, condFunc = function(v,o) return v > o.Int end, warnFunc = function(v) return "You do not meet the Intelligence requirement" end },
{ stat = "Omni", label = "Omniscience", color = colorCodes.RARE, fmt = "d" },
{ stat = "ReqOmni", label = "Omniscience Required", color = colorCodes.RARE, fmt = "d", lowerIsBetter = true, condFunc = function(v,o) return v > (o.Omni or 0) end, warnFunc = function(v) return "You do not meet the Omniscience requirement" end },
{ },
{ stat = "Devotion", label = "Devotion", color = colorCodes.RARE, fmt = "d" },
{ },
{ stat = "TotalEHP", label = "Effective Hit Pool", fmt = ".0f", compPercent = true },
{ stat = "PvPTotalTakenHit", label = "PvP Hit Taken", fmt = ".1f", flag = "isPvP", lowerIsBetter = true },
{ stat = "PhysicalMaximumHitTaken", label = "Phys Max Hit", fmt = ".0f", color = colorCodes.PHYS, compPercent = true, },
{ stat = "LightningMaximumHitTaken", label = "Elemental Max Hit", fmt = ".0f", color = colorCodes.LIGHTNING, compPercent = true, condFunc = function(v,o) return o.LightningMaximumHitTaken == o.ColdMaximumHitTaken and o.LightningMaximumHitTaken == o.FireMaximumHitTaken end },
{ stat = "FireMaximumHitTaken", label = "Fire Max Hit", fmt = ".0f", color = colorCodes.FIRE, compPercent = true, condFunc = function(v,o) return o.LightningMaximumHitTaken ~= o.ColdMaximumHitTaken or o.LightningMaximumHitTaken ~= o.FireMaximumHitTaken end },
{ stat = "ColdMaximumHitTaken", label = "Cold Max Hit", fmt = ".0f", color = colorCodes.COLD, compPercent = true, condFunc = function(v,o) return o.LightningMaximumHitTaken ~= o.ColdMaximumHitTaken or o.LightningMaximumHitTaken ~= o.FireMaximumHitTaken end },
{ stat = "LightningMaximumHitTaken", label = "Lightning Max Hit", fmt = ".0f", color = colorCodes.LIGHTNING, compPercent = true, condFunc = function(v,o) return o.LightningMaximumHitTaken ~= o.ColdMaximumHitTaken or o.LightningMaximumHitTaken ~= o.FireMaximumHitTaken end },
{ stat = "ChaosMaximumHitTaken", label = "Chaos Max Hit", fmt = ".0f", color = colorCodes.CHAOS, compPercent = true },
{ },
{ stat = "MainHand", childStat = "Accuracy", label = "MH Accuracy", fmt = "d", condFunc = function(v,o) return o.PreciseTechnique end, warnFunc = function(v,o) return v < o.Life and "You do not have enough Accuracy for Precise Technique" end, warnColor = true },
{ stat = "OffHand", childStat = "Accuracy", label = "OH Accuracy", fmt = "d", condFunc = function(v,o) return o.PreciseTechnique end, warnFunc = function(v,o) return v < o.Life and "You do not have enough Accuracy for Precise Technique" end, warnColor = true },
{ stat = "Life", label = "Total Life", fmt = "d", color = colorCodes.LIFE, compPercent = true },
{ stat = "Spec:LifeInc", label = "%Inc Life from Tree", fmt = "d%%", color = colorCodes.LIFE, condFunc = function(v,o) return v > 0 and o.Life > 1 end },
{ stat = "LifeUnreserved", label = "Unreserved Life", fmt = "d", color = colorCodes.LIFE, condFunc = function(v,o) return v < o.Life end, compPercent = true, warnFunc = function(v) return v <= 0 and "Your unreserved Life is below 1" end },
{ stat = "LifeRecoverable", label = "Life Recoverable", fmt = "d", color = colorCodes.LIFE, condFunc = function(v,o) return v < o.LifeUnreserved end, },
{ stat = "LifeUnreservedPercent", label = "Unreserved Life", fmt = "d%%", color = colorCodes.LIFE, condFunc = function(v,o) return v < 100 end },
{ stat = "LifeRegenRecovery", label = "Life Regen", fmt = ".1f", color = colorCodes.LIFE, condFunc = function(v,o) return o.LifeRecovery <= 0 and o.LifeRegenRecovery ~= 0 end },
{ stat = "LifeRegenRecovery", label = "Life Recovery", fmt = ".1f", color = colorCodes.LIFE, condFunc = function(v,o) return o.LifeRecovery > 0 and o.LifeRegenRecovery ~= 0 end },
{ stat = "LifeLeechGainRate", label = "Life Leech/On Hit Rate", fmt = ".1f", color = colorCodes.LIFE, compPercent = true },
{ stat = "LifeLeechGainPerHit", label = "Life Leech/Gain per Hit", fmt = ".1f", color = colorCodes.LIFE, compPercent = true },
{ },
{ stat = "Mana", label = "Total Mana", fmt = "d", color = colorCodes.MANA, compPercent = true },
{ stat = "Spec:ManaInc", label = "%Inc Mana from Tree", color = colorCodes.MANA, fmt = "d%%" },
{ stat = "ManaUnreserved", label = "Unreserved Mana", fmt = "d", color = colorCodes.MANA, condFunc = function(v,o) return v < o.Mana end, compPercent = true, warnFunc = function(v) return v < 0 and "Your unreserved Mana is negative" end },
{ stat = "ManaUnreservedPercent", label = "Unreserved Mana", fmt = "d%%", color = colorCodes.MANA, condFunc = function(v,o) return v < 100 end },
{ stat = "ManaRegenRecovery", label = "Mana Regen", fmt = ".1f", color = colorCodes.MANA, condFunc = function(v,o) return o.ManaRecovery <= 0 and o.ManaRegenRecovery ~= 0 end },
{ stat = "ManaRegenRecovery", label = "Mana Recovery", fmt = ".1f", color = colorCodes.MANA, condFunc = function(v,o) return o.ManaRecovery > 0 and o.ManaRegenRecovery ~= 0 end },
{ stat = "ManaLeechGainRate", label = "Mana Leech/On Hit Rate", fmt = ".1f", color = colorCodes.MANA, compPercent = true },
{ stat = "ManaLeechGainPerHit", label = "Mana Leech/Gain per Hit", fmt = ".1f", color = colorCodes.MANA, compPercent = true },
{ },
{ stat = "EnergyShield", label = "Energy Shield", fmt = "d", color = colorCodes.ES, compPercent = true },
{ stat = "EnergyShieldRecoveryCap", label = "Recoverable ES", color = colorCodes.ES, fmt = "d", condFunc = function(v,o) return o.CappingES end },
{ stat = "Spec:EnergyShieldInc", label = "%Inc ES from Tree", color = colorCodes.ES, fmt = "d%%" },
{ stat = "EnergyShieldRegenRecovery", label = "ES Regen", color = colorCodes.ES, fmt = ".1f", condFunc = function(v,o) return o.EnergyShieldRecovery <= 0 and o.EnergyShieldRegenRecovery ~= 0 end },
{ stat = "EnergyShieldRegenRecovery", label = "ES Recovery", color = colorCodes.ES, fmt = ".1f", condFunc = function(v,o) return o.EnergyShieldRecovery > 0 and o.EnergyShieldRegenRecovery ~= 0 end },
{ stat = "EnergyShieldLeechGainRate", label = "ES Leech/On Hit Rate", color = colorCodes.ES, fmt = ".1f", compPercent = true },
{ stat = "EnergyShieldLeechGainPerHit", label = "ES Leech/Gain per Hit", color = colorCodes.ES, fmt = ".1f", compPercent = true },
{ },
{ stat = "Ward", label = "Ward", fmt = "d", color = colorCodes.WARD, compPercent = true },
{ },
{ stat = "Rage", label = "Rage", fmt = "d", color = colorCodes.RAGE, compPercent = true },
{ stat = "RageRegenRecovery", label = "Rage Regen", fmt = ".1f", color = colorCodes.RAGE, compPercent = true },
{ },
{ stat = "TotalBuildDegen", label = "Total Degen", fmt = ".1f", lowerIsBetter = true },
{ stat = "TotalNetRegen", label = "Total Net Recovery", fmt = "+.1f" },
{ stat = "NetLifeRegen", label = "Net Life Recovery", fmt = "+.1f", color = colorCodes.LIFE },
{ stat = "NetManaRegen", label = "Net Mana Recovery", fmt = "+.1f", color = colorCodes.MANA },
{ stat = "NetEnergyShieldRegen", label = "Net ES Recovery", fmt = "+.1f", color = colorCodes.ES },
{ },
{ stat = "Evasion", label = "Evasion rating", fmt = "d", color = colorCodes.EVASION, compPercent = true },
{ stat = "Spec:EvasionInc", label = "%Inc Evasion from Tree", color = colorCodes.EVASION, fmt = "d%%" },
{ stat = "MeleeEvadeChance", label = "Evade Chance", fmt = "d%%", color = colorCodes.EVASION, condFunc = function(v,o) return v > 0 and o.MeleeEvadeChance == o.ProjectileEvadeChance end },
{ stat = "MeleeEvadeChance", label = "Melee Evade Chance", fmt = "d%%", color = colorCodes.EVASION, condFunc = function(v,o) return v > 0 and o.MeleeEvadeChance ~= o.ProjectileEvadeChance end },
{ stat = "ProjectileEvadeChance", label = "Projectile Evade Chance", fmt = "d%%", color = colorCodes.EVASION, condFunc = function(v,o) return v > 0 and o.MeleeEvadeChance ~= o.ProjectileEvadeChance end },
{ },
{ stat = "Armour", label = "Armour", fmt = "d", compPercent = true },
{ stat = "Spec:ArmourInc", label = "%Inc Armour from Tree", fmt = "d%%" },
{ stat = "PhysicalDamageReduction", label = "Phys. Damage Reduction", fmt = "d%%", condFunc = function() return true end },
{ },
{ stat = "EffectiveBlockChance", label = "Block Chance", fmt = "d%%", overCapStat = "BlockChanceOverCap" },
{ stat = "EffectiveSpellBlockChance", label = "Spell Block Chance", fmt = "d%%", overCapStat = "SpellBlockChanceOverCap" },
{ stat = "AttackDodgeChance", label = "Attack Dodge Chance", fmt = "d%%", overCapStat = "AttackDodgeChanceOverCap" },
{ stat = "SpellDodgeChance", label = "Spell Dodge Chance", fmt = "d%%", overCapStat = "SpellDodgeChanceOverCap" },
{ stat = "EffectiveSpellSuppressionChance", label = "Spell Suppression Chance", fmt = "d%%", overCapStat = "SpellSuppressionChanceOverCap" },
{ },
{ stat = "FireResist", label = "Fire Resistance", fmt = "d%%", color = colorCodes.FIRE, condFunc = function() return true end, overCapStat = "FireResistOverCap"},
{ stat = "FireResistOverCap", label = "Fire Res. Over Max", fmt = "d%%", hideStat = true },
{ stat = "ColdResist", label = "Cold Resistance", fmt = "d%%", color = colorCodes.COLD, condFunc = function() return true end, overCapStat = "ColdResistOverCap" },
{ stat = "ColdResistOverCap", label = "Cold Res. Over Max", fmt = "d%%", hideStat = true },
{ stat = "LightningResist", label = "Lightning Resistance", fmt = "d%%", color = colorCodes.LIGHTNING, condFunc = function() return true end, overCapStat = "LightningResistOverCap" },
{ stat = "LightningResistOverCap", label = "Lightning Res. Over Max", fmt = "d%%", hideStat = true },
{ stat = "ChaosResist", label = "Chaos Resistance", fmt = "d%%", color = colorCodes.CHAOS, condFunc = function(v,o) return not o.ChaosInoculation end, overCapStat = "ChaosResistOverCap" },
{ stat = "ChaosResistOverCap", label = "Chaos Res. Over Max", fmt = "d%%", hideStat = true },
{ label = "Chaos Resistance", val = "Immune", labelStat = "ChaosResist", color = colorCodes.CHAOS, condFunc = function(o) return o.ChaosInoculation end },
{ },
{ stat = "EffectiveMovementSpeedMod", label = "Movement Speed Modifier", fmt = "+d%%", mod = true, condFunc = function() return true end },
{ },
{ stat = "FullDPS", label = "Full DPS", fmt = ".1f", color = colorCodes.CURRENCY, compPercent = true },
{ stat = "FullDotDPS", label = "Full Dot DPS", fmt = ".1f", color = colorCodes.CURRENCY, compPercent = true, condFunc = function (v) return v >= data.misc.DotDpsCap end, warnFunc = function (v) return "Full Dot DPS exceeds in game limit" end },
{ },
{ stat = "SkillDPS", label = "Skill DPS", condFunc = function() return true end },
}
self.minionDisplayStats = {
{ stat = "AverageDamage", label = "Average Damage", fmt = ".1f", compPercent = true },
{ stat = "Speed", label = "Attack/Cast Rate", fmt = ".2f", compPercent = true, condFunc = function(v,o) return v > 0 and (o.TriggerTime or 0) == 0 end },
{ stat = "HitSpeed", label = "Hit Rate", fmt = ".2f" },
{ stat = "Speed", label = "Effective Trigger Rate", fmt = ".2f", compPercent = true, condFunc = function(v,o) return (o.TriggerTime or 0) ~= 0 end },
{ stat = "TotalDPS", label = "Hit DPS", fmt = ".1f", compPercent = true },
{ stat = "TotalDot", label = "DoT DPS", fmt = ".1f", compPercent = true },
{ stat = "WithDotDPS", label = "Total DPS inc. DoT", fmt = ".1f", compPercent = true, condFunc = function(v,o) return v ~= o.TotalDPS and (o.PoisonDPS or 0) == 0 and (o.IgniteDPS or 0) == 0 and (o.ImpaleDPS or 0) == 0 and (o.BleedDPS or 0) == 0 end },
{ stat = "BleedDPS", label = "Bleed DPS", fmt = ".1f", compPercent = true, warnFunc = function(v) return v >= data.misc.DotDpsCap and "Minion Bleed DPS exceeds in game limit" end },
{ stat = "WithBleedDPS", label = "Total DPS inc. Bleed", fmt = ".1f", compPercent = true, condFunc = function(v,o) return v ~= o.TotalDPS and (o.TotalDot or 0) == 0 and (o.PoisonDPS or 0) == 0 and (o.ImpaleDPS or 0) == 0 and (o.IgniteDPS or 0) == 0 end },
{ stat = "IgniteDPS", label = "Ignite DPS", fmt = ".1f", compPercent = true, warnFunc = function(v) return v >= data.misc.DotDpsCap and "Minion Ignite DPS exceeds in game limit" end },
{ stat = "WithIgniteDPS", label = "Total DPS inc. Ignite", fmt = ".1f", compPercent = true, condFunc = function(v,o) return v ~= o.TotalDPS and (o.TotalDot or 0) == 0 and (o.PoisonDPS or 0) == 0 and (o.ImpaleDPS or 0) == 0 and (o.BleedDPS or 0) == 0 end },
{ stat = "PoisonDPS", label = "Poison DPS", fmt = ".1f", compPercent = true, warnFunc = function(v) return v >= data.misc.DotDpsCap and "Minion Poison dps exceeds in game limit" end },
{ stat = "PoisonDamage", label = "Total Damage per Poison", fmt = ".1f", compPercent = true },
{ stat = "WithPoisonDPS", label = "Total DPS inc. Poison", fmt = ".1f", compPercent = true, condFunc = function(v,o) return v ~= o.TotalDPS and (o.TotalDot or 0) == 0 and (o.IgniteDPS or 0) == 0 and (o.ImpaleDPS or 0) == 0 and (o.BleedDPS or 0) == 0 end },
{ stat = "DecayDPS", label = "Decay DPS", fmt = ".1f", compPercent = true },
{ stat = "TotalDotDPS", label = "Total DoT DPS", fmt = ".1f", compPercent = true, condFunc = function(v,o) return v ~= o.TotalDot and v ~= o.ImpaleDPS and v ~= o.TotalPoisonDPS and v ~= (o.TotalIgniteDPS or o.IgniteDPS) and v ~= o.BleedDPS end, warnFunc = function(v) return v >= data.misc.DotDpsCap and "Minion DoT DPS exceeds in game limit" end },
{ stat = "ImpaleDPS", label = "Impale DPS", fmt = ".1f", compPercent = true, flag = "impale" },
{ stat = "WithImpaleDPS", label = "Total DPS inc. Impale", fmt = ".1f", compPercent = true, flag = "impale", condFunc = function(v,o) return v ~= o.TotalDPS and (o.TotalDot or 0) == 0 and (o.IgniteDPS or 0) == 0 and (o.PoisonDPS or 0) == 0 and (o.BleedDPS or 0) == 0 end },
{ stat = "CullingDPS", label = "Culling DPS", fmt = ".1f", compPercent = true, condFunc = function(v,o) return (o.CullingDPS or 0) > 0 end },
{ stat = "ReservationDPS", label = "Reservation DPS", fmt = ".1f", compPercent = true, condFunc = function(v,o) return (o.ReservationDPS or 0) > 0 end },
{ stat = "CombinedDPS", label = "Combined DPS", fmt = ".1f", compPercent = true, condFunc = function(v,o) return v ~= ((o.TotalDPS or 0) + (o.TotalDot or 0)) and v ~= o.WithImpaleDPS and v ~= o.WithPoisonDPS and v ~= o.WithIgniteDPS and v ~= o.WithBleedDPS end},
{ stat = "Cooldown", label = "Skill Cooldown", fmt = ".3fs", lowerIsBetter = true },
{ stat = "Life", label = "Total Life", fmt = ".1f", color = colorCodes.LIFE, compPercent = true },
{ stat = "LifeRegenRecovery", label = "Life Recovery", fmt = ".1f", color = colorCodes.LIFE },
{ stat = "LifeLeechGainRate", label = "Life Leech/On Hit Rate", fmt = ".1f", color = colorCodes.LIFE, compPercent = true },
{ stat = "EnergyShield", label = "Energy Shield", fmt = "d", color = colorCodes.ES, compPercent = true },
{ stat = "EnergyShieldRegenRecovery", label = "ES Recovery", fmt = ".1f", color = colorCodes.ES },
{ stat = "EnergyShieldLeechGainRate", label = "ES Leech/On Hit Rate", fmt = ".1f", color = colorCodes.ES, compPercent = true },
}
self.extraSaveStats = {
"PowerCharges",
"PowerChargesMax",
"FrenzyCharges",
"FrenzyChargesMax",
"EnduranceCharges",
"EnduranceChargesMax",
"ActiveTotemLimit",
"ActiveMinionLimit",
}
if buildName == "~~temp~~" then
-- Remove temporary build file
os.remove(self.dbFileName)
self.buildName = "Unnamed build"
self.dbFileName = false
self.dbFileSubPath = nil
self.modFlag = true
end
-- Controls: Side bar
self.anchorSideBar = new("Control", nil, 4, 36, 0, 0)
self.controls.modeImport = new("ButtonControl", {"TOPLEFT",self.anchorSideBar,"TOPLEFT"}, 0, 0, 134, 20, "Import/Export Build", function()
self.viewMode = "IMPORT"
end)
self.controls.modeImport.locked = function() return self.viewMode == "IMPORT" end
self.controls.modeNotes = new("ButtonControl", {"LEFT",self.controls.modeImport,"RIGHT"}, 4, 0, 58, 20, "Notes", function()
self.viewMode = "NOTES"
end)
self.controls.modeNotes.locked = function() return self.viewMode == "NOTES" end
self.controls.modeConfig = new("ButtonControl", {"TOPRIGHT",self.anchorSideBar,"TOPLEFT"}, 300, 0, 100, 20, "Configuration", function()
self.viewMode = "CONFIG"
end)
self.controls.modeConfig.locked = function() return self.viewMode == "CONFIG" end
self.controls.modeTree = new("ButtonControl", {"TOPLEFT",self.anchorSideBar,"TOPLEFT"}, 0, 26, 72, 20, "Tree", function()
self.viewMode = "TREE"
end)
self.controls.modeTree.locked = function() return self.viewMode == "TREE" end
self.controls.modeSkills = new("ButtonControl", {"LEFT",self.controls.modeTree,"RIGHT"}, 4, 0, 72, 20, "Skills", function()
self.viewMode = "SKILLS"
end)
self.controls.modeSkills.locked = function() return self.viewMode == "SKILLS" end
self.controls.modeItems = new("ButtonControl", {"LEFT",self.controls.modeSkills,"RIGHT"}, 4, 0, 72, 20, "Items", function()
self.viewMode = "ITEMS"
end)
self.controls.modeItems.locked = function() return self.viewMode == "ITEMS" end
self.controls.modeCalcs = new("ButtonControl", {"LEFT",self.controls.modeItems,"RIGHT"}, 4, 0, 72, 20, "Calcs", function()
self.viewMode = "CALCS"
end)
self.controls.modeCalcs.locked = function() return self.viewMode == "CALCS" end
self.controls.modeParty = new("ButtonControl", {"TOPLEFT",self.anchorSideBar,"TOPLEFT"}, 0, 52, 72, 20, "Party", function()
self.viewMode = "PARTY"
end)
self.controls.modeParty.locked = function() return self.viewMode == "PARTY" end
-- Skills
self.controls.mainSkillLabel = new("LabelControl", {"TOPLEFT",self.anchorSideBar,"TOPLEFT"}, 0, 80, 300, 16, "^7Main Skill:")
self.controls.mainSocketGroup = new("DropDownControl", {"TOPLEFT",self.controls.mainSkillLabel,"BOTTOMLEFT"}, 0, 2, 300, 18, nil, function(index, value)
self.mainSocketGroup = index
self.modFlag = true
self.buildFlag = true
end)
self.controls.mainSocketGroup.maxDroppedWidth = 500
self.controls.mainSocketGroup.tooltipFunc = function(tooltip, mode, index, value)
local socketGroup = self.skillsTab.socketGroupList[index]
if socketGroup and tooltip:CheckForUpdate(socketGroup, self.outputRevision) then
self.skillsTab:AddSocketGroupTooltip(tooltip, socketGroup)
end
end
self.controls.mainSkill = new("DropDownControl", {"TOPLEFT",self.controls.mainSocketGroup,"BOTTOMLEFT"}, 0, 2, 300, 18, nil, function(index, value)
local mainSocketGroup = self.skillsTab.socketGroupList[self.mainSocketGroup]
mainSocketGroup.mainActiveSkill = index
self.modFlag = true
self.buildFlag = true
end)
self.controls.mainSkillPart = new("DropDownControl", {"TOPLEFT",self.controls.mainSkill,"BOTTOMLEFT",true}, 0, 2, 300, 18, nil, function(index, value)
local mainSocketGroup = self.skillsTab.socketGroupList[self.mainSocketGroup]
local srcInstance = mainSocketGroup.displaySkillList[mainSocketGroup.mainActiveSkill].activeEffect.srcInstance
srcInstance.skillPart = index
self.modFlag = true
self.buildFlag = true
end)
self.controls.mainSkillStageCountLabel = new("LabelControl", {"TOPLEFT",self.controls.mainSkillPart,"BOTTOMLEFT",true}, 0, 3, 0, 16, "^7Stages:") {
shown = function()
return self.controls.mainSkillStageCount:IsShown()
end,
}
self.controls.mainSkillStageCount = new("EditControl", {"LEFT",self.controls.mainSkillStageCountLabel,"RIGHT",true}, 2, 0, 60, 18, nil, nil, "%D", nil, function(buf)
local mainSocketGroup = self.skillsTab.socketGroupList[self.mainSocketGroup]
local srcInstance = mainSocketGroup.displaySkillList[mainSocketGroup.mainActiveSkill].activeEffect.srcInstance
srcInstance.skillStageCount = tonumber(buf)
self.modFlag = true
self.buildFlag = true
end)
self.controls.mainSkillMineCountLabel = new("LabelControl", {"TOPLEFT",self.controls.mainSkillStageCountLabel,"BOTTOMLEFT",true}, 0, 3, 0, 16, "^7Active Mines:") {
shown = function()
return self.controls.mainSkillMineCount:IsShown()
end,
}
self.controls.mainSkillMineCount = new("EditControl", {"LEFT",self.controls.mainSkillMineCountLabel,"RIGHT",true}, 2, 0, 60, 18, nil, nil, "%D", nil, function(buf)
local mainSocketGroup = self.skillsTab.socketGroupList[self.mainSocketGroup]
local srcInstance = mainSocketGroup.displaySkillList[mainSocketGroup.mainActiveSkill].activeEffect.srcInstance
srcInstance.skillMineCount = tonumber(buf)
self.modFlag = true
self.buildFlag = true
end)
self.controls.mainSkillMinion = new("DropDownControl", {"TOPLEFT",self.controls.mainSkillMineCountLabel,"BOTTOMLEFT",true}, 0, 3, 178, 18, nil, function(index, value)
local mainSocketGroup = self.skillsTab.socketGroupList[self.mainSocketGroup]
local srcInstance = mainSocketGroup.displaySkillList[mainSocketGroup.mainActiveSkill].activeEffect.srcInstance
if value.itemSetId then
srcInstance.skillMinionItemSet = value.itemSetId
else
srcInstance.skillMinion = value.minionId
end
self.modFlag = true
self.buildFlag = true
end)
function self.controls.mainSkillMinion.CanReceiveDrag(control, type, value)
if type == "Item" and control.list[control.selIndex] and control.list[control.selIndex].itemSetId then
local mainSocketGroup = self.skillsTab.socketGroupList[self.mainSocketGroup]
local minionUses = mainSocketGroup.displaySkillList[mainSocketGroup.mainActiveSkill].activeEffect.grantedEffect.minionUses
return minionUses and minionUses[value:GetPrimarySlot()] -- O_O
end
end
function self.controls.mainSkillMinion.ReceiveDrag(control, type, value, source)
self.itemsTab:EquipItemInSet(value, control.list[control.selIndex].itemSetId)
end
function self.controls.mainSkillMinion.tooltipFunc(tooltip, mode, index, value)
tooltip:Clear()
if value.itemSetId then
self.itemsTab:AddItemSetTooltip(tooltip, self.itemsTab.itemSets[value.itemSetId])
tooltip:AddSeparator(14)
tooltip:AddLine(14, colorCodes.TIP.."Tip: You can drag items from the Items tab onto this dropdown to equip them onto the minion.")
end
end
self.controls.mainSkillMinionLibrary = new("ButtonControl", {"LEFT",self.controls.mainSkillMinion,"RIGHT"}, 2, 0, 120, 18, "Manage Spectres...", function()
self:OpenSpectreLibrary()
end)
self.controls.mainSkillMinionSkill = new("DropDownControl", {"TOPLEFT",self.controls.mainSkillMinion,"BOTTOMLEFT",true}, 0, 2, 200, 16, nil, function(index, value)
local mainSocketGroup = self.skillsTab.socketGroupList[self.mainSocketGroup]
local srcInstance = mainSocketGroup.displaySkillList[mainSocketGroup.mainActiveSkill].activeEffect.srcInstance
srcInstance.skillMinionSkill = index
self.modFlag = true
self.buildFlag = true
end)
self.controls.statBoxAnchor = new("Control", {"TOPLEFT",self.controls.mainSkillMinionSkill,"BOTTOMLEFT",true}, 0, 2, 0, 0)
self.controls.statBox = new("TextListControl", {"TOPLEFT",self.controls.statBoxAnchor,"BOTTOMLEFT"}, 0, 2, 300, 0, {{x=170,align="RIGHT_X"},{x=174,align="LEFT"}})
self.controls.statBox.height = function(control)
local x, y = control:GetPos()
local warnHeight = main.showWarnings and #self.controls.warnings.lines > 0 and 18 or 0
return main.screenH - main.mainBarHeight - 4 - y - warnHeight
end
self.controls.warnings = new("Control",{"TOPLEFT",self.controls.statBox,"BOTTOMLEFT",true}, 0, 0, 0, 18)
self.controls.warnings.lines = {}
self.controls.warnings.width = function(control)
return control.str and DrawStringWidth(16, "FIXED", control.str) + 8 or 0
end
self.controls.warnings.Draw = function(control)
if #self.controls.warnings.lines > 0 then
local count = 0
for _ in pairs(self.controls.warnings.lines) do count = count + 1 end
control.str = string.format(colorCodes.NEGATIVE.."%d Warnings", count)
local x, y = control:GetPos()
local width, height = control:GetSize()
DrawString(x, y + 2, "LEFT", 16, "FIXED", control.str)
if control:IsMouseInBounds() then
SetDrawLayer(nil, 10)
miscTooltip:Clear()
for k,v in pairs(self.controls.warnings.lines) do miscTooltip:AddLine(16, v) end
miscTooltip:Draw(x, y, width, height, main.viewPort)
SetDrawLayer(nil, 0)
end
else
control.str = {}
end
end
-- Initialise build components
self.latestTree = main.tree[latestTreeVersion]
data.setJewelRadiiGlobally(latestTreeVersion)
self.data = data
self.importTab = new("ImportTab", self)
self.notesTab = new("NotesTab", self)
self.partyTab = new("PartyTab", self)
self.configTab = new("ConfigTab", self)
self.itemsTab = new("ItemsTab", self)
self.treeTab = new("TreeTab", self)
self.skillsTab = new("SkillsTab", self)
self.calcsTab = new("CalcsTab", self)
-- Load sections from the build file
self.savers = {
["Config"] = self.configTab,
["Notes"] = self.notesTab,
["Party"] = self.partyTab,
["Tree"] = self.treeTab,
["TreeView"] = self.treeTab.viewer,
["Items"] = self.itemsTab,
["Skills"] = self.skillsTab,
["Calcs"] = self.calcsTab,
["Import"] = self.importTab,
}
self.legacyLoaders = { -- Special loaders for legacy sections
["Spec"] = self.treeTab,
}
--special rebuild to properly initialise boss placeholders
self.configTab:BuildModList()
-- Initialise class dropdown
for classId, class in pairs(self.latestTree.classes) do
local ascendancies = {}
-- Initialise ascendancy dropdown
for i = 0, #class.classes do
local ascendClass = class.classes[i]
t_insert(ascendancies, {
label = ascendClass.name,
ascendClassId = i,
})
end
t_insert(self.controls.classDrop.list, {
label = class.name,
classId = classId,
ascendancies = ascendancies,
})
end
table.sort(self.controls.classDrop.list, function(a, b) return a.label < b.label end)
-- Load legacy bandit and pantheon choices from build section
for _, control in ipairs({ "bandit", "pantheonMajorGod", "pantheonMinorGod" }) do
self.configTab.input[control] = self[control]
end
-- so we ran into problems with converted trees, trying to check passive tree routes and also consider thread jewels
-- but we can't check jewel info because items have not been loaded yet, and they come after passives in the xml.
-- the simplest solution seems to be making sure passive trees (which contain jewel sockets) are loaded last.
local deferredPassiveTrees = { }
for _, node in ipairs(self.xmlSectionList) do
-- Check if there is a saver that can load this section
local saver = self.savers[node.elem] or self.legacyLoaders[node.elem]
if saver then
-- if the saver is treeTab, defer it until everything is loaded
if saver == self.treeTab then
t_insert(deferredPassiveTrees, node)
else
if saver:Load(node, self.dbFileName) then
self:CloseBuild()
return
end
end
end
end
for _, node in ipairs(deferredPassiveTrees) do
-- Check if there is a saver that can load this section
if self.treeTab:Load(node, self.dbFileName) then
self:CloseBuild()
return
end
end
for _, saver in pairs(self.savers) do
if saver.PostLoad then
saver:PostLoad()
end
end
if next(self.configTab.input) == nil then
-- Check for old calcs tab settings
self.configTab:ImportCalcSettings()
end
-- Build calculation output tables
self.outputRevision = 1
self.calcsTab:BuildOutput()
self:RefreshStatList()
self.buildFlag = false
self.spec:SetWindowTitleWithBuildClass()
--[[
local testTooltip = new("Tooltip")
for _, item in pairs(main.uniqueDB.list) do
ConPrintf("%s", item.name)
self.itemsTab:AddItemTooltip(testTooltip, item)
testTooltip:Clear()
end
for _, item in pairs(main.rareDB.list) do
ConPrintf("%s", item.name)
self.itemsTab:AddItemTooltip(testTooltip, item)
testTooltip:Clear()
end
--]]
--[[
local start = GetTime()
SetProfiling(true)
for i = 1, 10 do
self.calcsTab:PowerBuilder()
end
SetProfiling(false)
ConPrintf("Power build time: %d ms", GetTime() - start)
--]]
self.abortSave = false
self:SyncLoadouts()
end
local acts = {
-- https://www.poewiki.net/wiki/Passive_skill
[1] = { level = 1, questPoints = 0 },
-- Act 1 : The Dweller of the Deep
-- Act 1 : The Marooned Mariner
[2] = { level = 12, questPoints = 2 },
-- Act 1,2 : The Way Forward (Reward after reaching Act 2)
-- Act 2 : Through Sacred Ground (Fellshrine Reward 3.25)
[3] = { level = 22, questPoints = 4 },
-- Act 3 : Victario's Secrets
-- Act 3 : Piety's Pets
[4] = { level = 32, questPoints = 6 },
-- Act 4 : An Indomitable Spirit
[5] = { level = 40, questPoints = 7 },
-- Act 5 : In Service to Science
-- Act 5 : Kitava's Torments
[6] = { level = 44, questPoints = 9 },
-- Act 6 : The Father of War
-- Act 6 : The Puppet Mistress
-- Act 6 : The Cloven One
[7] = { level = 50, questPoints = 12 },
-- Act 7 : The Master of a Million Faces
-- Act 7 : Queen of Despair
-- Act 7 : Kishara's Star
[8] = { level = 54, questPoints = 15 },
-- Act 8 : Love is Dead
-- Act 8 : Reflection of Terror
-- Act 8 : The Gemling Legion
[9] = { level = 60, questPoints = 18 },
-- Act 9 : Queen of the Sands
-- Act 9 : The Ruler of Highgate
[10] = { level = 64, questPoints = 20 },
-- Act 10 : Vilenta's Vengeance
-- Act 10 : An End to Hunger (+2)
[11] = { level = 67, questPoints = 23 },
}
local function actExtra(act, extra)
-- Act 2 : Deal With The Bandits (+1 if the player kills all bandits)
return act > 2 and extra or 0
end
function buildMode:SyncLoadouts()
self.controls.buildLoadouts.list = {"No Loadouts"}
local filteredList = {"^7^7Loadouts:"}
local treeList = {}
local itemList = {}
local skillList = {}
local configList = {}
-- used when clicking on the dropdown to set the correct setId for each SetActiveSet()
self.treeListSpecialLinks, self.itemListSpecialLinks, self.skillListSpecialLinks, self.configListSpecialLinks = {}, {}, {}, {}
local oneSkill = self.skillsTab and #self.skillsTab.skillSetOrderList == 1
local oneItem = self.itemsTab and #self.itemsTab.itemSetOrderList == 1
local oneConfig = self.configTab and #self.configTab.configSetOrderList == 1
if self.treeTab ~= nil and self.itemsTab ~= nil and self.skillsTab ~= nil and self.configTab ~= nil then
local transferTable = {}
local sortedTreeListSpecialLinks = {}
for id, spec in ipairs(self.treeTab.specList) do
local specTitle = spec.title or "Default"
-- only alphanumeric and comma are allowed in the braces { }
local linkIdentifier = string.match(specTitle, "%{([%w,]+)%}")
if linkIdentifier then
local setName = specTitle:gsub("%{" .. linkIdentifier .. "%}", ""):gsub("^%s*", ""):gsub("%s*$", "")
if not setName or setName == "" then
setName = "Default"
end
-- iterate over each identifier, delimited by comma, and set the index so we can grab it later
-- setId index is the id of the set in the global list needed for SetActiveSet
-- setName is only used for Tree currently and we strip the braces to get the plain name of the set, this is used as the name of the loadout
for linkId in string.gmatch(linkIdentifier, "[^%,]+") do
transferTable["setId"] = id
transferTable["setName"] = setName
transferTable["linkId"] = linkId
self.treeListSpecialLinks[linkId] = transferTable
t_insert(sortedTreeListSpecialLinks, transferTable)
transferTable = {}
end
else
t_insert(treeList, (spec.treeVersion ~= latestTreeVersion and ("["..treeVersions[spec.treeVersion].display.."] ") or "")..(specTitle))
end
end
-- item, skill, and config sets have identical structure
local function identifyLinks(setOrderList, tabSets, setList, specialLinks, treeLinks)
for id, set in ipairs(setOrderList) do
local setTitle = tabSets[set].title or "Default"
local linkIdentifier = string.match(setTitle, "%{([%w,]+)%}")
-- this if/else prioritizes group identifier in case the user creates sets with same name AND same identifiers
-- result is only the group is recognized and one loadout is created rather than a duplicate from each condition met
if linkIdentifier then
local setName = setTitle:gsub("%{" .. linkIdentifier .. "%}", ""):gsub("^%s*", ""):gsub("%s*$", "")
if not setName or setName == "" then
setName = "Default"
end
for linkId in string.gmatch(linkIdentifier, "[^%,]+") do
transferTable["setId"] = set
transferTable["setName"] = setName
specialLinks[linkId] = transferTable
transferTable = {}
end
else
setList[setTitle] = true
end
end
end
identifyLinks(self.itemsTab.itemSetOrderList, self.itemsTab.itemSets, itemList, self.itemListSpecialLinks, self.treeListSpecialLinks)
identifyLinks(self.skillsTab.skillSetOrderList, self.skillsTab.skillSets, skillList, self.skillListSpecialLinks, self.treeListSpecialLinks)
identifyLinks(self.configTab.configSetOrderList, self.configTab.configSets, configList, self.configListSpecialLinks, self.treeListSpecialLinks)
-- loop over all for exact match loadouts
for id, tree in ipairs(treeList) do
if (oneItem or itemList[tree]) and (oneSkill or skillList[tree]) and (oneConfig or configList[tree]) then
t_insert(filteredList, tree)
end
end
-- loop over the identifiers found within braces and set the loadout name to the TreeSet
for _, tree in ipairs(sortedTreeListSpecialLinks) do
local treeLinkId = tree.linkId
if ((oneItem or self.itemListSpecialLinks[treeLinkId]) and (oneSkill or self.skillListSpecialLinks[treeLinkId]) and (oneConfig or self.configListSpecialLinks[treeLinkId])) then
t_insert(filteredList, tree.setName .." {"..treeLinkId.."}")