-
Notifications
You must be signed in to change notification settings - Fork 2.4k
Expand file tree
/
Copy pathBuildDisplayStats.lua
More file actions
293 lines (288 loc) · 38.2 KB
/
Copy pathBuildDisplayStats.lua
File metadata and controls
293 lines (288 loc) · 38.2 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
-- Path of Building
--
-- Module: Build Display Stats
-- Loads the displayStats and extraSaveStats.
--
-- 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
---@param value number
---@param output table Actor output values
---@return "Immune"|"Guard"|nil suffix
local function maxHitSuffix(value, output)
return value == math.huge and "Immune" or output.GuardSkillActive and "Guard" or nil
end
local defenseIgnoredSections = { "Base from Gear", "Inc. from Tree" }
---@alias DisplayStatCondFunc fun(value: any, output: table):boolean? Show the row only when this returns truthy (usually receives the value and the actor output; the label-only variant receives output as its first arg)
---@alias DisplayStatWarnFunc fun(value: any, output: table):string|boolean|nil Returns a warning string to surface, or falsy for none
---@class DisplayStat One sidebar stat row (also drives node/item comparisons). An empty `{}` is a separator.
---@field stat? string Output value name, e.g. "Life", "TotalDPS", "MainHand"
---@field childStat? string Nested output field when `stat` is a table, e.g. "Accuracy" for MainHand.Accuracy
---@field label? string Display label shown before the value
---@field fmt? string C-style format spec applied to the value, e.g. "d", ".1f", ".2f%%", "+d%%"
---@field breakdown? string Override key for the sidebar breakdown. Should be set if the stat/childStat names don't match up with the breakdown names, or if the breakdown is conditional, but the cell isn't.
---@field modNames string[]? Modifier names to show in the breakdown popup. This should only be used if the breakdown itself doesn't list the mods. E.g. Life doesn't.
---@field ignoredSections string[]? A list of breakdown sections which shouldn't be shown. Useful to avoid duplicated info.
---@field color? string Colour escape code for the label (e.g. colorCodes.LIFE)
---@field flag? string Only show when the main skill has this skill flag
---@field notFlag? string Hide when the main skill has this skill flag
---@field condFunc? DisplayStatCondFunc Extra condition gating whether the row is shown
---@field warnFunc? DisplayStatWarnFunc Produces a warning message for the warnings panel
---@field warnColor? boolean Colour the value red when `warnFunc` returns truthy
---@field suffix? string|fun(value:any, output:table):string|nil Parenthesised suffix appended after the value
---@field suffixCondFunc? fun(value:any, output:table):boolean? Gates whether `suffix` is shown
---@field overCapStat? string Output name of the "over cap" counterpart shown alongside (e.g. resistances)
---@field overCapStatCondFunc? fun(value:any, output:table):boolean? Gates whether the over-cap value is shown
---@field compactValue? boolean Allow compact formatting (e.g. 712.3K) when the user setting is on
---@field compPercent? boolean Include a percentage delta in node/item comparisons
---@field lowerIsBetter? boolean Treat a decrease as an improvement in comparisons
---@field pc? boolean Multiply the value by 100 (fractional value shown as a percentage)
---@field mod? boolean Value is a modifier: scale by 100 and subtract 100 (shows the +/- portion)
---@field hideStat? boolean Compute/consider the stat but don't render a sidebar row
---@field chaosInoc? boolean Use the muted "immune" colour instead of red for a zero/negative value
---@field pool? string Associated resource pool name (e.g. "ManaUnreserved") for cost rows
---@field labelStat? string Output name rendered inline for a label-only row
---@field val? string Static parenthesised note for a label-only row
---@type DisplayStat[]
local displayStats = {
{ stat = "ActiveMinionLimit", label = "Active Minion Limit", fmt = "d" },
{ stat = "AverageHit", label = "Average Hit", fmt = ".1f", compactValue = true, compPercent = true },
{ stat = "PvpAverageHit", label = "PvP Average Hit", fmt = ".1f", compactValue = true, compPercent = true, flag = "isPvP" },
{ stat = "AverageDamage", label = "Average Damage", fmt = ".1f", compactValue = true, compPercent = true, flag = "attack" },
{ stat = "AverageDamage", label = "Average Damage", fmt = ".1f", compactValue = true, compPercent = true, flag = "monsterExplode", condFunc = function(v,o) return o.HitChance ~= 100 end },
{ stat = "AverageBurstDamage", label = "Average Burst Damage", fmt = ".1f", compactValue = true, 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", compactValue = true, 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, breakdown = "Speed", modNames = { "Speed" }, ignoredSections = { "Inc. Cast Speed", "More Cast Speed", } },
{ stat = "KineticFusilladeMaxEffectiveAPS", label = "Max Eff. KF Attack Rate", fmt = ".2f", compPercent = true, flag = "attack", condFunc = function(v) return v > 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, modNames = { "Speed" } },
{ stat = "Speed", label = "Effective Trigger Rate", fmt = ".2f", compPercent = true, notFlag = "skipEffectiveRate", 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%%", flag = "hit", breakdown = "PreEffectiveCritChance" },
{ stat = "CritChance", label = "Effective Crit Chance", fmt = ".2f%%", flag = "hit", condFunc = function(v, o) return v ~= o.PreEffectiveCritChance end, breakdown = "CritChance" },
{ stat = "CritMultiplier", label = "Crit Multiplier", fmt = "d%%", pc = true, condFunc = function(v, o) return (o.CritChance or 0) > 0 end, breakdown = "CritMultiplier" },
{ stat = "HitChance", label = "Hit Chance", fmt = ".0f%%", flag = "attack", breakdown = "HitChance" },
{ stat = "HitChance", label = "Hit Chance", fmt = ".0f%%", condFunc = function(v,o) return o.enemyHasSpellBlock end },
{ stat = "TotalDPS", label = "Hit DPS", fmt = ".1f", compactValue = true, compPercent = true, flag = "notAverage" },
{ stat = "PvpTotalDPS", label = "PvP Hit DPS", fmt = ".1f", compactValue = true, compPercent = true, flag = "notAveragePvP" },
{ stat = "TotalDPS", label = "Hit DPS", fmt = ".1f", compactValue = true, compPercent = true, flag = "showAverage", condFunc = function(v,o) return (o.TriggerTime or 0) ~= 0 end },
{ stat = "TotalDot", label = "DoT DPS", fmt = ".1f", compactValue = true, compPercent = true },
{ stat = "WithDotDPS", label = "Total DPS inc. DoT", fmt = ".1f", compactValue = true, 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", compactValue = true, compPercent = true, warnFunc = function(v) return v >= data.misc.DotDpsCap and "Bleed DPS exceeds in game limit" end },
{ stat = "CorruptingBloodDPS", label = "Corrupting Blood DPS", compactValue = true, 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", compactValue = true, compPercent = true, flag = "showAverage" },
{ stat = "WithBleedDPS", label = "Total DPS inc. Bleed", fmt = ".1f", compactValue = true, 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, compactValue = 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", compactValue = true, compPercent = true, flag = "showAverage" },
{ stat = "BurningGroundDPS", label = "Burning Ground DPS", fmt = ".1f", compactValue = true, 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", compactValue = true, 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", compactValue = true, 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", compactValue = true, fmt = ".1f", compPercent = true },
{ stat = "PoisonDPS", label = "Single Poison DPS", fmt = ".1f", compactValue = true, 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", compactValue = true, 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", compactValue = true, 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", compactValue = true, compPercent = true },
{ stat = "WithPoisonDPS", label = "Total DPS inc. Poison", fmt = ".1f", compactValue = true, 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", compactValue = true, compPercent = true },
{ stat = "TotalDotDPS", label = "Total DoT DPS", fmt = ".1f", compactValue = true, 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", compactValue = true, compPercent = true, flag = "impale", flag = "showAverage" },
{ stat = "WithImpaleDPS", label = "Damage inc. Impale", fmt = ".1f", compactValue = true, 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", compactValue = true, compPercent = true, flag = "impale", flag = "notAverage" },
{ stat = "WithImpaleDPS", label = "Total DPS inc. Impale", compactValue = true, 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", compactValue = true, compPercent = true, flag = "mirageArcher", condFunc = function(v,o) return v > 0 end },
{ stat = "MirageDPS", label = "Total Wisp DPS", fmt = ".1f", compactValue = true, compPercent = true, flag = "wisp", condFunc = function(v,o) return v > 0 end },
{ stat = "CullingDPS", label = "Culling DPS", fmt = ".1f", compactValue = true, compPercent = true, condFunc = function(v,o) return (o.CullingDPS or 0) > 0 end },
{ stat = "ReservationDPS", label = "Reservation DPS", fmt = ".1f", compactValue = true, compPercent = true, condFunc = function(v,o) return (o.ReservationDPS or 0) > 0 end },
{ stat = "CombinedDPS", label = "Combined DPS", fmt = ".1f", compactValue = true, 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", compactValue = true, 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", notFlag = "skipEffectiveRate", 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", breakdown = "AreaOfEffectRadius", modNames = { "AreaOfEffect" } },
{ 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", breakdown = "Str" },
{ stat = "ReqStr", label = "Strength Required", color = colorCodes.STRENGTH, fmt = "d", lowerIsBetter = true, condFunc = function(v,o) return v > o.Str end, warnFunc = function(v,o) return "You do not meet the Strength requirement of " .. (o.ReqStrItem.source == "Item" and o.ReqStrItem.sourceItem.name or o.ReqStrItem.source == "Gem" and o.ReqStrItem.sourceGem.nameSpec) end },
{ stat = "Dex", label = "Dexterity", color = colorCodes.DEXTERITY, fmt = "d", breakdown = "Dex" },
{ stat = "ReqDex", label = "Dexterity Required", color = colorCodes.DEXTERITY, fmt = "d", lowerIsBetter = true, condFunc = function(v,o) return v > o.Dex end, warnFunc = function(v,o) return "You do not meet the Dexterity requirement of " .. (o.ReqDexItem.source == "Item" and o.ReqDexItem.sourceItem.name or o.ReqDexItem.source == "Gem" and o.ReqDexItem.sourceGem.nameSpec) end },
{ stat = "Int", label = "Intelligence", color = colorCodes.INTELLIGENCE, fmt = "d", breakdown = "Int" },
{ stat = "ReqInt", label = "Intelligence Required", color = colorCodes.INTELLIGENCE, fmt = "d", lowerIsBetter = true, condFunc = function(v,o) return v > o.Int end, warnFunc = function(v,o) return "You do not meet the Intelligence requirement of " .. (o.ReqIntItem.source == "Item" and o.ReqIntItem.sourceItem.name or o.ReqIntItem.source == "Gem" and o.ReqIntItem.sourceGem.nameSpec) 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,o) return "You do not meet the Omniscience requirement of " .. (o.ReqOmniItem.source == "Item" and o.ReqOmniItem.sourceItem.name or o.ReqOmniItem.source == "Gem" and o.ReqOmniItem.sourceGem.nameSpec) end },
{ },
{ stat = "Devotion", label = "Devotion", color = colorCodes.RARE, fmt = "d" },
{ },
{ stat = "TotalEHP", label = "Effective Hit Pool", fmt = ".0f", suffix = "Guard", compactValue = true, compPercent = true, suffixCondFunc = function(v,o) return o.GuardSkillActive end },
{ stat = "PvPTotalTakenHit", label = "PvP Hit Taken", fmt = ".1f", flag = "isPvP", compactValue = true, lowerIsBetter = true },
{ stat = "PhysicalMaximumHitTaken", label = "Phys Max Hit", fmt = ".0f", compactValue = true, color = colorCodes.PHYS, compPercent = true, suffix = maxHitSuffix },
{ stat = "LightningMaximumHitTaken", label = "Elemental Max Hit", fmt = ".0f", compactValue = true, color = colorCodes.LIGHTNING, compPercent = true, suffix = maxHitSuffix, condFunc = function(v,o) return o.LightningMaximumHitTaken == o.ColdMaximumHitTaken and o.LightningMaximumHitTaken == o.FireMaximumHitTaken end },
{ stat = "FireMaximumHitTaken", label = "Fire Max Hit", fmt = ".0f", compactValue = true, color = colorCodes.FIRE, compPercent = true, suffix = maxHitSuffix, condFunc = function(v,o) return o.LightningMaximumHitTaken ~= o.ColdMaximumHitTaken or o.LightningMaximumHitTaken ~= o.FireMaximumHitTaken end },
{ stat = "ColdMaximumHitTaken", label = "Cold Max Hit", fmt = ".0f", compactValue = true, color = colorCodes.COLD, compPercent = true, suffix = maxHitSuffix, condFunc = function(v,o) return o.LightningMaximumHitTaken ~= o.ColdMaximumHitTaken or o.LightningMaximumHitTaken ~= o.FireMaximumHitTaken end },
{ stat = "LightningMaximumHitTaken", label = "Lightning Max Hit", fmt = ".0f", compactValue = true, color = colorCodes.LIGHTNING, compPercent = true, suffix = maxHitSuffix, condFunc = function(v,o) return o.LightningMaximumHitTaken ~= o.ColdMaximumHitTaken or o.LightningMaximumHitTaken ~= o.FireMaximumHitTaken end },
{ stat = "ChaosMaximumHitTaken", label = "Chaos Max Hit", fmt = ".0f", compactValue = true, color = colorCodes.CHAOS, compPercent = true, suffix = maxHitSuffix },
{ },
{ 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", compactValue = true, color = colorCodes.LIFE, compPercent = true, modNames = { "Life" }, ignoredSections = { "Base from Gear", "Inc. from Tree" } },
{ 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, breakdown = "LifeRegenRecovery" },
{ stat = "LifeRegenRecovery", label = "Life Recovery", fmt = ".1f", color = colorCodes.LIFE, condFunc = function(v, o) return o.LifeRecovery > 0 and o.LifeRegenRecovery ~= 0 end, breakdown = "LifeRegenRecovery" },
{ stat = "LifeRecharge", label = "Life Recharge", fmt = ".1f", color = colorCodes.LIFE, condFunc = function(v,o) return v > 0 end },
{ stat = "LifeLeechGainRate", label = "Life Leech/On Hit Rate", fmt = ".1f", color = colorCodes.LIFE, compPercent = true, breakdown = "LifeLeech" },
{ stat = "LifeLeechGainPerHit", label = "Life Leech/Gain per Hit", fmt = ".1f", color = colorCodes.LIFE, compPercent = true },
{ },
{ stat = "Mana", label = "Total Mana", fmt = "d", compactValue = true, color = colorCodes.MANA, compPercent = true, modNames = { "Mana" }, ignoredSections = { "Base from Gear", "Inc. from Tree" } },
{ 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, breakdown = "ManaReserved" },
{ 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 },
{ },
-- the breakdown for armour, es and ev doesn't have anything relevant, and
-- we are better off ignoring it and just showing relevant mods
{ stat = "EnergyShield", label = "Energy Shield", fmt = "d", compactValue = true, color = colorCodes.ES, compPercent = true, modNames = { "EnergyShield", "Defences" }, ignoredSections = defenseIgnoredSections },
{ 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", compactValue = true, color = colorCodes.WARD, compPercent = true, modNames = { "Ward" }, ignoredSections = defenseIgnoredSections },
{ },
{ 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, modNames = { "Evasion", "ArmourAndEvasion", "Defences" }, ignoredSections = defenseIgnoredSections },
{ 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, modNames = { "Armour", "ArmourAndEvasion", "Defences", "ArmourDefense" }, ignoredSections = defenseIgnoredSections },
{ 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 = ".3f%%", overCapStat = "BlockChanceOverCap", breakdown = "BlockChance" },
{ stat = "EffectiveSpellBlockChance", label = "Spell Block Chance", fmt = ".3f%%", overCapStat = "SpellBlockChanceOverCap", breakdown = "SpellBlockChance" },
{ 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 = ".2f%%", overCapStat = "SpellSuppressionChanceOverCap", breakdown = "", modNames = { "SpellSuppressionChance" } },
{ },
{ 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, overCapStat = "ChaosResistOverCap", overCapStatCondFunc = function(v,o) return not o.ChaosInoculation end, suffix = "Immune", suffixCondFunc = function(v,o) return o.ChaosInoculation end },
{ stat = "ChaosResistOverCap", label = "Chaos Res. Over Max", fmt = "d%%", hideStat = true },
{ },
{ stat = "EffectiveMovementSpeedMod", label = "Movement Speed Modifier", fmt = "+d%%", mod = true, condFunc = function() return true end },
--[[ potentially useful mods
{ stat = "QuantityMultiplier", label = "Quantity Multiplier", fmt = "+d%%" },
{ stat = "StoredUses", label = "Stored Uses", fmt = "d" },
{ stat = "Duration", label = "Skill Duration", fmt = ".2f", flag = "duration" },
{ stat = "DurationSecondary", label = "Secondary Duration", fmt = ".2f", flag = "duration" },
{ stat = "AuraDuration", label = "Aura Duration", fmt = ".2f" },
{ stat = "ReserveDuration", label = "Reserve Duration", fmt = ".2f" },
{ stat = "SoulGainPreventionDuration", label = "Soul Gain Prevent.", fmt = ".2f" },
{ stat = "SustainableTrauma", label = "Sustainable Trauma", fmt = "d" },
{ stat = "ProjectileCount", label = "Projectile Count", fmt = "d", flag = "projectile" },
{ stat = "PierceCountString", label = "Pierce Count", fmt = "d" },
{ stat = "ForkCountString", label = "Fork Count", fmt = "d" },
{ stat = "ChainMaxString", label = "Max Chain Count", fmt = "d" },
{ stat = "SplitCountString", label = "Proj, Split Count", fmt = "d", flag = "projectile" },
{ stat = "ProjectileSpeedMod", label = "Proj. Speed Mod", fmt = ".2f", flag = "projectile" },
{ stat = "BounceCount", label = "Bounces Count", fmt = "d", flag = "bounce" },
{ stat = "AuraEffectMod", label = "Aura Effect Mod", fmt = ".2f" },
{ stat = "CurseEffectMod", label = "Curse Effect Mod", fmt = ".2f" },
{ stat = "LootQuantity", label = "Item Quantity", fmt = "+d%%" },
{ stat = "LootRarity", label = "Item Rarity", fmt = "+d%%" },
--]]
{ },
{ stat = "FullDPS", label = "Full DPS", fmt = ".1f", compactValue = true, color = colorCodes.CURRENCY, compPercent = true },
{ stat = "FullDotDPS", label = "Full Dot DPS", fmt = ".1f", compactValue = true, 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", compactValue = true, condFunc = function() return true end },
}
---@type DisplayStat[]
local minionDisplayStats = {
{ stat = "AverageDamage", label = "Average Damage", fmt = ".1f", compactValue = true, 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, notFlag = "skipEffectiveRate", condFunc = function(v,o) return (o.TriggerTime or 0) ~= 0 end },
{ stat = "TotalDPS", label = "Hit DPS", fmt = ".1f", compactValue = true, compPercent = true },
{ stat = "TotalDot", label = "DoT DPS", fmt = ".1f", compactValue = true, compPercent = true },
{ stat = "WithDotDPS", label = "Total DPS inc. DoT", fmt = ".1f", compactValue = true, 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", compactValue = true, 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", compactValue = true, 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", compactValue = true, 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", compactValue = true, 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", compactValue = true, 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", compactValue = true, compPercent = true },
{ stat = "WithPoisonDPS", label = "Total DPS inc. Poison", fmt = ".1f", compactValue = true, 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", compactValue = true, compPercent = true },
{ stat = "TotalDotDPS", label = "Total DoT DPS", fmt = ".1f", compactValue = true, 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", compactValue = true, compPercent = true, flag = "impale" },
{ stat = "WithImpaleDPS", label = "Total DPS inc. Impale", fmt = ".1f", compactValue = true, 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", compactValue = true, compPercent = true, condFunc = function(v,o) return (o.CullingDPS or 0) > 0 end },
{ stat = "ReservationDPS", label = "Reservation DPS", fmt = ".1f", compactValue = true, compPercent = true, condFunc = function(v,o) return (o.ReservationDPS or 0) > 0 end },
{ stat = "CombinedDPS", label = "Combined DPS", fmt = ".1f", compactValue = true, 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", notFlag = "skipEffectiveRate", lowerIsBetter = true },
{ stat = "Life", label = "Total Life", fmt = ".1f", compactValue = true, color = colorCodes.LIFE, compPercent = true, modNames = { "Life" }, ignoredSections = { "Base from Gear", "Inc. from Tree" } },
{ 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", compactValue = true, color = colorCodes.ES, compPercent = true, modNames = { "EnergyShield", "Defences" }, ignoredSections = defenseIgnoredSections },
{ 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 },
}
-- Extra stats saved to the xml if not already saved there, mostly for 3rd party tools
---@type string[]
local extraSaveStats = {
"PowerCharges",
"PowerChargesMax",
"FrenzyCharges",
"FrenzyChargesMax",
"EnduranceCharges",
"EnduranceChargesMax",
"ActiveTotemLimit",
"ActiveMinionLimit",
}
return { displayStats = displayStats, minionDisplayStats = minionDisplayStats, extraSaveStats = extraSaveStats }