Skip to content

Commit c5bd375

Browse files
authored
Allow Trade to weight by multiple stats (#5507)
* Initial Support for sorting by different stat instead of DPS * rename calcSortIndex and apply default value checks * initial support for multiple stats * fix merge issues * fix merge issues * fix stat weight defualts and reuse old values when opening change popup * fix whitepsace * move to using a listController * update default item sort order and fix full dps issue * fix incorrect variable * fix merge issue * fix merge issues * fix merge issues * update formatting * fix spelling
1 parent 79d0d8e commit c5bd375

File tree

3 files changed

+252
-49
lines changed

3 files changed

+252
-49
lines changed

src/Classes/TradeQuery.lua

Lines changed: 164 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ local t_remove = table.remove
1313
local m_max = math.max
1414
local m_min = math.min
1515
local m_ceil = math.ceil
16+
local s_format = string.format
1617

1718
local baseSlots = { "Weapon 1", "Weapon 2", "Helmet", "Body Armour", "Gloves", "Boots", "Amulet", "Ring 1", "Ring 2", "Belt", "Flask 1", "Flask 2", "Flask 3", "Flask 4", "Flask 5" }
1819

@@ -28,7 +29,7 @@ local TradeQueryClass = newClass("TradeQuery", function(self, itemsTab)
2829
self.itemIndexTbl = { }
2930

3031
-- default set of trade item sort selection
31-
self.pbSortSelectionIndex = 1
32+
self.pbItemSortSelectionIndex = 1
3233
self.pbCurrencyConversion = { }
3334
self.currencyConversionTradeMap = { }
3435
self.lastCurrencyConversionRequest = 0
@@ -280,31 +281,61 @@ You can click this button to enter your POESESSID.
280281
- You can generate weighted search URLs but have to visit the trade site and manually import items.
281282
- You can only generate weighted searches for public leagues. (Generated searches can be modified
282283
on trade site to work on other leagues and realms)]]
284+
285+
-- Stat sort popup button
286+
self.statSortSelectionList = { }
287+
t_insert(self.statSortSelectionList, {
288+
label = "Full DPS",
289+
stat = "FullDPS",
290+
weightMult = 1.0,
291+
})
292+
t_insert(self.statSortSelectionList, {
293+
label = "Effective Hit Pool",
294+
stat = "TotalEHP",
295+
weightMult = 0.5,
296+
})
297+
self.controls.StatWeightMultipliersButton = new("ButtonControl", {"TOPRIGHT", nil, "TOPRIGHT"}, -12, 19, 100, 18, "^7Sort Calc By:", function()
298+
self:SetStatWeights()
299+
end)
300+
self.controls.StatWeightMultipliersButton.tooltipFunc = function(tooltip)
301+
tooltip:Clear()
302+
tooltip:AddLine(16, "Sorts the weights by the stats selected multiplied by a value")
303+
tooltip:AddLine(16, "Currently sorting by:")
304+
for _, stat in ipairs(self.statSortSelectionList) do
305+
tooltip:AddLine(16, s_format("%s: %.2f", stat.label, stat.weightMult))
306+
end
307+
end
283308
self.sortModes = {
284-
DPS = "DPS",
285-
DPS_PRICE = "DPS / Price",
286-
PRICE_ASCENDING = "Price (Lowest)",
287-
WEIGHT = "Weighted Sum",
309+
StatValue = "(Highest) Stat Value",
310+
StatValuePRICE = "Stat Value / Price",
311+
PRICE = "(Lowest) Price",
312+
WEIGHT = "(Highest) Weighted Sum",
288313
}
289314
-- Item sort dropdown
290-
self.sortSelectionList = {
291-
self.sortModes.DPS,
292-
self.sortModes.DPS_PRICE,
293-
self.sortModes.PRICE_ASCENDING,
315+
self.itemSortSelectionList = {
316+
self.sortModes.StatValue,
317+
self.sortModes.StatValuePRICE,
318+
self.sortModes.PRICE,
294319
self.sortModes.WEIGHT,
295320
}
296-
self.controls.itemSortSelection = new("DropDownControl", {"TOPRIGHT", nil, "TOPRIGHT"}, -12, 19, 100, 18, self.sortSelectionList, function(index, value)
297-
self.pbSortSelectionIndex = index
321+
self.controls.itemSortSelection = new("DropDownControl", {"TOPRIGHT",self.controls.StatWeightMultipliersButton,"BOTTOMRIGHT"}, 0, 4, 154, 18, self.itemSortSelectionList, function(index, value)
322+
self.pbItemSortSelectionIndex = index
298323
for index, _ in pairs(self.resultTbl) do
299324
self:UpdateControlsWithItems(slotTables[index], index)
300325
end
301326
end)
302-
self.controls.itemSortSelection.tooltipText = "Weighted Sum searches will always sort\nusing descending weighted sum."
303-
self.controls.itemSortSelection:SetSel(self.pbSortSelectionIndex)
304-
self.controls.itemSortSelectionLabel = new("LabelControl", {"TOPRIGHT", self.controls.itemSortSelection, "TOPLEFT"}, -4, 0, 60, 16, "^7Sort By:")
327+
self.controls.itemSortSelection.tooltipText =
328+
[[Weighted Sum searches will always sort using descending weighted sum
329+
Additional post filtering options can be done these include:
330+
Highest Stat Value - Sort from highest to lowest Stat Value change of equipping item
331+
Highest Stat Value / Price - Sorts from highest to lowest Stat Value per currency
332+
Lowest Price - Sorts from lowest to highest price of retrieved items
333+
Highest Weight - Displays the order retrieved from trade]]
334+
self.controls.itemSortSelection:SetSel(self.pbItemSortSelectionIndex)
335+
self.controls.itemSortSelectionLabel = new("LabelControl", {"TOPRIGHT", self.controls.itemSortSelection, "TOPLEFT"}, -4, 0, 100, 16, "^7Sort Items By:")
305336

306337
self.maxFetchPerSearchDefault = 2
307-
self.controls.fetchCountEdit = new("EditControl", {"TOPRIGHT",self.controls.itemSortSelection,"BOTTOMRIGHT"}, 0, 4, 154, row_height, "", "Fetch Pages", "%D", 3, function(buf)
338+
self.controls.fetchCountEdit = new("EditControl", {"TOPRIGHT", self.controls.itemSortSelectionLabel, "TOPLEFT"}, -8, 0, 150, row_height, "", "Fetch Pages", "%D", 3, function(buf)
308339
self.maxFetchPages = m_min(m_max(tonumber(buf) or self.maxFetchPerSearchDefault, 1), 10)
309340
self.tradeQueryRequests.maxFetchPerSearch = 10 * self.maxFetchPages
310341
self.controls.fetchCountEdit.focusValue = self.maxFetchPages
@@ -395,6 +426,91 @@ on trade site to work on other leagues and realms)]]
395426
main:OpenPopup(pane_width, pane_height, "Trader", self.controls)
396427
end
397428

429+
-- Popup to set stat weight multipliers for sorting
430+
function TradeQueryClass:SetStatWeights()
431+
432+
local controls = { }
433+
local statList = { }
434+
local sliderController = { index = 1 }
435+
local popupHeight = 285
436+
437+
controls.ListControl = new("TradeStatWeightMultiplierListControl", { "TOPLEFT", nil, "TOPRIGHT" }, -410, 45, 400, 200, statList, sliderController)
438+
439+
for id, stat in pairs(data.powerStatList) do
440+
if not stat.ignoreForItems and stat.label ~= "Name" then
441+
t_insert(statList, {
442+
label = "0 : "..stat.label,
443+
stat = {
444+
label = stat.label,
445+
stat = stat.stat,
446+
transform = stat.transform,
447+
weightMult = 0,
448+
}
449+
})
450+
end
451+
end
452+
453+
controls.SliderLabel = new("LabelControl", { "TOPLEFT", nil, "TOPRIGHT" }, -410, 20, 0, 16, "^7"..statList[1].stat.label..":")
454+
controls.Slider = new("SliderControl", { "TOPLEFT", controls.SliderLabel, "TOPRIGHT" }, 20, 0, 150, 16, function(value)
455+
if value == 0 then
456+
controls.SliderValue.label = "^7Disabled"
457+
statList[sliderController.index].stat.weightMult = 0
458+
statList[sliderController.index].label = s_format("%d : ", 0)..statList[sliderController.index].stat.label
459+
else
460+
controls.SliderValue.label = s_format("^7%.2f", 0.01 + value * 0.99)
461+
statList[sliderController.index].stat.weightMult = 0.01 + value * 0.99
462+
statList[sliderController.index].label = s_format("%.2f : ", 0.01 + value * 0.99)..statList[sliderController.index].stat.label
463+
end
464+
end)
465+
controls.SliderValue = new("LabelControl", { "TOPLEFT", controls.Slider, "TOPRIGHT" }, 20, 0, 0, 16, "^7Disabled")
466+
controls.Slider.tooltip.realDraw = controls.Slider.tooltip.Draw
467+
controls.Slider.tooltip.Draw = function(self, x, y, width, height, viewPort)
468+
local sliderOffsetX = round(184 * (1 - controls.Slider.val))
469+
local tooltipWidth, tooltipHeight = self:GetSize()
470+
if main.screenW >= 1338 - sliderOffsetX then
471+
return controls[stat.label.."Slider"].tooltip.realDraw(self, x - 8 - sliderOffsetX, y - 4 - tooltipHeight, width, height, viewPort)
472+
end
473+
return controls.Slider.tooltip.realDraw(self, x, y, width, height, viewPort)
474+
end
475+
sliderController.SliderLabel = controls.SliderLabel
476+
sliderController.Slider = controls.Slider
477+
sliderController.SliderValue = controls.SliderValue
478+
479+
480+
for _, statBase in ipairs(self.statSortSelectionList) do
481+
for _, stat in ipairs(statList) do
482+
if stat.stat.stat == statBase.stat then
483+
stat.stat.weightMult = statBase.weightMult
484+
stat.label = s_format("%.2f : ", statBase.weightMult)..statBase.label
485+
if statList[sliderController.index].stat.stat == statBase.stat then
486+
controls.Slider:SetVal(statBase.weightMult == 1 and 1 or statBase.weightMult - 0.01)
487+
end
488+
end
489+
end
490+
end
491+
492+
controls.finalise = new("ButtonControl", { "BOTTOM", nil, "BOTTOM" }, -45, -10, 80, 20, "Save", function()
493+
main:ClosePopup()
494+
495+
-- this needs to save the weights somewhere, maybe the XML? its not necessary but possibly useful QoL
496+
local statSortSelectionList = {}
497+
for stat, statTable in pairs(statList) do
498+
if statTable.stat.weightMult > 0 then
499+
t_insert(statSortSelectionList, statTable.stat)
500+
end
501+
end
502+
if (#statSortSelectionList) > 0 then
503+
--THIS SHOULD REALLY GIVE A WARNING NOT JUST USE PREVIOUS
504+
self.statSortSelectionList = statSortSelectionList
505+
end
506+
end)
507+
controls.cancel = new("ButtonControl", { "BOTTOM", nil, "BOTTOM" }, 45, -10, 80, 20, "Cancel", function()
508+
main:ClosePopup()
509+
end)
510+
main:OpenPopup(420, popupHeight, "Stat Weight Multipliers", controls)
511+
end
512+
513+
398514
-- Method to update the Currency Conversion button label
399515
function TradeQueryClass:SetCurrencyConversionButton()
400516
local currencyLabel = "Update Currency Conversion Rates"
@@ -459,12 +575,13 @@ end
459575

460576
-- Method to update controls after a search is completed
461577
function TradeQueryClass:UpdateControlsWithItems(slotTbl, index)
462-
local sortMode = self.sortSelectionList[self.pbSortSelectionIndex]
578+
local sortMode = self.itemSortSelectionList[self.pbItemSortSelectionIndex]
463579
local sortedItems, errMsg = self:SortFetchResults(slotTbl, index, sortMode)
464580
if errMsg == "MissingConversionRates" then
465-
self:SetNotice(self.controls.pbNotice, "^4Price sorting is not available, falling back to DPS sort.")
466-
sortedItems, errMsg = self:SortFetchResults(slotTbl, index, self.sortModes.DPS)
467-
elseif errMsg then
581+
self:SetNotice(self.controls.pbNotice, "^4Price sorting is not available, falling back to Stat Value sort.")
582+
sortedItems, errMsg = self:SortFetchResults(slotTbl, index, self.sortModes.StatValue)
583+
end
584+
if errMsg then
468585
self:SetNotice(self.controls.pbNotice, "Error: " .. errMsg)
469586
return
470587
else
@@ -473,7 +590,7 @@ function TradeQueryClass:UpdateControlsWithItems(slotTbl, index)
473590

474591
self.sortedResultTbl[index] = sortedItems
475592
self.itemIndexTbl[index] = self.sortedResultTbl[index][1].index
476-
self.controls["priceButton"..index].tooltipText = "Sorted by " .. self.sortSelectionList[self.pbSortSelectionIndex]
593+
self.controls["priceButton"..index].tooltipText = "Sorted by " .. self.itemSortSelectionList[self.pbItemSortSelectionIndex]
477594
local pb_index = self.sortedResultTbl[index][1].index
478595
self.totalPrice[index] = {
479596
currency = self.resultTbl[index][pb_index].currency,
@@ -501,17 +618,28 @@ function TradeQueryClass:SetFetchResultReturn(slotIndex, index)
501618
end
502619
end
503620

621+
-- Method to sort the fetched results
504622
function TradeQueryClass:SortFetchResults(slotTbl, trade_index, mode)
505-
local function getDpsTable()
623+
local function getStatValueTable()
506624
local out = {}
507625
local slotName = slotTbl.ref and "Jewel " .. tostring(slotTbl.ref) or slotTbl.name
508-
local calcFunc, _ = self.itemsTab.build.calcsTab:GetMiscCalculator()
626+
local storedFullDPS = GlobalCache.useFullDPS
627+
GlobalCache.useFullDPS = GlobalCache.numActiveSkillInFullDPS > 0
628+
local calcFunc, calcBase = self.itemsTab.build.calcsTab:GetMiscCalculator()
509629
for index, tbl in pairs(self.resultTbl[trade_index]) do
510630
local item = new("Item", tbl.item_string)
511631
local output = calcFunc({ repSlotName = slotName, repItem = item }, {})
512-
local newDPS = GlobalCache.useFullDPS and output.FullDPS or m_max(output.TotalDPS, m_max(output.TotalDot, output.CombinedAvg))
513-
out[index] = newDPS
632+
local newStatValue = 0
633+
for _, statTable in ipairs(self.statSortSelectionList) do
634+
if statTable.stat == "FullDPS" and not GlobalCache.useFullDPS then
635+
newStatValue = newStatValue + m_max(output.TotalDPS or 0, m_max(output.TotalDot or 0, output.CombinedAvg or 0)) * statTable.weightMult
636+
else
637+
newStatValue = newStatValue + ( output[statTable.stat] or 0 ) * statTable.weightMult
638+
end
639+
end
640+
out[index] = newStatValue
514641
end
642+
GlobalCache.useFullDPS = storedFullDPS
515643
return out
516644
end
517645
local function getPriceTable()
@@ -534,23 +662,23 @@ function TradeQueryClass:SortFetchResults(slotTbl, trade_index, mode)
534662
t_insert(newTbl, { outputAttr = index, index = index })
535663
end
536664
return newTbl
537-
elseif mode == self.sortModes.DPS then
538-
local dpsTable = getDpsTable()
539-
for index, dps in pairs(dpsTable) do
540-
t_insert(newTbl, { outputAttr = dps, index = index })
665+
elseif mode == self.sortModes.StatValue then
666+
local StatValueTable = getStatValueTable()
667+
for index, statValue in pairs(StatValueTable) do
668+
t_insert(newTbl, { outputAttr = statValue, index = index })
541669
end
542670
table.sort(newTbl, function(a,b) return a.outputAttr > b.outputAttr end)
543-
elseif mode == self.sortModes.DPS_PRICE then
544-
local dpsTable = getDpsTable()
671+
elseif mode == self.sortModes.StatValuePRICE then
672+
local StatValueTable = getStatValueTable()
545673
local priceTable = getPriceTable()
546674
if priceTable == nil then
547675
return nil, "MissingConversionRates"
548676
end
549-
for index, dps in pairs(dpsTable) do
550-
t_insert(newTbl, { outputAttr = dps / priceTable[index], index = index })
677+
for index, statValue in pairs(StatValueTable) do
678+
t_insert(newTbl, { outputAttr = statValue / priceTable[index], index = index })
551679
end
552680
table.sort(newTbl, function(a,b) return a.outputAttr > b.outputAttr end)
553-
elseif mode == self.sortModes.PRICE_ASCENDING then
681+
elseif mode == self.sortModes.PRICE then
554682
local priceTable = getPriceTable()
555683
if priceTable == nil then
556684
return nil, "MissingConversionRates"
@@ -585,7 +713,7 @@ function TradeQueryClass:PriceItemRowDisplay(str_cnt, slotTbl, top_pane_alignmen
585713
local activeSlotRef = slotTbl.ref and self.itemsTab.activeItemSet[slotTbl.ref] or self.itemsTab.activeItemSet[slotTbl.name]
586714
controls["name"..str_cnt] = new("LabelControl", top_pane_alignment_ref, top_pane_alignment_width, top_pane_alignment_height, 100, row_height-4, "^7"..slotTbl.name)
587715
controls["bestButton"..str_cnt] = new("ButtonControl", {"TOPLEFT",controls["name"..str_cnt],"TOPLEFT"}, 100 + 8, 0, 80, row_height, "Find best", function()
588-
self.tradeQueryGenerator:RequestQuery(slotTbl.ref and self.itemsTab.sockets[slotTbl.ref] or self.itemsTab.slots[slotTbl.name], { slotTbl = slotTbl, controls = controls, str_cnt = str_cnt }, function(context, query, errMsg)
716+
self.tradeQueryGenerator:RequestQuery(slotTbl.ref and self.itemsTab.sockets[slotTbl.ref] or self.itemsTab.slots[slotTbl.name], { slotTbl = slotTbl, controls = controls, str_cnt = str_cnt }, self.statSortSelectionList, function(context, query, errMsg)
589717
if errMsg then
590718
self:SetNotice(context.controls.pbNotice, colorCodes.NEGATIVE .. errMsg)
591719
return
@@ -598,7 +726,7 @@ function TradeQueryClass:PriceItemRowDisplay(str_cnt, slotTbl, top_pane_alignmen
598726
controls["uri"..context.str_cnt]:SetText(url, true)
599727
return
600728
end
601-
self.pbSortSelectionIndex = 1
729+
self.pbItemSortSelectionIndex = 1
602730
context.controls["priceButton"..context.str_cnt].label = "Searching..."
603731
self.tradeQueryRequests:SearchWithQueryWeightAdjusted(self.pbRealm, self.pbLeague, query,
604732
function(items, errMsg)
@@ -623,7 +751,7 @@ function TradeQueryClass:PriceItemRowDisplay(str_cnt, slotTbl, top_pane_alignmen
623751
end)
624752
end)
625753
controls["bestButton"..str_cnt].shown = function() return not self.resultTbl[str_cnt] end
626-
controls["bestButton"..str_cnt].tooltipText = "Creates a weighted search to find the highest DPS items for this slot."
754+
controls["bestButton"..str_cnt].tooltipText = "Creates a weighted search to find the highest Stat Value items for this slot."
627755
controls["uri"..str_cnt] = new("EditControl", {"TOPLEFT",controls["bestButton"..str_cnt],"TOPRIGHT"}, 8, 0, 518, row_height, nil, nil, "^%C\t\n", nil, function(buf)
628756
local subpath = buf:match("https://www.pathofexile.com/trade/search/(.+)$") or ""
629757
local paths = {}

0 commit comments

Comments
 (0)