This repository was archived by the owner on Oct 21, 2025. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 21
Expand file tree
/
Copy pathItemTools.lua
More file actions
368 lines (346 loc) · 13 KB
/
ItemTools.lua
File metadata and controls
368 lines (346 loc) · 13 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
-- Path of Building
--
-- Module: Item Tools
-- Various functions for dealing with items.
--
local t_insert = table.insert
local t_remove = table.remove
local m_min = math.min
local m_max = math.max
local m_floor = math.floor
itemLib = { }
-- Apply a value scalar to the first n of any numbers present
function itemLib.applyValueScalar(line, valueScalar, baseValueScalar, numbers, precision)
if not (valueScalar and type(valueScalar) == "number") then
valueScalar = 1
end
if valueScalar ~= 1 or (baseValueScalar and baseValueScalar ~= 1) then
if precision then
return line:gsub("(%d+%.?%d*)", function(num)
local power = 10 ^ precision
local numVal = tonumber(num)
if baseValueScalar then
numVal = round(numVal * baseValueScalar * power) / power
end
numVal = m_floor(numVal * valueScalar * power) / power
return tostring(numVal)
end, numbers)
else
return line:gsub("(%d+)([^%.])", function(num, suffix)
local numVal = tonumber(num)
if baseValueScalar then
numVal = round(num * baseValueScalar)
end
numVal = m_floor(numVal * valueScalar + 0.001)
return tostring(numVal)..suffix
end, numbers)
end
end
return line
end
-- precision is express a multiplier/divide and displayPrecision is expresed as decimal precision on rounding.
-- ifRequired determines whether trailing zeros are displayed or not.
function itemLib.formatValue(value, baseValueScalar, valueScalar, precision, displayPrecision, ifRequired)
value = roundSymmetric(value * precision) -- resolve range to internal value
if baseValueScalar and baseValueScalar ~= 1 then value = alwaysPositveRound(value * baseValueScalar) end -- apply corrupted mult
if valueScalar and valueScalar ~= 1 then value = floorSymmetric(value * valueScalar) end -- apply modifier magnitude
value = value / precision -- convert back to display space
if displayPrecision then value = roundSymmetric(value, displayPrecision) end -- presentation
if displayPrecision and not ifRequired then -- whitespace is needed
return string.format("%"..displayPrecision.."f", value)
elseif displayPrecision then
return tostring(roundSymmetric(value, displayPrecision))
else
return tostring(roundSymmetric(value, precision and m_min(2, m_floor(math.log(precision, 10))) or 2)) -- max decimals ingame is 2
end
end
local antonyms = {
["increased"] = "reduced",
["reduced"] = "increased",
["more"] = "less",
["less"] = "more",
}
local function antonymFunc(num, word)
local antonym = antonyms[word]
return antonym and (num.." "..antonym) or ("-"..num.." "..word)
end
-- Apply range value (0 to 1) to a modifier that has a range: "(x-x)" or "(x-x) to (x-x)"
function itemLib.applyRange(line, range, valueScalar, baseValueScalar)
-- stripLines down to # inplace of any number and store numbers inside values also remove all + signs are kept if value is positive
local values = { }
local strippedLine = line:gsub("([%+-]?)%((%-?%d+%.?%d*)%-(%-?%d+%.?%d*)%)", function(sign, min, max)
local value = min + range * (tonumber(max) - min)
if sign == "-" then value = value * -1 end
return (sign == "+" and value > 0 ) and sign..tostring(value) or tostring(value)
end)
:gsub("%-(%d+%.?%d*%%) (%a+)", antonymFunc)
:gsub("(%-?%d+%.?%d*)", function(value)
t_insert(values, value)
return "#"
end)
--- Takes a completely strippedLine where all values and ranges are replaced with a # + signs are kept for consistency upon resubsitution.
--- This will then subsitute back in the values until a line in scalabilityData is found this start with subsituting everything and until none.
--- This means if there is a more generic mod that might be scalable on both parameters but their is a narrower one that isn't it won't be scaled.
---@param line the modLine stripped of all values and ranges replaced by #
---@param values all values present in the modLine
---@return scalableLine line with only scalableValues replaced with #
---@return scalableValues values which can be scaled and added into scalableLine in place of a #
local function findScalableLine(line, values)
local function replaceNthInstance(input, pattern, replacement, n)
local count = 0
return input:gsub(pattern, function(match)
count = count + 1
if count == n then
return replacement
else
return match
end
end)
end
-- check combinations recursively largest to smallest
local function checkSubsitutionCombinations(i, numSubsitutions, indices)
if #indices == numSubsitutions then
local modifiedLine = line
local subsituted = 0
for _, i in ipairs(indices) do
modifiedLine = replaceNthInstance(modifiedLine, "#", values[i], i - subsituted)
subsituted = subsituted + 1
end
-- Check if the modified line matches any scalability data
local key = modifiedLine:gsub("+#", "#")
if data.modScalability[key] then
-- Return modified line and remaining values (those not substituted)
local remainingValues = {}
local used = { }
for _, index in ipairs(indices) do
used[index] = true
end
for i, value in ipairs(values) do
if not used[i] then
table.insert(remainingValues, value)
end
end
return modifiedLine, remainingValues
end
return
end
for j = i, #values do
table.insert(indices, j)
local modifiedLine, remainingValues = checkSubsitutionCombinations(j + 1, numSubsitutions, indices)
if modifiedLine then
return modifiedLine, remainingValues
end
table.remove(indices)
end
end
for i = #values, 1, -1 do
local modifiedLine, remainingValues = checkSubsitutionCombinations(1, i, {})
if modifiedLine then
return modifiedLine, remainingValues
end
end
-- Check scalability with 0 substitutions
local key = line:gsub("+#", "#")
if data.modScalability[key] then
return line, values
end
return
end
local scalableLine, scalableValues = findScalableLine(strippedLine, values)
if scalableLine then -- found scalability data
for i, scalability in ipairs(data.modScalability[scalableLine:gsub("+#", "#")]) do
local precision
local displayPrecision
local ifRequired
if scalability.formats then
for _, format in ipairs(scalability.formats) do
if format == "divide_by_two_0dp" then
precision = 2
displayPrecision = 0
ifRequired = true
elseif format == "divide_by_three" then
precision = 3
elseif format == "divide_by_four" then
precision = 4
elseif format == "divide_by_five" then
precision = 5
elseif format == "divide_by_six" then
precision = 6
elseif format == "divide_by_ten_0dp" then
precision = 10
displayPrecision = 0
elseif format == "divide_by_ten_1dp" then
precision = 10
displayPrecision = 1
elseif format == "divide_by_ten_1dp_if_required" then
precision = 10
displayPrecision = 1
ifRequired = true
elseif format == "divide_by_twelve" then
precision = 12
elseif format == "divide_by_fifteen_0dp" then
precision = 15
displayPrecision = 0
elseif format == "divide_by_twenty" then
precision = 20
elseif format == "divide_by_twenty_then_double_0dp" then -- might be incorrect?
precision = 10
displayPrecision = 0
elseif format == "divide_by_one_hundred" or format == "divide_by_one_hundred_and_negate" then
precision = 100
elseif format == "divide_by_one_hundred_0dp" then
precision = 100
displayPrecision = 0
elseif format == "divide_by_one_hundred_1dp" then
precision = 100
displayPrecision = 1
elseif format == "divide_by_one_hundred_2dp" then
precision = 100
displayPrecision = 2
elseif format == "divide_by_one_hundred_2dp_if_required" then
precision = 100
displayPrecision = 2
ifRequired = true
elseif format == "divide_by_one_thousand" then
precision = 1000
elseif format == "per_minute_to_per_second" then
precision = 60
elseif format == "per_minute_to_per_second_0dp" then
precision = 60
displayPrecision = 0
elseif format == "per_minute_to_per_second_1dp" then
precision = 60
displayPrecision = 1
elseif format == "per_minute_to_per_second_2dp" then
precision = 60
displayPrecision = 2
elseif format == "per_minute_to_per_second_2dp_if_required" then
precision = 60
displayPrecision = 2
ifRequired = true
elseif format == "milliseconds_to_seconds" then
precision = 1000
elseif format == "milliseconds_to_seconds_halved" then
precision = 1000
elseif format == "milliseconds_to_seconds_0dp" then
precision = 1000
displayPrecision = 0
elseif format == "milliseconds_to_seconds_1dp" then
precision = 1000
displayPrecision = 1
elseif format == "milliseconds_to_seconds_2dp" then
precision = 1000
displayPrecision = 2
elseif format == "milliseconds_to_seconds_2dp_if_required" then
precision = 1000
displayPrecision = 2
ifRequired = true
elseif format == "deciseconds_to_seconds" then
precision = 10
end
end
end
if scalability.isScalable and ((baseValueScalar and baseValueScalar ~= 1) or (valueScalar and valueScalar ~= 1)) then
scalableValues[i] = itemLib.formatValue(scalableValues[i], baseValueScalar, valueScalar, precision or 1, displayPrecision, ifRequired)
else
scalableValues[i] = itemLib.formatValue(scalableValues[i], 1, 1, precision or 1, displayPrecision, ifRequired)
end
end
for _, replacement in ipairs(scalableValues) do
scalableLine = scalableLine:gsub("#", replacement, 1)
end
return scalableLine
else -- fallback to old method for determining scalability
-- ConPrintf("Couldn't find scalability data falling back to old implementation: %s", strippedLine)
local precisionSame = true
-- Create a line with ranges removed to check if the mod is a high precision mod.
local testLine = not line:find("-", 1, true) and line or
line:gsub("(%+?)%((%-?%d+%.?%d*)%-(%-?%d+%.?%d*)%)",
function(plus, min, max)
min = tonumber(min)
local maxPrecision = min + range * (tonumber(max) - min)
local minPrecision = m_floor(maxPrecision + 0.5)
if minPrecision ~= maxPrecision then
precisionSame = false
end
return (minPrecision < 0 and "" or plus) .. tostring(minPrecision)
end)
:gsub("%-(%d+%%) (%a+)", antonymFunc)
if precisionSame and (not valueScalar or valueScalar == 1) and (not baseValueScalar or baseValueScalar == 1)then
return testLine
end
local precision = nil
local modList, extra = modLib.parseMod(testLine)
if modList and not extra then
for _, mod in pairs(modList) do
local subMod = mod
if type(mod.value) == "table" and mod.value.mod then
subMod = mod.value.mod
end
if type(subMod.value) == "number" and data.highPrecisionMods[subMod.name] and data.highPrecisionMods[subMod.name][subMod.type] then
precision = data.highPrecisionMods[subMod.name][subMod.type]
end
end
end
if not precision and line:match("(%d+%.%d*)") then
precision = data.defaultHighPrecision
end
local numbers = 0
line = line:gsub("(%+?)%((%-?%d+%.?%d*)%-(%-?%d+%.?%d*)%)",
function(plus, min, max)
numbers = numbers + 1
local power = 10 ^ (precision or 0)
local numVal = m_floor((tonumber(min) + range * (tonumber(max) - tonumber(min))) * power + 0.5) / power
return (numVal < 0 and "" or plus) .. tostring(numVal)
end)
:gsub("%-(%d+%%) (%a+)", antonymFunc)
if numbers == 0 and line:match("(%d+%.?%d*)%%? ") then --If a mod contains x or x% and is not already a ranged value, then assume only the first number will be scalable.
numbers = 1
end
return itemLib.applyValueScalar(line, valueScalar, baseValueScalar, numbers, precision)
end
end
function itemLib.formatModLine(modLine, dbMode)
local line = (not dbMode and modLine.range and itemLib.applyRange(modLine.line, modLine.range, modLine.valueScalar, modLine.corruptedRange)) or modLine.line
if not line:match("^Gain %d+%% to %d+%%") and (line:match("^%+?0%%? ") or (line:match(" %+?0%%? ") and not line:match("0 to [1-9]")) or line:match(" 0%-0 ") or line:match(" 0 to 0 ")) then -- Hack to hide 0-value modifiers
return
end
local colorCode
if modLine.extra then
colorCode = colorCodes.UNSUPPORTED
if launch.devModeAlt then
line = line .. " ^1'" .. modLine.extra .. "'"
end
else
colorCode = (modLine.enchant and colorCodes.ENCHANTED) or (modLine.custom and colorCodes.CUSTOM) or colorCodes.MAGIC
end
return colorCode..line
end
itemLib.wiki = {
key = "F1",
openGem = function(gemData)
local name
if gemData.name then -- skill
name = gemData.name
if gemData.tags.support then
name = name .. " Support"
end
else -- grantedEffect from item/passive
name = gemData;
end
itemLib.wiki.open(name)
end,
openItem = function(item)
local name = item.rarity == "UNIQUE" and item.title or item.baseName
itemLib.wiki.open(name)
end,
open = function(name)
local route = string.gsub(name, " ", "_")
OpenURL("https://www.poe2wiki.net/wiki/" .. route)
itemLib.wiki.triggered = true
end,
matchesKey = function(key)
return key == itemLib.wiki.key
end,
triggered = false
}