TradeSkillMaster/LibTSM/Service/AuctionScanClasses/ResultRow.lua

672 lines
20 KiB
Lua
Raw Permalink Normal View History

2020-11-13 14:13:12 -05:00
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- https://tradeskillmaster.com --
-- All Rights Reserved - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
local _, TSM = ...
local ResultRow = TSM.Init("Service.AuctionScanClasses.ResultRow")
local ItemString = TSM.Include("Util.ItemString")
local ObjectPool = TSM.Include("Util.ObjectPool")
local TempTable = TSM.Include("Util.TempTable")
local Table = TSM.Include("Util.Table")
local ItemInfo = TSM.Include("Service.ItemInfo")
local LibTSMClass = TSM.Include("LibTSMClass")
local Util = TSM.Include("Service.AuctionScanClasses.Util")
local AuctionHouseWrapper = TSM.Include("Service.AuctionHouseWrapper")
local ResultSubRow = TSM.Include("Service.AuctionScanClasses.ResultSubRow")
local ResultRowWrapper = LibTSMClass.DefineClass("ResultRowWrapper")
local private = {
objectPool = ObjectPool.New("AUCTION_SCAN_RESULT_ROW", ResultRowWrapper),
}
local SUB_ROW_SEARCH_INDEX_MULTIPLIER = 1000000
-- ============================================================================
-- Module Functions
-- ============================================================================
function ResultRow.Get(query, itemKey, minPrice, totalQuantity)
local row = private.objectPool:Get()
row:_Acquire(query, itemKey, minPrice, totalQuantity)
return row
end
-- ============================================================================
-- ResultRowWrapper - Meta Class Methods
-- ============================================================================
function ResultRowWrapper.__init(self)
self._query = nil
self._items = {}
self._baseItemString = nil
self._canHaveNonBaseItemString = nil
self._minPrice = nil
self._hasItemInfo = nil
self._isCommodity = nil
self._notFiltered = false
self._searchIndex = nil
self._subRows = {}
self._minBrowseId = nil
end
function ResultRowWrapper._Acquire(self, query, item, minPrice, totalQuantity)
self._query = query
if TSM.IsWowClassic() then
assert(not minPrice and not totalQuantity)
tinsert(self._items, item)
self._baseItemString = ItemString.GetBase(item)
else
item._minPrice = minPrice
item._totalQuantity = totalQuantity
tinsert(self._items, item)
self._baseItemString = ItemString.GetBaseFromItemKey(item)
end
self._canHaveNonBaseItemString = nil
self._minPrice = nil
end
-- ============================================================================
-- ResultRowWrapper - Public Class Methods
-- ============================================================================
function ResultRowWrapper.Merge(self, item, minPrice, totalQuantity)
-- check if we already have this item
for i = 1, #self._items do
if item == self._items[i] then
return
end
if type(item) == "table" then
local isEqual = true
for k in pairs(item) do
if item[k] ~= self._items[i][k] then
isEqual = false
break
end
end
if isEqual then
return
end
end
end
self._hasItemInfo = nil
if TSM.IsWowClassic() then
assert(not minPrice and not totalQuantity)
assert(self._baseItemString == ItemString.GetBase(item))
tinsert(self._items, item)
self._notFiltered = false
else
assert(self._baseItemString == ItemString.GetBaseFromItemKey(item))
item._minPrice = minPrice
item._totalQuantity = totalQuantity
tinsert(self._items, item)
self._notFiltered = false
end
self._canHaveNonBaseItemString = nil
end
function ResultRowWrapper.Release(self)
wipe(self._items)
self._baseItemString = nil
self._canHaveNonBaseItemString = nil
self._minPrice = nil
self._hasItemInfo = nil
self._isCommodity = nil
self._notFiltered = false
self._searchIndex = nil
self._minBrowseId = nil
for _, subRow in pairs(self._subRows) do
subRow:Release()
end
wipe(self._subRows)
private.objectPool:Recycle(self)
end
function ResultRowWrapper.IsSubRow(self)
return false
end
function ResultRowWrapper.PopulateBrowseData(self)
assert(self._baseItemString)
if self._hasItemInfo then
-- already have our item info
return true
elseif not Util.HasItemInfo(self._baseItemString) then
-- don't have item info yet
return false
end
if not TSM.IsWowClassic() then
-- cache the commodity status since it's referenced a ton
if self._isCommodity == nil then
self._isCommodity = ItemInfo.IsCommodity(self._baseItemString)
assert(self._isCommodity ~= nil)
end
end
-- check if we have info for all the items and try to fetch it if not
local missingInfo = false
for _, item in ipairs(self._items) do
if TSM.IsWowClassic() then
if not Util.HasItemInfo(ItemString.Get(item)) then
missingInfo = true
end
else
if not item._itemKeyInfo then
item._itemKeyInfo = C_AuctionHouse.GetItemKeyInfo(item, true)
if not item._itemKeyInfo then
missingInfo = true
end
end
end
end
if missingInfo then
return false
end
self._hasItemInfo = true
return true
end
function ResultRowWrapper.IsFiltered(self, query)
assert(#self._items > 0)
if self._notFiltered then
return false
end
-- check if the whole row is filtered
if query:_IsFiltered(self, false) then
return true
end
-- filter our items
for i = #self._items, 1, -1 do
if query:_IsFiltered(self, false, self._items[i]) then
tremove(self._items, i)
end
end
self._canHaveNonBaseItemString = nil
self._minPrice = nil
if #self._items == 0 then
-- no more items, so the entire row is filtered
return true
end
-- not filtered (cache this result)
self._notFiltered = true
return false
end
function ResultRowWrapper.SearchReset(self)
assert(not TSM.IsWowClassic())
assert(#self._items > 0)
self._searchIndex = 1
end
function ResultRowWrapper.SearchNext(self)
assert(not TSM.IsWowClassic())
assert(self._searchIndex)
if self._searchIndex == #self._items then
self._searchIndex = nil
return false
end
self._searchIndex = self._searchIndex + 1
return true
end
function ResultRowWrapper.SearchIsReady(self)
assert(not TSM.IsWowClassic())
assert(self._searchIndex)
-- the client needs to have the item key info cached before we can run the search
return C_AuctionHouse.GetItemKeyInfo(self._items[self._searchIndex], true) and true or false
end
function ResultRowWrapper.SearchSend(self)
assert(not TSM.IsWowClassic())
assert(self._searchIndex)
local itemKey = self._items[self._searchIndex]
-- send a sell query if we don't have browse results for the itemKey
-- for some reason sell queries don't work for commodities or pets
local isSellQuery = not self._isCommodity and not ItemString.IsPet(self._baseItemString) and not itemKey._totalQuantity
return AuctionHouseWrapper.SendSearchQuery(itemKey, isSellQuery)
end
function ResultRowWrapper.HasCachedSearchData(self)
local itemKey = self._items[self._searchIndex]
if self._isCommodity then
return C_AuctionHouse.HasFullCommoditySearchResults(itemKey.itemID)
else
return C_AuctionHouse.HasFullItemSearchResults(itemKey)
end
end
function ResultRowWrapper.SearchCheckStatus(self)
assert(not TSM.IsWowClassic())
assert(self._searchIndex)
local itemKey = self._items[self._searchIndex]
-- check if we have the full results
local hasFullResults = nil
if self._isCommodity then
hasFullResults = C_AuctionHouse.HasFullCommoditySearchResults(itemKey.itemID)
else
hasFullResults = C_AuctionHouse.HasFullItemSearchResults(itemKey)
end
if hasFullResults then
return true
end
-- request more results
if self._isCommodity then
return false, AuctionHouseWrapper.RequestMoreCommoditySearchResults(itemKey.itemID)
else
return false, AuctionHouseWrapper.RequestMoreItemSearchResults(itemKey)
end
end
function ResultRowWrapper.PopulateSubRows(self, browseId, index, itemLink)
if TSM.IsWowClassic() then
-- remove any prior results with a different browseId
assert(index and not self._searchIndex)
local subRow = ResultSubRow.Get(self)
subRow:_SetRawData(index, browseId, itemLink)
local _, hashNoSeller = subRow:GetHashes()
if self._minBrowseId and self._minBrowseId ~= browseId then
-- check if this subRow already exists with a prior browseId
for i, existingSubRow in ipairs(self._subRows) do
local _, existingHashNoSeller = existingSubRow:GetHashes()
local _, _, existingBrowseId = existingSubRow:GetListingInfo()
if hashNoSeller == existingHashNoSeller and browseId ~= existingBrowseId then
-- replace the existing subRow
existingSubRow:Release()
self._subRows[i] = subRow
return
end
end
end
tinsert(self._subRows, subRow)
else
assert(self._searchIndex and not index)
local subRowOffset = self._searchIndex * SUB_ROW_SEARCH_INDEX_MULTIPLIER
local itemKey = self._items[self._searchIndex]
local numAuctions = nil
if self:IsCommodity() then
numAuctions = C_AuctionHouse.GetNumCommoditySearchResults(itemKey.itemID)
else
numAuctions = C_AuctionHouse.GetNumItemSearchResults(itemKey)
end
if itemKey._numAuctions and numAuctions ~= itemKey._numAuctions then
-- the results changed so clear out our existing data
for i = itemKey._numAuctions, 1, -1 do
if i > numAuctions then
self._subRows[subRowOffset + i]:Release()
self._subRows[subRowOffset + i] = nil
else
self._subRows[subRowOffset + i]:_SetRawData(nil)
end
end
end
itemKey._numAuctions = numAuctions
for i = 1, numAuctions do
self._subRows[subRowOffset + i] = self._subRows[subRowOffset + i] or ResultSubRow.Get(self)
local subRow = self._subRows[subRowOffset + i]
if not subRow:HasRawData() or not subRow:HasOwners() then
local result = nil
if self:IsCommodity() then
result = C_AuctionHouse.GetCommoditySearchResultInfo(itemKey.itemID, i)
else
result = C_AuctionHouse.GetItemSearchResultInfo(itemKey, i)
end
subRow:_SetRawData(result, browseId)
end
end
end
self._minBrowseId = min(self._minBrowseId or math.huge, browseId)
end
function ResultRowWrapper.FilterSubRows(self, query)
local subRowOffset = TSM.IsWowClassic() and 0 or (self._searchIndex * SUB_ROW_SEARCH_INDEX_MULTIPLIER)
if TSM.IsWowClassic() then
for i = #self._subRows, 1, -1 do
if query:_IsFiltered(self._subRows[i], true) then
self:_RemoveSubRowByIndex(i)
end
end
else
local itemKey = self._items[self._searchIndex]
for j = itemKey._numAuctions, 1, -1 do
local subRow = self._subRows[subRowOffset + j]
if query:_IsFiltered(subRow, true) then
self:_RemoveSubRowByIndex(j)
end
end
end
-- merge subRows with identical hashes
local numSubRows = nil
local hashIndexLookup = TempTable.Acquire()
local index = 1
while true do
numSubRows = TSM.IsWowClassic() and #self._subRows or self._items[self._searchIndex]._numAuctions
if index > numSubRows then
break
end
local subRow = self._subRows[subRowOffset + index]
local hash = subRow:GetHashes()
local prevIndex = hashIndexLookup[hash]
if prevIndex then
-- there was a previous subRow with the same hash
self._subRows[subRowOffset + prevIndex]:Merge(subRow)
-- remove this subRow
self:_RemoveSubRowByIndex(index)
else
hashIndexLookup[hash] = index
index = index + 1
end
end
TempTable.Release(hashIndexLookup)
return numSubRows == 0
end
function ResultRowWrapper.GetNumSubRows(self)
if TSM.IsWowClassic() then
return #self._subRows
else
local result = 0
for _, itemKey in ipairs(self._items) do
result = result + (itemKey._numAuctions or 0)
end
return result
end
end
function ResultRowWrapper.SubRowIterator(self, searchOnly)
if TSM.IsWowClassic() then
return ipairs(self._subRows)
else
if searchOnly then
local result = TempTable.Acquire()
assert(self._searchIndex)
for i = 1, self._items[self._searchIndex]._numAuctions do
local subRow = self._subRows[self._searchIndex * SUB_ROW_SEARCH_INDEX_MULTIPLIER + i]
assert(subRow)
tinsert(result, subRow)
end
return TempTable.Iterator(result)
else
return private.SubRowIteratorHelper, self, SUB_ROW_SEARCH_INDEX_MULTIPLIER
end
end
end
function ResultRowWrapper.IsCommodity(self)
assert(self._isCommodity ~= nil)
return self._isCommodity
end
function ResultRowWrapper.HasItemInfo(self)
return self._hasItemInfo
end
function ResultRowWrapper.GetBaseItemString(self)
return self._baseItemString
end
function ResultRowWrapper.GetItemString(self)
if TSM.IsWowClassic() or not self._hasItemInfo or self._canHaveNonBaseItemString then
return nil
end
if self._canHaveNonBaseItemString == nil then
for _, itemKey in ipairs(self._items) do
if ItemInfo.CanHaveVariations(self._baseItemString) or itemKey.battlePetSpeciesID ~= 0 or itemKey.itemSuffix ~= 0 or itemKey.itemLevel ~= 0 then
-- this item can have variations, so we don't know its itemString
self._canHaveNonBaseItemString = true
return nil
end
end
self._canHaveNonBaseItemString = false
end
return self._baseItemString
end
function ResultRowWrapper.GetItemInfo(self, itemKey)
if TSM.IsWowClassic() or not self._hasItemInfo then
return nil, nil, nil, nil
end
itemKey = itemKey or (#self._items == 1 and self._items[1] or nil)
assert(not itemKey or itemKey._itemKeyInfo)
local baseItemString = self:GetBaseItemString()
local itemString = self:GetItemString()
local itemName, quality, itemLevel, maxItemLevel = nil, nil, nil, nil
if itemString then
-- this item can't have variations, so we can know the name / level / quality
itemName = ItemInfo.GetName(baseItemString)
itemLevel = ItemInfo.GetItemLevel(baseItemString)
quality = ItemInfo.GetQuality(baseItemString)
assert(itemName and itemLevel and quality)
else
if itemKey and not itemKey._totalQuantity then
-- if we didn't do a browse, then don't use this itemKey
itemKey = nil
end
if itemKey then
-- grab the name from the itemKeyInfo
itemName = itemKey._itemKeyInfo.itemName
assert(itemName)
end
local hasSingleAuction = itemKey and itemKey._totalQuantity == 1
if hasSingleAuction then
-- grab the quality from the itemKeyInfo since there's only one listing
quality = itemKey._itemKeyInfo.quality
assert(quality)
end
if not ItemString.IsPet(self._baseItemString) then
-- for non-pets, we can maybe grab the itemLevel from the itemKey
if itemKey then
itemLevel = itemKey.itemLevel ~= 0 and itemKey.itemLevel or nil
else
-- only use the itemLevel from the itemKeys if they are all the same
local itemKeyItemLevel = self._items[1].itemLevel
for i = 2, #self._items do
if self._items[i].itemLevel ~= itemKeyItemLevel then
itemKeyItemLevel = nil
break
end
end
itemLevel = (itemKeyItemLevel or 0) ~= 0 and itemKeyItemLevel or nil
end
elseif itemKey and itemKey._itemKeyInfo.battlePetLink then
if hasSingleAuction then
-- grab the itemLevel from the link since there's only one listing
itemLevel = ItemInfo.GetItemLevel(itemKey._itemKeyInfo.battlePetLink)
assert(itemLevel)
else
-- grab the maxItemLevel from the link
maxItemLevel = ItemInfo.GetItemLevel(itemKey._itemKeyInfo.battlePetLink)
assert(maxItemLevel)
end
end
end
return itemName, quality, itemLevel, maxItemLevel
end
function ResultRowWrapper.GetBuyouts(self, resultItemKey)
if TSM.IsWowClassic() then
return nil, nil, nil
end
assert(#self._items > 0)
if resultItemKey then
return nil, nil, resultItemKey._minPrice
else
if self._minPrice == nil then
for _, itemKey in ipairs(self._items) do
if not itemKey._minPrice then
self._minPrice = -1
return nil, nil, nil
end
self._minPrice = min(self._minPrice or math.huge, itemKey._minPrice)
end
elseif self._minPrice == -1 then
return nil, nil, nil
end
return nil, nil, self._minPrice
end
end
function ResultRowWrapper.GetQuantities(self)
local totalQuantity = 0
if TSM.IsWowClassic() then
for _, subRow in ipairs(self._subRows) do
local quantity, numAuctions = subRow:GetQuantities()
totalQuantity = totalQuantity + quantity * numAuctions
end
else
for _, itemKey in ipairs(self._items) do
if not itemKey._totalQuantity then
return
end
totalQuantity = totalQuantity + itemKey._totalQuantity
end
end
return totalQuantity, 1
end
function ResultRowWrapper.GetMaxQuantities(self)
assert(self:IsCommodity())
local totalQuantity = 0
for _, subRow in self:SubRowIterator() do
local _, numOwnerItems = subRow:GetOwnerInfo()
local quantityAvailable = subRow:GetQuantities() - numOwnerItems
totalQuantity = totalQuantity + quantityAvailable
end
return totalQuantity
end
function ResultRowWrapper.RemoveSubRow(self, subRow)
local index = Table.KeyByValue(self._subRows, subRow)
if TSM.IsWowClassic() then
self:_RemoveSubRowByIndex(index)
else
local searchIndex = floor(index / SUB_ROW_SEARCH_INDEX_MULTIPLIER)
index = index % SUB_ROW_SEARCH_INDEX_MULTIPLIER
assert(self._subRows[searchIndex * SUB_ROW_SEARCH_INDEX_MULTIPLIER + index] == subRow)
local prevSearchIndex = self._searchIndex
self._searchIndex = searchIndex
self:_RemoveSubRowByIndex(index)
self._searchIndex = prevSearchIndex
end
self._query:_OnSubRowRemoved(self)
end
function ResultRowWrapper.WipeSearchResults(self)
wipe(self._subRows)
if not TSM.IsWowClassic() then
for _, itemKey in ipairs(self._items) do
itemKey._numAuctions = nil
end
end
end
function ResultRowWrapper.GetQuery(self)
return self._query
end
function ResultRowWrapper.DecrementQuantity(self, amount)
assert(self:IsCommodity() and not TSM.IsWowClassic() and #self._items == 1)
local index = 1
while amount > 0 do
local subRow = self._subRows[index + SUB_ROW_SEARCH_INDEX_MULTIPLIER]
assert(subRow)
local _, numOwnerItems = subRow:GetOwnerInfo()
local quantityAvailable = subRow:GetQuantities() - numOwnerItems
if quantityAvailable > 0 then
local usedQuantity = min(quantityAvailable, amount)
local prevItemBuyout = floor(subRow._buyout / subRow._quantity)
amount = amount - usedQuantity
subRow._quantity = subRow._quantity - usedQuantity
subRow._buyout = prevItemBuyout * subRow._quantity
subRow._minBid = subRow._buyout
if numOwnerItems == 0 and subRow._quantity == 0 then
self:RemoveSubRow(subRow)
else
index = index + 1
end
else
index = index + 1
end
end
end
function ResultRowWrapper.GetMinBrowseId(self)
return self._minBrowseId
end
-- ============================================================================
-- ResultRowWrapper - Private Class Methods
-- ============================================================================
function ResultRowWrapper._RemoveSubRowByIndex(self, index)
if TSM.IsWowClassic() then
self._subRows[index]:Release()
tremove(self._subRows, index)
else
local subRowOffset = self._searchIndex * SUB_ROW_SEARCH_INDEX_MULTIPLIER
local itemKey = self._items[self._searchIndex]
self._subRows[subRowOffset + index]:Release()
self._subRows[subRowOffset + index] = nil
-- shift the other subRows for this item down
for i = index, itemKey._numAuctions - 1 do
self._subRows[subRowOffset + i] = self._subRows[subRowOffset + i + 1]
end
self._subRows[subRowOffset + itemKey._numAuctions] = nil
itemKey._numAuctions = itemKey._numAuctions - 1
end
end
function ResultRowWrapper._GetSearchProgress(self)
assert(not TSM.IsWowClassic())
if #self._items == 0 then
return 0
end
local numSearched = 0
for _, itemKey in ipairs(self._items) do
if itemKey._numAuctions then
numSearched = numSearched + 1
end
end
return numSearched / #self._items
end
-- ============================================================================
-- Private Helper Functions
-- ============================================================================
function private.SubRowIteratorHelper(row, index)
local searchIndex = floor(index / SUB_ROW_SEARCH_INDEX_MULTIPLIER)
local subRowIndex = index % SUB_ROW_SEARCH_INDEX_MULTIPLIER
while true do
local itemKey = row._items[searchIndex]
if not itemKey then
return
end
if subRowIndex >= (itemKey._numAuctions or 0) then
searchIndex = searchIndex + 1
subRowIndex = 0
else
subRowIndex = subRowIndex + 1
index = searchIndex * SUB_ROW_SEARCH_INDEX_MULTIPLIER + subRowIndex
return index, row._subRows[index]
end
end
end