From cf89a6131f627de64e4945c6f0898a2a393d6e44 Mon Sep 17 00:00:00 2001 From: vaisest Date: Fri, 26 Jun 2026 14:31:39 +0000 Subject: [PATCH 1/4] Apply changes from https://github.com/PathOfBuildingCommunity/PathOfBuilding-PoE2/pull/2261 --- .../TestTradeQueryCurrency_spec.lua.rej | 26 +++++++++++++ .../TestTradeQueryGenerator_spec.lua.rej | 10 +++++ spec/System/TestTradeQuery_spec.lua.rej | 38 +++++++++++++++++++ src/Classes/CalcsTab.lua | 19 +++------- src/Classes/CompareTab.lua | 5 +-- src/Classes/ItemDBControl.lua | 2 +- src/Classes/ItemDBControl.lua.rej | 11 ++++++ src/Classes/ItemsTab.lua | 10 ++++- src/Classes/NotableDBControl.lua | 14 ++----- src/Classes/TradeQuery.lua | 11 ++++-- src/Classes/TradeQuery.lua.rej | 13 +++++++ src/Classes/TradeQueryGenerator.lua.rej | 12 ++++++ .../TradeStatWeightMultiplierListControl.lua | 2 +- src/Classes/TreeTab.lua.rej | 10 +++++ src/Modules/Data.lua | 10 +++++ src/Modules/Data.lua.rej | 10 +++++ 16 files changed, 168 insertions(+), 35 deletions(-) create mode 100644 spec/System/TestTradeQueryCurrency_spec.lua.rej create mode 100644 spec/System/TestTradeQueryGenerator_spec.lua.rej create mode 100644 spec/System/TestTradeQuery_spec.lua.rej create mode 100644 src/Classes/ItemDBControl.lua.rej create mode 100644 src/Classes/TradeQuery.lua.rej create mode 100644 src/Classes/TradeQueryGenerator.lua.rej create mode 100644 src/Classes/TreeTab.lua.rej create mode 100644 src/Modules/Data.lua.rej diff --git a/spec/System/TestTradeQueryCurrency_spec.lua.rej b/spec/System/TestTradeQueryCurrency_spec.lua.rej new file mode 100644 index 00000000000..870856ac481 --- /dev/null +++ b/spec/System/TestTradeQueryCurrency_spec.lua.rej @@ -0,0 +1,26 @@ +diff a/spec/System/TestTradeQueryCurrency_spec.lua b/spec/System/TestTradeQueryCurrency_spec.lua (rejected hunks) +@@ -23,24 +23,6 @@ describe("TradeQuery Currency Conversion", function() + end) + end) + +- describe("ReduceOutput", function() +- it("uses selected minion stats for weighted result comparison", function() +- mock_tradeQuery.statSortSelectionList = { { stat = "AverageDamage" } } +- +- local result = mock_tradeQuery:ReduceOutput({ +- AverageDamage = 10, +- Life = 100, +- Minion = { +- AverageDamage = 250, +- Life = 200, +- }, +- }) +- +- assert.are.equals(250, result.AverageDamage) +- assert.is_nil(result.Life) +- end) +- end) +- + describe("PriceBuilderProcessPoENinjaResponse", function() + -- Pass: Processes without error, restoring map while adding a notice + -- Fail: Corrupts map or crashes, indicating fragile API response handling, breaking future conversions diff --git a/spec/System/TestTradeQueryGenerator_spec.lua.rej b/spec/System/TestTradeQueryGenerator_spec.lua.rej new file mode 100644 index 00000000000..4360660c03b --- /dev/null +++ b/spec/System/TestTradeQueryGenerator_spec.lua.rej @@ -0,0 +1,10 @@ +diff a/spec/System/TestTradeQueryGenerator_spec.lua b/spec/System/TestTradeQueryGenerator_spec.lua (rejected hunks) +@@ -54,7 +54,7 @@ describe("TradeQueryGenerator", function() + + local result = mock_queryGen.WeightedRatioOutputs(baseOutput, newOutput, statWeights) + +- local close_enough = (result - -0.1) < 0.0001 ++ local close_enough = math.abs(result - -0.1) < 0.0001 + assert.True(close_enough) + end) + diff --git a/spec/System/TestTradeQuery_spec.lua.rej b/spec/System/TestTradeQuery_spec.lua.rej new file mode 100644 index 00000000000..7d2c443fde9 --- /dev/null +++ b/spec/System/TestTradeQuery_spec.lua.rej @@ -0,0 +1,38 @@ +diff a/spec/System/TestTradeQuery_spec.lua b/spec/System/TestTradeQuery_spec.lua (rejected hunks) +@@ -1,8 +1,10 @@ + describe("TradeQuery", function () + local mock_tradeQuery ++ local mock_queryGen + + before_each(function() + mock_tradeQuery = new("TradeQuery", { itemsTab = {} }) ++ mock_queryGen = new("TradeQueryGenerator", { itemsTab = {} }) + end) + + describe("ReduceOutput", function() +@@ -21,5 +23,24 @@ describe("TradeQuery", function () + assert.are.equals(260, result.AverageDamage) + assert.is_nil(result.Life) + end) ++ ++ it("keeps fallback DPS stats when FullDPS is selected but not present", function() ++ mock_tradeQuery.statSortSelectionList = { { stat = "FullDPS", weightMult = 1 } } ++ ++ local baseOutput = { ++ CombinedDPS = 100, ++ TotalDPS = 100, ++ TotalDotDPS = 0, ++ } ++ local reducedOutput = mock_tradeQuery:ReduceOutput({ ++ CombinedDPS = 120, ++ TotalDPS = 120, ++ TotalDotDPS = 0, ++ }) ++ ++ local result = mock_queryGen.WeightedRatioOutputs(baseOutput, reducedOutput, mock_tradeQuery.statSortSelectionList) ++ ++ assert.are.equals(1.2, result) ++ end) + end) + end) +\ No newline at end of file diff --git a/src/Classes/CalcsTab.lua b/src/Classes/CalcsTab.lua index 690103ba096..a7139797377 100644 --- a/src/Classes/CalcsTab.lua +++ b/src/Classes/CalcsTab.lua @@ -712,16 +712,8 @@ function CalcsTabClass:PowerBuilder() end function CalcsTabClass:CalculatePowerStat(selection, original, modified) - if modified.Minion and selection.stat ~= "FullDPS" then - original = original.Minion - modified = modified.Minion - end - local originalValue = original[selection.stat] or 0 - local modifiedValue = modified[selection.stat] or 0 - if selection.transform then - originalValue = selection.transform(originalValue) - modifiedValue = selection.transform(modifiedValue) - end + local originalValue = data.powerStatList.GetFromOutput(original, selection) + local modifiedValue = data.powerStatList.GetFromOutput(modified, selection) return originalValue - modifiedValue end @@ -732,10 +724,9 @@ function CalcsTabClass:CalculateCombinedOffDefStat(original, modified) (original.Evasion - modified.Evasion) / m_max(10000, modified.Evasion) + (original.LifeRegenRecovery - modified.LifeRegenRecovery) / 500 + (original.EnergyShieldRegenRecovery - modified.EnergyShieldRegenRecovery) / 1000 - if modified.Minion then - return (original.Minion.CombinedDPS - modified.Minion.CombinedDPS) / modified.Minion.CombinedDPS, defence - end - return (original.CombinedDPS - modified.CombinedDPS) / modified.CombinedDPS, defence + local modifiedDps = modified.CombinedDPS + (modified.Minion and modified.Minion.CombinedDPS or 0) + local dpsIncr = original.CombinedDPS + (original.Minion and original.Minion.CombinedDPS or 0) - modifiedDps + return dpsIncr / modifiedDps, defence end function CalcsTabClass:GetNodeCalculator() diff --git a/src/Classes/CompareTab.lua b/src/Classes/CompareTab.lua index 24506313c6c..946c580f4ba 100644 --- a/src/Classes/CompareTab.lua +++ b/src/Classes/CompareTab.lua @@ -2544,10 +2544,7 @@ function CompareTabClass:ComparePowerBuilder(compareEntry, powerStat, categories end -- Get baseline stat value for percentage calculation - local baseStatValue = calcBase[powerStat.stat] or 0 - if powerStat.transform then - baseStatValue = powerStat.transform(baseStatValue) - end + local baseStatValue = data.powerStatList.GetFromOutput(calcBase, powerStat) -- Helper to format an impact value and compute percentage local function formatImpact(impact) diff --git a/src/Classes/ItemDBControl.lua b/src/Classes/ItemDBControl.lua index 3136d1a8476..0fc8303b2f3 100644 --- a/src/Classes/ItemDBControl.lua +++ b/src/Classes/ItemDBControl.lua @@ -190,7 +190,7 @@ end function ItemDBClass:BuildSortOrder() wipeTable(self.sortDropList) - for id,stat in pairs(data.powerStatList) do + for id, stat in ipairs(data.powerStatList) do if not stat.ignoreForItems then t_insert(self.sortDropList, { label="Sort by "..stat.label, diff --git a/src/Classes/ItemDBControl.lua.rej b/src/Classes/ItemDBControl.lua.rej new file mode 100644 index 00000000000..bb65957caa8 --- /dev/null +++ b/src/Classes/ItemDBControl.lua.rej @@ -0,0 +1,11 @@ +diff a/src/Classes/ItemDBControl.lua b/src/Classes/ItemDBControl.lua (rejected hunks) +@@ -244,9 +244,6 @@ function ItemDBClass:ListBuilder() + if self.itemsTab:IsItemValidForSlot(item, slotName) and not slot.inactive and (not slot.weaponSet or slot.weaponSet == (self.itemsTab.activeItemSet.useSecondWeaponSet and 2 or 1)) then + local output = calcFunc(item.base.flask and { toggleFlask = item } or item.base.charm and { toggleCharm = item } or { repSlotName = slotName, repItem = item }, useFullDPS) + local measuredPower = data.powerStatList.GetFromOutput(output, self.sortDetail) +- if self.sortDetail.transform then +- measuredPower = self.sortDetail.transform(measuredPower) +- end + item.measuredPower = item.measuredPower and m_max(item.measuredPower, measuredPower) or measuredPower + end + end diff --git a/src/Classes/ItemsTab.lua b/src/Classes/ItemsTab.lua index e6083ea7839..6320011f410 100644 --- a/src/Classes/ItemsTab.lua +++ b/src/Classes/ItemsTab.lua @@ -1122,7 +1122,15 @@ function ItemsTabClass:Load(xml, dbFileName) stat = child.attrib.stat, weightMult = tonumber(child.attrib.weightMult) } - t_insert(self.tradeQuery.statSortSelectionList, statSort) + for _, statEntry in ipairs(data.powerStatList) do + if statSort.stat == statEntry.stat then + -- update information which can be out of data or missing in the xml + statSort.label = statEntry.label + statSort.transform = statEntry.transform + t_insert(self.tradeQuery.statSortSelectionList, statSort) + break + end + end end end end diff --git a/src/Classes/NotableDBControl.lua b/src/Classes/NotableDBControl.lua index d08ec4080c9..e3fb85b5bb4 100644 --- a/src/Classes/NotableDBControl.lua +++ b/src/Classes/NotableDBControl.lua @@ -87,7 +87,7 @@ end function NotableDBClass:BuildSortOrder() wipeTable(self.sortDropList) - for id,stat in pairs(data.powerStatList) do + for id, stat in ipairs(data.powerStatList) do if not stat.ignoreForItems then t_insert(self.sortDropList, { label="Sort by "..stat.label, @@ -111,16 +111,8 @@ function NotableDBClass:BuildSortOrder() end function NotableDBClass:CalculatePowerStat(selection, original, modified) - if modified.Minion then - original = original.Minion - modified = modified.Minion - end - local originalValue = original[selection.stat] or 0 - local modifiedValue = modified[selection.stat] or 0 - if selection.transform then - originalValue = selection.transform(originalValue) - modifiedValue = selection.transform(modifiedValue) - end + local originalValue = data.powerStatList.GetFromOutput(original, selection) + local modifiedValue = data.powerStatList.GetFromOutput(modified, selection) return originalValue - modifiedValue end diff --git a/src/Classes/TradeQuery.lua b/src/Classes/TradeQuery.lua index 9e1308bfb9e..697fa84f879 100644 --- a/src/Classes/TradeQuery.lua +++ b/src/Classes/TradeQuery.lua @@ -553,11 +553,16 @@ function TradeQueryClass:SetStatWeights(previousSelectionList) local controls = { } local statList = { } local sliderController = { index = 1 } - local popupHeight = 285 + local popupHeight = 500 - controls.ListControl = new("TradeStatWeightMultiplierListControl", {"TOPLEFT", nil, "TOPRIGHT"}, {-410, 45, 400, 200}, statList, sliderController) + local listYOffset = 45 + -- account for top gap, bottom button size and gap, and a gap before buttons + local listHeight = popupHeight - 45 - 30 - 10 - for id, stat in pairs(data.powerStatList) do + controls.ListControl = new("TradeStatWeightMultiplierListControl", { "TOPLEFT", nil, "TOPRIGHT" }, + { -410, 45, 400, listHeight }, statList, sliderController) + + for _, stat in ipairs(data.powerStatList) do if not stat.ignoreForItems and stat.label ~= "Name" then t_insert(statList, { label = "0 : "..stat.label, diff --git a/src/Classes/TradeQuery.lua.rej b/src/Classes/TradeQuery.lua.rej new file mode 100644 index 00000000000..20900eede40 --- /dev/null +++ b/src/Classes/TradeQuery.lua.rej @@ -0,0 +1,13 @@ +diff a/src/Classes/TradeQuery.lua b/src/Classes/TradeQuery.lua (rejected hunks) +@@ -780,6 +780,11 @@ function TradeQueryClass:ReduceOutput(output) + local smallOutput = {} + for _, statTable in ipairs(self.statSortSelectionList) do + smallOutput[statTable.stat] = data.powerStatList.GetFromOutput(output, statTable) ++ if statTable.stat == "FullDPS" and not output.FullDPS then ++ smallOutput.TotalDPS = data.powerStatList.GetFromOutput(output, { stat = "TotalDPS" }) ++ smallOutput.TotalDotDPS = data.powerStatList.GetFromOutput(output, { stat = "TotalDotDPS" }) ++ smallOutput.CombinedDPS = data.powerStatList.GetFromOutput(output, { stat = "CombinedDPS" }) ++ end + end + return smallOutput + end diff --git a/src/Classes/TradeQueryGenerator.lua.rej b/src/Classes/TradeQueryGenerator.lua.rej new file mode 100644 index 00000000000..9255b3fe21c --- /dev/null +++ b/src/Classes/TradeQueryGenerator.lua.rej @@ -0,0 +1,12 @@ +diff a/src/Classes/TradeQueryGenerator.lua b/src/Classes/TradeQueryGenerator.lua (rejected hunks) +@@ -162,8 +162,8 @@ function TradeQueryGeneratorClass.WeightedRatioOutputs(baseOutput, newOutput, st + local baseModSum = 0 + local newModSum = 0 + for _, mod in ipairs({ ... }) do +- baseModSum = baseModSum + data.powerStatList.GetFromOutput(baseOutput, mod) +- newModSum = newModSum + data.powerStatList.GetFromOutput(newOutput, mod) ++ baseModSum = baseModSum + data.powerStatList.GetFromOutput(baseOutput, mod, true) ++ newModSum = newModSum + data.powerStatList.GetFromOutput(newOutput, mod, true) + end + + if baseModSum == math.huge then diff --git a/src/Classes/TradeStatWeightMultiplierListControl.lua b/src/Classes/TradeStatWeightMultiplierListControl.lua index 0d528470f53..f0260d89de0 100644 --- a/src/Classes/TradeStatWeightMultiplierListControl.lua +++ b/src/Classes/TradeStatWeightMultiplierListControl.lua @@ -25,7 +25,7 @@ end function TradeStatWeightMultiplierListControlClass:AddValueTooltip(tooltip, index, data) tooltip:Clear() if not self.noTooltip then - tooltip:AddLine(16, "^7Double click to modify this stats weight multiplier.") + tooltip:AddLine(16, "^7Click to modify this stats weight multiplier.") end end diff --git a/src/Classes/TreeTab.lua.rej b/src/Classes/TreeTab.lua.rej new file mode 100644 index 00000000000..48703412368 --- /dev/null +++ b/src/Classes/TreeTab.lua.rej @@ -0,0 +1,10 @@ +diff a/src/Classes/TreeTab.lua b/src/Classes/TreeTab.lua (rejected hunks) +@@ -1850,7 +1850,7 @@ function TreeTabClass:FindTimelessJewel() + + controls.fallbackWeightsLabel = new("LabelControl", {"TOPRIGHT", nil, "TOPLEFT"}, {405, 225, 0, 16}, "^7Fallback Weight Mode:") + local fallbackWeightsList = { } +- for id, stat in pairs(data.powerStatList) do ++ for _, stat in ipairs(data.powerStatList) do + if not stat.ignoreForItems and stat.label ~= "Name" then + t_insert(fallbackWeightsList, { + label = "Sort by " .. stat.label, diff --git a/src/Modules/Data.lua b/src/Modules/Data.lua index a880019c7f8..c581cb661ff 100644 --- a/src/Modules/Data.lua +++ b/src/Modules/Data.lua @@ -108,6 +108,16 @@ data = { } -- Misc data tables LoadModule("Data/Misc", data) +---@class StatTable +---@field stat? string stat ID +---@field label string A short description of the stat +---@field transform fun(in: number|string): number|string A function to e.g. invert the value, if the stat represents something where lower is better +---@field combinedOffDef? boolean +---@field ignoreForNodes? boolean +---@field ignoreForItems? boolean +---@field reverseSort? boolean + +---@type StatTable[] data.powerStatList = { { stat=nil, label="Offence/Defence", combinedOffDef=true, ignoreForItems=true }, { stat=nil, label="Name", itemField="Name", ignoreForNodes=true, reverseSort=true, transform=function(value) return value:gsub("^The ","") end}, diff --git a/src/Modules/Data.lua.rej b/src/Modules/Data.lua.rej new file mode 100644 index 00000000000..0b84cbfa092 --- /dev/null +++ b/src/Modules/Data.lua.rej @@ -0,0 +1,10 @@ +diff a/src/Modules/Data.lua b/src/Modules/Data.lua (rejected hunks) +@@ -189,7 +189,7 @@ function data.powerStatList.GetFromOutput(output, statTable, skipTransform) + return output[statTable.stat] or 0 + end + -- if the user doesn't have full dps, we default to adding the player and minion dps together +- return (output.CombinedDPS or 0) + (output.Minion and output.Minion.CombinedDPS) ++ return (output.CombinedDPS or 0) + (output.Minion and output.Minion.CombinedDPS or 0) + end + -- minion-only stats + local minionStat = statTable.stat:match("^Minion(.+)") From d8d01f2173a9ec3ea607d8b75c4762b0d4a5d0f1 Mon Sep 17 00:00:00 2001 From: vaisest <4550061+vaisest@users.noreply.github.com> Date: Sun, 5 Jul 2026 12:37:59 +0300 Subject: [PATCH 2/4] Fix merge conflicts --- .../TestTradeQueryCurrency_spec.lua.rej | 26 -------- spec/System/TestTradeQueryGenerator_spec.lua | 66 +++++++++++++++++++ .../TestTradeQueryGenerator_spec.lua.rej | 10 --- spec/System/TestTradeQuery_spec.lua | 44 +++++++++++++ spec/System/TestTradeQuery_spec.lua.rej | 38 ----------- src/Classes/ItemDBControl.lua | 5 +- src/Classes/ItemDBControl.lua.rej | 11 ---- src/Classes/TradeQuery.lua | 7 +- src/Classes/TradeQuery.lua.rej | 13 ---- src/Classes/TradeQueryGenerator.lua | 16 +++-- src/Classes/TradeQueryGenerator.lua.rej | 12 ---- src/Classes/TreeTab.lua | 2 +- src/Classes/TreeTab.lua.rej | 10 --- src/Modules/Data.lua | 56 +++++++++++++++- src/Modules/Data.lua.rej | 10 --- 15 files changed, 185 insertions(+), 141 deletions(-) delete mode 100644 spec/System/TestTradeQueryCurrency_spec.lua.rej delete mode 100644 spec/System/TestTradeQueryGenerator_spec.lua.rej delete mode 100644 spec/System/TestTradeQuery_spec.lua.rej delete mode 100644 src/Classes/ItemDBControl.lua.rej delete mode 100644 src/Classes/TradeQuery.lua.rej delete mode 100644 src/Classes/TradeQueryGenerator.lua.rej delete mode 100644 src/Classes/TreeTab.lua.rej delete mode 100644 src/Modules/Data.lua.rej diff --git a/spec/System/TestTradeQueryCurrency_spec.lua.rej b/spec/System/TestTradeQueryCurrency_spec.lua.rej deleted file mode 100644 index 870856ac481..00000000000 --- a/spec/System/TestTradeQueryCurrency_spec.lua.rej +++ /dev/null @@ -1,26 +0,0 @@ -diff a/spec/System/TestTradeQueryCurrency_spec.lua b/spec/System/TestTradeQueryCurrency_spec.lua (rejected hunks) -@@ -23,24 +23,6 @@ describe("TradeQuery Currency Conversion", function() - end) - end) - -- describe("ReduceOutput", function() -- it("uses selected minion stats for weighted result comparison", function() -- mock_tradeQuery.statSortSelectionList = { { stat = "AverageDamage" } } -- -- local result = mock_tradeQuery:ReduceOutput({ -- AverageDamage = 10, -- Life = 100, -- Minion = { -- AverageDamage = 250, -- Life = 200, -- }, -- }) -- -- assert.are.equals(250, result.AverageDamage) -- assert.is_nil(result.Life) -- end) -- end) -- - describe("PriceBuilderProcessPoENinjaResponse", function() - -- Pass: Processes without error, restoring map while adding a notice - -- Fail: Corrupts map or crashes, indicating fragile API response handling, breaking future conversions diff --git a/spec/System/TestTradeQueryGenerator_spec.lua b/spec/System/TestTradeQueryGenerator_spec.lua index befb96a6570..a7e937b4d78 100644 --- a/spec/System/TestTradeQueryGenerator_spec.lua +++ b/spec/System/TestTradeQueryGenerator_spec.lua @@ -35,6 +35,72 @@ describe("TradeQueryGenerator", function() local result = mock_queryGen.WeightedRatioOutputs(baseOutput, newOutput, statWeights) assert.are.equal(result, 100) end) + it("uses minion output for non-FullDPS stats when minion output is desired", function() + local baseOutput = { Life = 10, Minion = { Life = 100 } } + local newOutput = { Life = 10, Minion = { Life = 250 } } + local statWeights = { { stat = "MinionLife", weightMult = 1 } } + data.misc.maxStatIncrease = 1000 + + local result = mock_queryGen.WeightedRatioOutputs(baseOutput, newOutput, statWeights) + + assert.are.equal(result, 2.5) + end) + + it("uses lower is better stats correctly", function() + local baseOutput = { MaxHit = 100 } + local newOutput = { MaxHit = 10 } + local statWeights = { { stat = "MaxHit", weightMult = 1, transform = function(number) return -number end } } + data.misc.maxStatIncrease = 1000 + + local result = mock_queryGen.WeightedRatioOutputs(baseOutput, newOutput, statWeights) + + local close_enough = math.abs(result - -0.1) < 0.0001 + assert.True(close_enough) + end) + + it("uses player and minion output for FullDPS", function() + -- minion output gets assigned to the player's full dps in reality + local baseOutput = { FullDPS = 100, Minion = { FullDPS = 100 } } + local newOutput = { FullDPS = 250, Minion = { FullDPS = 1000 } } + local statWeights = { { stat = "FullDPS", weightMult = 1 } } + data.misc.maxStatIncrease = 1000 + + local result = mock_queryGen.WeightedRatioOutputs(baseOutput, newOutput, statWeights) + + assert.are.equal(result, 2.5) + end) + + it("uses player output for non-FullDPS even when minion output is available", function() + local baseOutput = { Life = 100, Minion = { Life = 100 } } + local newOutput = { Life = 250, Minion = { Life = 1000 } } + local statWeights = { { stat = "Life", weightMult = 1 } } + data.misc.maxStatIncrease = 1000 + + local result = mock_queryGen.WeightedRatioOutputs(baseOutput, newOutput, statWeights) + assert.are.equal(result, 2.5) + end) + + it("uses the fallback DPS ratio once when FullDPS is unavailable", function() + local baseOutput = { Minion = { TotalDPS = 10, TotalDotDPS = 0, CombinedDPS = 10 } } + local newOutput = { Minion = { TotalDPS = 25, TotalDotDPS = 0, CombinedDPS = 25 } } + local statWeights = { { stat = "FullDPS", weightMult = 1 } } + data.misc.maxStatIncrease = 1000 + + local result = mock_queryGen.WeightedRatioOutputs(baseOutput, newOutput, statWeights) + + assert.are.equal(result, 2.5) + end) + + it("falls back to player output when the selected stat is not on minion output", function() + local baseOutput = { Spirit = 100, Minion = { AverageDamage = 100 } } + local newOutput = { Spirit = 120, Minion = { AverageDamage = 100 } } + local statWeights = { { stat = "Spirit", weightMult = 1 } } + data.misc.maxStatIncrease = 1000 + + local result = mock_queryGen.WeightedRatioOutputs(baseOutput, newOutput, statWeights) + + assert.are.equal(result, 1.2) + end) end) describe("Filter prioritization", function() diff --git a/spec/System/TestTradeQueryGenerator_spec.lua.rej b/spec/System/TestTradeQueryGenerator_spec.lua.rej deleted file mode 100644 index 4360660c03b..00000000000 --- a/spec/System/TestTradeQueryGenerator_spec.lua.rej +++ /dev/null @@ -1,10 +0,0 @@ -diff a/spec/System/TestTradeQueryGenerator_spec.lua b/spec/System/TestTradeQueryGenerator_spec.lua (rejected hunks) -@@ -54,7 +54,7 @@ describe("TradeQueryGenerator", function() - - local result = mock_queryGen.WeightedRatioOutputs(baseOutput, newOutput, statWeights) - -- local close_enough = (result - -0.1) < 0.0001 -+ local close_enough = math.abs(result - -0.1) < 0.0001 - assert.True(close_enough) - end) - diff --git a/spec/System/TestTradeQuery_spec.lua b/spec/System/TestTradeQuery_spec.lua index 332374a8395..31a62e94ded 100644 --- a/spec/System/TestTradeQuery_spec.lua +++ b/spec/System/TestTradeQuery_spec.lua @@ -1,4 +1,11 @@ describe("TradeQuery", function() + local mock_tradeQuery + local mock_queryGen + + before_each(function() + mock_tradeQuery = new("TradeQuery", { itemsTab = {} }) + mock_queryGen = new("TradeQueryGenerator", { itemsTab = {} }) + end) describe("result dropdown tooltipFunc", function() -- Builds a TradeQuery with the strict minimum needed for -- PriceItemRowDisplay to construct row 1 without exploding. Only the @@ -54,4 +61,41 @@ describe("TradeQuery", function() assert.are.equal(0, #tooltip.lines) end) end) + describe("ReduceOutput", function() + it("uses selected minion stats for weighted result comparison", function() + mock_tradeQuery.statSortSelectionList = { { stat = "AverageDamage" } } + + local result = mock_tradeQuery:ReduceOutput({ + AverageDamage = 10, + Life = 100, + Minion = { + AverageDamage = 250, + Life = 200, + }, + }) + + assert.are.equals(260, result.AverageDamage) + assert.is_nil(result.Life) + end) + + it("keeps fallback DPS stats when FullDPS is selected but not present", function() + mock_tradeQuery.statSortSelectionList = { { stat = "FullDPS", weightMult = 1 } } + + local baseOutput = { + CombinedDPS = 100, + TotalDPS = 100, + TotalDotDPS = 0, + } + local reducedOutput = mock_tradeQuery:ReduceOutput({ + CombinedDPS = 120, + TotalDPS = 120, + TotalDotDPS = 0, + }) + + local result = mock_queryGen.WeightedRatioOutputs(baseOutput, reducedOutput, + mock_tradeQuery.statSortSelectionList) + + assert.are.equals(1.2, result) + end) + end) end) diff --git a/spec/System/TestTradeQuery_spec.lua.rej b/spec/System/TestTradeQuery_spec.lua.rej deleted file mode 100644 index 7d2c443fde9..00000000000 --- a/spec/System/TestTradeQuery_spec.lua.rej +++ /dev/null @@ -1,38 +0,0 @@ -diff a/spec/System/TestTradeQuery_spec.lua b/spec/System/TestTradeQuery_spec.lua (rejected hunks) -@@ -1,8 +1,10 @@ - describe("TradeQuery", function () - local mock_tradeQuery -+ local mock_queryGen - - before_each(function() - mock_tradeQuery = new("TradeQuery", { itemsTab = {} }) -+ mock_queryGen = new("TradeQueryGenerator", { itemsTab = {} }) - end) - - describe("ReduceOutput", function() -@@ -21,5 +23,24 @@ describe("TradeQuery", function () - assert.are.equals(260, result.AverageDamage) - assert.is_nil(result.Life) - end) -+ -+ it("keeps fallback DPS stats when FullDPS is selected but not present", function() -+ mock_tradeQuery.statSortSelectionList = { { stat = "FullDPS", weightMult = 1 } } -+ -+ local baseOutput = { -+ CombinedDPS = 100, -+ TotalDPS = 100, -+ TotalDotDPS = 0, -+ } -+ local reducedOutput = mock_tradeQuery:ReduceOutput({ -+ CombinedDPS = 120, -+ TotalDPS = 120, -+ TotalDotDPS = 0, -+ }) -+ -+ local result = mock_queryGen.WeightedRatioOutputs(baseOutput, reducedOutput, mock_tradeQuery.statSortSelectionList) -+ -+ assert.are.equals(1.2, result) -+ end) - end) - end) -\ No newline at end of file diff --git a/src/Classes/ItemDBControl.lua b/src/Classes/ItemDBControl.lua index 0fc8303b2f3..77964163c43 100644 --- a/src/Classes/ItemDBControl.lua +++ b/src/Classes/ItemDBControl.lua @@ -231,10 +231,7 @@ function ItemDBClass:ListBuilder() for slotName, slot in pairs(self.itemsTab.slots) do if self.itemsTab:IsItemValidForSlot(item, slotName) and not slot.inactive and (not slot.weaponSet or slot.weaponSet == (self.itemsTab.activeItemSet.useSecondWeaponSet and 2 or 1)) then local output = calcFunc(item.base.flask and { toggleFlask = item } or item.base.tincture and { toggleTincture = item } or { repSlotName = slotName, repItem = item }, useFullDPS) - local measuredPower = output.Minion and output.Minion[self.sortMode] or output[self.sortMode] or 0 - if self.sortDetail.transform then - measuredPower = self.sortDetail.transform(measuredPower) - end + local measuredPower = data.powerStatList.GetFromOutput(output, self.sortDetail) item.measuredPower = m_max(item.measuredPower, measuredPower) end end diff --git a/src/Classes/ItemDBControl.lua.rej b/src/Classes/ItemDBControl.lua.rej deleted file mode 100644 index bb65957caa8..00000000000 --- a/src/Classes/ItemDBControl.lua.rej +++ /dev/null @@ -1,11 +0,0 @@ -diff a/src/Classes/ItemDBControl.lua b/src/Classes/ItemDBControl.lua (rejected hunks) -@@ -244,9 +244,6 @@ function ItemDBClass:ListBuilder() - if self.itemsTab:IsItemValidForSlot(item, slotName) and not slot.inactive and (not slot.weaponSet or slot.weaponSet == (self.itemsTab.activeItemSet.useSecondWeaponSet and 2 or 1)) then - local output = calcFunc(item.base.flask and { toggleFlask = item } or item.base.charm and { toggleCharm = item } or { repSlotName = slotName, repItem = item }, useFullDPS) - local measuredPower = data.powerStatList.GetFromOutput(output, self.sortDetail) -- if self.sortDetail.transform then -- measuredPower = self.sortDetail.transform(measuredPower) -- end - item.measuredPower = item.measuredPower and m_max(item.measuredPower, measuredPower) or measuredPower - end - end diff --git a/src/Classes/TradeQuery.lua b/src/Classes/TradeQuery.lua index 697fa84f879..a022c8f2dd7 100644 --- a/src/Classes/TradeQuery.lua +++ b/src/Classes/TradeQuery.lua @@ -719,7 +719,12 @@ end function TradeQueryClass:ReduceOutput(output) local smallOutput = {} for _, statTable in ipairs(self.statSortSelectionList) do - smallOutput[statTable.stat] = output[statTable.stat] + smallOutput[statTable.stat] = data.powerStatList.GetFromOutput(output, statTable) + if statTable.stat == "FullDPS" and not output.FullDPS then + smallOutput.TotalDPS = data.powerStatList.GetFromOutput(output, { stat = "TotalDPS" }) + smallOutput.TotalDotDPS = data.powerStatList.GetFromOutput(output, { stat = "TotalDotDPS" }) + smallOutput.CombinedDPS = data.powerStatList.GetFromOutput(output, { stat = "CombinedDPS" }) + end end return smallOutput end diff --git a/src/Classes/TradeQuery.lua.rej b/src/Classes/TradeQuery.lua.rej deleted file mode 100644 index 20900eede40..00000000000 --- a/src/Classes/TradeQuery.lua.rej +++ /dev/null @@ -1,13 +0,0 @@ -diff a/src/Classes/TradeQuery.lua b/src/Classes/TradeQuery.lua (rejected hunks) -@@ -780,6 +780,11 @@ function TradeQueryClass:ReduceOutput(output) - local smallOutput = {} - for _, statTable in ipairs(self.statSortSelectionList) do - smallOutput[statTable.stat] = data.powerStatList.GetFromOutput(output, statTable) -+ if statTable.stat == "FullDPS" and not output.FullDPS then -+ smallOutput.TotalDPS = data.powerStatList.GetFromOutput(output, { stat = "TotalDPS" }) -+ smallOutput.TotalDotDPS = data.powerStatList.GetFromOutput(output, { stat = "TotalDotDPS" }) -+ smallOutput.CombinedDPS = data.powerStatList.GetFromOutput(output, { stat = "CombinedDPS" }) -+ end - end - return smallOutput - end diff --git a/src/Classes/TradeQueryGenerator.lua b/src/Classes/TradeQueryGenerator.lua index eeb2fdeaab2..7a200b0833d 100644 --- a/src/Classes/TradeQueryGenerator.lua +++ b/src/Classes/TradeQueryGenerator.lua @@ -173,12 +173,13 @@ end function TradeQueryGeneratorClass.WeightedRatioOutputs(baseOutput, newOutput, statWeights) local meanStatDiff = 0 + local function ratioModSums(...) local baseModSum = 0 local newModSum = 0 for _, mod in ipairs({ ... }) do - baseModSum = baseModSum + (baseOutput[mod] or 0) - newModSum = newModSum + (newOutput[mod] or 0) + baseModSum = baseModSum + data.powerStatList.GetFromOutput(baseOutput, mod, true) + newModSum = newModSum + data.powerStatList.GetFromOutput(newOutput, mod, true) end if baseModSum == math.huge then @@ -192,10 +193,17 @@ function TradeQueryGeneratorClass.WeightedRatioOutputs(baseOutput, newOutput, st end end for _, statTable in ipairs(statWeights) do + local modSumRatio if statTable.stat == "FullDPS" and not (baseOutput["FullDPS"] and newOutput["FullDPS"]) then - meanStatDiff = meanStatDiff + ratioModSums("TotalDPS", "TotalDotDPS", "CombinedDPS") * statTable.weightMult + modSumRatio = ratioModSums({ stat = "TotalDPS" }, { stat = "TotalDotDPS" }, { stat = "CombinedDPS" }) + else + modSumRatio = ratioModSums(statTable) + end + -- some weights, such as damage taken from hit need to be negated as lower is better for them + if statTable.transform then + modSumRatio = statTable.transform(modSumRatio) end - meanStatDiff = meanStatDiff + ratioModSums(statTable.stat) * statTable.weightMult + meanStatDiff = meanStatDiff + modSumRatio * statTable.weightMult end return meanStatDiff end diff --git a/src/Classes/TradeQueryGenerator.lua.rej b/src/Classes/TradeQueryGenerator.lua.rej deleted file mode 100644 index 9255b3fe21c..00000000000 --- a/src/Classes/TradeQueryGenerator.lua.rej +++ /dev/null @@ -1,12 +0,0 @@ -diff a/src/Classes/TradeQueryGenerator.lua b/src/Classes/TradeQueryGenerator.lua (rejected hunks) -@@ -162,8 +162,8 @@ function TradeQueryGeneratorClass.WeightedRatioOutputs(baseOutput, newOutput, st - local baseModSum = 0 - local newModSum = 0 - for _, mod in ipairs({ ... }) do -- baseModSum = baseModSum + data.powerStatList.GetFromOutput(baseOutput, mod) -- newModSum = newModSum + data.powerStatList.GetFromOutput(newOutput, mod) -+ baseModSum = baseModSum + data.powerStatList.GetFromOutput(baseOutput, mod, true) -+ newModSum = newModSum + data.powerStatList.GetFromOutput(newOutput, mod, true) - end - - if baseModSum == math.huge then diff --git a/src/Classes/TreeTab.lua b/src/Classes/TreeTab.lua index 77bfe3ffdbf..b84ce742e7b 100644 --- a/src/Classes/TreeTab.lua +++ b/src/Classes/TreeTab.lua @@ -1991,7 +1991,7 @@ function TreeTabClass:FindTimelessJewel() end local fallbackWeightsList = { } - for id, stat in pairs(data.powerStatList) do + for _, stat in ipairs(data.powerStatList) do if not stat.ignoreForItems and stat.label ~= "Name" then t_insert(fallbackWeightsList, { label = "Sort by " .. stat.label, diff --git a/src/Classes/TreeTab.lua.rej b/src/Classes/TreeTab.lua.rej deleted file mode 100644 index 48703412368..00000000000 --- a/src/Classes/TreeTab.lua.rej +++ /dev/null @@ -1,10 +0,0 @@ -diff a/src/Classes/TreeTab.lua b/src/Classes/TreeTab.lua (rejected hunks) -@@ -1850,7 +1850,7 @@ function TreeTabClass:FindTimelessJewel() - - controls.fallbackWeightsLabel = new("LabelControl", {"TOPRIGHT", nil, "TOPLEFT"}, {405, 225, 0, 16}, "^7Fallback Weight Mode:") - local fallbackWeightsList = { } -- for id, stat in pairs(data.powerStatList) do -+ for _, stat in ipairs(data.powerStatList) do - if not stat.ignoreForItems and stat.label ~= "Name" then - t_insert(fallbackWeightsList, { - label = "Sort by " .. stat.label, diff --git a/src/Modules/Data.lua b/src/Modules/Data.lua index c581cb661ff..5b25c267216 100644 --- a/src/Modules/Data.lua +++ b/src/Modules/Data.lua @@ -108,10 +108,11 @@ data = { } -- Misc data tables LoadModule("Data/Misc", data) +---@alias TransformFunc fun(in: number|string): (number|string)? ---@class StatTable ---@field stat? string stat ID ---@field label string A short description of the stat ----@field transform fun(in: number|string): number|string A function to e.g. invert the value, if the stat represents something where lower is better +---@field transform TransformFunc?: number|string A function to e.g. invert the value, if the stat represents something where lower is better ---@field combinedOffDef? boolean ---@field ignoreForNodes? boolean ---@field ignoreForItems? boolean @@ -170,6 +171,59 @@ data.powerStatList = { { stat="SpellSuppressionChance", label="Spell Suppression Chance" }, } +---@param output any Calc output +---@param statTable StatTable Table with stats as in data.powerStatList +---@param skipTransform? boolean Whether the stat transform should be skipped. This is useful if you want to e.g. divide two less is better stats +---@return number +function data.powerStatList.GetFromOutput(output, statTable, skipTransform) + local function getEntry() + if statTable.stat == "FullDPS" then + if output[statTable.stat] ~= nil then + return output[statTable.stat] or 0 + end + -- if the user doesn't have full dps, we default to adding the player and minion dps together + return (output.CombinedDPS or 0) + (output.Minion and output.Minion.CombinedDPS or 0) + end + -- minion-only stats + local minionStat = statTable.stat:match("^Minion(.+)") + if minionStat then + return output.Minion and output.Minion[minionStat] or 0 + end + -- damage stats use a combination of player and minion dps + local isDamageStat = statTable.stat == "AverageDamage" or statTable.stat == "TotalDot" or + statTable.stat:match("DPS") + if isDamageStat then + return (output[statTable.stat] or 0) + (output.Minion and output.Minion[statTable.stat] or 0) + end + return output[statTable.stat] or 0 + end + if statTable.transform and not skipTransform then + return statTable.transform(getEntry()) + end + return getEntry() +end + +-- these stats don't exist on minions or generally don't exist on both player and minion +local minionNonApplicableStats = { + AverageDamage = true, + TotalDot = true, + Str = true, + Dex = true, + Int = true, + Spirit = true, + EffectiveLootRarityMod = true, +} +for i = 1, #data.powerStatList do + local statEntry = data.powerStatList[i] + if (not statEntry.stat) or statEntry.stat:match("DPS") or minionNonApplicableStats[statEntry.stat] then + goto statContinue + end + local minionStat = copyTable(statEntry) + minionStat.stat = "Minion" .. minionStat.stat + minionStat.label = "Minion " .. minionStat.label + t_insert(data.powerStatList, minionStat) + ::statContinue:: +end data.misc = { -- magic numbers ServerTickTime = 0.033, ServerTickRate = 1 / 0.033, diff --git a/src/Modules/Data.lua.rej b/src/Modules/Data.lua.rej deleted file mode 100644 index 0b84cbfa092..00000000000 --- a/src/Modules/Data.lua.rej +++ /dev/null @@ -1,10 +0,0 @@ -diff a/src/Modules/Data.lua b/src/Modules/Data.lua (rejected hunks) -@@ -189,7 +189,7 @@ function data.powerStatList.GetFromOutput(output, statTable, skipTransform) - return output[statTable.stat] or 0 - end - -- if the user doesn't have full dps, we default to adding the player and minion dps together -- return (output.CombinedDPS or 0) + (output.Minion and output.Minion.CombinedDPS) -+ return (output.CombinedDPS or 0) + (output.Minion and output.Minion.CombinedDPS or 0) - end - -- minion-only stats - local minionStat = statTable.stat:match("^Minion(.+)") From c00641f4398298e35adf727074ced860cb84734d Mon Sep 17 00:00:00 2001 From: vaisest <4550061+vaisest@users.noreply.github.com> Date: Sun, 5 Jul 2026 13:30:29 +0300 Subject: [PATCH 3/4] Rework timeless fallback weights --- src/Classes/TreeTab.lua | 61 ++++++++++++----------------------------- 1 file changed, 18 insertions(+), 43 deletions(-) diff --git a/src/Classes/TreeTab.lua b/src/Classes/TreeTab.lua index b84ce742e7b..c551b3f06c3 100644 --- a/src/Classes/TreeTab.lua +++ b/src/Classes/TreeTab.lua @@ -1825,54 +1825,29 @@ function TreeTabClass:FindTimelessJewel() end end - local function generateFallbackWeights(nodes, selection) + local function generateFallbackWeights(nodes, powerStat) local calcFunc, calcBase = self.build.calcsTab:GetMiscCalculator(self.build) local newList = { } - local baseOutput = calcFunc() - if baseOutput.Minion then - baseOutput = baseOutput.Minion - end - local baseValue = baseOutput[selection.stat] or 1 - if selection.transform then - baseValue = selection.transform(baseValue) - end + local basePower = data.powerStatList.GetFromOutput(calcBase, powerStat) for _, newNode in ipairs(nodes) do - local output = nil - if newNode.calcMultiple then - output = calcFunc({ addNodes = { [newNode.node[1]] = true } }) - else - output = calcFunc({ addNodes = { [newNode] = true } }) - end - if output.Minion then - output = output.Minion - end - local outputValue = output[selection.stat] or 0 - if selection.transform then - outputValue = selection.transform(outputValue) - end - outputValue = outputValue / baseValue - if outputValue ~= outputValue then - outputValue = 1 - end - t_insert(newList, { - id = newNode.id, - weight1 = (outputValue - 1) / (newNode.divisor or 1) - }) - if newNode.calcMultiple then - output = calcFunc({ addNodes = { [newNode.node[2]] = true } }) - if output.Minion then - output = output.Minion - end - outputValue = output[selection.stat] or 0 - if selection.transform then - outputValue = selection.transform(outputValue) - end - outputValue = outputValue / baseValue - if outputValue ~= outputValue then - outputValue = 1 + local powerEntry = { id = newNode.id } + -- nodes that have multiple lines are represented as a list in newNode.node + local nodeLines = newNode.node or { newNode } + for i = 1, #nodeLines do + local node = nodeLines[i] + local nodeOutput = calcFunc({ addNodes = { [node] = true } }) + local nodePower = data.powerStatList.GetFromOutput(nodeOutput, powerStat) + -- avoid infinity + if basePower == 0 then + powerEntry["weight" .. i] = 0 + else + local powerGain = (nodePower - basePower) / + -- normalize with absolute base power so that the result isn't negative + math.abs(basePower) + powerEntry["weight" .. i] = powerGain / (newNode.divisor or 1) end - newList[#newList].weight2 = (outputValue - 1) / (newNode.divisor or 1) end + t_insert(newList, powerEntry) end return newList end From 3a8f43ccdaec6f063463d6cac78bf80b4df7f7d0 Mon Sep 17 00:00:00 2001 From: vaisest <4550061+vaisest@users.noreply.github.com> Date: Sun, 5 Jul 2026 13:44:12 +0300 Subject: [PATCH 4/4] Removed dead variable --- src/Classes/TradeQuery.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Classes/TradeQuery.lua b/src/Classes/TradeQuery.lua index a022c8f2dd7..a0afe80006b 100644 --- a/src/Classes/TradeQuery.lua +++ b/src/Classes/TradeQuery.lua @@ -555,7 +555,6 @@ function TradeQueryClass:SetStatWeights(previousSelectionList) local sliderController = { index = 1 } local popupHeight = 500 - local listYOffset = 45 -- account for top gap, bottom button size and gap, and a gap before buttons local listHeight = popupHeight - 45 - 30 - 10