diff --git a/UI/BagFrame.lua b/UI/BagFrame.lua
index 7832b74..99cb8e0 100644
--- a/UI/BagFrame.lua
+++ b/UI/BagFrame.lua
@@ -342,10 +342,19 @@ function BagFrame:Update()
if viewType == "category" then
self:DisplayItemsByCategory(bagData, isOtherChar, charName)
- if getglobal("Guda_BagFrame_SortButton") then getglobal("Guda_BagFrame_SortButton"):Hide() end
+ -- Show sort button with merge icon/tooltip for category view
+ local sortBtn = getglobal("Guda_BagFrame_SortButton")
+ if sortBtn then
+ sortBtn:Show()
+ sortBtn.isCategoryView = true
+ end
else
self:DisplayItems(bagData, isOtherChar, charName)
- if getglobal("Guda_BagFrame_SortButton") then getglobal("Guda_BagFrame_SortButton"):Show() end
+ local sortBtn = getglobal("Guda_BagFrame_SortButton")
+ if sortBtn then
+ sortBtn:Show()
+ sortBtn.isCategoryView = false
+ end
end
-- Update money
@@ -1304,6 +1313,13 @@ function Guda_BagFrame_Sort()
return
end
+ -- Check if we're in category view - only merge stacks
+ local sortBtn = getglobal("Guda_BagFrame_SortButton")
+ if sortBtn and sortBtn.isCategoryView then
+ Guda_BagFrame_MergeStacks()
+ return
+ end
+
local success, message = addon.Modules.SortEngine:ExecuteSort(
function() return addon.Modules.SortEngine:SortBagsPass() end,
function() return addon.Modules.SortEngine:AnalyzeBags() end,
@@ -1316,6 +1332,170 @@ function Guda_BagFrame_Sort()
end
end
+-- Merge stacks only (for category view) - queue-based approach like BagShui
+function Guda_BagFrame_MergeStacks()
+ if currentViewChar then
+ addon:Print("Cannot merge stacks for another character!")
+ return
+ end
+
+ local bagIDs = addon.Constants.BAGS
+ local moveQueue = {}
+
+ -- Collect all partial stacks grouped by item
+ local partialStacks = {}
+ for _, bagID in ipairs(bagIDs) do
+ local numSlots = addon.Modules.Utils:GetBagSlotCount(bagID)
+ if numSlots and numSlots > 0 then
+ for slot = 1, numSlots do
+ local link = GetContainerItemLink(bagID, slot)
+ if link then
+ local texture, count = GetContainerItemInfo(bagID, slot)
+ local _, _, itemID = string.find(link, "item:(%d+)")
+ itemID = tonumber(itemID)
+
+ if itemID then
+ local _, _, _, _, _, _, itemStackCount = GetItemInfo(itemID)
+ local maxStack = tonumber(itemStackCount) or 1
+
+ -- Only track items that can stack and aren't full
+ if maxStack > 1 and count < maxStack then
+ local groupKey = tostring(itemID)
+ if not partialStacks[groupKey] then
+ partialStacks[groupKey] = {
+ maxStack = maxStack,
+ stacks = {}
+ }
+ end
+ table.insert(partialStacks[groupKey].stacks, {
+ bagID = bagID,
+ slot = slot,
+ count = count
+ })
+ end
+ end
+ end
+ end
+ end
+ end
+
+ -- Build move queue for each item group
+ for _, group in pairs(partialStacks) do
+ if table.getn(group.stacks) > 1 then
+ -- Sort stacks: larger stacks first (targets), smaller stacks last (sources)
+ table.sort(group.stacks, function(a, b)
+ return a.count > b.count
+ end)
+
+ local sourceLoopStart = table.getn(group.stacks)
+
+ -- Process targets from start, sources from end
+ for targetIdx = 1, table.getn(group.stacks) - 1 do
+ local target = group.stacks[targetIdx]
+
+ if target.count < group.maxStack and target.count > 0 then
+ for sourceIdx = sourceLoopStart, targetIdx + 1, -1 do
+ local source = group.stacks[sourceIdx]
+
+ if source.count > 0 and target.count < group.maxStack then
+ -- Queue this move
+ table.insert(moveQueue, {
+ source = source,
+ target = target,
+ maxStack = group.maxStack
+ })
+
+ -- Calculate changes (for queue planning)
+ local oldTargetCount = target.count
+ target.count = math.min(target.count + source.count, group.maxStack)
+ source.count = source.count - (target.count - oldTargetCount)
+
+ -- Move source pointer if depleted
+ if source.count == 0 then
+ sourceLoopStart = sourceLoopStart - 1
+ end
+
+ -- Stop if target is full
+ if target.count >= group.maxStack then
+ break
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+
+ if table.getn(moveQueue) == 0 then
+ addon:Print("No stacks to merge")
+ return
+ end
+
+ -- Process queue with delays
+ local queueIndex = 1
+ local retryCount = 0
+ local totalMoves = table.getn(moveQueue)
+
+ local function ProcessNextMove()
+ if queueIndex > table.getn(moveQueue) then
+ addon:Print("Merged " .. totalMoves .. " stack(s)")
+ BagFrame:Update()
+ return
+ end
+
+ local move = moveQueue[queueIndex]
+ local source = move.source
+ local target = move.target
+
+ -- Check if items are locked
+ local _, _, sourceLocked = GetContainerItemInfo(source.bagID, source.slot)
+ local _, _, targetLocked = GetContainerItemInfo(target.bagID, target.slot)
+
+ if sourceLocked or targetLocked then
+ retryCount = retryCount + 1
+ if retryCount < 10 then
+ -- Retry after delay
+ Guda_ScheduleTimer(0.3, ProcessNextMove)
+ return
+ else
+ -- Give up on this move
+ retryCount = 0
+ queueIndex = queueIndex + 1
+ Guda_ScheduleTimer(0.1, ProcessNextMove)
+ return
+ end
+ end
+
+ -- Perform the move
+ ClearCursor()
+ PickupContainerItem(source.bagID, source.slot)
+ PickupContainerItem(target.bagID, target.slot)
+ ClearCursor()
+
+ -- Move to next
+ retryCount = 0
+ queueIndex = queueIndex + 1
+ Guda_ScheduleTimer(0.15, ProcessNextMove)
+ end
+
+ ProcessNextMove()
+end
+
+-- Simple timer helper
+function Guda_ScheduleTimer(delay, callback)
+ local frame = CreateFrame("Frame")
+ frame.elapsed = 0
+ frame.delay = delay
+ frame.callback = callback
+ frame:SetScript("OnUpdate", function()
+ this.elapsed = this.elapsed + arg1
+ if this.elapsed >= this.delay then
+ this:SetScript("OnUpdate", nil)
+ this.callback()
+ end
+ end)
+end
+
-- Hook bag container buttons to open Guda Bag View
local function HookBagContainers()
-- Hook the main bag container buttons (bags 1-4)
diff --git a/UI/BagFrame.xml b/UI/BagFrame.xml
index e13f8cf..ec71d30 100644
--- a/UI/BagFrame.xml
+++ b/UI/BagFrame.xml
@@ -145,7 +145,12 @@
GameTooltip:SetOwner(this, "ANCHOR_TOP")
- GameTooltip:SetText("Sort Bags")
+ if this.isCategoryView then
+ GameTooltip:SetText("Merge Stacks")
+ GameTooltip:AddLine("Combines split stacks of the same item", 0.7, 0.7, 0.7)
+ else
+ GameTooltip:SetText("Sort Bags")
+ end
GameTooltip:Show()
diff --git a/UI/BankFrame.lua b/UI/BankFrame.lua
index 18c9b63..b876140 100644
--- a/UI/BankFrame.lua
+++ b/UI/BankFrame.lua
@@ -192,10 +192,19 @@ function BankFrame:Update()
if viewType == "category" then
self:DisplayItemsByCategory(bankData, isOtherChar, charName)
- if getglobal("Guda_BankFrame_SortButton") then getglobal("Guda_BankFrame_SortButton"):Hide() end
+ -- Show sort button with merge icon/tooltip for category view
+ local sortBtn = getglobal("Guda_BankFrame_SortButton")
+ if sortBtn then
+ sortBtn:Show()
+ sortBtn.isCategoryView = true
+ end
else
self:DisplayItems(bankData, isOtherChar, charName)
- if getglobal("Guda_BankFrame_SortButton") then getglobal("Guda_BankFrame_SortButton"):Show() end
+ local sortBtn = getglobal("Guda_BankFrame_SortButton")
+ if sortBtn then
+ sortBtn:Show()
+ sortBtn.isCategoryView = false
+ end
end
-- Update money
@@ -939,6 +948,13 @@ function Guda_BankFrame_Sort()
return
end
+ -- Check if we're in category view - only merge stacks
+ local sortBtn = getglobal("Guda_BankFrame_SortButton")
+ if sortBtn and sortBtn.isCategoryView then
+ Guda_BankFrame_MergeStacks()
+ return
+ end
+
local success, message = addon.Modules.SortEngine:ExecuteSort(
function() return addon.Modules.SortEngine:SortBankPass() end,
function() return addon.Modules.SortEngine:AnalyzeBank() end,
@@ -951,6 +967,160 @@ function Guda_BankFrame_Sort()
end
end
+-- Merge stacks only (for category view) - queue-based approach like BagShui
+function Guda_BankFrame_MergeStacks()
+ if isReadOnlyMode or currentViewChar then
+ addon:Print("Cannot merge stacks in read-only mode!")
+ return
+ end
+
+ if not addon.Modules.BankScanner:IsBankOpen() then
+ addon:Print("Bank must be open to merge stacks!")
+ return
+ end
+
+ local bagIDs = addon.Constants.BANK_BAGS
+ local moveQueue = {}
+
+ -- Collect all partial stacks grouped by item
+ local partialStacks = {}
+ for _, bagID in ipairs(bagIDs) do
+ local numSlots = addon.Modules.Utils:GetBagSlotCount(bagID)
+ if numSlots and numSlots > 0 then
+ for slot = 1, numSlots do
+ local link = GetContainerItemLink(bagID, slot)
+ if link then
+ local texture, count = GetContainerItemInfo(bagID, slot)
+ local _, _, itemID = string.find(link, "item:(%d+)")
+ itemID = tonumber(itemID)
+
+ if itemID then
+ local _, _, _, _, _, _, itemStackCount = GetItemInfo(itemID)
+ local maxStack = tonumber(itemStackCount) or 1
+
+ -- Only track items that can stack and aren't full
+ if maxStack > 1 and count < maxStack then
+ local groupKey = tostring(itemID)
+ if not partialStacks[groupKey] then
+ partialStacks[groupKey] = {
+ maxStack = maxStack,
+ stacks = {}
+ }
+ end
+ table.insert(partialStacks[groupKey].stacks, {
+ bagID = bagID,
+ slot = slot,
+ count = count
+ })
+ end
+ end
+ end
+ end
+ end
+ end
+
+ -- Build move queue for each item group
+ for _, group in pairs(partialStacks) do
+ if table.getn(group.stacks) > 1 then
+ -- Sort stacks: larger stacks first (targets), smaller stacks last (sources)
+ table.sort(group.stacks, function(a, b)
+ return a.count > b.count
+ end)
+
+ local sourceLoopStart = table.getn(group.stacks)
+
+ -- Process targets from start, sources from end
+ for targetIdx = 1, table.getn(group.stacks) - 1 do
+ local target = group.stacks[targetIdx]
+
+ if target.count < group.maxStack and target.count > 0 then
+ for sourceIdx = sourceLoopStart, targetIdx + 1, -1 do
+ local source = group.stacks[sourceIdx]
+
+ if source.count > 0 and target.count < group.maxStack then
+ -- Queue this move
+ table.insert(moveQueue, {
+ source = source,
+ target = target,
+ maxStack = group.maxStack
+ })
+
+ -- Calculate changes (for queue planning)
+ local oldTargetCount = target.count
+ target.count = math.min(target.count + source.count, group.maxStack)
+ source.count = source.count - (target.count - oldTargetCount)
+
+ -- Move source pointer if depleted
+ if source.count == 0 then
+ sourceLoopStart = sourceLoopStart - 1
+ end
+
+ -- Stop if target is full
+ if target.count >= group.maxStack then
+ break
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+
+ if table.getn(moveQueue) == 0 then
+ addon:Print("No stacks to merge")
+ return
+ end
+
+ -- Process queue with delays
+ local queueIndex = 1
+ local retryCount = 0
+ local totalMoves = table.getn(moveQueue)
+
+ local function ProcessNextMove()
+ if queueIndex > table.getn(moveQueue) then
+ addon:Print("Merged " .. totalMoves .. " stack(s)")
+ BankFrame:Update()
+ return
+ end
+
+ local move = moveQueue[queueIndex]
+ local source = move.source
+ local target = move.target
+
+ -- Check if items are locked
+ local _, _, sourceLocked = GetContainerItemInfo(source.bagID, source.slot)
+ local _, _, targetLocked = GetContainerItemInfo(target.bagID, target.slot)
+
+ if sourceLocked or targetLocked then
+ retryCount = retryCount + 1
+ if retryCount < 10 then
+ -- Retry after delay
+ Guda_ScheduleTimer(0.3, ProcessNextMove)
+ return
+ else
+ -- Give up on this move
+ retryCount = 0
+ queueIndex = queueIndex + 1
+ Guda_ScheduleTimer(0.1, ProcessNextMove)
+ return
+ end
+ end
+
+ -- Perform the move
+ ClearCursor()
+ PickupContainerItem(source.bagID, source.slot)
+ PickupContainerItem(target.bagID, target.slot)
+ ClearCursor()
+
+ -- Move to next
+ retryCount = 0
+ queueIndex = queueIndex + 1
+ Guda_ScheduleTimer(0.15, ProcessNextMove)
+ end
+
+ ProcessNextMove()
+end
+
-- Switch to Blizzard bank UI
function Guda_BankFrame_SwitchToBlizzardUI()
-- Hide custom bank frame
diff --git a/UI/BankFrame.xml b/UI/BankFrame.xml
index 225f974..0784841 100644
--- a/UI/BankFrame.xml
+++ b/UI/BankFrame.xml
@@ -98,7 +98,12 @@
GameTooltip:SetOwner(this, "ANCHOR_TOP")
- GameTooltip:SetText("Sort Bank")
+ if this.isCategoryView then
+ GameTooltip:SetText("Merge Stacks")
+ GameTooltip:AddLine("Combines split stacks of the same item", 0.7, 0.7, 0.7)
+ else
+ GameTooltip:SetText("Sort Bank")
+ end
GameTooltip:Show()