fix: occupied bank container
This commit is contained in:
+73
-34
@@ -6,9 +6,19 @@ local BagReplacer = {}
|
|||||||
addon.Modules.BagReplacer = BagReplacer
|
addon.Modules.BagReplacer = BagReplacer
|
||||||
|
|
||||||
BagReplacer.inProgress = false
|
BagReplacer.inProgress = false
|
||||||
|
BagReplacer.slowMode = false -- true while replacing a bank bag (bank writes are server-validated and slower)
|
||||||
|
|
||||||
local C = addon.Constants
|
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)
|
-- Lock-wait mechanism (independent from SortEngine)
|
||||||
--===========================================================================
|
--===========================================================================
|
||||||
@@ -62,7 +72,8 @@ replacerFrame:SetScript("OnEvent", function()
|
|||||||
end)
|
end)
|
||||||
|
|
||||||
local function WaitForLocksCleared(callback, timeout, minDelay)
|
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()
|
Guda_ScheduleTimer(minDelay, function()
|
||||||
if not AnyPendingLocked() then
|
if not AnyPendingLocked() then
|
||||||
ClearPendingLocks()
|
ClearPendingLocks()
|
||||||
@@ -71,7 +82,7 @@ local function WaitForLocksCleared(callback, timeout, minDelay)
|
|||||||
end
|
end
|
||||||
waitingForLocks = true
|
waitingForLocks = true
|
||||||
onLocksCleared = callback
|
onLocksCleared = callback
|
||||||
local remaining = (timeout or 1.5) - minDelay
|
local remaining = timeout - minDelay
|
||||||
if remaining < 0.3 then remaining = 0.3 end
|
if remaining < 0.3 then remaining = 0.3 end
|
||||||
Guda_ScheduleTimer(remaining, function()
|
Guda_ScheduleTimer(remaining, function()
|
||||||
if waitingForLocks then
|
if waitingForLocks then
|
||||||
@@ -106,27 +117,33 @@ end
|
|||||||
-- Build list of free slots across all bags except the target bag
|
-- Build list of free slots across all bags except the target bag
|
||||||
-- Returns: { {bagID, slotID, bagType}, ... }
|
-- Returns: { {bagID, slotID, bagType}, ... }
|
||||||
--===========================================================================
|
--===========================================================================
|
||||||
local function GetAvailableFreeSlots(excludeBagID)
|
local function GetAvailableFreeSlots(excludeBagID, isBank)
|
||||||
local freeSlots = {}
|
local freeSlots = {}
|
||||||
local count = 0
|
local count = 0
|
||||||
|
|
||||||
-- Iterate bags 0-4, skipping the excluded bag
|
local function scan(bagID)
|
||||||
for bagID = 0, C.BAG_LAST do
|
if bagID == excludeBagID then return end
|
||||||
if bagID ~= excludeBagID then
|
local numSlots = GetContainerNumSlots(bagID)
|
||||||
local numSlots = GetContainerNumSlots(bagID)
|
if not numSlots or numSlots == 0 then return end
|
||||||
if numSlots and numSlots > 0 then
|
local bagType = addon.Modules.Utils:GetSpecializedBagType(bagID)
|
||||||
local bagType = addon.Modules.Utils:GetSpecializedBagType(bagID)
|
for slot = 1, numSlots do
|
||||||
for slot = 1, numSlots do
|
local texture = GetContainerItemInfo(bagID, slot)
|
||||||
local texture = GetContainerItemInfo(bagID, slot)
|
if not texture then
|
||||||
if not texture then
|
count = count + 1
|
||||||
count = count + 1
|
freeSlots[count] = { bagID = bagID, slot = slot, bagType = bagType }
|
||||||
freeSlots[count] = { bagID = bagID, slot = slot, bagType = bagType }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
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
|
return freeSlots, count
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -159,14 +176,14 @@ end
|
|||||||
-- BuildEvacuationPlan: assign each item to a free slot, reserve stash slot
|
-- BuildEvacuationPlan: assign each item to a free slot, reserve stash slot
|
||||||
-- Returns: plan table or nil, errorMsg
|
-- Returns: plan table or nil, errorMsg
|
||||||
--===========================================================================
|
--===========================================================================
|
||||||
function BagReplacer:BuildEvacuationPlan(targetBagID)
|
function BagReplacer:BuildEvacuationPlan(targetBagID, isBank)
|
||||||
local items, itemCount = GetBagItems(targetBagID)
|
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
|
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
|
end
|
||||||
|
|
||||||
local freeSlots, freeCount = GetAvailableFreeSlots(targetBagID)
|
local freeSlots, freeCount = GetAvailableFreeSlots(targetBagID, isBank)
|
||||||
local needed = itemCount + 1 -- +1 for stashing the new bag from cursor
|
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)
|
addon:DebugSort("[BagReplacer] Need %d free slots (items+stash), have %d", needed, freeCount)
|
||||||
|
|
||||||
@@ -268,7 +285,8 @@ function BagReplacer:BuildEvacuationPlan(targetBagID)
|
|||||||
stashBag = stashBag,
|
stashBag = stashBag,
|
||||||
stashSlot = stashSlot,
|
stashSlot = stashSlot,
|
||||||
itemCount = itemCount,
|
itemCount = itemCount,
|
||||||
targetBagID = targetBagID
|
targetBagID = targetBagID,
|
||||||
|
isBank = isBank
|
||||||
}, nil
|
}, nil
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -282,6 +300,7 @@ function BagReplacer:Abort(reason)
|
|||||||
waitingForLocks = false
|
waitingForLocks = false
|
||||||
onLocksCleared = nil
|
onLocksCleared = nil
|
||||||
self.inProgress = false
|
self.inProgress = false
|
||||||
|
self.slowMode = false
|
||||||
if reason then
|
if reason then
|
||||||
addon:Print(reason)
|
addon:Print(reason)
|
||||||
end
|
end
|
||||||
@@ -294,22 +313,31 @@ end
|
|||||||
--===========================================================================
|
--===========================================================================
|
||||||
-- FinishReplacement: pick up new bag from stash, equip it, handle old bag
|
-- FinishReplacement: pick up new bag from stash, equip it, handle old bag
|
||||||
--===========================================================================
|
--===========================================================================
|
||||||
local function FinalizeReplacement()
|
local function FinalizeReplacement(plan)
|
||||||
ClearPendingLocks()
|
ClearPendingLocks()
|
||||||
waitingForLocks = false
|
waitingForLocks = false
|
||||||
onLocksCleared = nil
|
onLocksCleared = nil
|
||||||
BagReplacer.inProgress = false
|
BagReplacer.inProgress = false
|
||||||
|
BagReplacer.slowMode = false
|
||||||
addon:Print(Guda_L["Bag replaced successfully!"])
|
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)
|
-- (bag slot count changed, incremental update can't handle that)
|
||||||
if addon.Modules.BagScanner and addon.Modules.BagScanner.InvalidateCache then
|
if addon.Modules.BagScanner and addon.Modules.BagScanner.InvalidateCache then
|
||||||
addon.Modules.BagScanner:InvalidateCache()
|
addon.Modules.BagScanner:InvalidateCache()
|
||||||
end
|
end
|
||||||
|
if plan and plan.isBank
|
||||||
|
and addon.Modules.BankScanner and addon.Modules.BankScanner.InvalidateCache then
|
||||||
|
addon.Modules.BankScanner:InvalidateCache()
|
||||||
|
end
|
||||||
-- Refresh UI
|
-- Refresh UI
|
||||||
if addon.Modules.BagFrame and addon.Modules.BagFrame.Update then
|
if addon.Modules.BagFrame and addon.Modules.BagFrame.Update then
|
||||||
addon.Modules.BagFrame:Update()
|
addon.Modules.BagFrame:Update()
|
||||||
end
|
end
|
||||||
|
if plan and plan.isBank
|
||||||
|
and addon.Modules.BankFrame and addon.Modules.BankFrame.Update then
|
||||||
|
addon.Modules.BankFrame:Update()
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Wait for BAG_UPDATE on the target bag to confirm the server processed the equip,
|
-- 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
|
bagUpdateCallback = nil
|
||||||
bagUpdateTarget = nil
|
bagUpdateTarget = nil
|
||||||
-- Small delay to let all related events settle
|
-- Small delay to let all related events settle
|
||||||
Guda_ScheduleTimer(0.15, cb)
|
Guda_ScheduleTimer(T(0.15), cb)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
@@ -335,7 +363,7 @@ local function WaitForBagUpdate(targetBagID, callback)
|
|||||||
bagUpdateTarget = targetBagID
|
bagUpdateTarget = targetBagID
|
||||||
bagUpdateFrame:RegisterEvent("BAG_UPDATE")
|
bagUpdateFrame:RegisterEvent("BAG_UPDATE")
|
||||||
-- Safety timeout
|
-- Safety timeout
|
||||||
Guda_ScheduleTimer(1.5, function()
|
Guda_ScheduleTimer(T(1.5), function()
|
||||||
if bagUpdateCallback then
|
if bagUpdateCallback then
|
||||||
addon:DebugSort("[BagReplacer] BAG_UPDATE timeout for bag %d, proceeding", targetBagID)
|
addon:DebugSort("[BagReplacer] BAG_UPDATE timeout for bag %d, proceeding", targetBagID)
|
||||||
bagUpdateFrame:UnregisterEvent("BAG_UPDATE")
|
bagUpdateFrame:UnregisterEvent("BAG_UPDATE")
|
||||||
@@ -347,8 +375,9 @@ local function WaitForBagUpdate(targetBagID, callback)
|
|||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
function BagReplacer:FinishReplacement(invSlot, stashBag, stashSlot, targetBagID)
|
function BagReplacer:FinishReplacement(invSlot, plan)
|
||||||
addon:DebugSort("[BagReplacer] FinishReplacement: picking up new bag from bag%d/slot%d, equipping to invSlot %d (bag %d)", stashBag, stashSlot, invSlot, targetBagID)
|
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
|
-- Verify new bag is still in stash slot
|
||||||
local stashTexture = GetContainerItemInfo(stashBag, stashSlot)
|
local stashTexture = GetContainerItemInfo(stashBag, stashSlot)
|
||||||
if not stashTexture then
|
if not stashTexture then
|
||||||
@@ -361,7 +390,7 @@ function BagReplacer:FinishReplacement(invSlot, stashBag, stashSlot, targetBagID
|
|||||||
AddPendingLock(stashBag, stashSlot)
|
AddPendingLock(stashBag, stashSlot)
|
||||||
|
|
||||||
-- Wait briefly for pickup, then equip
|
-- Wait briefly for pickup, then equip
|
||||||
Guda_ScheduleTimer(0.15, function()
|
Guda_ScheduleTimer(T(0.15), function()
|
||||||
if CursorHasItem and CursorHasItem() then
|
if CursorHasItem and CursorHasItem() then
|
||||||
addon:DebugSort("[BagReplacer] Equipping new bag to invSlot %d", invSlot)
|
addon:DebugSort("[BagReplacer] Equipping new bag to invSlot %d", invSlot)
|
||||||
EquipCursorItem(invSlot)
|
EquipCursorItem(invSlot)
|
||||||
@@ -371,7 +400,8 @@ function BagReplacer:FinishReplacement(invSlot, stashBag, stashSlot, targetBagID
|
|||||||
if CursorHasItem and CursorHasItem() then
|
if CursorHasItem and CursorHasItem() then
|
||||||
addon:DebugSort("[BagReplacer] Old bag on cursor, finding free slot to place it")
|
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
|
-- 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
|
for i = 1, freeCount do
|
||||||
local fs = freeSlots[i]
|
local fs = freeSlots[i]
|
||||||
if not fs.bagType then -- regular slot only
|
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
|
-- If still on cursor, that's fine - user can place manually
|
||||||
end
|
end
|
||||||
|
|
||||||
FinalizeReplacement()
|
FinalizeReplacement(plan)
|
||||||
end)
|
end)
|
||||||
else
|
else
|
||||||
BagReplacer:Abort("Bag replacement failed: could not pick up new bag.")
|
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
|
-- All moves done - finish replacement
|
||||||
if index > totalMoves then
|
if index > totalMoves then
|
||||||
addon:DebugSort("[BagReplacer] All %d moves complete, finishing replacement", totalMoves)
|
addon:DebugSort("[BagReplacer] All %d moves complete, finishing replacement", totalMoves)
|
||||||
self:FinishReplacement(invSlot, plan.stashBag, plan.stashSlot, plan.targetBagID)
|
self:FinishReplacement(invSlot, plan)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -473,8 +503,8 @@ end
|
|||||||
--===========================================================================
|
--===========================================================================
|
||||||
-- Execute: main entry point
|
-- Execute: main entry point
|
||||||
--===========================================================================
|
--===========================================================================
|
||||||
function BagReplacer:Execute(targetBagID, invSlot)
|
function BagReplacer:Execute(targetBagID, invSlot, isBank)
|
||||||
addon:DebugSort("[BagReplacer] Execute: targetBagID=%d, invSlot=%d", targetBagID, invSlot)
|
addon:DebugSort("[BagReplacer] Execute: targetBagID=%d, invSlot=%d, isBank=%s", targetBagID, invSlot, tostring(isBank))
|
||||||
|
|
||||||
-- Guard: already in progress
|
-- Guard: already in progress
|
||||||
if self.inProgress then
|
if self.inProgress then
|
||||||
@@ -498,6 +528,14 @@ function BagReplacer:Execute(targetBagID, invSlot)
|
|||||||
return
|
return
|
||||||
end
|
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)
|
-- Check if bag is empty (normal equip works)
|
||||||
local _, itemCount = GetBagItems(targetBagID)
|
local _, itemCount = GetBagItems(targetBagID)
|
||||||
if itemCount == 0 then
|
if itemCount == 0 then
|
||||||
@@ -511,7 +549,7 @@ function BagReplacer:Execute(targetBagID, invSlot)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- Build evacuation plan
|
-- Build evacuation plan
|
||||||
local plan, errorMsg = self:BuildEvacuationPlan(targetBagID)
|
local plan, errorMsg = self:BuildEvacuationPlan(targetBagID, isBank)
|
||||||
if not plan then
|
if not plan then
|
||||||
addon:DebugSort("[BagReplacer] Plan failed: %s", errorMsg)
|
addon:DebugSort("[BagReplacer] Plan failed: %s", errorMsg)
|
||||||
addon:Print(errorMsg)
|
addon:Print(errorMsg)
|
||||||
@@ -520,6 +558,7 @@ function BagReplacer:Execute(targetBagID, invSlot)
|
|||||||
|
|
||||||
-- Start replacement
|
-- Start replacement
|
||||||
self.inProgress = true
|
self.inProgress = true
|
||||||
|
self.slowMode = isBank and true or false
|
||||||
addon:Print(string.format("Replacing bag: moving %d items...", plan.itemCount))
|
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)
|
addon:DebugSort("[BagReplacer] Starting replacement: %d moves, stash at bag%d/slot%d", table.getn(plan.moves), plan.stashBag, plan.stashSlot)
|
||||||
|
|
||||||
|
|||||||
@@ -2898,6 +2898,20 @@ function Guda_BankBagSlot_OnReceiveDrag(button, bagID)
|
|||||||
local purchased = (bankButtonID and bankButtonID <= GetNumBankSlots())
|
local purchased = (bankButtonID and bankButtonID <= GetNumBankSlots())
|
||||||
if not purchased then return end
|
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
|
if EquipCursorItem then
|
||||||
EquipCursorItem(invSlot)
|
EquipCursorItem(invSlot)
|
||||||
elseif PutItemInBag then
|
elseif PutItemInBag then
|
||||||
|
|||||||
Reference in New Issue
Block a user