Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
66 changes: 66 additions & 0 deletions spec/System/TestTradeQueryGenerator_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
44 changes: 44 additions & 0 deletions spec/System/TestTradeQuery_spec.lua
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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)
19 changes: 5 additions & 14 deletions src/Classes/CalcsTab.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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()
Expand Down
5 changes: 1 addition & 4 deletions src/Classes/CompareTab.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2544,10 +2544,7 @@
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)
Expand Down Expand Up @@ -2660,7 +2657,7 @@

-- if our comparison has abyssal jewels, but the primary build
-- doesn't, add those temporarily to the build to work around
-- calcfunc not being able to take in multiple items

Check warning on line 2660 in src/Classes/CompareTab.lua

View workflow job for this annotation

GitHub Actions / spellcheck

Unknown word (calcfunc)
local cmpJewels = {}
local oldEquipped = {}
if newItem.abyssalSocketCount > 0 then
Expand Down Expand Up @@ -3423,12 +3420,12 @@
local fontSize = 14
local drawY = startY
local maxLineW = 0
local function emit(lx, ly, align, fs, fstyle, str)

Check warning on line 3423 in src/Classes/CompareTab.lua

View workflow job for this annotation

GitHub Actions / spellcheck

Unknown word (fstyle)
if measureMode then
local w = DrawStringWidth(fs, fstyle, str)

Check warning on line 3425 in src/Classes/CompareTab.lua

View workflow job for this annotation

GitHub Actions / spellcheck

Unknown word (fstyle)
if w > maxLineW then maxLineW = w end
else
DrawString(lx, ly, align, fs, fstyle, str)

Check warning on line 3428 in src/Classes/CompareTab.lua

View workflow job for this annotation

GitHub Actions / spellcheck

Unknown word (fstyle)
end
end

Expand Down
7 changes: 2 additions & 5 deletions src/Classes/ItemDBControl.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down
10 changes: 9 additions & 1 deletion src/Classes/ItemsTab.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
14 changes: 3 additions & 11 deletions src/Classes/NotableDBControl.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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

Expand Down
17 changes: 13 additions & 4 deletions src/Classes/TradeQuery.lua
Original file line number Diff line number Diff line change
Expand Up @@ -553,11 +553,15 @@ 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)
-- 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,
Expand Down Expand Up @@ -714,7 +718,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
Expand Down
16 changes: 12 additions & 4 deletions src/Classes/TradeQueryGenerator.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/Classes/TradeStatWeightMultiplierListControl.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
63 changes: 19 additions & 44 deletions src/Classes/TreeTab.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -1991,7 +1966,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,
Expand Down
Loading
Loading