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

-- ------------------------------------------------------------------------------ --
-- 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