fix: occupied bank container

This commit is contained in:
Vati
2026-04-15 18:42:51 +04:00
parent 85e837b1bd
commit e57f59dadb
2 changed files with 87 additions and 34 deletions
+73 -34
View File
@@ -6,9 +6,19 @@ local BagReplacer = {}
addon.Modules.BagReplacer = BagReplacer
BagReplacer.inProgress = false
BagReplacer.slowMode = false -- true while replacing a bank bag (bank writes are server-validated and slower)
local C = addon.Constants
-- Time scaler: bank operations need longer debounce/timeout because the server
-- authoritatively validates every slot write (locks, bag updates) with round-trip delay.
local function T(seconds)
if BagReplacer.slowMode then
return seconds * 2.5
end
return seconds
end
--===========================================================================
-- Lock-wait mechanism (independent from SortEngine)
--===========================================================================
@@ -62,7 +72,8 @@ replacerFrame:SetScript("OnEvent", function()
end)
local function WaitForLocksCleared(callback, timeout, minDelay)
minDelay = minDelay or 0.15
minDelay = minDelay or T(0.15)
timeout = timeout or T(1.5)
Guda_ScheduleTimer(minDelay, function()
if not AnyPendingLocked() then
ClearPendingLocks()
@@ -71,7 +82,7 @@ local function WaitForLocksCleared(callback, timeout, minDelay)
end
waitingForLocks = true
onLocksCleared = callback
local remaining = (timeout or 1.5) - minDelay
local remaining = timeout - minDelay
if remaining < 0.3 then remaining = 0.3 end
Guda_ScheduleTimer(remaining, function()
if waitingForLocks then
@@ -106,27 +117,33 @@ end
-- Build list of free slots across all bags except the target bag
-- Returns: { {bagID, slotID, bagType}, ... }
--===========================================================================
local function GetAvailableFreeSlots(excludeBagID)
local function GetAvailableFreeSlots(excludeBagID, isBank)
local freeSlots = {}
local count = 0
-- Iterate bags 0-4, skipping the excluded bag
for bagID = 0, C.BAG_LAST do
if bagID ~= excludeBagID then
local numSlots = GetContainerNumSlots(bagID)
if numSlots and numSlots > 0 then
local bagType = addon.Modules.Utils:GetSpecializedBagType(bagID)
for slot = 1, numSlots do
local texture = GetContainerItemInfo(bagID, slot)
if not texture then
count = count + 1
freeSlots[count] = { bagID = bagID, slot = slot, bagType = bagType }
end
end
local function scan(bagID)
if bagID == excludeBagID then return end
local numSlots = GetContainerNumSlots(bagID)
if not numSlots or numSlots == 0 then return end
local bagType = addon.Modules.Utils:GetSpecializedBagType(bagID)
for slot = 1, numSlots do
local texture = GetContainerItemInfo(bagID, slot)
if not texture then
count = count + 1
freeSlots[count] = { bagID = bagID, slot = slot, bagType = bagType }
end
end
end
if isBank then
-- Main bank container + equipped bank bags
scan(-1)
for bagID = C.BANK_FIRST, C.BANK_LAST do scan(bagID) end
else
-- Backpack + inventory bags
for bagID = 0, C.BAG_LAST do scan(bagID) end
end
return freeSlots, count
end
@@ -159,14 +176,14 @@ end
-- BuildEvacuationPlan: assign each item to a free slot, reserve stash slot
-- Returns: plan table or nil, errorMsg
--===========================================================================
function BagReplacer:BuildEvacuationPlan(targetBagID)
function BagReplacer:BuildEvacuationPlan(targetBagID, isBank)
local items, itemCount = GetBagItems(targetBagID)
addon:DebugSort("[BagReplacer] BuildEvacuationPlan: bag %d has %d items", targetBagID, itemCount)
addon:DebugSort("[BagReplacer] BuildEvacuationPlan: bag %d has %d items (isBank=%s)", targetBagID, itemCount, tostring(isBank))
if itemCount == 0 then
return { moves = {}, stashBag = nil, stashSlot = nil, itemCount = 0 }, nil
return { moves = {}, stashBag = nil, stashSlot = nil, itemCount = 0, isBank = isBank }, nil
end
local freeSlots, freeCount = GetAvailableFreeSlots(targetBagID)
local freeSlots, freeCount = GetAvailableFreeSlots(targetBagID, isBank)
local needed = itemCount + 1 -- +1 for stashing the new bag from cursor
addon:DebugSort("[BagReplacer] Need %d free slots (items+stash), have %d", needed, freeCount)
@@ -268,7 +285,8 @@ function BagReplacer:BuildEvacuationPlan(targetBagID)
stashBag = stashBag,
stashSlot = stashSlot,
itemCount = itemCount,
targetBagID = targetBagID
targetBagID = targetBagID,
isBank = isBank
}, nil
end
@@ -282,6 +300,7 @@ function BagReplacer:Abort(reason)
waitingForLocks = false
onLocksCleared = nil
self.inProgress = false
self.slowMode = false
if reason then
addon:Print(reason)
end
@@ -294,22 +313,31 @@ end
--===========================================================================
-- FinishReplacement: pick up new bag from stash, equip it, handle old bag
--===========================================================================
local function FinalizeReplacement()
local function FinalizeReplacement(plan)
ClearPendingLocks()
waitingForLocks = false
onLocksCleared = nil
BagReplacer.inProgress = false
BagReplacer.slowMode = false
addon:Print(Guda_L["Bag replaced successfully!"])
-- Invalidate bag scanner cache BEFORE update so it does a full rescan
-- Invalidate scanner cache BEFORE update so it does a full rescan
-- (bag slot count changed, incremental update can't handle that)
if addon.Modules.BagScanner and addon.Modules.BagScanner.InvalidateCache then
addon.Modules.BagScanner:InvalidateCache()
end
if plan and plan.isBank
and addon.Modules.BankScanner and addon.Modules.BankScanner.InvalidateCache then
addon.Modules.BankScanner:InvalidateCache()
end
-- Refresh UI
if addon.Modules.BagFrame and addon.Modules.BagFrame.Update then
addon.Modules.BagFrame:Update()
end
if plan and plan.isBank
and addon.Modules.BankFrame and addon.Modules.BankFrame.Update then
addon.Modules.BankFrame:Update()
end
end
-- Wait for BAG_UPDATE on the target bag to confirm the server processed the equip,
@@ -326,7 +354,7 @@ bagUpdateFrame:SetScript("OnEvent", function()
bagUpdateCallback = nil
bagUpdateTarget = nil
-- Small delay to let all related events settle
Guda_ScheduleTimer(0.15, cb)
Guda_ScheduleTimer(T(0.15), cb)
end
end)
@@ -335,7 +363,7 @@ local function WaitForBagUpdate(targetBagID, callback)
bagUpdateTarget = targetBagID
bagUpdateFrame:RegisterEvent("BAG_UPDATE")
-- Safety timeout
Guda_ScheduleTimer(1.5, function()
Guda_ScheduleTimer(T(1.5), function()
if bagUpdateCallback then
addon:DebugSort("[BagReplacer] BAG_UPDATE timeout for bag %d, proceeding", targetBagID)
bagUpdateFrame:UnregisterEvent("BAG_UPDATE")
@@ -347,8 +375,9 @@ local function WaitForBagUpdate(targetBagID, callback)
end)
end
function BagReplacer:FinishReplacement(invSlot, stashBag, stashSlot, targetBagID)
addon:DebugSort("[BagReplacer] FinishReplacement: picking up new bag from bag%d/slot%d, equipping to invSlot %d (bag %d)", stashBag, stashSlot, invSlot, targetBagID)
function BagReplacer:FinishReplacement(invSlot, plan)
local stashBag, stashSlot, targetBagID = plan.stashBag, plan.stashSlot, plan.targetBagID
addon:DebugSort("[BagReplacer] FinishReplacement: picking up new bag from bag%d/slot%d, equipping to invSlot %d (bag %d, isBank=%s)", stashBag, stashSlot, invSlot, targetBagID, tostring(plan.isBank))
-- Verify new bag is still in stash slot
local stashTexture = GetContainerItemInfo(stashBag, stashSlot)
if not stashTexture then
@@ -361,7 +390,7 @@ function BagReplacer:FinishReplacement(invSlot, stashBag, stashSlot, targetBagID
AddPendingLock(stashBag, stashSlot)
-- Wait briefly for pickup, then equip
Guda_ScheduleTimer(0.15, function()
Guda_ScheduleTimer(T(0.15), function()
if CursorHasItem and CursorHasItem() then
addon:DebugSort("[BagReplacer] Equipping new bag to invSlot %d", invSlot)
EquipCursorItem(invSlot)
@@ -371,7 +400,8 @@ function BagReplacer:FinishReplacement(invSlot, stashBag, stashSlot, targetBagID
if CursorHasItem and CursorHasItem() then
addon:DebugSort("[BagReplacer] Old bag on cursor, finding free slot to place it")
-- Old bag is on cursor - try to find a free slot for it
local freeSlots, freeCount = GetAvailableFreeSlots(-999) -- exclude nothing
-- Use the same pool (bank or inventory) as the replacement
local freeSlots, freeCount = GetAvailableFreeSlots(-999, plan.isBank)
for i = 1, freeCount do
local fs = freeSlots[i]
if not fs.bagType then -- regular slot only
@@ -384,7 +414,7 @@ function BagReplacer:FinishReplacement(invSlot, stashBag, stashSlot, targetBagID
-- If still on cursor, that's fine - user can place manually
end
FinalizeReplacement()
FinalizeReplacement(plan)
end)
else
BagReplacer:Abort("Bag replacement failed: could not pick up new bag.")
@@ -410,7 +440,7 @@ function BagReplacer:ExecuteNextBatch(plan, index, invSlot)
-- All moves done - finish replacement
if index > totalMoves then
addon:DebugSort("[BagReplacer] All %d moves complete, finishing replacement", totalMoves)
self:FinishReplacement(invSlot, plan.stashBag, plan.stashSlot, plan.targetBagID)
self:FinishReplacement(invSlot, plan)
return
end
@@ -473,8 +503,8 @@ end
--===========================================================================
-- Execute: main entry point
--===========================================================================
function BagReplacer:Execute(targetBagID, invSlot)
addon:DebugSort("[BagReplacer] Execute: targetBagID=%d, invSlot=%d", targetBagID, invSlot)
function BagReplacer:Execute(targetBagID, invSlot, isBank)
addon:DebugSort("[BagReplacer] Execute: targetBagID=%d, invSlot=%d, isBank=%s", targetBagID, invSlot, tostring(isBank))
-- Guard: already in progress
if self.inProgress then
@@ -498,6 +528,14 @@ function BagReplacer:Execute(targetBagID, invSlot)
return
end
-- Guard: bank must be open for bank-bag swaps (server rejects bank writes otherwise)
if isBank and addon.Modules.BankScanner
and not addon.Modules.BankScanner:IsBankOpen() then
addon:DebugSort("[BagReplacer] Blocked: bank not open")
addon:Print("Cannot replace bank bag: bank is not open.")
return
end
-- Check if bag is empty (normal equip works)
local _, itemCount = GetBagItems(targetBagID)
if itemCount == 0 then
@@ -511,7 +549,7 @@ function BagReplacer:Execute(targetBagID, invSlot)
end
-- Build evacuation plan
local plan, errorMsg = self:BuildEvacuationPlan(targetBagID)
local plan, errorMsg = self:BuildEvacuationPlan(targetBagID, isBank)
if not plan then
addon:DebugSort("[BagReplacer] Plan failed: %s", errorMsg)
addon:Print(errorMsg)
@@ -520,6 +558,7 @@ function BagReplacer:Execute(targetBagID, invSlot)
-- Start replacement
self.inProgress = true
self.slowMode = isBank and true or false
addon:Print(string.format("Replacing bag: moving %d items...", plan.itemCount))
addon:DebugSort("[BagReplacer] Starting replacement: %d moves, stash at bag%d/slot%d", table.getn(plan.moves), plan.stashBag, plan.stashSlot)
+14
View File
@@ -2898,6 +2898,20 @@ function Guda_BankBagSlot_OnReceiveDrag(button, bagID)
local purchased = (bankButtonID and bankButtonID <= GetNumBankSlots())
if not purchased then return end
-- If the target bank bag has items, evacuate them before swapping
local numSlots = GetContainerNumSlots(bagID)
local hasItems = false
if numSlots and numSlots > 0 then
for slot = 1, numSlots do
if GetContainerItemInfo(bagID, slot) then hasItems = true; break end
end
end
if hasItems and addon.Modules.BagReplacer then
addon.Modules.BagReplacer:Execute(bagID, invSlot, true)
return -- BagReplacer handles its own UI updates
end
if EquipCursorItem then
EquipCursorItem(invSlot)
elseif PutItemInBag then