Skip to content
Merged
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
a95ae8c
Add proper support for importing lesser and greater runes.
QuickStick123 Apr 15, 2025
19558d2
Fix forgetting to pass the starting count and sum
QuickStick123 Apr 15, 2025
21ccf49
Optimise pruning on bottom to only decrease if value is still reachable.
QuickStick123 Apr 15, 2025
25300fd
Spelling
QuickStick123 Apr 15, 2025
69d5816
Fix bug with decimals needs a bit more checking if floating point pre…
QuickStick123 Apr 15, 2025
e2a0e0b
Fix bug with no forming a proper solution for match of all duplicate …
QuickStick123 Apr 15, 2025
fbe6fe0
Fix crashes with decimals
QuickStick123 Apr 15, 2025
24c20f5
Fix floating point issues and crash if failed to find a match.
QuickStick123 Apr 15, 2025
9a12bc9
Refactor unique check to occur on both increment and decrement and fi…
QuickStick123 Apr 15, 2025
d2a2478
Fix vistor check and result greater than 0 check.
QuickStick123 Apr 15, 2025
f1d8f6b
Fix bug regarding incorrectly handling count
QuickStick123 Apr 15, 2025
03e7449
Fix the too small value pruning it should not work properly based off…
QuickStick123 Apr 15, 2025
d103778
Add comment about something to refactor in the future
QuickStick123 Apr 15, 2025
971ec94
Spelling
QuickStick123 Apr 15, 2025
2adc0f7
Minor cleanup no need to do FP checks on a count.
QuickStick123 Apr 16, 2025
920a9c6
Remove count >= 0 check which currently does nothing.
QuickStick123 Apr 16, 2025
ec1f614
Improve the description a bit a fix a typo in a comment. This also un…
QuickStick123 Apr 16, 2025
f315999
Spelling
QuickStick123 Apr 16, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
155 changes: 141 additions & 14 deletions src/Classes/Item.lua
Original file line number Diff line number Diff line change
Expand Up @@ -788,24 +788,151 @@ function ItemClass:ParseRaw(raw, rarity, highQuality)
if self.base.weapon or self.base.armour then
local shouldFixRunesOnItem = #self.runes == 0

-- Form a key value table with the following format
-- { [strippedModLine] = { { runeName1, runeValue1 }, etc, }, etc}
-- This will be used to more easily grab the relevant runes that combinations will need to be of.
-- This could be refactored to only needs to be called once.
local statGroupedRunes = { }
local type = self.base.weapon and "weapon" or "armour" -- minor optimisation
for runeName, runeMods in pairs(data.itemMods.Runes) do
-- gets the first value in the mod and its stripped line.
local runeValue
local runeStrippedModeLine = runeMods[type][1]:gsub("(%d%.?%d*)", function(val)
runeValue = val
return "#"
end)
if statGroupedRunes[runeStrippedModeLine] == nil then
statGroupedRunes[runeStrippedModeLine] = { }
end
t_insert(statGroupedRunes[runeStrippedModeLine], { runeName, runeValue });
end

-- Sort table to ensure first entries are always largest.
for _, runes in pairs(statGroupedRunes) do
table.sort(runes, function(a, b) return a[2] > b[2] end)
end

