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.

779 lines
27 KiB

3 years ago
-- ------------------------------------------------------------------------------ --
-- 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")
3 years ago
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")
3 years ago
local TempTable = TSM.Include("Util.TempTable")
local ItemString = TSM.Include("Util.ItemString")
local DefaultUI = TSM.Include("Service.DefaultUI")
3 years ago
local ItemInfo = TSM.Include("Service.ItemInfo")
local TooltipScanning = TSM.Include("Service.TooltipScanning")
3 years ago
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,
3 years ago
callbackQuery = nil, -- luacheck: ignore 1004 - just stored for GC reasons
quantityCallbackQuery = nil, -- luacheck: ignore 1004 - just stored for GC reasons
3 years ago
callbacks = {},
quantityCallbacks = {},
bagUpdateTimer = nil,
bagUpdateDelayedTimer = nil,
bankSlotUpdateTimer = nil,
reagentBankSlotUpdateTimer = nil,
bagTrackingCallbackTimer = nil,
bagTrackingQuantityCallbackTimer = nil,
itemLocation = ItemLocation:CreateEmpty(),
prevQuantities = {},
3 years ago
}
local BANK_BAG_SLOTS = {}
-- ============================================================================
-- Population of the Static Data
-- ============================================================================
do
BANK_BAG_SLOTS[BANK_CONTAINER] = true
for _, bag in Container.BankBagIterator() do
BANK_BAG_SLOTS[bag] = true
3 years ago
end
if Environment.HasFeature(Environment.FEATURES.REAGENT_BANK) then
3 years ago
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)
3 years ago
Event.Register("PLAYERBANKSLOTS_CHANGED", private.BankSlotChangedHandler)
if Environment.HasFeature(Environment.FEATURES.REAGENT_BANK) then
3 years ago
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")
3 years ago
:Commit()
private.baseItemQuantityQuery = private.quantityDB:NewQuery()
:Select("bagQuantity", "bankQuantity", "reagentBankQuantity")
:Equal("baseItemString", Database.BoundQueryParam())
3 years ago
private.callbackQuery = private.slotDB:NewQuery()
:SetUpdateCallback(private.OnCallbackQueryUpdated)
private.quantityCallbackQuery = private.quantityDB:NewQuery()
:SetUpdateCallback(private.OnQuantityCallbackQueryUpdated)
3 years ago
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.bagTrackingCallbackTimer = Delay.CreateTimer("BAG_TRACKING_CALLBACK", private.DelayedBagTrackingCallback)
private.bagTrackingQuantityCallbackTimer = Delay.CreateTimer("BAG_TRACKING_QUANTITY_CALLBACK", private.DelayedBagTrackingQuantityCallback)
Table.Filter(private.settings.bagQuantity, private.FilterNonItemLevelStrings)
Table.Filter(private.settings.bankQuantity, private.FilterNonItemLevelStrings)
Table.Filter(private.settings.reagentBankQuantity, private.FilterNonItemLevelStrings)
3 years ago
local items = TempTable.Acquire()
for levelItemString in pairs(private.settings.bagQuantity) do
items[levelItemString] = true
3 years ago
end
for levelItemString in pairs(private.settings.bankQuantity) do
items[levelItemString] = true
3 years ago
end
for levelItemString in pairs(private.settings.reagentBankQuantity) do
items[levelItemString] = true
3 years ago
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)
3 years ago
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
3 years ago
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
3 years ago
-- remove this row
assert(oldBagQuantity > 0)
3 years ago
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
3 years ago
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.RegisterQuantityCallback(callback)
tinsert(private.quantityCallbacks, callback)
end
function BagTracking.QuantityIterator()
3 years ago
return private.quantityDB:NewQuery()
:Select("levelItemString", "bagQuantity", "bankQuantity", "reagentBankQuantity")
3 years ago
:IteratorAndRelease()
end
function BagTracking.FilterQueryBags(query)
return query
:GreaterThanOrEqual("slotId", SlotId.Join(0, 1))
:LessThanOrEqual("slotId", SlotId.Join(Container.GetNumBags() + 1, 0))
3 years ago
end
function BagTracking.CreateQueryBags()
return BagTracking.FilterQueryBags(private.slotDB:NewQuery())
end
function BagTracking.CreateQueryBagsBank()
return private.slotDB:NewQuery()
end
3 years ago
function BagTracking.CreateQueryBagsAuctionable()
return BagTracking.CreateQueryBags()
:Equal("isBoP", false)
:Equal("isBoA", false)
:Custom(private.IsAuctionableQueryFilter)
3 years ago
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)
3 years ago
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
3 years ago
return
end
private.slotDB:SetQueryUpdatesPaused(true)
local query = private.slotDB:NewQuery()
:Equal("itemString", itemString)
:InTable("bag", BANK_BAG_SLOTS)
3 years ago
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
3 years ago
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
3 years ago
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
3 years ago
end
function BagTracking.GetCraftingMatQuantity(itemString)
if Environment.IsRetail() then
return BagTracking.GetTotalQuantity(itemString)
else
return BagTracking.GetBagQuantity(itemString)
end
end
3 years ago
-- ============================================================================
-- Event Handlers
-- ============================================================================
function private.BankVisible()
3 years ago
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)
3 years ago
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
3 years ago
-- remove this row
assert(oldValue > 0)
private.quantityDB:DeleteRow(row)
elseif oldValue ~= 0 then
-- update this row
row:SetField("bankQuantity", 0)
:SetField("reagentBankQuantity", 0)
3 years ago
:Update()
end
end
query:Release()
private.quantityDB:SetQueryUpdatesPaused(false)
end
private.BagUpdateHandler(nil, BANK_CONTAINER)
for _, bag in Container.BankBagIterator() 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
3 years ago
private.BagUpdateDelayedHandler()
private.BankSlotUpdateDelayed()
private.ReagentBankSlotUpdateDelayed()
3 years ago
end
function private.BagUpdateHandler(_, bag)
if private.bagUpdates.pending[bag] then
return
end
private.bagUpdates.pending[bag] = true
if bag >= BACKPACK_CONTAINER and bag <= Container.GetNumBags() then
3 years ago
tinsert(private.bagUpdates.bagList, bag)
elseif bag == BANK_CONTAINER or Container.IsBankBag(bag) then
3 years ago
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)
3 years ago
end
if DefaultUI.IsBankVisible() then
3 years ago
-- 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)
3 years ago
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)
3 years ago
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
3 years ago
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)
3 years ago
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)
3 years ago
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
3 years ago
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)
3 years ago
end
private.slotDB:SetQueryUpdatesPaused(false)
end
-- ============================================================================
-- Scanning Functions
-- ============================================================================
function private.ScanBagOrBank(bag)
local numSlots = Container.GetNumSlots(bag)
3 years ago
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
3 years ago
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)
3 years ago
if quantity and not itemId then
-- We are pending item info for this slot so try again later to scan it
3 years ago
return false
elseif quantity == 0 then
-- This item is going away, so try again later to scan it
3 years ago
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
3 years ago
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 "?")
row:Release()
3 years ago
return false
end
row:SetField("itemLink", link)
:SetField("itemString", ItemString.Get(link))
:SetField("itemTexture", texture or ItemInfo.GetTexture(link))
:SetField("isBoP", isBoP)
:SetField("isBoA", isBoA)
3 years ago
end
local oldLevelItemString, oldQuantity = row:GetFields("levelItemString", "quantity")
if levelItemString ~= oldLevelItemString then
-- Remove the old item and add the new one
private.ChangeBagItemTotal(bag, oldLevelItemString, -oldQuantity)
private.ChangeBagItemTotal(bag, levelItemString, quantity)
row:SetField("quantity", quantity)
elseif quantity ~= oldQuantity then
-- Update the quantity
private.ChangeBagItemTotal(bag, levelItemString, quantity - oldQuantity)
row:SetField("quantity", quantity)
end
row:CreateOrUpdateAndRelease()
3 years ago
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
private.slotDB:NewRow()
3 years ago
:SetField("slotId", slotId)
:SetField("bag", bag)
:SetField("slot", slot)
: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)
3 years ago
end
elseif row then
-- Nothing here now so delete the row and remove from the item totals
3 years ago
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.DelayedBagTrackingQuantityCallback()
local newQuantities = TempTable.Acquire()
private.quantityDB:NewQuery()
:VirtualField("totalQuantity", "number", private.TotalQuantityVirtualField)
:Select("levelItemString", "totalQuantity")
:AsTable(newQuantities)
:Release()
local updatedItems = TempTable.Acquire()
Table.GetChangedKeys(private.prevQuantities, newQuantities, updatedItems)
if next(updatedItems) then
-- Add the base items
local baseItemStrings = TempTable.Acquire()
for levelItemString in pairs(updatedItems) do
baseItemStrings[ItemString.GetBaseFast(levelItemString)] = true
end
for baseItemString in pairs(baseItemStrings) do
updatedItems[baseItemString] = true
end
TempTable.Release(baseItemStrings)
wipe(private.prevQuantities)
for levelItemString, quantity in pairs(newQuantities) do
private.prevQuantities[levelItemString] = quantity
end
for _, callback in ipairs(private.quantityCallbacks) do
callback(updatedItems)
end
end
TempTable.Release(newQuantities)
TempTable.Release(updatedItems)
end
3 years ago
function private.OnCallbackQueryUpdated()
private.bagTrackingCallbackTimer:RunForFrames(2)
end
function private.OnQuantityCallbackQueryUpdated(...)
private.bagTrackingQuantityCallbackTimer:RunForFrames(2)
3 years ago
end
function private.ChangeBagItemTotal(bag, levelItemString, changeQuantity)
assert(changeQuantity ~= 0)
3 years ago
local totalsTable = nil
local field = nil
if bag >= BACKPACK_CONTAINER and bag <= Container.GetNumBags() then
3 years ago
totalsTable = private.settings.bagQuantity
field = "bagQuantity"
elseif bag == BANK_CONTAINER or Container.IsBankBag(bag) then
3 years ago
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
local row = private.quantityDB:GetUniqueRow("levelItemString", levelItemString)
if row then
local oldTotalQuantity = 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 oldTotalQuantity == oldValue then
-- Remove this row
private.quantityDB:DeleteRow(row)
else
-- Update this row
row:SetField(field, oldValue + changeQuantity)
:Update()
end
row:Release()
else
-- Create a new row
assert(changeQuantity > 0)
3 years ago
private.quantityDB:NewRow()
:SetField("levelItemString", levelItemString)
:SetField("bagQuantity", 0)
:SetField("bankQuantity", 0)
:SetField("reagentBankQuantity", 0)
:SetField(field, changeQuantity)
3 years ago
:Create()
end
assert(totalsTable[levelItemString] >= 0)
if totalsTable[levelItemString] == 0 then
totalsTable[levelItemString] = nil
end
end
function private.FilterNonItemLevelStrings(levelItemString)
return levelItemString ~= ItemString.ToLevel(levelItemString)
3 years ago
end
function private.TotalQuantityVirtualField(row)
return row:GetField("bagQuantity") + row:GetField("bankQuantity") + row:GetField("reagentBankQuantity")
end