- UiFrame_OnShow requests a full categorization pass only if the last one was more than 60 seconds ago. - Lock-only cache updates refresh just the affected item buttons (AssignItemToItemButton) instead of running UpdateWindow(). - SortGroups() reuses a group's previous sorted order when none of its items were re-categorized and it holds exactly the same items; any full categorization pass invalidates all sorted orders. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019W3rwt5PZSpEDkXYzRuRhH
632 lines
30 KiB
Lua
632 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)
|
|
-- 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)
|