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
74 changes: 73 additions & 1 deletion spec/System/TestTradeQuery_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,76 @@ describe("TradeQuery", function ()
assert.are.equals(1.2, result)
end)
end)
end)

describe("ComputeStatDetails", function()
it("uses Trader's FullDPS fallback inputs", function()
mock_tradeQuery.statSortSelectionList = { { label = "Full DPS", stat = "FullDPS", weightMult = 1 } }

local result = mock_tradeQuery:ComputeStatDetails({
CombinedDPS = 100,
TotalDPS = 100,
TotalDotDPS = 0,
}, {
CombinedDPS = 100,
TotalDPS = 200,
TotalDotDPS = 0,
})

assert.are.equals(50, result[1].percentChange)
end)

it("reports lower-is-better stat improvements as positive", function()
mock_tradeQuery.statSortSelectionList = {
{
label = "Taken Phys dmg",
stat = "PhysicalTakenHit",
weightMult = 1,
transform = function(value) return -value end,
},
}

local result = mock_tradeQuery:ComputeStatDetails({ PhysicalTakenHit = 100 }, { PhysicalTakenHit = 80 })

assert.is_true(math.abs(result[1].percentChange - 20) < 0.0001)
end)

it("caps displayed increases to Trader's scoring maximum", function()
mock_tradeQuery.statSortSelectionList = { { label = "Life", stat = "Life", weightMult = 1 } }
local maxStatIncrease = data.misc.maxStatIncrease

local result = mock_tradeQuery:ComputeStatDetails({ Life = 1 }, { Life = maxStatIncrease + 1 })

assert.are.equals((maxStatIncrease - 1) * 100, result[1].percentChange)
end)

it("reports unchanged zero-value stats as unchanged", function()
mock_tradeQuery.statSortSelectionList = { { label = "Block Chance", stat = "BlockChance", weightMult = 1 } }

local result = mock_tradeQuery:ComputeStatDetails({ BlockChance = 0 }, { BlockChance = 0 })

assert.are.equals(0, result[1].percentChange)
end)

it("reports improvements from zero as positive", function()
mock_tradeQuery.statSortSelectionList = { { label = "Block Chance", stat = "BlockChance", weightMult = 1 } }
local maxStatIncrease = data.misc.maxStatIncrease

local result = mock_tradeQuery:ComputeStatDetails({ BlockChance = 0 }, { BlockChance = 0.5 })

assert.are.equals((maxStatIncrease - 1) * 100, result[1].percentChange)
end)
end)

describe("GetResultScorePercent", function()
it("returns the weighted average stat delta", function()
local result = mock_tradeQuery:GetResultScorePercent({
statDetails = {
{ percentChange = 10, weightMult = 1 },
{ percentChange = -10, weightMult = 0.5 },
},
})

assert.is_true(math.abs(result - (10 / 3)) < 0.0001)
end)
end)
end)
27 changes: 26 additions & 1 deletion src/Classes/DropDownControl.lua
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ local m_min = math.min
local m_max = math.max
local m_floor = math.floor

local function drawStrikethrough(label, y, lineHeight)
local strWidth = DrawStringWidth(lineHeight, "VAR", label or "")
SetDrawColor(0.6, 0.6, 0.6)
DrawImage(nil, 0, y + lineHeight / 2, strWidth, 1)
end

