From f3018e9e5541fdb3be2c3719687d94824e62c1ef Mon Sep 17 00:00:00 2001 From: ikidoncc Date: Wed, 19 Aug 2026 02:23:06 -0300 Subject: [PATCH] Allow negative values in trade filters and support Max filter in required mods --- spec/System/TestTradeHelpers_spec.lua | 14 +++++ spec/System/TestTradeQueryGenerator_spec.lua | 56 ++++++++++++++++++++ src/Classes/TradeHelpers.lua | 2 +- src/Classes/TradeQueryGenerator.lua | 31 +++++++++-- 4 files changed, 98 insertions(+), 5 deletions(-) diff --git a/spec/System/TestTradeHelpers_spec.lua b/spec/System/TestTradeHelpers_spec.lua index 806116e4e35..700015d94c0 100644 --- a/spec/System/TestTradeHelpers_spec.lua +++ b/spec/System/TestTradeHelpers_spec.lua @@ -117,4 +117,18 @@ describe("TradeHelpers trade hash matching", function() "unique_thread_of_hope_base_resist_all_elements_%" }))) end) end) + + describe("newPlainNumericEdit", function() + it("sets correct filter pattern for integers", function() + local ctrl = tradeHelpers.newPlainNumericEdit(nil, { 0, 0, 100, 20 }, "", "Min", 6, true) + assert.equal("^%-%d", ctrl.filter) + assert.equal("[^%-%d]", ctrl.filterPattern) + end) + + it("sets correct filter pattern for floats", function() + local ctrl = tradeHelpers.newPlainNumericEdit(nil, { 0, 0, 100, 20 }, "", "Min", 6, false) + assert.equal("^%-%d.", ctrl.filter) + assert.equal("[^%-%d.]", ctrl.filterPattern) + end) + end) end) diff --git a/spec/System/TestTradeQueryGenerator_spec.lua b/spec/System/TestTradeQueryGenerator_spec.lua index e11ea701b9b..0f795d65454 100644 --- a/spec/System/TestTradeQueryGenerator_spec.lua +++ b/spec/System/TestTradeQueryGenerator_spec.lua @@ -192,4 +192,60 @@ describe("TradeQueryGenerator", function() assert.is_not_nil(query.filters.socket_filters.filters.links) end) end) + + describe("Query generation with Min and Max required mods", function() + it("includes min and max in query when both are provided", function() + local queryGen = new("TradeQueryGenerator"):TradeQueryGenerator({ itemsTab = { items = {} } }) + queryGen.modWeights = { } + queryGen.calcContext = { + testItem = new("Item"):Item("Rarity: RARE\nNew Item\nGold Ring\nImplicits: 0"), + baseOutput = { }, + baseStatValue = 0, + itemCategoryQueryStr = "accessory.ring", + special = { }, + requiredMods = { + { tradeId = "explicit.stat_1234", value = 5, maxValue = 10 }, + { tradeId = "explicit.stat_5678", value = -3 }, + { tradeId = "explicit.stat_9012", maxValue = -1 }, + { tradeId = "explicit.stat_3456" } + }, + options = { + statWeights = { }, + influence1 = 1, + influence2 = 1, + includeMirrored = false, + }, + } + queryGen.tradeTypeIndex = 1 + local query + queryGen.requesterCallback = function(_, queryJson) + query = require("dkjson").decode(queryJson).query + end + + queryGen:FinishQuery() + + -- The required mods are stored in stats[2] because stats[1] is the weighted sum list + local modFilters = query.stats[2].filters + assert.are.equal(4, #modFilters) + + -- Test both min and max + assert.are.equal("explicit.stat_1234", modFilters[1].id) + assert.are.equal(5, modFilters[1].value.min) + assert.are.equal(10, modFilters[1].value.max) + + -- Test only min + assert.are.equal("explicit.stat_5678", modFilters[2].id) + assert.are.equal(-3, modFilters[2].value.min) + assert.is_nil(modFilters[2].value.max) + + -- Test only max + assert.are.equal("explicit.stat_9012", modFilters[3].id) + assert.is_nil(modFilters[3].value.min) + assert.are.equal(-1, modFilters[3].value.max) + + -- Test neither (no value object) + assert.are.equal("explicit.stat_3456", modFilters[4].id) + assert.is_nil(modFilters[4].value) + end) + end) end) diff --git a/src/Classes/TradeHelpers.lua b/src/Classes/TradeHelpers.lua index 4448bd0a032..7f05424b2a2 100644 --- a/src/Classes/TradeHelpers.lua +++ b/src/Classes/TradeHelpers.lua @@ -548,7 +548,7 @@ end -- Helper: create a numeric EditControl without +/- spinner buttons, and -- with a preset changeFunc intended for mod values function M.newPlainNumericEdit(anchor, rect, init, prompt, limit, integer, changeFunc) - local format = integer and "%D" or "^%d." + local format = integer and "^%-%d" or "^%-%d." local ctrl = new("EditControl"):EditControl(anchor, rect, init, prompt, format, limit, changeFunc) -- Remove the +/- spinner buttons that "%D" filter triggers ctrl.isNumeric = false diff --git a/src/Classes/TradeQueryGenerator.lua b/src/Classes/TradeQueryGenerator.lua index 9b7e3f5b406..8036fe5b6a8 100644 --- a/src/Classes/TradeQueryGenerator.lua +++ b/src/Classes/TradeQueryGenerator.lua @@ -1044,7 +1044,18 @@ function TradeQueryGeneratorClass:FinishQuery() end end for _, entry in ipairs(requiredMods) do - t_insert(requiredModFilters.filters, { id = entry.tradeId, value = { min = entry.value } }) + local filterValue = {} + if entry.value then + filterValue.min = entry.value + end + if entry.maxValue then + filterValue.max = entry.maxValue + end + local filterEntry = { id = entry.tradeId } + if next(filterValue) then + filterEntry.value = filterValue + end + t_insert(requiredModFilters.filters, filterEntry) end if not options.includeMirrored then queryTable.query.filters.misc_filters = { @@ -1420,13 +1431,13 @@ Remove: %s will be removed from the search results.]], term, term, term) -- intended width of the whole row, including dropdown and aux controls local totalWidth = 340 - -- size of min value input + -- size of min/max value inputs local fieldWidth = 60 -- size of clear button local buttonSize = 20 -- gap between controls local xSpacing = 4 - local auxControlWidth = buttonSize + fieldWidth + 2 * xSpacing + local auxControlWidth = buttonSize + 2 * fieldWidth + 3 * xSpacing local _, lastItemY = lastItemAnchor:GetPos() local _, lastItemH = lastItemAnchor:GetSize() @@ -1494,6 +1505,7 @@ Remove: %s will be removed from the search results.]], term, term, term) local mod = selectedMods[i] local selector = controls["modSelector" .. i] local minimumBox = controls["modSelectorMin" .. i] + local maximumBox = controls["modSelectorMax" .. i] if modList then selector:SetList(modList) end @@ -1501,6 +1513,7 @@ Remove: %s will be removed from the search results.]], term, term, term) selector:SelByValue(mod.label, "label") selector.width = totalWidth - auxControlWidth minimumBox.buf = mod.value and tostring(mod.value) or "" + maximumBox.buf = mod.maxValue and tostring(mod.maxValue) or "" else selector.selIndex = 1 selector.width = totalWidth @@ -1538,8 +1551,18 @@ Remove: %s will be removed from the search results.]], term, term, term) end controls["modSelectorMin" .. i] = minimumBox + -- box that sets maximum value for filter + local maximumBox = tradeHelpers.newPlainNumericEdit({ "LEFT", minimumBox, "RIGHT" }, + { xSpacing, 0, fieldWidth, buttonSize }, "", "Max", 6, false, function(val) + selectedMods[i].maxValue = tonumber(val) + end) + maximumBox.shown = function() + return not not selectedMods[i] + end + controls["modSelectorMax" .. i] = maximumBox + -- button which removes the mod row - local clearButton = new("ButtonControl"):ButtonControl({ "LEFT", minimumBox, "RIGHT" }, { xSpacing, 0, buttonSize, buttonSize }, + local clearButton = new("ButtonControl"):ButtonControl({ "LEFT", maximumBox, "RIGHT" }, { xSpacing, 0, buttonSize, buttonSize }, "x", function() table.remove(selectedMods, i) setModSelectors(controls)