Skip to content

Commit a3c464b

Browse files
committed
Port matching by tradehash in comparison buy similar menu
1 parent 7659023 commit a3c464b

2 files changed

Lines changed: 148 additions & 105 deletions

File tree

src/Classes/CompareBuySimilar.lua

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ local REALM_API_IDS = {
1919

2020
-- Listed status display names and their API option values
2121
local LISTED_STATUS_OPTIONS = {
22-
{ label = "Instant Buyout & In Person", apiValue = "available" },
2322
{ label = "Instant Buyout", apiValue = "securable" },
23+
{ label = "Instant Buyout & In Person", apiValue = "available" },
2424
{ label = "In Person (Online)", apiValue = "online" },
2525
{ label = "Any", apiValue = "any" },
2626
}
@@ -84,7 +84,7 @@ local function buildURL(item, slotName, controls, modEntries, defenceEntries, is
8484
end
8585
else
8686
-- Category filter
87-
local categoryStr = tradeHelpers.getTradeCategory(slotName, item)
87+
local categoryStr, _ = tradeHelpers.getTradeCategory(slotName, item)
8888
if categoryStr then
8989
queryFilters.type_filters = {
9090
filters = {
@@ -142,8 +142,17 @@ local function buildURL(item, slotName, controls, modEntries, defenceEntries, is
142142
local maxVal = tonumber(controls[prefix .. "Max"].buf)
143143
local filter = { id = entry.tradeId }
144144
local value = {}
145-
if minVal then value.min = minVal end
146-
if maxVal then value.max = maxVal end
145+
if minVal then
146+
value.min = minVal
147+
end
148+
if maxVal then
149+
value.max = maxVal
150+
end
151+
if entry.invert then
152+
value.min, value.max = value.max, value.min
153+
value.min = value.min and -value.min
154+
value.max = value.max and -value.max
155+
end
147156
if next(value) then
148157
filter.value = value
149158
end
@@ -191,26 +200,29 @@ function M.openPopup(item, slotName, primaryBuild)
191200
local modTypeSources = {
192201
{ list = item.implicitModLines, type = "implicit" },
193202
{ list = item.enchantModLines, type = "enchant" },
194-
{ list = item.scourgeModLines, type = "explicit" },
195203
{ list = item.explicitModLines, type = "explicit" },
196-
{ list = item.crucibleModLines, type = "explicit" },
204+
{ list = item.scourgeModLines, type = "scourge" },
205+
{ list = item.crucibleModLines, type = "crucible" },
197206
}
198207
for _, source in ipairs(modTypeSources) do
199208
if source.list then
200209
for _, modLine in ipairs(source.list) do
201210
if item:CheckModLineVariant(modLine) then
202211
local formatted = itemLib.formatModLine(modLine)
212+
formatted = formatted and formatted:gsub(" %^8%(Not supported in PoB yet%)", "")
203213
if formatted then
204214
-- Use range-resolved text for matching
205215
local resolvedLine = (modLine.range and itemLib.applyRange(modLine.line, modLine.range, modLine.valueScalar)) or modLine.line
206-
local tradeId = tradeHelpers.findTradeModId(resolvedLine, source.type)
216+
local tradeHash = tradeHelpers.findTradeHash(item, resolvedLine, source.type, modLine.desecrated)
217+
local identifier = tradeHash and string.format("%s.stat_%s", source.type, tradeHash)
207218
local value = tradeHelpers.modLineValue(resolvedLine)
208219
t_insert(modEntries, {
209220
line = modLine.line,
210221
formatted = formatted:gsub("%^x%x%x%x%x%x%x", ""):gsub("%^%x", ""), -- strip color codes
211-
tradeId = tradeId,
222+
tradeId = identifier,
212223
value = value,
213224
modType = source.type,
225+
invert = tradeHelpers.shouldBeInverted(identifier, resolvedLine, source.type)
214226
})
215227
end
216228
end
@@ -273,6 +285,13 @@ function M.openPopup(item, slotName, primaryBuild)
273285
t_insert(leagueList, "Ruthless")
274286
t_insert(leagueList, "Hardcore Ruthless")
275287
controls.leagueDrop:SetList(leagueList)
288+
-- default to sc
289+
for i,v in ipairs(controls.leagueDrop.list) do
290+
if not v:match("^HC") then
291+
controls.leagueDrop:SetSel(i)
292+
break
293+
end
294+
end
276295
end)
277296
end
278297

src/Classes/CompareTradeHelpers.lua

Lines changed: 121 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,6 @@
55
--
66
local m_floor = math.floor
77
local dkjson = require "dkjson"
8-
local queryModsData
9-
do
10-
local queryModFile = io.open("Data/QueryMods.lua", "r")
11-
if queryModFile then
12-
queryModFile:close()
13-
queryModsData = LoadModule("Data/QueryMods")
14-
end
15-
end
168

179
local M = {}
1810

@@ -36,48 +28,11 @@ function M.modLineValue(line)
3628
return tonumber(line:match("[%d]+%.?[%d]*")) or 0
3729
end
3830

39-
-- Helper: lazily build a reverse lookup from QueryMods tradeMod.text → tradeMod.id
40-
local _tradeModLookup = nil
41-
local function getTradeModLookup()
42-
if _tradeModLookup then return _tradeModLookup end
43-
_tradeModLookup = {}
44-
if not queryModsData then return _tradeModLookup end
45-
for _groupName, mods in pairs(queryModsData) do
46-
for _modKey, modData in pairs(mods) do
47-
if type(modData) == "table" and modData.tradeMod then
48-
local text = modData.tradeMod.text
49-
local modType = modData.tradeMod.type or "explicit"
50-
local id = modData.tradeMod.id
51-
local key = text .. "|" .. modType
52-
_tradeModLookup[key] = id
53-
if not _tradeModLookup[text] then
54-
_tradeModLookup[text] = id
55-
end
56-
-- Also store with template-converted text for mods with literal numbers
57-
-- (e.g. "1 Added Passive Skill is X" → "# Added Passive Skill is X")
58-
local template = M.modLineTemplate(text)
59-
if template ~= text then
60-
local templateKey = template .. "|" .. modType
61-
if not _tradeModLookup[templateKey] then
62-
_tradeModLookup[templateKey] = id
63-
end
64-
if not _tradeModLookup[template] then
65-
_tradeModLookup[template] = id
66-
end
67-
end
68-
end
69-
end
70-
end
71-
return _tradeModLookup
72-
end
73-
74-
-- Helper: lazily fetch and cache the trade API stats for comprehensive mod matching
75-
-- Covers mods not in QueryMods.lua (cluster enchants, unique-specific mods, etc.)
76-
local _tradeStatsLookup = nil
31+
-- Helper: fetch and cache the trade API stats
32+
local _tradeStats = nil
7733
local _tradeStatsFetched = false
7834
local function getTradeStatsLookup()
79-
if _tradeStatsFetched then return _tradeStatsLookup end
80-
_tradeStatsFetched = true
35+
if _tradeStats then return _tradeStats end
8136
local tradeStats = ""
8237
local easy = common.curl.easy()
8338
if not easy then return nil end
@@ -89,24 +44,10 @@ local function getTradeStatsLookup()
8944
end)
9045
local ok = easy:perform()
9146
easy:close()
92-
if not ok or tradeStats == "" then return nil end
47+
if not ok or tradeStats == "" then return {} end
9348
local parsed = dkjson.decode(tradeStats)
94-
if not parsed or not parsed.result then return nil end
95-
_tradeStatsLookup = {}
96-
for _, category in ipairs(parsed.result) do
97-
local catLabel = category.label
98-
for _, entry in ipairs(category.entries) do
99-
local stripped = entry.text:gsub("[#()0-9%-%+%.]", "")
100-
local key = stripped .. "|" .. catLabel
101-
if not _tradeStatsLookup[key] then
102-
_tradeStatsLookup[key] = entry
103-
end
104-
if not _tradeStatsLookup[stripped] then
105-
_tradeStatsLookup[stripped] = entry
106-
end
107-
end
108-
end
109-
return _tradeStatsLookup
49+
_tradeStats = parsed.result
50+
return _tradeStats
11051
end
11152

11253
-- Map source types used in OpenBuySimilarPopup to trade API category labels
@@ -116,50 +57,133 @@ M.sourceTypeToCategory = {
11657
["enchant"] = "Enchant",
11758
}
11859

60+
function M.shouldBeInverted(tradeId, modLine, modType)
61+
local formattedLine = M.formatDatabaseText(M.formatDatabaseText(modLine))
62+
for _, category in ipairs(getTradeStatsLookup()) do
63+
if category.id == modType then
64+
for _, stat in ipairs(category.entries) do
65+
if tradeId == stat.id then
66+
-- remove radius jewel extra text
67+
local formattedTradeSiteText = M.formatDatabaseText(stat.text)
68+
-- local modifiers don't seem to be inverted. same goes for
69+
-- the single stat that has (charm) in it
70+
if formattedTradeSiteText:match("(Local)") or formattedTradeSiteText:match(" %(Charm%)$") then
71+
return false
72+
end
73+
-- trade site sometimes has a + sign, sometimes not
74+
return not (formattedLine == formattedTradeSiteText or formattedLine:gsub("^%+", "") == formattedTradeSiteText)
75+
end
76+
end
77+
end
78+
end
79+
end
80+
81+
-- Helper: normalise data texts to # format
82+
function M.formatDatabaseText(text)
83+
-- decimal -> integer
84+
text = text:gsub("%d+%.%d+", "1")
85+
-- (123-124) -> #
86+
text = text:gsub("%(%d+%-%d+%)", "#")
87+
text = text:gsub("%d+", "#")
88+
-- remove radius jewel text. the same description is used for regular and
89+
-- radius jewels in the exports
90+
text = text:gsub("^Notable Passive Skills in Radius also grant ", "")
91+
text = text:gsub("^Small Passive Skills in Radius also grant ", "")
92+
return text
93+
end
94+
11995
-- Helper: find the trade stat ID for a mod line
120-
function M.findTradeModId(modLine, modType)
121-
-- Try QueryMods-based lookup
122-
local lookup = getTradeModLookup()
123-
local template = M.modLineTemplate(modLine)
124-
-- Try exact match with type first
125-
local key = template .. "|" .. modType
126-
if lookup[key] then
127-
return lookup[key]
96+
function M.findTradeHash(item, modLine, modType, isVeiled)
97+
local formattedLine = M.formatDatabaseText(modLine)
98+
-- the data export splits some mods into different parts, even though they
99+
-- are technically just one stat. we handle that here
100+
function findStat(dbMod, allowDefault)
101+
local excludeTags = (not allowDefault) and { default = true } or nil
102+
if #dbMod.weightKey > 0 and not (item:GetModSpawnWeight(dbMod, nil, excludeTags) > 0) then
103+
return nil
104+
end
105+
for tradeHash, description in pairs(dbMod.tradeHashes) do
106+
for _, line in ipairs(description) do
107+
local dbFormatted = M.formatDatabaseText(line)
108+
if formattedLine == dbFormatted then
109+
return tradeHash
110+
end
111+
end
112+
end
128113
end
129-
-- Try without leading +/- sign
130-
local stripped = template:gsub("^[%+%-]", "")
131-
key = stripped .. "|" .. modType
132-
if lookup[key] then
133-
return lookup[key]
114+
115+
-- implicit mods
116+
if modType == "implicit" then
117+
for _, dbName in ipairs({"Implicit", "Synthesis", "Eldritch"}) do
118+
for _, dbMod in pairs(data.itemMods[dbName]) do
119+
local tradeHashMaybe = findStat(dbMod)
120+
if tradeHashMaybe then
121+
return tradeHashMaybe
122+
end
123+
end
124+
end
134125
end
135-
-- Fallback: match by template text only (any type)
136-
if lookup[template] then
137-
return lookup[template]
126+
127+
--enchantments TODO
128+
129+
-- scourge mods
130+
if modType == "scourge" then
131+
for _, dbMod in pairs(data.itemMods.Scourge) do
132+
local tradeHashMaybe = findStat(dbMod)
133+
if tradeHashMaybe then
134+
return tradeHashMaybe
135+
end
136+
end
138137
end
139-
if lookup[stripped] then
140-
return lookup[stripped]
138+
139+
-- crucible mods
140+
-- TODO: add trade hash to these
141+
if modType == "crucible" then
142+
for _, dbMod in pairs(data.crucible) do
143+
local tradeHashMaybe = findStat(dbMod)
144+
if tradeHashMaybe then
145+
return tradeHashMaybe
146+
end
147+
end
141148
end
142149

143-
-- Try trade API stats (covers mods not in QueryMods)
144-
local tradeStats = getTradeStatsLookup()
145-
if tradeStats then
146-
local strippedLine = modLine:gsub("[#()0-9%-%+%.]", "")
147-
local category = M.sourceTypeToCategory[modType]
148-
if category then
149-
local catKey = strippedLine .. "|" .. category
150-
if tradeStats[catKey] then
151-
return tradeStats[catKey].id
150+
-- veiled mods
151+
152+
for _, dbMod in pairs(data.veiledMods) do
153+
local tradeHashMaybe = findStat(dbMod)
154+
if tradeHashMaybe then
155+
return tradeHashMaybe
156+
end
157+
end
158+
-- rest of the explicit mods
159+
for _, dbName in ipairs({ "Delve", "Explicit" }) do
160+
for _, dbMod in pairs(data.itemMods[dbName]) do
161+
local tradeHashMaybe = findStat(dbMod)
162+
if tradeHashMaybe then
163+
return tradeHashMaybe
152164
end
153165
end
154-
-- Fallback: any category
155-
if tradeStats[strippedLine] then
156-
return tradeStats[strippedLine].id
166+
end
167+
168+
for _, dbMod in pairs(data.itemMods.Scourge) do
169+
local tradeHashMaybe = findStat(dbMod)
170+
if tradeHashMaybe then
171+
return tradeHashMaybe
172+
end
173+
end
174+
175+
-- implicit mods
176+
if modType == "explicit" then
177+
for _, dbMod in pairs(data.itemMods.Implicit) do
178+
local tradeHashMaybe = findStat(dbMod)
179+
if tradeHashMaybe then
180+
return tradeHashMaybe
181+
end
157182
end
158183
end
159184

160-
return nil
185+
-- TODO flask, graft, jewels
161186
end
162-
163187
-- Map slot name + item type to (trade API category string, itemCategoryTags key).
164188
-- queryStr: e.g. "armour.shield", "weapon.onemace"
165189
-- categoryLabel: e.g. "Shield", "1HMace", "1HWeapon" (nil for flask / generic jewel / unsupported)

0 commit comments

Comments
 (0)