local DropDownClass = newClass("DropDownControl", "Control", "ControlHost", "TooltipHost", "SearchHost", function(self, anchor, rect, list, selFunc, tooltipText)
self.Control(anchor, rect)
self.ControlHost()
Expand Down Expand Up @@ -304,19 +310,26 @@ function DropDownClass:Draw(viewPort, noTooltip)
-- draw selected label or search term
local selLabel = nil
local selDetail = nil
local selStrikethrough = false
if self:IsSearchActive() then
selLabel = "Search: " .. self:GetSearchTermPretty()
else
local selItem = self.list[self.selIndex]
if type(selItem) == "table" then
selLabel = selItem.label
selDetail = selItem.detail
selStrikethrough = selItem.strikethrough
else
selLabel = selItem
end
end
SetViewport(x + 2, y + 2, width - height, lineHeight)
DrawString(0, 0, "LEFT", lineHeight, "VAR", selLabel or "")
if selStrikethrough then
drawStrikethrough(selLabel, 0, lineHeight)
local textColor = enabled and 1 or 0.66
SetDrawColor(textColor, textColor, textColor)
end
if selDetail ~= nil then
local dx = DrawStringWidth(lineHeight, "VAR", selDetail)
DrawString(width - dx - 22, 0, "LEFT", lineHeight, "VAR", selDetail)
Expand Down Expand Up @@ -376,6 +389,14 @@ function DropDownClass:Draw(viewPort, noTooltip)
label = listVal
end
DrawString(0, y, "LEFT", lineHeight, "VAR", label)
if type(listVal) == "table" and listVal.strikethrough then
drawStrikethrough(label, y, lineHeight)
if index == self.hoverSel or index == self.selIndex then
SetDrawColor(1, 1, 1)
else
SetDrawColor(0.66, 0.66, 0.66)
end
end
if detail ~= nil then
local detail = listVal.detail
dx = DrawStringWidth(lineHeight, "VAR", detail)
Expand Down Expand Up @@ -515,11 +536,15 @@ function DropDownClass:CheckDroppedWidth(enable)
-- do not be smaller than the created width
local dWidth = self.width
for _, line in ipairs(self.list) do
local detailWidth = 0
if type(line) == "table" then
if line.detail then
detailWidth = DrawStringWidth(lineHeight, "VAR", line.detail) + 10
end
line = line.label or ""
end
-- +10 to stop clipping
dWidth = m_max(dWidth, DrawStringWidth(lineHeight, "VAR", line or "") + 10)
dWidth = m_max(dWidth, DrawStringWidth(lineHeight, "VAR", line or "") + detailWidth + 10)
end
-- no greater than self.maxDroppedWidth
self.droppedWidth = m_min(dWidth + scrollWidth, self.maxDroppedWidth)
Expand Down
111 changes: 99 additions & 12 deletions src/Classes/TradeQuery.lua
Original file line number Diff line number Diff line change
Expand Up @@ -790,6 +790,64 @@ function TradeQueryClass:ReduceOutput(output)
return smallOutput
end

local function getTradeStatValue(output, statTable, useFullDpsFallback)
if useFullDpsFallback then
return data.powerStatList.GetFromOutput(output, { stat = "TotalDPS" }, true) +
data.powerStatList.GetFromOutput(output, { stat = "TotalDotDPS" }, true) +
data.powerStatList.GetFromOutput(output, { stat = "CombinedDPS" }, true)
end
return data.powerStatList.GetFromOutput(output, statTable, true)
end

local function getTradeStatRatio(baseOutput, newOutput, statTable)
local useFullDpsFallback = statTable.stat == "FullDPS" and not (baseOutput.FullDPS and newOutput.FullDPS)
local baseStat = getTradeStatValue(baseOutput, statTable, useFullDpsFallback)
local newStat = getTradeStatValue(newOutput, statTable, useFullDpsFallback)
if baseStat == math.huge then
return newStat == math.huge and 1 or 0
elseif newStat == math.huge then
return data.misc.maxStatIncrease
elseif baseStat == 0 then
if newStat == 0 then
return 1
end
return newStat > 0 and data.misc.maxStatIncrease or 0
end
return m_min(newStat / ((baseStat ~= 0) and baseStat or 1), data.misc.maxStatIncrease)
end

function TradeQueryClass:ComputeStatDetails(baseOutput, newOutput)
local details = {}
for _, statTable in ipairs(self.statSortSelectionList) do
local statRatio = getTradeStatRatio(baseOutput, newOutput, statTable)
local percentChange = (statRatio - 1) * 100
if statTable.transform then
percentChange = (statTable.transform(statRatio) - statTable.transform(1)) * 100
end
t_insert(details, {
label = statTable.label,
stat = statTable.stat,
percentChange = percentChange,
weightMult = statTable.weightMult or 0,
})
end
return details
end

function TradeQueryClass:GetResultScorePercent(evaluation)
if not evaluation or not evaluation.statDetails then
return nil
end
local totalWeight = 0
local scorePercent = 0
for _, detail in ipairs(evaluation.statDetails) do
local weightMult = detail.weightMult or 0
totalWeight = totalWeight + weightMult
scorePercent = scorePercent + (detail.percentChange or 0) * weightMult
end
return totalWeight > 0 and scorePercent / totalWeight or nil
end

-- Method to evaluate a result by getting it's output and weight
function TradeQueryClass:GetResultEvaluation(row_idx, result_index, calcFunc, baseOutput)
local result = self.resultTbl[row_idx][result_index]
Expand Down Expand Up @@ -822,35 +880,48 @@ function TradeQueryClass:GetResultEvaluation(row_idx, result_index, calcFunc, ba
end
end

local output = self:ReduceOutput(calcFunc({ addNodes = addedNodes }))
local fullNewOutput = calcFunc({ addNodes = addedNodes })
local output = self:ReduceOutput(fullNewOutput)
local weight = self.tradeQueryGenerator.WeightedRatioOutputs(baseOutput, output, self.statSortSelectionList)
result.evaluation = {{ output = output, weight = weight }}
local statDetails = self:ComputeStatDetails(baseOutput, fullNewOutput)
result.evaluation = {{ output = output, weight = weight, statDetails = statDetails }}
else
local item = new("Item", result.item_string)

local output = self:ReduceOutput(calcFunc({ repSlotName = slotName, repItem = item }))
local fullNewOutput = calcFunc({ repSlotName = slotName, repItem = item })
local output = self:ReduceOutput(fullNewOutput)
local weight = self.tradeQueryGenerator.WeightedRatioOutputs(baseOutput, output, self.statSortSelectionList)
result.evaluation = {{ output = output, weight = weight }}
local statDetails = self:ComputeStatDetails(baseOutput, fullNewOutput)
result.evaluation = {{ output = output, weight = weight, statDetails = statDetails }}
end
return result.evaluation
end

-- Method to update controls after a search is completed
function TradeQueryClass:UpdateDropdownList(row_idx)
local dropdownLabels = {}
local dropdown = self.controls["resultDropdown".. row_idx]

if not self.resultTbl[row_idx] then return end

for result_index = 1, #self.resultTbl[row_idx] do
if not dropdown or not self.resultTbl[row_idx] or not self.sortedResultTbl[row_idx] then return end

for result_index = 1, #self.sortedResultTbl[row_idx] do
local pb_index = self.sortedResultTbl[row_idx][result_index].index
local result = self.resultTbl[row_idx][pb_index]
local price = string.format(" %s(%d %s)", colorCodes["CURRENCY"], result.amount, result.currency)
local item = new("Item", result.item_string)
table.insert(dropdownLabels, colorCodes[item.rarity] .. item.name .. price)
if result then
local price = s_format(" %s(%s %s)", colorCodes["CURRENCY"], tostring(result.amount), result.currency)
local item = new("Item", result.item_string)
local eval = self:GetResultEvaluation(row_idx, pb_index)
local scorePercent = eval and self:GetResultScorePercent(eval[1])
local scoreDetail = scorePercent and s_format("%s%+.1f%%", scorePercent >= 0 and colorCodes.POSITIVE or colorCodes.NEGATIVE, scorePercent)
t_insert(dropdownLabels, {
label = colorCodes[item.rarity] .. item.name .. price,
detail = scoreDetail,
strikethrough = scorePercent and scorePercent < 0,
})
end
end
self.controls["resultDropdown".. row_idx].selIndex = 1
self.controls["resultDropdown".. row_idx]:SetList(dropdownLabels)
dropdown.selIndex = 1
dropdown:SetList(dropdownLabels)
end
function TradeQueryClass:ResetResultRow(rowIdx)
self.itemIndexTbl[rowIdx] = nil
Expand Down Expand Up @@ -1176,6 +1247,8 @@ you can add them, copy the link here, and press "Price Item" to evaluate the ite
self.itemIndexTbl[row_idx] = self.sortedResultTbl[row_idx][index].index
self:SetFetchResultReturn(row_idx, self.itemIndexTbl[row_idx])
end)
controls["resultDropdown"..row_idx].enableDroppedWidth = true
controls["resultDropdown"..row_idx].maxDroppedWidth = 600
self:UpdateDropdownList(row_idx)
controls["resultDropdown"..row_idx].tooltipFunc = function(tooltip, dropdown_mode, dropdown_index, dropdown_display_string)
local sortedRow = self.sortedResultTbl[row_idx]
Expand All @@ -1192,6 +1265,20 @@ you can add them, copy the link here, and press "Price Item" to evaluate the ite
local tooltipSlot = slotTbl.selectedJewelNodeId and self.itemsTab.sockets[slotTbl.selectedJewelNodeId] or activeSlot
self.itemsTab:AddItemTooltip(tooltip, item, tooltipSlot)
tooltip:AddSeparator(10)
local eval = result.evaluation
if eval and eval[1] and eval[1].statDetails then
local scorePercent = self:GetResultScorePercent(eval[1])
local scoreColor = scorePercent and scorePercent >= 0 and colorCodes.POSITIVE or colorCodes.NEGATIVE
tooltip:AddLine(16, "^7Score Breakdown:")
for _, detail in ipairs(eval[1].statDetails) do
local color = detail.percentChange >= 0 and colorCodes.POSITIVE or colorCodes.NEGATIVE
tooltip:AddLine(16, s_format(" %s%s: %+.1f%%^7 (weight: %.2f)", color, detail.label, detail.percentChange, detail.weightMult))
end
if scorePercent then
tooltip:AddLine(16, s_format(" %sOverall: %+.1f%%", scoreColor, scorePercent))
end
tooltip:AddSeparator(10)
end
tooltip:AddLine(16, string.format("^7Price: %s %s", result.amount, result.currency))
end
controls["importButton"..row_idx] = new("ButtonControl", { "TOPLEFT", controls["resultDropdown"..row_idx], "TOPRIGHT"}, {8, 0, 100, row_height}, "Import Item", function()
Expand Down