314 lines
10 KiB
Lua
314 lines
10 KiB
Lua
-- ------------------------------------------------------------------------------ --
|
|
-- TradeSkillMaster --
|
|
-- http://www.curse.com/addons/wow/tradeskill-master --
|
|
-- --
|
|
-- A TradeSkillMaster Addon (http://tradeskillmaster.com) --
|
|
-- All Rights Reserved* - Detailed license information included with addon. --
|
|
-- ------------------------------------------------------------------------------ --
|
|
|
|
-- This file contains code for scanning the auction house
|
|
local TSM = select(2, ...)
|
|
local private = {}
|
|
TSMAPI:RegisterForTracing(private, "TradeSkillMaster.AuctionQueryUtil_private")
|
|
|
|
|
|
local ITEM_CLASS_LOOKUP = {}
|
|
for i, class in ipairs({GetAuctionItemClasses()}) do
|
|
ITEM_CLASS_LOOKUP[class] = {}
|
|
ITEM_CLASS_LOOKUP[class].index = i
|
|
for j, subclass in pairs({GetAuctionItemSubClasses(i)}) do
|
|
ITEM_CLASS_LOOKUP[class][subclass] = j
|
|
end
|
|
end
|
|
|
|
local function GetItemClasses(itemString)
|
|
local class, subClass = select(6, TSMAPI:GetSafeItemInfo(itemString))
|
|
if not class or not ITEM_CLASS_LOOKUP[class] then return end
|
|
return ITEM_CLASS_LOOKUP[class].index, ITEM_CLASS_LOOKUP[class][subClass]
|
|
end
|
|
|
|
function TSMAPI:GetAuctionQueryInfo(itemString)
|
|
local name, _, rarity, _, minLevel, class, subClass, _, equipLoc = TSMAPI:GetSafeItemInfo(itemString)
|
|
local class, subClass = GetItemClasses(itemString)
|
|
if not name then return end
|
|
return {name=name, minLevel=minLevel, maxLevel=minLevel, invType=0, class=class, subClass=subClass, quality=rarity}
|
|
end
|
|
|
|
local function GetCommonQueryInfo(name, items)
|
|
local queries = {}
|
|
for _, itemString in ipairs(items) do
|
|
local itemQuery = TSMAPI:GetAuctionQueryInfo(itemString)
|
|
local existingQuery
|
|
for _, query in ipairs(queries) do
|
|
if query.class == itemQuery.class then
|
|
existingQuery = query
|
|
break
|
|
end
|
|
end
|
|
if existingQuery then
|
|
existingQuery.minLevel = min(existingQuery.minLevel, itemQuery.minLevel)
|
|
existingQuery.maxLevel = max(existingQuery.maxLevel, itemQuery.maxLevel)
|
|
existingQuery.quality = min(existingQuery.quality, itemQuery.quality)
|
|
if existingQuery.subClass ~= itemQuery.subClass then
|
|
existingQuery.subClass = nil
|
|
end
|
|
tinsert(existingQuery.items, itemString)
|
|
else
|
|
itemQuery.name = name
|
|
itemQuery.items = {itemString}
|
|
tinsert(queries, itemQuery)
|
|
end
|
|
end
|
|
return queries
|
|
end
|
|
|
|
local function GetCommonQueryInfoClass(class, items)
|
|
local resultQuery = TSMAPI:GetAuctionQueryInfo(items[1])
|
|
resultQuery.name = ""
|
|
resultQuery.class = class
|
|
for i=2, #items do
|
|
local itemQuery = TSMAPI:GetAuctionQueryInfo(items[i])
|
|
resultQuery.minLevel = min(resultQuery.minLevel, itemQuery.minLevel)
|
|
resultQuery.maxLevel = max(resultQuery.maxLevel, itemQuery.maxLevel)
|
|
resultQuery.quality = min(resultQuery.quality, itemQuery.quality)
|
|
if resultQuery.subClass ~= itemQuery.subClass then resultQuery.subClass = nil end
|
|
end
|
|
resultQuery.items = items
|
|
return {resultQuery}
|
|
end
|
|
|
|
local function GreatestSubstring(str1, str2)
|
|
local parts1 = {(" "):split(str1)}
|
|
local parts2 = {(" "):split(str2)}
|
|
for i=1, #parts1 do
|
|
if parts1[i] ~= parts2[i] then
|
|
local subStr = table.concat(parts1, " ", 1, i-1)
|
|
return subStr ~= "" and subStr
|
|
end
|
|
end
|
|
return table.concat(parts1, " ")
|
|
end
|
|
|
|
local function ReduceStrings(strList)
|
|
local didReduction = true
|
|
while didReduction do
|
|
didReduction = false
|
|
for i=1, #strList-1 do
|
|
if i > #strList-1 then break end
|
|
local subStr = GreatestSubstring(strList[i], strList[i+1])
|
|
if subStr then
|
|
strList[i] = subStr
|
|
tremove(strList, i+1)
|
|
didReduction = true
|
|
end
|
|
end
|
|
if not private.thread then return end
|
|
private.thread:Yield()
|
|
end
|
|
return true
|
|
end
|
|
|
|
local function NumPagesCallback(event, numPages)
|
|
if event == "NUM_PAGES" then
|
|
local skippedItems = {}
|
|
local score = max(#private.combinedQueries[1].items-numPages, 0)
|
|
if private.combinedQueries[1].name == "" then
|
|
-- This is a common class term so determine if we should use this or not.
|
|
local cost = 0
|
|
for _, query in ipairs(private.queries) do
|
|
if query.score and query.class == private.combinedQueries[1].class then
|
|
cost = cost + query.score
|
|
end
|
|
end
|
|
if score >= cost and score > 0 then
|
|
-- use the common class term
|
|
for i=#private.queries, 1, -1 do
|
|
local query = private.queries[i]
|
|
local shouldRemove = (query.class == private.combinedQueries[1].class)
|
|
if shouldRemove then
|
|
tremove(private.queries, i)
|
|
end
|
|
end
|
|
tinsert(private.queries, private.combinedQueries[1])
|
|
end
|
|
else
|
|
if numPages > #private.combinedQueries[1].items then
|
|
for _, itemString in ipairs(private.combinedQueries[1].items) do
|
|
local query = TSMAPI:GetAuctionQueryInfo(itemString)
|
|
query.items = {itemString}
|
|
query.score = 0
|
|
tinsert(private.queries, query)
|
|
end
|
|
elseif numPages == 0 then
|
|
for _, itemString in ipairs(private.combinedQueries[1].items) do
|
|
tinsert(skippedItems, itemString)
|
|
end
|
|
else
|
|
-- use the common search term
|
|
private.combinedQueries[1].score = score
|
|
tinsert(private.queries, private.combinedQueries[1])
|
|
end
|
|
end
|
|
tremove(private.combinedQueries, 1)
|
|
private.callback("QUERY_UPDATE", private.totalQueries-#private.combinedQueries, private.totalQueries, skippedItems)
|
|
end
|
|
private:CheckNextCombinedQuery()
|
|
end
|
|
|
|
function private:CheckNextCombinedQuery()
|
|
if not private.isScanning then return end
|
|
|
|
if #private.combinedQueries == 0 then
|
|
-- we're done
|
|
sort(private.queries, function(a, b) return a.name < b.name end)
|
|
TSM:StopGeneratingQueries()
|
|
TSMAPI:CreateTimeDelay("queryUtilCallbackDelay", 0.05, function() private.callback("QUERY_COMPLETE", private.queries) end)
|
|
return
|
|
end
|
|
|
|
for _, itemString in ipairs(private.combinedQueries[1].items) do
|
|
if strlower(private.combinedQueries[1].name) == strlower(TSMAPI:GetSafeItemInfo(itemString)) then
|
|
-- One of the items in this combined query is the same as the common search term,
|
|
-- so it's always worth using this common search term.
|
|
NumPagesCallback("NUM_PAGES", 1)
|
|
return
|
|
end
|
|
end
|
|
|
|
TSMAPI.AuctionScan:GetNumPages(private.combinedQueries[1], NumPagesCallback)
|
|
end
|
|
|
|
local function GenerateSearchTerms(names, itemList, isReversed)
|
|
sort(names)
|
|
if not ReduceStrings(names) then return end -- run the reduction
|
|
|
|
-- create a table associating all the reduced names to a list of items
|
|
local temp = {}
|
|
for i, filterName in ipairs(names) do
|
|
for j, itemString in ipairs(itemList) do
|
|
local itemName = TSMAPI:GetSafeItemInfo(itemString)
|
|
itemName = itemName and isReversed and strrev(itemName) or itemName -- reverse item name if necessary
|
|
if itemName and strfind(itemName, "^"..TSMAPI:StrEscape(filterName)) then
|
|
temp[filterName] = temp[filterName] or {}
|
|
tinsert(temp[filterName], itemString)
|
|
end
|
|
end
|
|
if not private.thread then return end
|
|
private.thread:Yield()
|
|
end
|
|
|
|
return temp
|
|
end
|
|
|
|
local function GenerateQueriesThread(self)
|
|
private.thread = self
|
|
local function GenerateFilters(reverse)
|
|
-- create a list of all item names
|
|
local names = {}
|
|
for _, itemString in ipairs(private.itemList) do
|
|
local name = TSMAPI:GetSafeItemInfo(itemString)
|
|
if type(name) == "string" and name ~= "" then
|
|
tinsert(names, reverse and strrev(name) or name)
|
|
end
|
|
end
|
|
if not private.thread then return end
|
|
|
|
local filters, tempFilters, tempItems = {}, {}, {}
|
|
local numFilters = 0
|
|
local tbl = GenerateSearchTerms(names, private.itemList, reverse)
|
|
if not tbl then return end
|
|
for filterName, items in pairs(tbl) do
|
|
if #items > 1 then
|
|
filters[reverse and strrev(filterName) or filterName] = items
|
|
numFilters = numFilters + 1
|
|
else
|
|
tinsert(tempFilters, strrev(filterName)) -- reverse name for second pass
|
|
for _, itemString in ipairs(items) do
|
|
tinsert(tempItems, itemString)
|
|
end
|
|
end
|
|
end
|
|
|
|
-- try to find common search terms of reversed item names
|
|
local tbl = GenerateSearchTerms(tempFilters, tempItems, not reverse)
|
|
if not tbl then return end
|
|
for filterName, items in pairs(tbl) do
|
|
filters[reverse and filterName or strrev(filterName)] = items
|
|
numFilters = numFilters + 1
|
|
end
|
|
|
|
return filters, numFilters
|
|
end
|
|
|
|
local endTime = debugprofilestop() + 5000
|
|
while debugprofilestop() < endTime do
|
|
-- request all the item info
|
|
local tryAgain = false
|
|
for _, itemString in ipairs(private.itemList) do
|
|
if not TSMAPI:GetSafeItemInfo(itemString) then
|
|
tryAgain = true
|
|
end
|
|
end
|
|
if not tryAgain then break end
|
|
self:Sleep(0.1)
|
|
end
|
|
|
|
local filters1, num1 = GenerateFilters()
|
|
local filters2, num2 = GenerateFilters(true)
|
|
if not filters1 or not filters2 then return end
|
|
local filters = num2 < num1 and filters2 or filters1
|
|
|
|
-- generate class filters
|
|
local itemClasses = {}
|
|
local classes = {GetAuctionItemClasses()}
|
|
for _, itemString in ipairs(private.itemList) do
|
|
local classIndex = GetItemClasses(itemString)
|
|
if classIndex then
|
|
itemClasses[classIndex] = itemClasses[classIndex] or {}
|
|
tinsert(itemClasses[classIndex], itemString)
|
|
end
|
|
end
|
|
|
|
-- create the actual queries
|
|
local queries, combinedQueries = {}, {}
|
|
for filterName, items in pairs(filters) do
|
|
for _, query in ipairs(GetCommonQueryInfo(filterName, items)) do
|
|
if #query.items > 1 then
|
|
tinsert(combinedQueries, query)
|
|
else
|
|
tinsert(queries, query)
|
|
end
|
|
end
|
|
end
|
|
for class, items in pairs(itemClasses) do
|
|
for _, query in ipairs(GetCommonQueryInfoClass(class, items)) do
|
|
if #query.items > 1 then
|
|
tinsert(combinedQueries, query)
|
|
end
|
|
end
|
|
end
|
|
|
|
private.isScanning = true
|
|
private.queries = queries
|
|
private.combinedQueries = combinedQueries
|
|
private.totalQueries = #combinedQueries
|
|
end
|
|
|
|
function TSMAPI:GenerateQueries(itemList, callback)
|
|
if private.thread then return end
|
|
private.itemList = itemList
|
|
private.callback = callback
|
|
|
|
local function ThreadDone()
|
|
if private.thread then
|
|
private.thread = nil
|
|
private:CheckNextCombinedQuery()
|
|
end
|
|
end
|
|
TSMAPI.Threading:Start(GenerateQueriesThread, 0.5, ThreadDone)
|
|
end
|
|
|
|
function TSM:StopGeneratingQueries()
|
|
private.thread = nil
|
|
private.isScanning = nil
|
|
end |