diff --git a/spec/System/TestTradeQueryGenerator_spec.lua b/spec/System/TestTradeQueryGenerator_spec.lua index fc7002d195..39ab994b53 100644 --- a/spec/System/TestTradeQueryGenerator_spec.lua +++ b/spec/System/TestTradeQueryGenerator_spec.lua @@ -147,23 +147,42 @@ describe("TradeQueryGenerator", function() end) describe("Filter prioritization", function() - -- Pass: Limits mods to MAX_FILTERS (2 in test), preserving top priorities - -- Fail: Exceeds limit, indicating over-generation of filters, risking API query size errors or rate limits - it("respects MAX_FILTERS", function() - local orig_max = _G.MAX_FILTERS - _G.MAX_FILTERS = 2 - mock_queryGen.modWeights = { { weight = 10, tradeModId = "id1" }, { weight = 5, tradeModId = "id2" } } - table.sort(mock_queryGen.modWeights, function(a, b) - return math.abs(a.weight) > math.abs(b.weight) - end) - local prioritized = {} - for i, entry in ipairs(mock_queryGen.modWeights) do - if #prioritized < _G.MAX_FILTERS then - table.insert(prioritized, entry) - end + it("counts socket and link constraints against MAX_FILTERS", function() + local queryGen = new("TradeQueryGenerator", { itemsTab = { items = { } } }) + queryGen.modWeights = { } + for index = 1, 40 do + table.insert(queryGen.modWeights, { + tradeModId = "explicit.stat_" .. index, + weight = 1, + meanStatDiff = 41 - index, + }) + end + queryGen.calcContext = { + testItem = new("Item", "Rarity: RARE\nNew Item\nGold Ring\nImplicits: 0"), + baseOutput = { }, + baseStatValue = 0, + itemCategoryQueryStr = "accessory.ring", + special = { }, + options = { + statWeights = { }, + influence1 = 1, + influence2 = 1, + includeMirrored = false, + sockets = 6, + links = 6, + }, + } + queryGen.tradeTypeIndex = 1 + local query + queryGen.requesterCallback = function(_, queryJson) + query = require("dkjson").decode(queryJson).query end - assert.are.equal(#prioritized, 2) - _G.MAX_FILTERS = orig_max + + queryGen:FinishQuery() + + assert.are.equal(31, #query.stats[1].filters) + assert.is_not_nil(query.filters.socket_filters.filters.sockets) + assert.is_not_nil(query.filters.socket_filters.filters.links) end) end) end) diff --git a/src/Classes/TradeQueryGenerator.lua b/src/Classes/TradeQueryGenerator.lua index 4a11f0835c..f4a824c1f6 100644 --- a/src/Classes/TradeQueryGenerator.lua +++ b/src/Classes/TradeQueryGenerator.lua @@ -147,7 +147,7 @@ local eldritchModSlots = { ["Boots"] = true } -local MAX_FILTERS = 35 +local MAX_FILTERS = 36 local function logToFile(...) ConPrintf(...) @@ -947,19 +947,71 @@ function TradeQueryGeneratorClass:FinishQuery() if options.sockets and options.sockets > 0 then num_extra = num_extra + 1 end + if options.links and options.links > 0 then + num_extra = num_extra + 1 + end local effective_max = MAX_FILTERS - num_extra - local prioritizedMods = {} + local pseudoMap = { + ["3372524247"] = "pseudo.pseudo_total_fire_resistance", + ["4220027924"] = "pseudo.pseudo_total_cold_resistance", + ["1671376347"] = "pseudo.pseudo_total_lightning_resistance", + ["2923486259"] = "pseudo.pseudo_total_chaos_resistance", + ["4080418644"] = "pseudo.pseudo_total_strength", + ["3261801346"] = "pseudo.pseudo_total_dexterity", + ["328541901"] = "pseudo.pseudo_total_intelligence", + } + local ignoredStats = { + -- % all resistances + ["2901986750"] = true, + -- all attributes + ["1379411836"] = true, + ["2897413282"] = true, + } + -- block all hybrid resistance stats + local resElements = { "fire", "cold", "lightning", "chaos" } + for _, elem1 in ipairs(resElements) do + for _, elem2 in ipairs(resElements) do + local stats = { string.format("%s_and_%s_damage_resistance_%%", elem1, elem2) } + ignoredStats[tostring(HashStats(stats))] = true + end + end + -- block all hybrid attribute stats + local attributeElements = { "dexterity", "strength", "intelligence" } + for _, elem1 in ipairs(attributeElements) do + for _, elem2 in ipairs(attributeElements) do + local stats = { string.format("base_%s_and_%s", elem1, elem2) } + ignoredStats[tostring(HashStats(stats))] = true + stats = { string.format("additional_%s_and_%s", elem1, elem2) } + ignoredStats[tostring(HashStats(stats))] = true + end + end + local statFilters = {} + local pseudoMods = {} for _, entry in ipairs(self.modWeights) do - if #prioritizedMods < effective_max then - table.insert(prioritizedMods, entry) + local hash = entry.tradeModId:match("stat_(%d+)") + local filterEntry = { id = entry.tradeModId, value = { weight = (entry.invert == true and entry.weight * -1 or entry.weight) } } + -- avoid adding hybrid stats since we get the weight for them from + -- individual stats + if ignoredStats[hash] then + goto weightContinue + elseif pseudoMap[hash] then + local tradeId = pseudoMap[hash] + filterEntry.id = tradeId + -- avoid adding duplicate pseudo filters: update existing + if pseudoMods[tradeId] then + pseudoMods[tradeId].value.weight = math.max(filterEntry.value.weight, pseudoMods[tradeId].value.weight) + else + pseudoMods[tradeId] = filterEntry + table.insert(statFilters, filterEntry) + end else - break + table.insert(statFilters, filterEntry) end - end - self.modWeights = prioritizedMods + ::weightContinue:: + end for k, v in pairs(self.calcContext.special.queryExtra or {}) do queryTable.query[k] = v @@ -980,8 +1032,8 @@ function TradeQueryGeneratorClass:FinishQuery() t_insert(queryTable.query.stats, andFilters) end - for _, entry in ipairs(self.modWeights) do - t_insert(queryTable.query.stats[1].filters, { id = entry.tradeModId, value = { weight = (entry.invert == true and entry.weight * -1 or entry.weight) } }) + for _, entry in ipairs(statFilters) do + t_insert(queryTable.query.stats[1].filters, entry) filters = filters + 1 if filters == effective_max then break diff --git a/src/Modules/Common.lua b/src/Modules/Common.lua index 752456d0d4..c7dc008210 100644 --- a/src/Modules/Common.lua +++ b/src/Modules/Common.lua @@ -1068,7 +1068,7 @@ local GGG_STAT_HASH32_SEED = 0xC58F1A7B -- used for calculating the trade hash from stat hash fields local GGG_TRADE_SEED = 0x02312233 ---@param stats string[] ----@param extraStat string extra stat for time-lost jewels +---@param extraStat string? extra stat for time-lost jewels ---@return integer function HashStats(stats, extraStat) if extraStat then