local remainingRunes = self.itemSocketCount
for i, modLine in ipairs(self.runeModLines) do
local value
local strippedModeLine = modLine.line:gsub("(%d%.?%d*)", function(val)
local strippedModLine = modLine.line:gsub("(%d%.?%d*)", function(val)
value = val
return "#"
end)
for name, runeMods in pairs(data.itemMods.Runes) do
local runeValue
local runeStrippedModeLine = (self.base.weapon and runeMods.weapon or runeMods.armour)[1]:gsub("(%d%.?%d*)", function(val)
runeValue = val
return "#"
end)
if strippedModeLine == runeStrippedModeLine then
modLine.soulcore = name:match("Soul Core") ~= nil
modLine.runeCount = round(value/runeValue)
local groupedRunes = statGroupedRunes[strippedModLine]
if groupedRunes then -- found the rune category with the relevant stat.
-- First a greedy base is found using the runes in the groupedRunes. If this matches the target value then that set of runes is applied.
-- If the greedy base isn't a solution we search all the possible combinations that could lead to a valid combination.
-- This done by recursing through all combinations that could lead to a valid value and pruning values that exceed the number
-- of runes and solutions that it would be impossible to reach the target value from. Visited combinations are recorded and are used such
-- that candidates are only searched once. This makes for a fairly efficient algorithm that doesn't search unneeded values very much.
local function getNumberOfRunesOfEachType(values, target)
local function adjustCombination(values, target, result, best, visited, sum, count)
-- This is used to avoid unnecessary checks on decrement.
local function checkAndAdjustCombination(values, target, result, best, visited, sum, count)
-- If it's a valid solution, update best
if math.abs(sum-target) < 1e-9 then
if not best.count or count < best.count then
best.count = count
-- Copy solution to avoid side effects from continued searching.
local solution = {}
for k, v in pairs(result) do
solution[k] = v
end
best.solution = solution
end
return
end

-- Prune if we already used more runes than the best found
if best.count and count >= best.count then return end

return adjustCombination(values, target, result, best, visited, sum, count)
end

for _, v in ipairs(values) do
local function checkUnique(result)
-- Generate a unique key from the result table this prevents duplicates combinations being searched
local key = ""
for value, count in pairs(result) do
if count > 0 then
key = key .. value .. "x" .. count .. " "
end
end
if visited[key] then
return false
else
visited[key] = true
return true
end
end

-- Incrementing is done first as to reach the target you will need to add a count as such it should be more efficient.
-- Try increasing (if it doesn't overshoot or exceed maximum number of remaining runes)
if sum + tonumber(v) <= target + 1e-9 and count < remainingRunes then
result[v] = (result[v] or 0) + 1
if checkUnique(result) then
checkAndAdjustCombination(values, target, result, best, visited, sum + v, count + 1)
end
result[v] = result[v] - 1
end

-- Try decreasing (if possible and only if target is still reachable).
if (result[v] or 0) > 0 and (not best.count or target - 1e-9 < sum - tonumber(v) + values[1] * (best.count - count + 1)) then
result[v] = result[v] - 1
if checkUnique(result) then
adjustCombination(values, target, result, best, visited, sum - v, count - 1)
end
result[v] = result[v] + 1
end
end
end

-- Step 1: Perform greedy search and tests if a single rune is used as these are the most common use case.
local greedySolution = {}
local leftover = target

for _, v in ipairs(values) do
local count = math.floor(leftover / v)
greedySolution[v] = count
leftover = leftover - count * v
end

local greedyCount = 0
for v, c in pairs(greedySolution) do
greedyCount = greedyCount + c
end
if math.abs(leftover) <= 1e-9 then -- Greedy search found a solution
return greedySolution, greedyCount
end

-- Step 2. Perform search starting from the greedy base
local best = {count = nil, solution = nil}
local visited = {}

adjustCombination(values, target, greedySolution, best, visited, target - leftover, greedyCount)

return best.solution, best.count
end

local values = { }
for i, runes in ipairs(groupedRunes) do
t_insert(values, runes[2])
end
local result, numRunes = getNumberOfRunesOfEachType(values, tonumber(value))

if result then -- we have found a valid combo for that rune category
remainingRunes = remainingRunes - numRunes
-- this code should probably be refactored to based off stored self.runes rather than the recomputed amounts off the runeModLines this
-- is too avoid having to run the relatively expensive recomputation every time the item is parsed even if we know the runes on the item already.
modLine.soulcore = groupedRunes[1][1]:match("Soul Core") ~= nil
modLine.runeCount = numRunes

if shouldFixRunesOnItem then
for i = 1, modLine.runeCount do
t_insert(self.runes, name)
for i, rune in ipairs(groupedRunes) do
for _ = 1, tonumber(result[rune[2]]) do
t_insert(self.runes, groupedRunes[i][1])
end
end
end
end
Expand Down Expand Up @@ -1122,8 +1249,8 @@ function ItemClass:UpdateRunes()
if statOrder[order] then
-- Combine stats
local start = 1
statOrder[order].line = statOrder[order].line:gsub("%d+", function(num)
local s, e, other = line:find("(%d+)", start)
statOrder[order].line = statOrder[order].line:gsub("(%d%.?%d*)", function(num)
local s, e, other = line:find("(%d%.?%d*)", start)
start = e + 1
return tonumber(num) + tonumber(other)
end)
Expand Down