Files
Bagshui/Components/Inventory.Cache.lua
OEVGandClaude Opus 5.5 03cd03c3e6 Restack moves items into matching specialty bags
FindSpecialtyBagMoves() pairs items in general-purpose bags with free
slots in the same inventory's specialty bags (C_Item.GetItemFamily on
item and bag; bags themselves excluded). Restack() queues those moves
first and then consolidates stacks. UpdateCache() refreshes
specialtyMovesAvailable when items changed, which enables the Restack
toolbar button. Restack tooltip text updated (enUS).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019W3rwt5PZSpEDkXYzRuRhH
2026-11-02 14:41:44 +00:00

637 lines
30 KiB
Lua

-- Bagshui Inventory Prototype: Cache Management
Bagshui:AddComponent(function()
local Inventory = Bagshui.prototypes.Inventory
--- Primary cache management function that copies inventory contents into `self.inventory[bag][slot]`.
---
--- Also responsible for deciding:
--- * Whether resort/window update is required.
--- * Item stock states (including saving/restoring when an items/bags are moved around).
--- * If each empty slot is allowed to stack.
--- * When toolbar icons that act on the inventory should be enabled (toolbar updates are performed in Inventory.layout.lua).
function Inventory:UpdateCache()
-- self:PrintDebug("UpdateCache()")
-- Don't even try any updates until they're permitted.
if not self.inventoryUpdateAllowed or not self.online then
return
end
-- forceFullCacheUpdate should also satisfy any forceCacheUpdate condition.
self.forceCacheUpdate = self.forceCacheUpdate or self.forceFullCacheUpdate
-- If an update wasn't actually required, skip it.
if not self.cacheUpdateNeeded and not self.forceCacheUpdate then
return
end
-- If this was triggered by a bag change, wait for the next update.
if self.lastEvent == "ITEM_LOCK_CHANGED" and BsUtil.TrueTableSize(self.lastUpdateLockedContainers) > 0 then
BsUtil.TableClear(self.lastUpdateLockedContainers)
-- Queuing another update here instead of completely ignoring to ensure that an update
-- does happen eventually (and it should typically happen sooner than the delay here).
self:QueueUpdate(0.1)
return
end
self:PrintDebug("Updating inventory cache")
-- We've passed the initial checks and can start preparing to update the cache.
-- Reference to self.inventory[bagNum][slotNum].
local item
-- The `*Changes` variables are used to figure out what needs to occur be once this function has finished.
-- New items added, items removed, counts changed, etc. Most commonly true of the three `*Changes` variables.
local majorChanges = false
-- Usually just means that an item has locked or unlocked.
local cosmeticChanges = false
-- Not currently used; used to be called `resort_suggested` in EngInventory. Leaving in case it comes in handy in the future.
local minorChanges = false
-- A slot's contents changed (anything beyond its locked state), so items need to be re-categorized.
local itemChanges = false
-- Track whether there are multiple partial stacks of the same item to decide whether the
-- Restack button should be enabled.
self.multiplePartialStacks = false
-- Update this on every cache pass, since those happen fairly frequently.
-- Used to determine whether change highlighting should be enabled.
self.hasChanges = false
-- Whether any item in the cache has the `_bagshuiPreventEmptySlotStack` property set to `true`
-- Used by `ManageDryRun()` as a factor to determine whether `enableResortIcon` should be true.
self.hasSlotsWithStackingPrevented = false
-- Bag (outer loop) variables.
local bagName, bagNumSlots, bagTexture, bagType, bagSlotLink, bagItemCode, bagInfo
-- Slot (inner loop) variables.
local itemReadable
-- Used to compare current cache item and actual item now present in the slot
local preCharges, preCount, preItemLink, preLocked, preItemString, preTooltip, nowCount, nowItemLink, nowLocked, nowCharges
-- These two variables are used to maintain and restore from the stock state cache.
-- (See Bagshui:PickupInventoryItem() for reasoning.)
local shadowId, prevShadowId
-- Reset tracking tables.
if not self.lockOnlyChangedItems then
self.lockOnlyChangedItems = {}
end
BsUtil.TableClear(self.lockOnlyChangedItems)
BsUtil.TableClear(self.partialStacks)
BsUtil.TableClear(self.emptyGenericContainerSlots)
-- Identify any container changes triggered by moving bags between slots.
if Bagshui.pickedUpBagSlotNum and Bagshui.putDownBagSlotNum then
-- Container changes are only applicable if it's one of our containers.
local pickedUpContainerId = self.inventoryIdsToContainerIds[Bagshui.pickedUpBagSlotNum]
local putDownContainerId = self.inventoryIdsToContainerIds[Bagshui.putDownBagSlotNum]
if pickedUpContainerId and putDownContainerId then
self.pendingContainerChanges[pickedUpContainerId] = putDownContainerId
self.pendingContainerChanges[putDownContainerId] = pickedUpContainerId
end
end
local hasPendingContainerChanges = BsUtil.TrueTableSize(self.pendingContainerChanges) > 0
-- Loop through bags.
-- Using _bagIndex instead of _ as the throwaway variable for this loop because
-- _ is used heavily within, and overwriting the loop variable can cause issues.
for _bagIndex, bagNum in ipairs(self.containerIds) do
-- Initialize bag cache if needed.
if self.inventory[bagNum] == nil then
self.inventory[bagNum] = {}
end
-- Get bag info.
bagName = nil
bagNumSlots = self:GetContainerNumSlots(bagNum) -- self:GetContainerNumSlots() intelligently handles this for for all container types.
bagTexture = nil
bagType = nil
bagSlotLink = nil
bagItemCode = nil
if self.primaryContainer.id == bagNum then
-- Can't pull primary container information from the API.
bagName = self.primaryContainer.name
bagTexture = self.primaryContainer.texture
bagType = self.primaryContainer.name
else
bagName = _G.GetBagName(bagNum)
bagSlotLink = _G.GetInventoryItemLink("player", _G.ContainerIDToInventoryID(bagNum))
if bagSlotLink ~= nil then
_, _, bagItemCode = string.find(bagSlotLink, "(%d+):")
_, _, _, _, _, bagType, _, _, bagTexture = _G.GetItemInfo(bagItemCode)
end
end
-- Keep track of whether this bag got locked to help coordinate bag change updates after it's unlocked.
self.lastUpdateLockedContainers[bagNum] = _G.IsInventoryItemLocked(_G.ContainerIDToInventoryID(bagNum))
-- No reason to store full texture paths since they're always in Interface\Icons.
bagTexture = bagTexture
-- self.containers[bagNum] always already exists in SavedVariables or has been
-- initialized to {} during Inventory:Init().
bagInfo = self.containers[bagNum]
bagInfo.bagNum = bagNum
-- Bag has changed or isn't initialized.
if
self.pendingContainerChanges[bagNum]
or bagInfo.name ~= bagName
or bagInfo.numSlots ~= bagNumSlots
or bagInfo.texture ~= bagTexture
or bagInfo.type ~= bagType
or (
-- Need to check for numSlots > 0 to avoid constantly rebuilding container info for empty bag slots
bagInfo.numSlots > 0 and (not bagInfo.name or not bagInfo.type)
)
or self.initialInventoryUpdateNeeded
then
-- When the bag changes after startup, wipe its cache.
if (bagInfo.numSlots or 0) > 0 and not self.initialInventoryUpdateNeeded then
BsUtil.TableClear(self.inventory[bagNum])
end
bagInfo.name = bagName
bagInfo.numSlots = bagNumSlots
bagInfo.texture = bagTexture
bagInfo.type = bagType
-- isProfessionBag and genericType are used primarily for empty slot stacking.
-- Anything that isn't a normal bag class and isn't the primary container
-- is going to be considered a profession bag.
bagInfo.isProfessionBag = (
bagType ~= BsGameInfo.itemSubclasses["Container"]["Bag"] and bagType ~= self.primaryContainer.name
)
-- A bag's "generic type" is the the item class returned by GetItemInfo for profession
-- bags or the localized version of "Bag" for any other bag and the primary container.
bagInfo.genericType = bagInfo.isProfessionBag and bagInfo.type or BsGameInfo.itemSubclasses["Container"]["Bag"]
-- Bag changes require a resort even if the window is visible.
self.forceResort = true
end
-- Reset tracking of filled slots.
self.containers[bagNum].slotsFilled = 0
-- Make sure this bag has slots to process.
if bagNumSlots > 0 then
-- Prepare to stack empty slots (actual stacking is handled during UpdateWindow()).
self:InitializeEmptySlotStackTracking(bagInfo)
-- Time to go through all the bag slots and look at each item.
for slotNum = 1, bagNumSlots do
shadowId = nil
prevShadowId = nil
-- Initialize this slot in the cache when it doesn't exist yet.
-- Using table.insert here to ensure table.getn() works correctly.
if table.getn(self.inventory[bagNum]) < slotNum then
table.insert(self.inventory[bagNum], {})
BsItemInfo:InitializeItem(self.inventory[bagNum][slotNum])
end
item = self.inventory[bagNum][slotNum]
-- Store previous information so we can determine whether changes occurred.
preItemLink = item.itemLink
preItemString = item.itemString
preCharges = item.charges or 0
preCount = item.count or 0
preLocked = item.locked
preTooltip = item.tooltip
-- Get current item information.
nowItemLink = _G.GetContainerItemLink(bagNum, slotNum)
-- Ignoring quality (return value 4) from GetContainerItemInfo because
-- it sometimes returns -1, which according to https://warcraft.wiki.gg/index.php?title=API_GetContainerItemInfo&oldid=2497880
-- means it's somehow special (stackable, unique or a quest item).
-- We'll obtain it from GetItemInfo() instead. (We also don't need
-- texture since that will be obtained during ItemInfo:Get()).
_, nowCount, nowLocked, _, itemReadable = _G.GetContainerItemInfo(bagNum, slotNum)
-- The API returns 1/nil (or true/false), but the cache stores 1/0 (BS_ITEM_SKELETON.locked).
-- Without this, `preLocked ~= nowLocked` was true (0 ~= nil) for every unlocked slot, so every
-- cache update re-read item info and re-scanned the tooltip of every slot.
nowLocked = (nowLocked and nowLocked ~= 0) and 1 or BS_ITEM_SKELETON.locked
-- Update filled slot count for this container.
if nowItemLink ~= nil then
self.containers[bagNum].slotsFilled = self.containers[bagNum].slotsFilled + 1
end
-- If count was nil, set it to 0 so that the cache isn't constantly getting
-- re-processed (otherwise preCount won't equal nowCount for empty slots).
nowCount = nowCount or 0
-- Assume there are no charges. This property will be filled either by Bagshui's
-- native tooltip charges parsing in ItemInfo:GetTooltip() or just below
-- if SuperWoW is loaded. Making item.charges not equal preCharges is also the
-- trigger for native charges parsing when the item doesn't change.
item.charges = 0
nowCharges = 0
-- Item charges from SuperWoW: move negative item counts, which indicate charges, into the charges field.
if nowCount < 0 then
nowCharges = math.abs(nowCount)
item.charges = nowCharges
nowCount = 1
end
-- ClassicAPI reports charges directly, so Bagshui doesn't have to depend on
-- SuperWoW's negative item counts or tooltip parsing. Only ask for filled slots,
-- and don't override charges already obtained above.
-- (Note that C_Container.GetContainerItemInfo() reports a count of 1 for charged
-- items, which is why the count above still comes from GetContainerItemInfo().)
if nowCharges == 0 and nowItemLink ~= nil then
local apiCharges = _G.C_Container.GetContainerItemCharges(bagNum, slotNum)
apiCharges = apiCharges and tonumber(apiCharges)
if apiCharges and apiCharges > 0 then
nowCharges = apiCharges
item.charges = apiCharges
end
end
-- Only the locked state changed (an item was picked up, put down, or is moving between
-- bags and bank): record it without re-reading item info or the tooltip, and without
-- flagging the slot as changed for categorization. The window still redraws (see the
-- `cosmeticChanges` check below) so the item turns gray/normal.
if
preLocked ~= nowLocked
and preItemLink == nowItemLink
and item.bagNum ~= nil
and preCount == nowCount
and preTooltip == item.tooltip
and not self.initialInventoryUpdateNeeded
and not self.pendingContainerChanges[bagNum]
and not item._getItemInfoFailed
and not self.forceFullCacheUpdate
then
item.locked = nowLocked
self.lockOnlyChangedItems[item] = true
-- Process item if there are changes.
elseif
preItemLink ~= nowItemLink -- Item has changed.
or item.bagNum == nil -- AddItemBagInfo() hasn't been called on this slot yet.
or preLocked ~= nowLocked -- Locked/unlocked.
or preCount ~= nowCount -- Count has changed (changes to charges are checked separately).
or preTooltip ~= item.tooltip
or self.initialInventoryUpdateNeeded
or self.pendingContainerChanges[bagNum]
or item._getItemInfoFailed -- Item was flagged as requiring a refresh.
or self.forceFullCacheUpdate -- Update even items that may not appear to need it.
then
-- Clear the failure flag (see explanation just below the call to ItemInfo:Get()).
item._getItemInfoFailed = nil
itemChanges = true
-- Bag information is needed regardless of whether the slot is empty (bagNum, slotNum, bagType).
-- This must happen before calling ItemInfo:Get() so that bagNum/slotNum are available.
self:AddItemBagInfo(item, bagInfo)
item.slotNum = slotNum
-- Populate basic item information.
if
not BsItemInfo:Get(
nowItemLink, -- itemIdentifier
item, -- itemInfoTable
false, -- initialize
(preItemLink ~= nowItemLink), -- reinitialize
false, -- forceIntoLocalGameCache
self -- Inventory class instance
)
then
-- If GetItemInfo() returns nil, ItemInfo:Get() will return false. When this occurs,
-- bail and rely on cached inventory data until the next update attempt. This should only
-- happen during the first second or two after login, so we do delay the initial inventory
-- cache update to attempt to account for this. However, it's still safest to keep the
-- check in place.
-- Add a flag to this item's cache entry so we know that it absolutely has to be refreshed
-- next time around.
item._getItemInfoFailed = true
return
end
-- Need to store different data for filled vs. empty slots.
if item.itemString ~= nil then
-- Slot contains an item.
item.count = nowCount
item.locked = nowLocked or BS_ITEM_SKELETON.locked
item.readable = itemReadable or BS_ITEM_SKELETON.readable
-- We got charges from GetContainerItemInfo() and need to restore
-- because ItemInfo:Get() might have wiped them.
if nowCharges > 0 then
item.charges = nowCharges
end
else
-- Slot is empty.
BsItemInfo:InitializeEmptySlotItem(item)
if self.containers[bagNum].isProfessionBag then
item.name = item.bagType .. " " .. item.name
end
-- Slot has just become empty - don't allow it to collapse back
-- into the stack until re-sorting occurs.
if preItemLink ~= nowItemLink then
item._bagshuiPreventEmptySlotStack = true
end
end
-- Check for stock state changes.
if
item.itemString ~= nil
and not self.freshCache
-- Nothing has actually changed if the item was just picked up and put back down.
and Bagshui.lastCursorItemUniqueId ~= BsItemInfo:GetUniqueItemId(item)
then
if item.itemString ~= preItemString and item.bagshuiDate == -1 then
-- Item itself has changed, so flag it as new.
item._proposedStockState = BS_ITEM_STOCK_STATE.NEW
item._proposedDate = _G.time()
-- Enable resorting because an item DID change.
-- Even if the stock state change isn't approved, resort may be needed.
majorChanges = true
elseif item.itemString == preItemString and item.count ~= preCount and preCount ~= -1 then
-- Item has not changed, but count did, so stock state needs to be updated.
if item.count > preCount then
item._proposedStockState = BS_ITEM_STOCK_STATE.UP
else
item._proposedStockState = BS_ITEM_STOCK_STATE.DOWN
end
item._proposedDate = _G.time()
-- Also enable resorting since count is usually a sort property.
-- This isn't dependent on the stock change being approved since the item
-- slot count DID change, which can require a resort.
majorChanges = true
elseif
self.initialInventoryUpdateNeeded
and Bagshui.currentCharacterData.lastLogout
and (item.bagshuiDate or 0) > 0
then
-- Just logged in, so move item dates forward by the amount of time since this
-- character last logged out. This makes it so that only in-game time is counted.
item.bagshuiDate = item.bagshuiDate + (_G.time() - Bagshui.currentCharacterData.lastLogout)
end
end
elseif
not BS_SUPER_WOW_LOADED
and preCharges ~= item.charges
-- Tooltip gets loaded earlier when a full cache update happens.
and not self.forceFullCacheUpdate
then
-- None of the "big" changes happened to trigger a call to ItemInfo:Get(),
-- but the item had some number of charges that we parsed from the tooltip
-- previously, so we need to refresh it.
-- (Normally the call to GetTooltip() happens automatically within ItemInfo:Get().)
BsItemInfo:GetTooltip(item, self)
end -- Item changes check.
-- Add to empty slot tracking table. This list is rebuilt on every cache update, so it has
-- to cover unchanged empty slots too (not just slots that changed in this update).
-- See `Inventory:SwapBag()` comments regarding the exclusion of profession bags.
if item.emptySlot == 1 and bagInfo.genericType == BsGameInfo.itemSubclasses["Container"]["Bag"] then
table.insert(self.emptyGenericContainerSlots, item)
end
-- When charges has changed, a re-sort is probably needed.
-- This check is here so that it works for both SuperWoW and native charges parsing.
if item.charges ~= preCharges then
majorChanges = true
end
-- Build "shadow" IDs for non-empty slots. (See comment above the declaration of shadowId for full explanation.)
if item.emptySlot ~= 1 then
shadowId = BsItemInfo:GetUniqueItemId(item)
-- Determine whether a restoration of stock state is required.
if self.pendingContainerChanges[bagNum] then
-- Bag swap.
prevShadowId = BsItemInfo:GetUniqueItemId(item, self.pendingContainerChanges[bagNum])
elseif
Bagshui.pickedUpItemBagNum
and Bagshui.pickedUpItemSlotNum
and self.myContainerIds[Bagshui.pickedUpItemBagNum]
and Bagshui.putDownItemBagNum
and Bagshui.putDownItemSlotNum
and self.myContainerIds[Bagshui.putDownItemBagNum]
and (
(item.bagNum == Bagshui.pickedUpItemBagNum and item.slotNum == Bagshui.pickedUpItemSlotNum)
or (item.bagNum == Bagshui.putDownItemBagNum and item.slotNum == Bagshui.putDownItemSlotNum)
)
then
-- Item moved.
if item.bagNum == Bagshui.pickedUpItemBagNum and item.slotNum == Bagshui.pickedUpItemSlotNum then
prevShadowId = BsItemInfo:GetUniqueItemId(item, Bagshui.putDownItemBagNum, Bagshui.putDownItemSlotNum)
else
prevShadowId = BsItemInfo:GetUniqueItemId(item, Bagshui.pickedUpItemBagNum, Bagshui.pickedUpItemSlotNum)
end
end
end
-- Reasons to restore or clear the item stock state.
if prevShadowId and self.shadowStockState[prevShadowId] then
-- Restore stock state from the item's most recent location after a container move.
-- (See Bagshui:PickupInventoryItem() for reasoning).
item.bagshuiDate = (self.shadowBagshuiDate[prevShadowId] or 0)
item.bagshuiStockState = self.shadowStockState[prevShadowId]
elseif item.bagshuiDate == -1 then
-- -1 means to clear the stock state.
-- Among other things, this can be triggered by Inventory:ItemButton_OnClick().
item.bagshuiDate = 0
item.bagshuiStockState = BS_ITEM_STOCK_STATE.NO_CHANGE
end
-- Update partial stack tracking.
if item.count < (tonumber(item.maxStackCount) or 0) then
if self.partialStacks[item.id] == nil then
self.partialStacks[item.id] = 1
else
self.partialStacks[item.id] = self.partialStacks[item.id] + 1
self.multiplePartialStacks = true
end
end
-- Item isn't assigned to a group, so resort is required.
if item.bagshuiGroupId == "" then
majorChanges = true
end
-- Locked status has changed, so an update is a good idea.
if item.locked ~= preLocked then
cosmeticChanges = true
end
-- Clean up any vestigial properties at startup.
if self.initialInventoryUpdateNeeded then
for key, _ in pairs(item) do
if BS_ITEM_SKELETON[key] == nil then
item[key] = nil
end
end
end
-- Update the "shadow" stock state cache, but only if all pending
-- changes are cleared so that we don't overwrite anything too soon.
-- (See Bagshui:PickupInventoryItem() for reasoning.)
if shadowId and not hasPendingContainerChanges then
self.shadowBagshuiDate[shadowId] = item.bagshuiDate
self.shadowStockState[shadowId] = item.bagshuiStockState
end
-- Update item counts for use in stock state rectification.
if item.itemString then
if not self.postUpdateItemCounts[item.itemString] then
self.postUpdateItemCounts[item.itemString] = 0
end
self.postUpdateItemCounts[item.itemString] = self.postUpdateItemCounts[item.itemString] + item.count
end
end -- Item slot loop within each bag.
else
-- There are no slots, so wipe the cache for this bag.
-- self:PrintDebug("no slots in bag "..bagNum.." wiping cache")
if table.getn(self.inventory[bagNum]) ~= 0 then
majorChanges = true
end
BsUtil.TableClear(self.inventory[bagNum])
end
end -- Bag [container] loop.
-- Post-update stock state rectification.
-- Only allow changes to stock state when they make sense at an inventory level,
-- not just because an individual slot changed.
for _, container in pairs(self.inventory) do
for _, item in pairs(container) do
if
item._proposedStockState
and (
(
-- Down: New count must be less than old count.
item._proposedStockState == BS_ITEM_STOCK_STATE.DOWN
and self.postUpdateItemCounts[item.itemString] < (self.preUpdateItemCounts[item.itemString] or 0)
)
or (
-- New/Up: New count must be greater than old count.
item._proposedStockState ~= BS_ITEM_STOCK_STATE.DOWN
and self.postUpdateItemCounts[item.itemString] > (self.preUpdateItemCounts[item.itemString] or 0)
)
)
then
-- self:PrintDebug(item.name .. " proposed " .. tostring(item._proposedStockState) .. " " .. tostring(self.preUpdateItemCounts[item.itemString]) .. " -> " .. tostring(self.postUpdateItemCounts[item.itemString]) .. " approved")
item.bagshuiStockState = item._proposedStockState
item.bagshuiDate = item._proposedDate
-- elseif item._proposedStockState then
-- self:PrintDebug(item.name .. " proposed " .. tostring(item._proposedStockState) .. " " .. tostring(self.preUpdateItemCounts[item.itemString]) .. " -> " .. tostring(self.postUpdateItemCounts[item.itemString]) .. " DENIED")
end
item._proposedStockState = nil
item._proposedDate = nil
item._allowStockStateChange = nil
-- Update tracking of whether there are highlight-able items.
if item.bagshuiStockState ~= BS_ITEM_STOCK_STATE.NO_CHANGE then
self.hasChanges = true
end
-- Update tracking of empty slots peeled off the stack.
if item._bagshuiPreventEmptySlotStack then
self.hasSlotsWithStackingPrevented = true
end
end
end
-- This update cycle's post-update counts become next cycle's pre-update counts.
BsUtil.TableCopy(self.postUpdateItemCounts, self.preUpdateItemCounts)
BsUtil.TableClear(self.postUpdateItemCounts)
-- Store resort/update status for use by Update() and friends in Inventory.Layout.lua.
if majorChanges then
self.resortNeeded = true
self.windowUpdateNeeded = true
elseif minorChanges then
self.resortNeeded = self.resortNeeded or false
self.windowUpdateNeeded = true
elseif cosmeticChanges then
self.windowUpdateNeeded = true
end
self.cacheChanged = (majorChanges or cosmeticChanges or minorChanges)
-- Lock-only (cosmetic) changes redraw the window but don't need every item re-categorized.
self.cacheNeedsCategorize = (majorChanges or minorChanges or itemChanges)
-- Can Restack move anything into a specialty bag? Only re-checked when items changed.
if self.cacheNeedsCategorize or self.specialtyMovesAvailable == nil then
self.specialtyMovesAvailable = self:FindSpecialtyBagMoves(false) > 0
end
-- Nothing but items locking/unlocking: Update() can refresh just those buttons.
self.cacheOnlyLockChanges = (cosmeticChanges and not self.cacheNeedsCategorize) and true or false
-- Make sure we don't do a full cache rebuild again.
self.initialInventoryUpdateNeeded = false
-- Item stock states can now be tracked.
self.freshCache = false
-- Reset cache flags.
self.cacheUpdateNeeded = false
self.forceCacheUpdate = false
self.forceFullCacheUpdate = false
-- Reset triggers for container-change based stock state restoration.
-- (See Bagshui:PickupInventoryItem() for reasoning.)
if hasPendingContainerChanges and Bagshui.pickedUpBagSlotNum and Bagshui.putDownBagSlotNum then
Bagshui.pickedUpBagSlotNum = nil
Bagshui.putDownBagSlotNum = nil
BsUtil.TableClear(self.pendingContainerChanges)
end
-- Raise change event.
if majorChanges or minorChanges or cosmeticChanges then
Bagshui:RaiseEvent("BAGSHUI_INVENTORY_CACHE_UPDATE")
end
end
--- Smart version of GetContainerNumSlots that will switch functions if it's
--- a special container that requires a different function.
---@param bagNum integer Container number for which to get the slot count.
---@return integer bagSlotCount
function Inventory:GetContainerNumSlots(bagNum)
local bagNumSlotsFunction = _G.GetContainerNumSlots
if self.primaryContainer.numSlotsFunction and self.primaryContainer.id == bagNum then
bagNumSlotsFunction = self.primaryContainer.numSlotsFunction
end
return bagNumSlotsFunction(bagNum)
end
--- Add bag information to the given cache item.
---@param item table self.inventory cache entry.
---@param bagInfo table self.containers entry.
---@param isEmptySlotStack boolean? true if this slot
function Inventory:AddItemBagInfo(item, bagInfo, isEmptySlotStack)
item.bagNum = bagInfo.bagNum
-- Empty slot stack proxy items expected to have their "generic type" as their bagType
-- instead of the value returned by GetItemInfo() for the bag. This is needed
-- so that profession bag slots can be named properly in InitializeEmptySlotItem().
item.bagType = isEmptySlotStack and bagInfo.genericType or bagInfo.type
end
--- Prepare an entry in the emptySlotStacks table for to track the empty slot count for a given bag.
---@param bagInfo table self.containers entry.
function Inventory:InitializeEmptySlotStackTracking(bagInfo)
if not bagInfo.genericType then
return
end
if not self.emptySlotStacks[bagInfo.genericType] then
self.emptySlotStacks[bagInfo.genericType] = {}
end
-- Always re-initialize when called to ensure bag changes don't result in incorrect empty slot textures.
BsItemInfo:InitializeItem(self.emptySlotStacks[bagInfo.genericType])
self:AddItemBagInfo(self.emptySlotStacks[bagInfo.genericType], bagInfo, true)
BsItemInfo:InitializeEmptySlotItem(self.emptySlotStacks[bagInfo.genericType], true)
end
end)