You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
711 lines
25 KiB
711 lines
25 KiB
-- ------------------------------------------------------------------------------ --
|
|
-- TradeSkillMaster --
|
|
-- https://tradeskillmaster.com --
|
|
-- All Rights Reserved - Detailed license information included with addon. --
|
|
-- ------------------------------------------------------------------------------ --
|
|
|
|
local TSM = select(2, ...) ---@type TSM
|
|
local BagTracking = TSM.Init("Service.BagTracking") ---@class Service.BagTracking
|
|
local Environment = TSM.Include("Environment")
|
|
local Container = TSM.Include("Util.Container")
|
|
local Database = TSM.Include("Util.Database")
|
|
local Delay = TSM.Include("Util.Delay")
|
|
local Event = TSM.Include("Util.Event")
|
|
local SlotId = TSM.Include("Util.SlotId")
|
|
local Log = TSM.Include("Util.Log")
|
|
local Table = TSM.Include("Util.Table")
|
|
local TempTable = TSM.Include("Util.TempTable")
|
|
local ItemString = TSM.Include("Util.ItemString")
|
|
local DefaultUI = TSM.Include("Service.DefaultUI")
|
|
local ItemInfo = TSM.Include("Service.ItemInfo")
|
|
local TooltipScanning = TSM.Include("Service.TooltipScanning")
|
|
local InventoryInfo = TSM.Include("Service.InventoryInfo")
|
|
local Settings = TSM.Include("Service.Settings")
|
|
local private = {
|
|
slotDB = nil,
|
|
quantityDB = nil,
|
|
settings = nil,
|
|
bagUpdates = {
|
|
pending = {},
|
|
bagList = {},
|
|
bankList = {},
|
|
},
|
|
bankSlotUpdates = {
|
|
pending = {},
|
|
list = {},
|
|
},
|
|
reagentBankSlotUpdates = {
|
|
pending = {},
|
|
list = {},
|
|
},
|
|
isFirstBankOpen = true,
|
|
baseItemQuantityQuery = nil,
|
|
callbackQuery = nil, -- luacheck: ignore 1004 - just stored for GC reasons
|
|
callbacks = {},
|
|
bagUpdateTimer = nil,
|
|
bagUpdateDelayedTimer = nil,
|
|
bankSlotUpdateTimer = nil,
|
|
reagentBankSlotUpdateTimer = nil,
|
|
bagTrackingTimer = nil,
|
|
itemLocation = ItemLocation:CreateEmpty(),
|
|
}
|
|
local BANK_BAG_SLOTS = {}
|
|
|
|
|
|
|
|
-- ============================================================================
|
|
-- Population of the Static Data
|
|
-- ============================================================================
|
|
|
|
do
|
|
BANK_BAG_SLOTS[BANK_CONTAINER] = true
|
|
local firstBankBag, lastBankBag = Container.GetBankBagIndexes()
|
|
for i = firstBankBag, lastBankBag do
|
|
BANK_BAG_SLOTS[i] = true
|
|
end
|
|
if Environment.HasFeature(Environment.FEATURES.REAGENT_BANK) then
|
|
BANK_BAG_SLOTS[REAGENTBANK_CONTAINER] = true
|
|
end
|
|
end
|
|
|
|
|
|
|
|
-- ============================================================================
|
|
-- Module Loading
|
|
-- ============================================================================
|
|
|
|
BagTracking:OnSettingsLoad(function()
|
|
Event.Register("BAG_UPDATE", private.BagUpdateHandler)
|
|
if Environment.IsWrathClassic() or Environment.IsRetail() then
|
|
-- In Wrath 3.4.1 and in Retail 10.0.5, BAG_UPDATE_DELAYED doesnt fire for non-backpack slots, so emulate it
|
|
private.bagUpdateDelayedTimer = Delay.CreateTimer("BAG_TRACKING_BAG_UPDATE_DELAYED", private.BagUpdateDelayedHandler)
|
|
Event.Register("BAG_UPDATE", function() private.bagUpdateDelayedTimer:RunForFrames(0) end)
|
|
else
|
|
Event.Register("BAG_UPDATE_DELAYED", private.BagUpdateDelayedHandler)
|
|
end
|
|
DefaultUI.RegisterBankVisibleCallback(private.BankVisible, true)
|
|
Event.Register("PLAYERBANKSLOTS_CHANGED", private.BankSlotChangedHandler)
|
|
if Environment.HasFeature(Environment.FEATURES.REAGENT_BANK) then
|
|
Event.Register("PLAYERREAGENTBANKSLOTS_CHANGED", private.ReagentBankSlotChangedHandler)
|
|
end
|
|
private.slotDB = Database.NewSchema("BAG_TRACKING_SLOTS")
|
|
:AddUniqueNumberField("slotId")
|
|
:AddNumberField("bag")
|
|
:AddNumberField("slot")
|
|
:AddStringField("itemLink")
|
|
:AddStringField("itemString")
|
|
:AddSmartMapField("baseItemString", ItemString.GetBaseMap(), "itemString")
|
|
:AddSmartMapField("levelItemString", ItemString.GetLevelMap(), "itemString")
|
|
:AddNumberField("itemTexture")
|
|
:AddNumberField("quantity")
|
|
:AddBooleanField("isBoP")
|
|
:AddBooleanField("isBoA")
|
|
:AddIndex("slotId")
|
|
:AddIndex("bag")
|
|
:AddIndex("itemString")
|
|
:AddIndex("baseItemString")
|
|
:AddIndex("levelItemString")
|
|
:Commit()
|
|
private.quantityDB = Database.NewSchema("BAG_TRACKING_QUANTITY")
|
|
:AddUniqueStringField("levelItemString")
|
|
:AddNumberField("bagQuantity")
|
|
:AddNumberField("bankQuantity")
|
|
:AddNumberField("reagentBankQuantity")
|
|
:AddSmartMapField("baseItemString", ItemString.GetBaseMap(), "levelItemString")
|
|
:AddIndex("baseItemString")
|
|
:Commit()
|
|
private.baseItemQuantityQuery = private.quantityDB:NewQuery()
|
|
:Select("bagQuantity", "bankQuantity", "reagentBankQuantity")
|
|
:Equal("baseItemString", Database.BoundQueryParam())
|
|
private.callbackQuery = private.slotDB:NewQuery()
|
|
:SetUpdateCallback(private.OnCallbackQueryUpdated)
|
|
private.settings = Settings.NewView()
|
|
:AddKey("sync", "internalData", "bagQuantity")
|
|
:AddKey("sync", "internalData", "bankQuantity")
|
|
:AddKey("sync", "internalData", "reagentBankQuantity")
|
|
|
|
private.bagUpdateTimer = Delay.CreateTimer("BAG_TRACKING_BAG_UPDATE", private.BagUpdateDelayedHandler)
|
|
private.bankSlotUpdateTimer = Delay.CreateTimer("BAG_TRACKING_BANK_SLOT_UPDATE", private.BankSlotUpdateDelayed)
|
|
private.reagentBankSlotUpdateTimer = Delay.CreateTimer("BAG_TRACKING_REAGENT_BANK_SLOT_UPDATE", private.ReagentBankSlotUpdateDelayed)
|
|
private.bagTrackingTimer = Delay.CreateTimer("BAG_TRACKING_BAG_TRACKING", private.DelayedBagTrackingCallback)
|
|
Table.Filter(private.settings.bagQuantity, private.FilterNonItemLevelStrings)
|
|
Table.Filter(private.settings.bankQuantity, private.FilterNonItemLevelStrings)
|
|
Table.Filter(private.settings.reagentBankQuantity, private.FilterNonItemLevelStrings)
|
|
local items = TempTable.Acquire()
|
|
for levelItemString in pairs(private.settings.bagQuantity) do
|
|
items[levelItemString] = true
|
|
end
|
|
for levelItemString in pairs(private.settings.bankQuantity) do
|
|
items[levelItemString] = true
|
|
end
|
|
for levelItemString in pairs(private.settings.reagentBankQuantity) do
|
|
items[levelItemString] = true
|
|
end
|
|
private.quantityDB:BulkInsertStart()
|
|
for levelItemString in pairs(items) do
|
|
local bagQuantity = private.settings.bagQuantity[levelItemString] or 0
|
|
local bankQuantity = private.settings.bankQuantity[levelItemString] or 0
|
|
local reagentBankQuantity = private.settings.reagentBankQuantity[levelItemString] or 0
|
|
if (bagQuantity + bankQuantity + reagentBankQuantity) > 0 then
|
|
private.quantityDB:BulkInsertNewRow(levelItemString, bagQuantity, bankQuantity, reagentBankQuantity)
|
|
end
|
|
end
|
|
private.quantityDB:BulkInsertEnd()
|
|
TempTable.Release(items)
|
|
end)
|
|
|
|
BagTracking:OnGameDataLoad(function()
|
|
-- we'll scan all the bags right away, so wipe the existing quantities
|
|
wipe(private.settings.bagQuantity)
|
|
private.quantityDB:SetQueryUpdatesPaused(true)
|
|
local query = private.quantityDB:NewQuery()
|
|
for _, row in query:Iterator() do
|
|
local oldBagQuantity = row:GetField("bagQuantity")
|
|
local oldTotalBankQuantity = row:GetField("bankQuantity") + row:GetField("reagentBankQuantity")
|
|
if oldTotalBankQuantity == 0 then
|
|
-- remove this row
|
|
assert(oldBagQuantity > 0)
|
|
private.quantityDB:DeleteRow(row)
|
|
else
|
|
local updated = false
|
|
if Environment.IsRetail() and oldTotalBankQuantity > 0 then
|
|
-- Update commodity quantities using GetItemCount()
|
|
local levelItemString = row:GetField("levelItemString")
|
|
if levelItemString == ItemString.GetBaseFast(levelItemString) and ItemInfo.IsCommodity(levelItemString) then
|
|
local itemId = ItemString.ToId(levelItemString)
|
|
-- GetItemCount() is a bit buggy and not all combinations of arguments work, so carefully call it to calculate the quantities
|
|
local bagQuantity = GetItemCount(itemId, false, false, false)
|
|
local reagentBankQuantity = GetItemCount(itemId, false, false, true) - bagQuantity
|
|
local bankQuantity = GetItemCount(itemId, true, false, true) - bagQuantity - reagentBankQuantity
|
|
if reagentBankQuantity ~= row:GetField("reagentBankQuantity") or bankQuantity ~= row:GetField("bankQuantity") then
|
|
updated = true
|
|
if reagentBankQuantity + bankQuantity == 0 then
|
|
private.quantityDB:DeleteRow(row)
|
|
else
|
|
row:SetField("bagQuantity", 0)
|
|
:SetField("bankQuantity", bankQuantity)
|
|
:SetField("reagentBankQuantity", reagentBankQuantity)
|
|
:Update()
|
|
end
|
|
end
|
|
end
|
|
end
|
|
if not updated and oldBagQuantity ~= 0 then
|
|
-- update this row
|
|
row:SetField("bagQuantity", 0)
|
|
:Update()
|
|
end
|
|
end
|
|
end
|
|
query:Release()
|
|
private.quantityDB:SetQueryUpdatesPaused(false)
|
|
|
|
-- WoW does not fire an update event for the backpack when you log in, so trigger one
|
|
private.BagUpdateHandler(nil, 0)
|
|
private.BagUpdateDelayedHandler()
|
|
end)
|
|
|
|
|
|
|
|
-- ============================================================================
|
|
-- Module Functions
|
|
-- ============================================================================
|
|
|
|
function BagTracking.RegisterCallback(callback)
|
|
tinsert(private.callbacks, callback)
|
|
end
|
|
|
|
function BagTracking.QuantityIterator()
|
|
return private.quantityDB:NewQuery()
|
|
:Select("levelItemString", "bagQuantity", "bankQuantity", "reagentBankQuantity")
|
|
:IteratorAndRelease()
|
|
end
|
|
|
|
function BagTracking.FilterQueryBags(query)
|
|
return query
|
|
:GreaterThanOrEqual("slotId", SlotId.Join(0, 1))
|
|
:LessThanOrEqual("slotId", SlotId.Join(Container.GetNumBags() + 1, 0))
|
|
end
|
|
|
|
function BagTracking.CreateQueryBags()
|
|
return BagTracking.FilterQueryBags(private.slotDB:NewQuery())
|
|
end
|
|
|
|
function BagTracking.CreateQueryBagsAuctionable()
|
|
return BagTracking.CreateQueryBags()
|
|
:Equal("isBoP", false)
|
|
:Equal("isBoA", false)
|
|
:Custom(private.IsAuctionableQueryFilter)
|
|
end
|
|
|
|
function BagTracking.CreateQueryBagsItem(itemString)
|
|
local query = BagTracking.CreateQueryBags()
|
|
if itemString == ItemString.GetBaseFast(itemString) then
|
|
query:Equal("baseItemString", itemString)
|
|
elseif ItemString.IsLevel(itemString) then
|
|
query:Equal("levelItemString", itemString)
|
|
else
|
|
query:Equal("itemString", itemString)
|
|
end
|
|
return query
|
|
end
|
|
|
|
function BagTracking.CreateQueryBagsItemAuctionable(itemString)
|
|
return BagTracking.CreateQueryBagsItem(itemString)
|
|
:Equal("isBoP", false)
|
|
:Equal("isBoA", false)
|
|
:Custom(private.IsAuctionableQueryFilter)
|
|
end
|
|
|
|
function BagTracking.GetNumMailable(itemString)
|
|
return BagTracking.CreateQueryBagsItem(itemString)
|
|
:Equal("isBoP", false)
|
|
:SumAndRelease("quantity")
|
|
end
|
|
|
|
function BagTracking.CreateQueryBank()
|
|
return private.slotDB:NewQuery()
|
|
:InTable("bag", BANK_BAG_SLOTS)
|
|
end
|
|
|
|
function BagTracking.CreateQueryBankItem(itemString)
|
|
local query = BagTracking.CreateQueryBank()
|
|
if itemString == ItemString.GetBaseFast(itemString) then
|
|
query:Equal("baseItemString", itemString)
|
|
elseif ItemString.IsLevel(itemString) then
|
|
query:Equal("levelItemString", itemString)
|
|
else
|
|
query:Equal("itemString", itemString)
|
|
end
|
|
return query
|
|
end
|
|
|
|
function BagTracking.ForceBankQuantityDeduction(itemString, quantity)
|
|
if DefaultUI.IsBankVisible() then
|
|
return
|
|
end
|
|
private.slotDB:SetQueryUpdatesPaused(true)
|
|
local query = private.slotDB:NewQuery()
|
|
:Equal("itemString", itemString)
|
|
:InTable("bag", BANK_BAG_SLOTS)
|
|
local levelItemString = ItemString.ToLevel(itemString)
|
|
for _, row in query:Iterator() do
|
|
if quantity > 0 then
|
|
local rowQuantity, rowBag = row:GetFields("quantity", "bag")
|
|
if rowQuantity <= quantity then
|
|
private.ChangeBagItemTotal(rowBag, levelItemString, -rowQuantity)
|
|
private.slotDB:DeleteRow(row)
|
|
quantity = quantity - rowQuantity
|
|
else
|
|
row:SetField("quantity", rowQuantity - quantity)
|
|
:Update()
|
|
private.ChangeBagItemTotal(rowBag, levelItemString, -quantity)
|
|
quantity = 0
|
|
end
|
|
end
|
|
end
|
|
query:Release()
|
|
private.slotDB:SetQueryUpdatesPaused(false)
|
|
end
|
|
|
|
function BagTracking.GetQuantityBySlotId(slotId)
|
|
return private.slotDB:GetUniqueRowField("slotId", slotId, "quantity")
|
|
end
|
|
|
|
function BagTracking.GetBagQuantity(itemString)
|
|
if not ItemString.IsLevel(itemString) and itemString == ItemString.GetBaseFast(itemString) then
|
|
return private.baseItemQuantityQuery
|
|
:BindParams(itemString)
|
|
:Sum("bagQuantity")
|
|
else
|
|
local levelItemString = ItemString.ToLevel(itemString)
|
|
return private.quantityDB:GetUniqueRowField("levelItemString", levelItemString, "bagQuantity") or 0
|
|
end
|
|
end
|
|
|
|
function BagTracking.GetBankQuantity(itemString)
|
|
if not ItemString.IsLevel(itemString) and itemString == ItemString.GetBaseFast(itemString) then
|
|
return private.baseItemQuantityQuery
|
|
:BindParams(itemString)
|
|
:Sum("bankQuantity")
|
|
else
|
|
local levelItemString = ItemString.ToLevel(itemString)
|
|
return private.quantityDB:GetUniqueRowField("levelItemString", levelItemString, "bankQuantity") or 0
|
|
end
|
|
end
|
|
|
|
function BagTracking.GetReagentBankQuantity(itemString)
|
|
if not ItemString.IsLevel(itemString) and itemString == ItemString.GetBaseFast(itemString) then
|
|
return private.baseItemQuantityQuery
|
|
:BindParams(itemString)
|
|
:Sum("reagentBankQuantity")
|
|
else
|
|
local levelItemString = ItemString.ToLevel(itemString)
|
|
return private.quantityDB:GetUniqueRowField("levelItemString", levelItemString, "reagentBankQuantity") or 0
|
|
end
|
|
end
|
|
|
|
function BagTracking.GetQuantities(itemString)
|
|
if not ItemString.IsLevel(itemString) and itemString == ItemString.GetBaseFast(itemString) then
|
|
private.baseItemQuantityQuery:BindParams(itemString)
|
|
return private.baseItemQuantityQuery:Sum("bagQuantity"), private.baseItemQuantityQuery:Sum("bankQuantity"), private.baseItemQuantityQuery:Sum("reagentBankQuantity")
|
|
else
|
|
local levelItemString = ItemString.ToLevel(itemString)
|
|
local bagQuantity, bankQuantity, reagentBankQuantity = private.quantityDB:GetUniqueRowFields("levelItemString", levelItemString, "bagQuantity", "bankQuantity", "reagentBankQuantity")
|
|
return bagQuantity or 0, bankQuantity or 0, reagentBankQuantity or 0
|
|
end
|
|
end
|
|
|
|
function BagTracking.GetTotalQuantity(itemString)
|
|
local bagQuantity, bankQuantity, reagentBankQuantity = BagTracking.GetQuantities(itemString)
|
|
return bagQuantity + bankQuantity + reagentBankQuantity
|
|
end
|
|
|
|
function BagTracking.GetCraftingMatQuantity(itemString)
|
|
if Environment.IsRetail() then
|
|
return BagTracking.GetTotalQuantity(itemString)
|
|
else
|
|
return BagTracking.GetBagQuantity(itemString)
|
|
end
|
|
end
|
|
|
|
|
|
|
|
-- ============================================================================
|
|
-- Event Handlers
|
|
-- ============================================================================
|
|
|
|
function private.BankVisible()
|
|
if private.isFirstBankOpen then
|
|
private.isFirstBankOpen = false
|
|
-- this is the first time opening the bank so we'll scan all the items so wipe our existing quantities
|
|
wipe(private.settings.bankQuantity)
|
|
wipe(private.settings.reagentBankQuantity)
|
|
private.quantityDB:SetQueryUpdatesPaused(true)
|
|
local query = private.quantityDB:NewQuery()
|
|
for _, row in query:Iterator() do
|
|
local oldValue = row:GetField("bankQuantity") + row:GetField("reagentBankQuantity")
|
|
if row:GetField("bagQuantity") == 0 then
|
|
-- remove this row
|
|
assert(oldValue > 0)
|
|
private.quantityDB:DeleteRow(row)
|
|
elseif oldValue ~= 0 then
|
|
-- update this row
|
|
row:SetField("bankQuantity", 0)
|
|
:SetField("reagentBankQuantity", 0)
|
|
:Update()
|
|
end
|
|
end
|
|
query:Release()
|
|
private.quantityDB:SetQueryUpdatesPaused(false)
|
|
end
|
|
private.BagUpdateHandler(nil, BANK_CONTAINER)
|
|
local firstBankBag, lastBankBag = Container.GetBankBagIndexes()
|
|
for bag = firstBankBag, lastBankBag do
|
|
private.BagUpdateHandler(nil, bag)
|
|
end
|
|
if Environment.HasFeature(Environment.FEATURES.REAGENT_BANK) and IsReagentBankUnlocked() then
|
|
for slot = 1, Container.GetNumSlots(REAGENTBANK_CONTAINER) do
|
|
private.ReagentBankSlotChangedHandler(nil, slot)
|
|
end
|
|
end
|
|
private.BagUpdateDelayedHandler()
|
|
private.BankSlotUpdateDelayed()
|
|
private.ReagentBankSlotUpdateDelayed()
|
|
end
|
|
|
|
function private.BagUpdateHandler(_, bag)
|
|
if private.bagUpdates.pending[bag] then
|
|
return
|
|
end
|
|
private.bagUpdates.pending[bag] = true
|
|
local firstBankBag, lastBankBag = Container.GetBankBagIndexes()
|
|
if bag >= BACKPACK_CONTAINER and bag <= Container.GetNumBags() then
|
|
tinsert(private.bagUpdates.bagList, bag)
|
|
elseif bag == BANK_CONTAINER or (bag >= firstBankBag and bag <= lastBankBag) then
|
|
tinsert(private.bagUpdates.bankList, bag)
|
|
elseif bag ~= KEYRING_CONTAINER then
|
|
error("Unexpected bag: "..tostring(bag))
|
|
end
|
|
end
|
|
|
|
function private.BagUpdateDelayedHandler()
|
|
private.slotDB:SetQueryUpdatesPaused(true)
|
|
|
|
-- scan any pending bags
|
|
for i = #private.bagUpdates.bagList, 1, -1 do
|
|
local bag = private.bagUpdates.bagList[i]
|
|
if private.ScanBagOrBank(bag) then
|
|
private.bagUpdates.pending[bag] = nil
|
|
tremove(private.bagUpdates.bagList, i)
|
|
end
|
|
end
|
|
if #private.bagUpdates.bagList > 0 then
|
|
-- some failed to scan so try again
|
|
private.bagUpdateTimer:RunForFrames(2)
|
|
end
|
|
|
|
if DefaultUI.IsBankVisible() then
|
|
-- scan any pending bank bags
|
|
for i = #private.bagUpdates.bankList, 1, -1 do
|
|
local bag = private.bagUpdates.bankList[i]
|
|
if private.ScanBagOrBank(bag) then
|
|
private.bagUpdates.pending[bag] = nil
|
|
tremove(private.bagUpdates.bankList, i)
|
|
end
|
|
end
|
|
if #private.bagUpdates.bankList > 0 then
|
|
-- some failed to scan so try again
|
|
private.bagUpdateTimer:RunForFrames(2)
|
|
end
|
|
end
|
|
|
|
private.slotDB:SetQueryUpdatesPaused(false)
|
|
end
|
|
|
|
function private.BankSlotChangedHandler(_, slot)
|
|
if slot > NUM_BANKGENERIC_SLOTS then
|
|
private.BagUpdateHandler(nil, slot - NUM_BANKGENERIC_SLOTS)
|
|
return
|
|
end
|
|
if private.bankSlotUpdates.pending[slot] then
|
|
return
|
|
end
|
|
private.bankSlotUpdates.pending[slot] = true
|
|
tinsert(private.bankSlotUpdates.list, slot)
|
|
private.bankSlotUpdateTimer:RunForFrames(2)
|
|
end
|
|
|
|
-- this is not a WoW event, but we fake it based on a delay from private.BankSlotChangedHandler
|
|
function private.BankSlotUpdateDelayed()
|
|
if not DefaultUI.IsBankVisible() then
|
|
return
|
|
end
|
|
private.slotDB:SetQueryUpdatesPaused(true)
|
|
|
|
-- scan any pending slots
|
|
for i = #private.bankSlotUpdates.list, 1, -1 do
|
|
local slot = private.bankSlotUpdates.list[i]
|
|
if private.ScanBankSlot(slot) then
|
|
private.bankSlotUpdates.pending[slot] = nil
|
|
tremove(private.bankSlotUpdates.list, i)
|
|
end
|
|
end
|
|
if #private.bankSlotUpdates.list > 0 then
|
|
-- some failed to scan so try again
|
|
private.bankSlotUpdateTimer:RunForFrames(2)
|
|
end
|
|
|
|
private.slotDB:SetQueryUpdatesPaused(false)
|
|
end
|
|
|
|
function private.ReagentBankSlotChangedHandler(_, slot)
|
|
if private.reagentBankSlotUpdates.pending[slot] then
|
|
return
|
|
end
|
|
private.reagentBankSlotUpdates.pending[slot] = true
|
|
tinsert(private.reagentBankSlotUpdates.list, slot)
|
|
private.reagentBankSlotUpdateTimer:RunForFrames(2)
|
|
end
|
|
|
|
-- this is not a WoW event, but we fake it based on a delay from private.ReagentBankSlotChangedHandler
|
|
function private.ReagentBankSlotUpdateDelayed()
|
|
if not DefaultUI.IsBankVisible() then
|
|
return
|
|
end
|
|
private.slotDB:SetQueryUpdatesPaused(true)
|
|
|
|
-- scan any pending slots
|
|
for i = #private.reagentBankSlotUpdates.list, 1, -1 do
|
|
local slot = private.reagentBankSlotUpdates.list[i]
|
|
if private.ScanReagentBankSlot(slot) then
|
|
private.reagentBankSlotUpdates.pending[slot] = nil
|
|
tremove(private.reagentBankSlotUpdates.list, i)
|
|
end
|
|
end
|
|
if #private.reagentBankSlotUpdates.list > 0 then
|
|
-- some failed to scan so try again
|
|
private.reagentBankSlotUpdateTimer:RunForFrames(2)
|
|
end
|
|
|
|
private.slotDB:SetQueryUpdatesPaused(false)
|
|
end
|
|
|
|
|
|
|
|
-- ============================================================================
|
|
-- Scanning Functions
|
|
-- ============================================================================
|
|
|
|
function private.ScanBagOrBank(bag)
|
|
local numSlots = Container.GetNumSlots(bag)
|
|
private.RemoveExtraSlots(bag, numSlots)
|
|
local result = true
|
|
for slot = 1, numSlots do
|
|
if not private.ScanBagSlot(bag, slot) then
|
|
result = false
|
|
end
|
|
end
|
|
return result
|
|
end
|
|
|
|
function private.ScanBankSlot(slot)
|
|
return private.ScanBagSlot(BANK_CONTAINER, slot)
|
|
end
|
|
|
|
function private.ScanReagentBankSlot(slot)
|
|
return private.ScanBagSlot(REAGENTBANK_CONTAINER, slot)
|
|
end
|
|
|
|
|
|
|
|
-- ============================================================================
|
|
-- Private Helper Functions
|
|
-- ============================================================================
|
|
|
|
function private.IsAuctionableQueryFilter(row)
|
|
if Environment.HasFeature(Environment.FEATURES.C_AUCTION_HOUSE) then
|
|
private.itemLocation:Clear()
|
|
private.itemLocation:SetBagAndSlot(row:GetFields("bag", "slot"))
|
|
return private.itemLocation:IsValid() and C_AuctionHouse.IsSellItemValid(private.itemLocation, false)
|
|
else
|
|
return not TooltipScanning.HasUsedCharges(row:GetFields("bag", "slot"))
|
|
end
|
|
end
|
|
|
|
function private.RemoveExtraSlots(bag, numSlots)
|
|
-- the number of slots of this bag may have changed, in which case we should remove any higher ones from our DB
|
|
local query = private.slotDB:NewQuery()
|
|
:Equal("bag", bag)
|
|
:GreaterThan("slot", numSlots)
|
|
for _, row in query:Iterator() do
|
|
local levelItemString, quantity = row:GetFields("levelItemString", "quantity")
|
|
private.ChangeBagItemTotal(bag, levelItemString, -quantity)
|
|
private.slotDB:DeleteRow(row)
|
|
end
|
|
query:Release()
|
|
end
|
|
|
|
function private.ScanBagSlot(bag, slot)
|
|
local texture, quantity, _, _, _, _, link, _, _, itemId = Container.GetItemInfo(bag, slot)
|
|
if quantity and not itemId then
|
|
-- we are pending item info for this slot so try again later to scan it
|
|
return false
|
|
elseif quantity == 0 then
|
|
-- this item is going away, so try again later to scan it
|
|
return false
|
|
end
|
|
local itemString = ItemString.Get(link)
|
|
local levelItemString = itemString and ItemString.ToLevel(itemString)
|
|
local slotId = SlotId.Join(bag, slot)
|
|
local row = private.slotDB:GetUniqueRow("slotId", slotId)
|
|
if levelItemString then
|
|
local isBoP, isBoA = nil, nil
|
|
if row then
|
|
if row:GetField("itemLink") == link then
|
|
-- the item didn't change, so use the previous values
|
|
isBoP, isBoA = row:GetFields("isBoP", "isBoA")
|
|
else
|
|
isBoP, isBoA = InventoryInfo.IsSoulbound(bag, slot)
|
|
if isBoP == nil then
|
|
Log.Err("Failed to get soulbound info for %d,%d (%s)", bag, slot, link or "?")
|
|
return false
|
|
end
|
|
end
|
|
-- remove the old row from the item totals
|
|
local oldLevelItemString, oldQuantity = row:GetFields("levelItemString", "quantity")
|
|
private.ChangeBagItemTotal(bag, oldLevelItemString, -oldQuantity)
|
|
else
|
|
isBoP, isBoA = InventoryInfo.IsSoulbound(bag, slot)
|
|
if isBoP == nil then
|
|
Log.Err("Failed to get soulbound info for %d,%d (%s)", bag, slot, link or "?")
|
|
return false
|
|
end
|
|
-- there was nothing here previously so create a new row
|
|
row = private.slotDB:NewRow()
|
|
:SetField("slotId", slotId)
|
|
:SetField("bag", bag)
|
|
:SetField("slot", slot)
|
|
end
|
|
-- update the row
|
|
row:SetField("itemLink", link)
|
|
:SetField("itemString", ItemString.Get(link))
|
|
:SetField("itemTexture", texture or ItemInfo.GetTexture(link))
|
|
:SetField("quantity", quantity)
|
|
:SetField("isBoP", isBoP)
|
|
:SetField("isBoA", isBoA)
|
|
:CreateOrUpdateAndRelease()
|
|
-- add to the item totals
|
|
private.ChangeBagItemTotal(bag, levelItemString, quantity)
|
|
elseif row then
|
|
-- nothing here now so delete the row and remove from the item totals
|
|
local oldLevelItemString, oldQuantity = row:GetFields("levelItemString", "quantity")
|
|
private.ChangeBagItemTotal(bag, oldLevelItemString, -oldQuantity)
|
|
private.slotDB:DeleteRow(row)
|
|
row:Release()
|
|
end
|
|
return true
|
|
end
|
|
|
|
function private.DelayedBagTrackingCallback()
|
|
for _, callback in ipairs(private.callbacks) do
|
|
callback()
|
|
end
|
|
end
|
|
|
|
function private.OnCallbackQueryUpdated()
|
|
private.bagTrackingTimer:RunForFrames(2)
|
|
end
|
|
|
|
function private.ChangeBagItemTotal(bag, levelItemString, changeQuantity)
|
|
assert(changeQuantity ~= 0)
|
|
local totalsTable = nil
|
|
local field = nil
|
|
local firstBankBag, lastBankBag = Container.GetBankBagIndexes()
|
|
if bag >= BACKPACK_CONTAINER and bag <= Container.GetNumBags() then
|
|
totalsTable = private.settings.bagQuantity
|
|
field = "bagQuantity"
|
|
elseif bag == BANK_CONTAINER or (bag >= firstBankBag and bag <= lastBankBag) then
|
|
totalsTable = private.settings.bankQuantity
|
|
field = "bankQuantity"
|
|
elseif bag == REAGENTBANK_CONTAINER then
|
|
totalsTable = private.settings.reagentBankQuantity
|
|
field = "reagentBankQuantity"
|
|
else
|
|
error("Unexpected bag: "..tostring(bag))
|
|
end
|
|
totalsTable[levelItemString] = (totalsTable[levelItemString] or 0) + changeQuantity
|
|
|
|
if not private.quantityDB:HasUniqueRow("levelItemString", levelItemString) then
|
|
-- create a new row
|
|
private.quantityDB:NewRow()
|
|
:SetField("levelItemString", levelItemString)
|
|
:SetField("bagQuantity", 0)
|
|
:SetField("bankQuantity", 0)
|
|
:SetField("reagentBankQuantity", 0)
|
|
:Create()
|
|
end
|
|
local row = private.quantityDB:GetUniqueRow("levelItemString", levelItemString)
|
|
local totalQuantity = row:GetField("bagQuantity") + row:GetField("bankQuantity") + row:GetField("reagentBankQuantity")
|
|
local oldValue = row:GetField(field)
|
|
local newValue = oldValue + changeQuantity
|
|
assert(newValue >= 0)
|
|
if newValue == 0 and totalQuantity == oldValue then
|
|
-- remove this row
|
|
private.quantityDB:DeleteRow(row)
|
|
else
|
|
-- update this row
|
|
row:SetField(field, oldValue + changeQuantity)
|
|
:Update()
|
|
end
|
|
row:Release()
|
|
|
|
assert(totalsTable[levelItemString] >= 0)
|
|
if totalsTable[levelItemString] == 0 then
|
|
totalsTable[levelItemString] = nil
|
|
end
|
|
end
|
|
|
|
function private.FilterNonItemLevelStrings(levelItemString)
|
|
return levelItemString ~= ItemString.ToLevel(levelItemString)
|
|
end
|
|
|