Skip to content

Commit f6d803b

Browse files
committed
adds CompareCalcsHelpers for isolated calcs logic
1 parent 7e792db commit f6d803b

2 files changed

Lines changed: 327 additions & 308 deletions

File tree

Lines changed: 320 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,320 @@
1+
-- Path of Building
2+
--
3+
-- Module: Compare Calcs Helpers
4+
-- Stateless calcs tooltip helper functions for the Compare Tab.
5+
-- Handles modifier formatting, source resolution, tabulation, and tooltip rendering.
6+
--
7+
local t_insert = table.insert
8+
local s_format = string.format
9+
10+
local M = {}
11+
12+
-- Format a modifier value with its type for display
13+
function M.FormatCalcModValue(value, modType)
14+
if modType == "BASE" then
15+
return s_format("%+g base", value)
16+
elseif modType == "INC" then
17+
if value >= 0 then
18+
return value .. "% increased"
19+
else
20+
return (-value) .. "% reduced"
21+
end
22+
elseif modType == "MORE" then
23+
if value >= 0 then
24+
return value .. "% more"
25+
else
26+
return (-value) .. "% less"
27+
end
28+
elseif modType == "OVERRIDE" then
29+
return "Override: " .. tostring(value)
30+
elseif modType == "FLAG" then
31+
return value and "True" or "False"
32+
else
33+
return tostring(value)
34+
end
35+
end
36+
37+
-- Format CamelCase mod name to spaced words
38+
function M.FormatCalcModName(modName)
39+
return modName:gsub("([%l%d]:?)(%u)", "%1 %2"):gsub("(%l)(%d)", "%1 %2")
40+
end
41+
42+
-- Resolve a modifier's source to a human-readable name
43+
function M.ResolveSourceName(mod, build)
44+
if not mod.source then return "" end
45+
local sourceType = mod.source:match("[^:]+") or ""
46+
if sourceType == "Item" then
47+
local itemId = mod.source:match("Item:(%d+):.+")
48+
local item = build.itemsTab and build.itemsTab.items[tonumber(itemId)]
49+
if item then
50+
return colorCodes[item.rarity] .. item.name
51+
end
52+
elseif sourceType == "Tree" then
53+
local nodeId = mod.source:match("Tree:(%d+)")
54+
if nodeId then
55+
local nodeIdNum = tonumber(nodeId)
56+
local node = (build.spec and build.spec.nodes[nodeIdNum])
57+
or (build.spec and build.spec.tree and build.spec.tree.nodes[nodeIdNum])
58+
or (build.latestTree and build.latestTree.nodes[nodeIdNum])
59+
if node then
60+
return node.dn or node.name or ""
61+
end
62+
end
63+
elseif sourceType == "Skill" then
64+
local skillId = mod.source:match("Skill:(.+)")
65+
if skillId and build.data and build.data.skills[skillId] then
66+
return build.data.skills[skillId].name
67+
end
68+
elseif sourceType == "Pantheon" then
69+
return mod.source:match("Pantheon:(.+)") or ""
70+
elseif sourceType == "Spectre" then
71+
return mod.source:match("Spectre:(.+)") or ""
72+
end
73+
return ""
74+
end
75+
76+
-- Get the modDB and config for a sectionData entry and actor
77+
function M.GetModStoreAndCfg(sectionData, actor)
78+
local cfg = {}
79+
if sectionData.cfg and actor.mainSkill and actor.mainSkill[sectionData.cfg .. "Cfg"] then
80+
cfg = copyTable(actor.mainSkill[sectionData.cfg .. "Cfg"], true)
81+
end
82+
cfg.source = sectionData.modSource
83+
cfg.actor = sectionData.actor
84+
85+
local modStore
86+
if sectionData.enemy and actor.enemy then
87+
modStore = actor.enemy.modDB
88+
elseif sectionData.cfg and actor.mainSkill then
89+
modStore = actor.mainSkill.skillModList
90+
else
91+
modStore = actor.modDB
92+
end
93+
return modStore, cfg
94+
end
95+
96+
-- Tabulate modifiers for a sectionData entry and actor
97+
function M.TabulateMods(sectionData, actor)
98+
local modStore, cfg = M.GetModStoreAndCfg(sectionData, actor)
99+
if not modStore then return {} end
100+
101+
local rowList
102+
if type(sectionData.modName) == "table" then
103+
rowList = modStore:Tabulate(sectionData.modType, cfg, unpack(sectionData.modName))
104+
else
105+
rowList = modStore:Tabulate(sectionData.modType, cfg, sectionData.modName)
106+
end
107+
return rowList or {}
108+
end
109+
110+
-- Build a unique key for a modifier row to match between builds
111+
function M.ModRowKey(row)
112+
local src = row.mod.source or ""
113+
local name = row.mod.name or ""
114+
local mtype = row.mod.type or ""
115+
-- Normalize Item sources by stripping the build-specific numeric ID
116+
-- "Item:5:Body Armour" -> "Item:Body Armour" so same items match across builds
117+
local normalizedSrc = src:gsub("^(Item):%d+:", "%1:")
118+
return normalizedSrc .. "|" .. name .. "|" .. mtype
119+
end
120+
121+
-- Format a single modifier row as a tooltip line
122+
function M.FormatModRow(row, sectionData, build)
123+
local displayValue
124+
if not sectionData.modType then
125+
displayValue = M.FormatCalcModValue(row.value, row.mod.type)
126+
else
127+
displayValue = formatRound(row.value, 2)
128+
end
129+
130+
local sourceType = row.mod.source and row.mod.source:match("[^:]+") or "?"
131+
local sourceName = M.ResolveSourceName(row.mod, build)
132+
local modName = ""
133+
if type(sectionData.modName) == "table" then
134+
modName = " " .. M.FormatCalcModName(row.mod.name)
135+
end
136+
137+
return displayValue, sourceType, sourceName, modName
138+
end
139+
140+
-- Get breakdown text lines for a build's actor
141+
function M.GetBreakdownLines(sectionData, build)
142+
if not sectionData.breakdown then return nil end
143+
local calcsActor = build.calcsTab and build.calcsTab.calcsEnv and build.calcsTab.calcsEnv.player
144+
if not calcsActor or not calcsActor.breakdown then return nil end
145+
146+
local breakdown
147+
local ns, name = sectionData.breakdown:match("^(%a+)%.(%a+)$")
148+
if ns then
149+
breakdown = calcsActor.breakdown[ns] and calcsActor.breakdown[ns][name]
150+
else
151+
breakdown = calcsActor.breakdown[sectionData.breakdown]
152+
end
153+
154+
if not breakdown or #breakdown == 0 then return nil end
155+
156+
local lines = {}
157+
for _, line in ipairs(breakdown) do
158+
if type(line) == "string" then
159+
t_insert(lines, line)
160+
end
161+
end
162+
return #lines > 0 and lines or nil
163+
end
164+
165+
-- Draw the calcs hover tooltip showing breakdown for both builds with common/unique grouping
166+
-- tooltip, primaryBuild, primaryLabel passed as args instead of self
167+
function M.DrawCalcsTooltip(tooltip, primaryBuild, primaryLabel, colData, rowLabel, rowX, rowY, rowW, rowH, vp, compareEntry)
168+
if tooltip:CheckForUpdate(colData, rowLabel) then
169+
-- Get calcsEnv actors (these have breakdown data populated)
170+
local primaryCalcsActor = primaryBuild.calcsTab and primaryBuild.calcsTab.calcsEnv
171+
and primaryBuild.calcsTab.calcsEnv.player
172+
local compareCalcsActor = compareEntry.calcsTab and compareEntry.calcsTab.calcsEnv
173+
and compareEntry.calcsTab.calcsEnv.player
174+
175+
local primaryActor = primaryCalcsActor or (primaryBuild.calcsTab.mainEnv and primaryBuild.calcsTab.mainEnv.player)
176+
local compareActor = compareCalcsActor or (compareEntry.calcsTab.mainEnv and compareEntry.calcsTab.mainEnv.player)
177+
178+
if not primaryActor and not compareActor then
179+
return
180+
end
181+
182+
local compareLabel = compareEntry.label or "Compare Build"
183+
184+
-- Tooltip header
185+
tooltip:AddLine(16, "^7" .. (rowLabel or ""))
186+
tooltip:AddSeparator(10)
187+
188+
-- Process each sectionData entry in colData
189+
for _, sectionData in ipairs(colData) do
190+
-- Show breakdown formulas per build (these are always build-specific)
191+
if sectionData.breakdown then
192+
local primaryLines = M.GetBreakdownLines(sectionData, primaryBuild)
193+
local compareLines = M.GetBreakdownLines(sectionData, compareEntry)
194+
195+
if primaryLines then
196+
tooltip:AddLine(14, colorCodes.POSITIVE .. primaryLabel .. ":")
197+
for _, line in ipairs(primaryLines) do
198+
tooltip:AddLine(14, "^7 " .. line)
199+
end
200+
end
201+
if compareLines then
202+
tooltip:AddLine(14, colorCodes.WARNING .. compareLabel .. ":")
203+
for _, line in ipairs(compareLines) do
204+
tooltip:AddLine(14, "^7 " .. line)
205+
end
206+
end
207+
if primaryLines or compareLines then
208+
tooltip:AddSeparator(10)
209+
end
210+
end
211+
212+
-- Show modifier sources split into common / primary-only / compare-only
213+
if sectionData.modName then
214+
local pRows = primaryActor and M.TabulateMods(sectionData, primaryActor) or {}
215+
local cRows = compareActor and M.TabulateMods(sectionData, compareActor) or {}
216+
217+
if #pRows > 0 or #cRows > 0 then
218+
-- Build lookup of compare rows by key
219+
local cByKey = {}
220+
for _, row in ipairs(cRows) do
221+
local key = M.ModRowKey(row)
222+
cByKey[key] = row
223+
end
224+
225+
-- Classify into common, primary-only, compare-only
226+
local common = {} -- { { pRow, cRow }, ... }
227+
local pOnly = {}
228+
local cMatched = {} -- keys that were matched
229+
230+
for _, pRow in ipairs(pRows) do
231+
local key = M.ModRowKey(pRow)
232+
if cByKey[key] then
233+
t_insert(common, { pRow, cByKey[key] })
234+
cMatched[key] = true
235+
else
236+
t_insert(pOnly, pRow)
237+
end
238+
end
239+
240+
local cOnly = {}
241+
for _, cRow in ipairs(cRows) do
242+
local key = M.ModRowKey(cRow)
243+
if not cMatched[key] then
244+
t_insert(cOnly, cRow)
245+
end
246+
end
247+
248+
-- Sub-section header (e.g., "Sources", "Increased Life Regeneration Rate")
249+
local sectionLabel = sectionData.label or "Player modifiers"
250+
tooltip:AddLine(14, "^7" .. sectionLabel .. ":")
251+
252+
-- Common modifiers
253+
if #common > 0 then
254+
-- Sort by primary value descending
255+
table.sort(common, function(a, b)
256+
if type(a[1].value) == "number" and type(b[1].value) == "number" then
257+
return a[1].value > b[1].value
258+
end
259+
return false
260+
end)
261+
tooltip:AddLine(12, "^x808080 Common:")
262+
for _, pair in ipairs(common) do
263+
local pVal, sourceType, sourceName, modName = M.FormatModRow(pair[1], sectionData, primaryBuild)
264+
local cVal = M.FormatModRow(pair[2], sectionData, compareEntry)
265+
local valStr
266+
if pVal == cVal then
267+
valStr = s_format("^7%-10s", pVal)
268+
else
269+
valStr = colorCodes.POSITIVE .. s_format("%-5s", pVal) .. "^7/" .. colorCodes.WARNING .. s_format("%-5s", cVal)
270+
end
271+
local line = s_format(" %s ^7%-6s ^7%s%s", valStr, sourceType, sourceName, modName)
272+
tooltip:AddLine(12, line)
273+
end
274+
end
275+
276+
-- Primary-only modifiers
277+
if #pOnly > 0 then
278+
table.sort(pOnly, function(a, b)
279+
if type(a.value) == "number" and type(b.value) == "number" then
280+
return a.value > b.value
281+
end
282+
return false
283+
end)
284+
tooltip:AddLine(12, colorCodes.POSITIVE .. " " .. primaryLabel .. " only:")
285+
for _, row in ipairs(pOnly) do
286+
local displayValue, sourceType, sourceName, modName = M.FormatModRow(row, sectionData, primaryBuild)
287+
local line = s_format(" ^7%-10s ^7%-6s ^7%s%s", displayValue, sourceType, sourceName, modName)
288+
tooltip:AddLine(12, line)
289+
end
290+
end
291+
292+
-- Compare-only modifiers
293+
if #cOnly > 0 then
294+
table.sort(cOnly, function(a, b)
295+
if type(a.value) == "number" and type(b.value) == "number" then
296+
return a.value > b.value
297+
end
298+
return false
299+
end)
300+
tooltip:AddLine(12, colorCodes.WARNING .. " " .. compareLabel .. " only:")
301+
for _, row in ipairs(cOnly) do
302+
local displayValue, sourceType, sourceName, modName = M.FormatModRow(row, sectionData, compareEntry)
303+
local line = s_format(" ^7%-10s ^7%-6s ^7%s%s", displayValue, sourceType, sourceName, modName)
304+
tooltip:AddLine(12, line)
305+
end
306+
end
307+
308+
-- Separator between sub-sections
309+
tooltip:AddSeparator(6)
310+
end
311+
end
312+
end
313+
end
314+
315+
SetDrawLayer(nil, 100)
316+
tooltip:Draw(rowX, rowY, rowW, rowH, vp)
317+
SetDrawLayer(nil, 0)
318+
end
319+
320+
return M

0 commit comments

Comments
 (0)