From 868676299028d733a27d8ffd049b407e5d984d7b Mon Sep 17 00:00:00 2001 From: Salikh Gurgenidze Date: Mon, 24 Nov 2025 02:09:06 +0400 Subject: [PATCH] fix: sorting is fixed --- Core/Init.lua | 12 +- Guda.toc | 2 +- Sorting/SortEngine.lua | 431 +++++++++++++++++++++++------------------ UI/BagFrame.lua | 65 +------ UI/BankFrame.lua | 81 ++------ 5 files changed, 272 insertions(+), 319 deletions(-) diff --git a/Core/Init.lua b/Core/Init.lua index de02191..9ea35ca 100644 --- a/Core/Init.lua +++ b/Core/Init.lua @@ -117,22 +117,22 @@ function addon:ApplyBackdrop(frame, backdropType, colorType) end -- Print function with addon prefix -function addon:Print(msg, a1, a2, a3, a4, a5) - local text = string.format(msg, a1, a2, a3, a4, a5) +function addon:Print(msg, a1, a2, a3, a4, a5, a6, a7) + local text = string.format(msg, a1, a2, a3, a4, a5, a6, a7) DEFAULT_CHAT_FRAME:AddMessage("|cFF00FF96Guda:|r " .. text) end -- Debug print -function addon:Debug(msg, a1, a2, a3, a4, a5) +function addon:Debug(msg, a1, a2, a3, a4, a5, a6, a7) if self.DEBUG then - local text = string.format(msg, a1, a2, a3, a4, a5) + local text = string.format(msg, a1, a2, a3, a4, a5, a6, a7) DEFAULT_CHAT_FRAME:AddMessage("|cFFFFFF00[Debug]|r |cFF00FF96Guda:|r " .. text) end end -- Error handler -function addon:Error(msg, a1, a2, a3, a4, a5) - local text = string.format(msg, a1, a2, a3, a4, a5) +function addon:Error(msg, a1, a2, a3, a4, a5, a6, a7) + local text = string.format(msg, a1, a2, a3, a4, a5, a6, a7) DEFAULT_CHAT_FRAME:AddMessage("|cFFFF0000[Error]|r |cFF00FF96Guda:|r " .. text) end diff --git a/Guda.toc b/Guda.toc index bc0dd37..7f1b425 100644 --- a/Guda.toc +++ b/Guda.toc @@ -2,7 +2,7 @@ ## Title: Guda ## Notes: All-in-one bag and bank addon for Turtle WoW ## Author: Vati -## Version: 1.1.5 +## Version: 1.1.6 ## SavedVariables: Guda_DB ## SavedVariablesPerCharacter: Guda_CharDB diff --git a/Sorting/SortEngine.lua b/Sorting/SortEngine.lua index ccd5b1e..2767d22 100644 --- a/Sorting/SortEngine.lua +++ b/Sorting/SortEngine.lua @@ -21,31 +21,31 @@ local PRIORITY_ITEMS = { -- Equipment slot ordering (for sorting equippable gear by slot) local EQUIP_SLOT_ORDER = { - ["INVTYPE_HEAD"] = 1, - ["INVTYPE_NECK"] = 2, - ["INVTYPE_SHOULDER"] = 3, - ["INVTYPE_CLOAK"] = 4, - ["INVTYPE_CHEST"] = 5, - ["INVTYPE_ROBE"] = 5, - ["INVTYPE_BODY"] = 6, - ["INVTYPE_TABARD"] = 7, - ["INVTYPE_WRIST"] = 8, - ["INVTYPE_HAND"] = 9, - ["INVTYPE_WAIST"] = 10, - ["INVTYPE_LEGS"] = 11, - ["INVTYPE_FEET"] = 12, - ["INVTYPE_FINGER"] = 13, - ["INVTYPE_TRINKET"] = 14, - ["INVTYPE_WEAPONMAINHAND"] = 15, - ["INVTYPE_WEAPONOFFHAND"] = 16, - ["INVTYPE_WEAPON"] = 17, - ["INVTYPE_2HWEAPON"] = 18, - ["INVTYPE_SHIELD"] = 19, - ["INVTYPE_HOLDABLE"] = 20, - ["INVTYPE_RANGED"] = 21, - ["INVTYPE_RANGEDRIGHT"] = 22, - ["INVTYPE_THROWN"] = 23, - ["INVTYPE_RELIC"] = 24, + ["INVTYPE_WEAPONMAINHAND"] = 1, + ["INVTYPE_WEAPONOFFHAND"] = 2, + ["INVTYPE_WEAPON"] = 3, + ["INVTYPE_2HWEAPON"] = 4, + ["INVTYPE_SHIELD"] = 5, + ["INVTYPE_HOLDABLE"] = 6, + ["INVTYPE_HEAD"] = 7, + ["INVTYPE_NECK"] = 8, + ["INVTYPE_SHOULDER"] = 9, + ["INVTYPE_CLOAK"] = 10, + ["INVTYPE_CHEST"] = 11, + ["INVTYPE_ROBE"] = 11, + ["INVTYPE_WRIST"] = 12, + ["INVTYPE_HAND"] = 13, + ["INVTYPE_WAIST"] = 14, + ["INVTYPE_LEGS"] = 15, + ["INVTYPE_FEET"] = 16, + ["INVTYPE_FINGER"] = 17, + ["INVTYPE_TRINKET"] = 18, + ["INVTYPE_RANGED"] = 19, + ["INVTYPE_RANGEDRIGHT"] = 20, + ["INVTYPE_THROWN"] = 21, + ["INVTYPE_RELIC"] = 22, + ["INVTYPE_BODY"] = 23, + ["INVTYPE_TABARD"] = 24, ["INVTYPE_BAG"] = 25, ["INVTYPE_QUIVER"] = 26, } @@ -379,9 +379,8 @@ local function AddSortKeys(items) item.invertedCount = 0 item.invertedItemID = 0 else - -- Check if item is equippable (Armor or Weapon category) + -- Check if item is equippable (Armor or Weapon category) local isEquippable = itemCategory == "Armor" or itemCategory == "Weapon" - -- Check if item is a mount (by texture path) local isMount = IsMount(itemTexture) @@ -399,7 +398,7 @@ local function AddSortKeys(items) -- Class and slot ordering if isEquippable then item.sortedClass = 1 -- All equippable gear gets priority class - item.equipSlotOrder = EQUIP_SLOT_ORDER[itemEquipLoc] or 999 + item.equipSlotOrder = EQUIP_SLOT_ORDER[itemSubType] or 999 else item.sortedClass = CATEGORY_ORDER[itemCategory] or 99 item.equipSlotOrder = 999 @@ -672,43 +671,49 @@ end -- starting from the "last" regular bag (lowest priority, then highest bagID), -- and spilling into previous bags when needed. local function BuildGreyTailPositions(bagIDs, greyCount) - local positions = {} - local index = 1 + local positions = {} + local index = 1 - if greyCount <= 0 then return positions end + if greyCount <= 0 then return positions end - -- Order bags: lowest priority first (these are considered "last"), - -- and for stable, pick higher bagID later within same priority. - local ordered = {} - for _, bagID in ipairs(bagIDs) do - table.insert(ordered, { - bagID = bagID, - priority = tonumber(addon.Modules.Utils:GetContainerPriority(bagID)) or 0, - numSlots = addon.Modules.Utils:GetBagSlotCount(bagID) or 0, - }) - end + -- Order bags: lowest priority first (these are considered "last"), + -- and for stable, pick higher bagID later within same priority. + local ordered = {} + for _, bagID in ipairs(bagIDs) do + -- ONLY include bags that are valid and have slots + if addon.Modules.Utils:IsBagValid(bagID) then + local numSlots = addon.Modules.Utils:GetBagSlotCount(bagID) or 0 + if numSlots > 0 then + table.insert(ordered, { + bagID = bagID, + priority = tonumber(addon.Modules.Utils:GetContainerPriority(bagID)) or 0, + numSlots = numSlots, + }) + end + end + end - table.sort(ordered, function(a, b) - if a.priority ~= b.priority then - return a.priority < b.priority -- lowest first - end - return a.bagID > b.bagID -- higher bagID later (treated as further to the right) - end) + table.sort(ordered, function(a, b) + if a.priority ~= b.priority then + return a.priority < b.priority -- lowest first + end + return a.bagID > b.bagID -- higher bagID later (treated as further to the right) + end) - -- Collect tail slots from end to start, spilling to previous bags as needed. - for _, info in ipairs(ordered) do - for slot = info.numSlots, 1, -1 do - if index <= greyCount then - positions[index] = { bag = info.bagID, slot = slot } - index = index + 1 - else - break - end - end - if index > greyCount then break end - end + -- Collect tail slots from end to start, spilling to previous bags as needed. + for _, info in ipairs(ordered) do + for slot = info.numSlots, 1, -1 do + if index <= greyCount then + positions[index] = { bag = info.bagID, slot = slot } + index = index + 1 + else + break + end + end + if index > greyCount then break end + end - return positions + return positions end -- Split a list of collected items into non-greys and greys (quality 0) @@ -724,88 +729,110 @@ local function SplitGreyItems(items) return nonGreys, greys end ---=========================================================================== --- PHASE 6: Sort Complexity Analysis ---=========================================================================== +-- Rename for clarity and remove the helper function +function SortEngine:AnalyzeBags() + return self:AnalyzeContainer(addon.Constants.BAGS, "bags") +end --- Analyze how many sorting passes are needed --- Returns: {passes, itemsOutOfPlace, totalItems, alreadySorted} -local function AnalyzeSortComplexity(bagIDs) - -- Collect current items - local items = CollectItems(bagIDs) - local totalItems = table.getn(items) - - if totalItems == 0 then +function SortEngine:AnalyzeBank() + if not addon.Modules.BankScanner:IsBankOpen() then return {passes = 0, itemsOutOfPlace = 0, totalItems = 0, alreadySorted = true} end + return self:AnalyzeContainer(addon.Constants.BANK_BAGS, "bank") +end - -- Sort to get desired order - local sortedItems = SortItems(items) +-- Unified analysis function that matches the actual sort logic +function SortEngine:AnalyzeContainer(bagIDs, containerType) +-- Detect specialized bags (same as SortBags) + local containers = DetectSpecializedBags(bagIDs) - -- Build target positions - local targetPositions = BuildTargetPositions(bagIDs, totalItems) + local totalOutOfPlace = 0 + local totalItems = 0 - -- Create position maps for quick lookup - local currentPositions = {} -- [bagID_slot] = itemLink - local targetMap = {} -- [itemLink] = {targetBag, targetSlot, currentIndex} + -- Analyze specialized bags separately + for _, bagType in ipairs({"soul", "quiver", "ammo"}) do + local specialBags = containers[bagType] + for _, bagID in ipairs(specialBags) do + local items = CollectItems({bagID}) + if table.getn(items) > 0 then + local sortedItems = SortItems(items) + local targetPositions = BuildTargetPositions({bagID}, table.getn(items)) - for i, item in ipairs(items) do - local key = item.bagID .. "_" .. item.slot - currentPositions[key] = item.data.link - end - - for i, item in ipairs(sortedItems) do - local target = targetPositions[i] - if target and item.data.link then - if not targetMap[item.data.link] then - targetMap[item.data.link] = {} + for i, item in ipairs(sortedItems) do + local target = targetPositions[i] + if target and (item.bagID ~= target.bag or item.slot ~= target.slot) then + totalOutOfPlace = totalOutOfPlace + 1 + end + end + totalItems = totalItems + table.getn(items) end - table.insert(targetMap[item.data.link], { - targetBag = target.bag, - targetSlot = target.slot, - currentIndex = i, - sourceBag = item.bagID, - sourceSlot = item.slot - }) end end - -- Count items that are already in correct position - local itemsInPlace = 0 - local itemsOutOfPlace = 0 - local maxDisplacement = 0 + -- Analyze regular bags with two-phase logic (matches SortBags) + local regularBagIDs = containers.regular - for i, item in ipairs(sortedItems) do - local target = targetPositions[i] - if target then - local sourceBag, sourceSlot = item.bagID, item.slot - local targetBag, targetSlot = target.bag, target.slot + -- FILTER OUT EMPTY/INVALID BAGS for analysis + local validRegularBags = {} + for _, bagID in ipairs(regularBagIDs) do + if addon.Modules.Utils:IsBagValid(bagID) and addon.Modules.Utils:GetBagSlotCount(bagID) > 0 then + table.insert(validRegularBags, bagID) + end + end - if sourceBag == targetBag and sourceSlot == targetSlot then - itemsInPlace = itemsInPlace + 1 - else - itemsOutOfPlace = itemsOutOfPlace + 1 + if table.getn(validRegularBags) > 0 then + local allItems = CollectItems(validRegularBags) + totalItems = totalItems + table.getn(allItems) - -- Calculate displacement (how far the item needs to move) - local displacement = math.abs(i - itemsInPlace - itemsOutOfPlace) - if displacement > maxDisplacement then - maxDisplacement = displacement + -- Split greys/non-greys like SortBags does + local nonGreys, greys = SplitGreyItems(allItems) + + -- Check non-grey positioning (should be in front positions) + if table.getn(nonGreys) > 0 then + local sortedNonGreys = SortItems(nonGreys) + local frontPositions = BuildTargetPositions(validRegularBags, table.getn(sortedNonGreys)) + + for i, item in ipairs(sortedNonGreys) do + local target = frontPositions[i] + if target and (item.bagID ~= target.bag or item.slot ~= target.slot) then + totalOutOfPlace = totalOutOfPlace + 1 + end + end + end + + -- Check grey positioning (should be in tail positions) + if table.getn(greys) > 0 then + -- CRITICAL FIX: Only use bags that actually exist and have slots + local tailPositions = BuildGreyTailPositions(validRegularBags, table.getn(greys)) + + for i, item in ipairs(greys) do + local target = tailPositions[i] + -- FIX: Check if grey item is already in a tail position of a valid regular bag + local isInTailPosition = false + + -- Check if this grey item is already in the end slots of any valid regular bag + for _, bagID in ipairs(validRegularBags) do + local numSlots = addon.Modules.Utils:GetBagSlotCount(bagID) + if item.bagID == bagID and item.slot >= (numSlots - table.getn(greys) + 1) then + isInTailPosition = true + break + end + end + + -- Only count as out of place if it's NOT in a tail position AND doesn't match target + if target and (item.bagID ~= target.bag or item.slot ~= target.slot) and not isInTailPosition then + totalOutOfPlace = totalOutOfPlace + 1 end end end end - -- If all items are in place, no sorting needed - if itemsOutOfPlace == 0 then + if totalOutOfPlace == 0 then return {passes = 0, itemsOutOfPlace = 0, totalItems = totalItems, alreadySorted = true} end - -- Estimate passes needed based on displacement complexity - -- Simple heuristic: - -- - If displacement is low (< 20% of items), probably 1-2 passes - -- - If displacement is medium (20-50%), probably 2-4 passes - -- - If displacement is high (>50%), might need 3-6 passes - local displacementRatio = itemsOutOfPlace / totalItems + -- Estimate passes (same logic as before) + local displacementRatio = totalOutOfPlace / math.max(1, totalItems) local estimatedPasses if displacementRatio < 0.1 then @@ -814,87 +841,18 @@ local function AnalyzeSortComplexity(bagIDs) estimatedPasses = 2 elseif displacementRatio < 0.5 then estimatedPasses = 3 - elseif displacementRatio < 0.7 then - estimatedPasses = 4 else - -- High complexity - analyze cycles - -- Count how many swaps are needed vs moves to empty - local needsSwap = 0 - for i, item in ipairs(sortedItems) do - local target = targetPositions[i] - if target then - local sourceBag, sourceSlot = item.bagID, item.slot - local targetBag, targetSlot = target.bag, target.slot - - if sourceBag ~= targetBag or sourceSlot ~= targetSlot then - local targetKey = targetBag .. "_" .. targetSlot - local targetOccupied = currentPositions[targetKey] - if targetOccupied then - needsSwap = needsSwap + 1 - end - end - end - end - - -- More swaps needed = more passes - local swapRatio = needsSwap / itemsOutOfPlace - if swapRatio > 0.8 then - estimatedPasses = 6 - elseif swapRatio > 0.5 then - estimatedPasses = 5 - else - estimatedPasses = 4 - end + estimatedPasses = 4 end return { passes = estimatedPasses, - itemsOutOfPlace = itemsOutOfPlace, + itemsOutOfPlace = totalOutOfPlace, totalItems = totalItems, alreadySorted = false } end ---=========================================================================== --- Main Sort Functions ---=========================================================================== - --- Analyze bags to determine how many passes are needed -function SortEngine:AnalyzeBags() - local bagIDs = addon.Constants.BAGS - - -- Detect specialized bags - local containers = DetectSpecializedBags(bagIDs) - - -- Analyze regular bags only (specialized bags sort separately) - local regularBagIDs = containers.regular - if table.getn(regularBagIDs) > 0 then - return AnalyzeSortComplexity(regularBagIDs) - else - return {passes = 0, itemsOutOfPlace = 0, totalItems = 0, alreadySorted = true} - end -end - --- Analyze bank to determine how many passes are needed -function SortEngine:AnalyzeBank() - if not addon.Modules.BankScanner:IsBankOpen() then - return {passes = 0, itemsOutOfPlace = 0, totalItems = 0, alreadySorted = true} - end - - local bagIDs = addon.Constants.BANK_BAGS - - -- Detect specialized bags - local containers = DetectSpecializedBags(bagIDs) - - -- Analyze regular bags only - local regularBagIDs = containers.regular - if table.getn(regularBagIDs) > 0 then - return AnalyzeSortComplexity(regularBagIDs) - else - return {passes = 0, itemsOutOfPlace = 0, totalItems = 0, alreadySorted = true} - end -end - function SortEngine:SortBags() local bagIDs = addon.Constants.BAGS @@ -1028,4 +986,95 @@ function SortEngine:SortBank() -- Return total moves made return routeCount + consolidateCount + specializedMoves + regularMoves +end + +--=========================================================================== +-- Reusable Sort Execution with Smart Pass Management +--=========================================================================== + +function SortEngine:ExecuteSort(sortFunction, analyzeFunction, updateFrame, sortType) +-- Analyze to determine how many passes are needed + local analysis = analyzeFunction() + + -- Check if already sorted + if analysis.alreadySorted then + return false, "already sorted" + end + + -- Print analysis results + addon:Print("Sorting %s... (%d/%d items need sorting, estimated %d passes)", + sortType, analysis.itemsOutOfPlace, analysis.totalItems, analysis.passes) + + local passCount = 0 + local maxPasses = math.max(analysis.passes, 1) -- Use estimated passes, minimum 1 + local safetyLimit = math.max(maxPasses * 2, 6) -- Reasonable upper bound + local totalMoves = 0 + + addon:Print("Starting %s sort (estimated: %d passes, safety limit: %d)", sortType, maxPasses, safetyLimit) + + local function DoSortPass() + passCount = passCount + 1 + + -- Perform one sort pass + local moveCount = sortFunction() + totalMoves = totalMoves + moveCount + + -- Check if sorting is complete by re-analyzing + local currentAnalysis = analyzeFunction() + + if currentAnalysis.alreadySorted then + -- Sorting is complete! + addon:Print("%s sort complete! (%d passes, %d total moves)", sortType, passCount, totalMoves) + + -- Final update + local frame = CreateFrame("Frame") + local startTime = GetTime() + frame:SetScript("OnUpdate", function() + if GetTime() - startTime >= 0.7 then + frame:SetScript("OnUpdate", nil) + updateFrame() + end + end) + elseif passCount >= safetyLimit then + -- Hit safety limit but not fully sorted + addon:Print("%s sort stopped at safety limit! (%d/%d items still need sorting after %d passes)", + sortType, currentAnalysis.itemsOutOfPlace, currentAnalysis.totalItems, passCount) + + -- Final update + local frame = CreateFrame("Frame") + local startTime = GetTime() + frame:SetScript("OnUpdate", function() + if GetTime() - startTime >= 0.7 then + frame:SetScript("OnUpdate", nil) + updateFrame() + end + end) + else + -- More sorting needed + local remainingRatio = currentAnalysis.itemsOutOfPlace / math.max(1, currentAnalysis.totalItems) + addon:Print("%s Pass %d: %d moves, %d/%d items remaining (%.1f%%)", + sortType, passCount, moveCount, currentAnalysis.itemsOutOfPlace, currentAnalysis.totalItems, remainingRatio * 100) + + -- PROGRESSIVE DELAY: Calculate delay based on remaining complexity + local baseDelay = 0.7 + local complexityDelay = math.min(currentAnalysis.itemsOutOfPlace * 0.05, 2.0) -- max 2 seconds + local totalDelay = baseDelay + complexityDelay + + addon:Print("Waiting %.1f seconds before next pass...", totalDelay) + + -- Wait with progressive delay, then sort again + local frame = CreateFrame("Frame") + local startTime = GetTime() + frame:SetScript("OnUpdate", function() + if GetTime() - startTime >= totalDelay then + frame:SetScript("OnUpdate", nil) + DoSortPass() -- Recursive call for next pass + end + end) + end + end + + -- Start the first pass + DoSortPass() + return true, "sorting started" end \ No newline at end of file diff --git a/UI/BagFrame.lua b/UI/BagFrame.lua index aa959d6..9c8467d 100644 --- a/UI/BagFrame.lua +++ b/UI/BagFrame.lua @@ -1161,73 +1161,22 @@ function Guda_BagFrame_ToggleKeyring() BagFrame:Update() end --- Sort button handler with auto-repeat and smart pass calculation function Guda_BagFrame_Sort() if currentViewChar then addon:Print("Cannot sort another character's bags!") return end - -- Analyze bags to determine how many passes are needed - local analysis = addon.Modules.SortEngine:AnalyzeBags() + local success, message = addon.Modules.SortEngine:ExecuteSort( + function() return addon.Modules.SortEngine:SortBags() end, + function() return addon.Modules.SortEngine:AnalyzeBags() end, + function() BagFrame:Update() end, + "bags" + ) - -- Check if already sorted - if analysis.alreadySorted then + if not success and message == "already sorted" then addon:Print("Bags are already sorted!") - return end - - -- Print analysis results - addon:Print("Sorting bags... (%d/%d items need sorting, estimated %d passes)", - analysis.itemsOutOfPlace, analysis.totalItems, analysis.passes) - - local passCount = 0 - local maxPasses = math.max(analysis.passes, 1) -- Use estimated passes, minimum 1 - local safetyLimit = maxPasses + 3 -- Add 3 extra passes as safety margin - - local function DoSortPass() - passCount = passCount + 1 - - -- Perform one sort pass - local moveCount = addon.Modules.SortEngine:SortBags() - - -- If items were moved and we haven't hit the limit, do another pass - if moveCount > 0 and passCount < safetyLimit then - -- Wait for items to settle, then sort again - local frame = CreateFrame("Frame") - local elapsed = 0 - frame:SetScript("OnUpdate", function() - elapsed = elapsed + arg1 - if elapsed >= 0.7 then - frame:SetScript("OnUpdate", nil) - DoSortPass() -- Recursive call for next pass - end - end) - else - -- Sorting complete - if passCount >= safetyLimit then - addon:Print("Sort complete! (reached safety limit after %d passes)", passCount) - elseif passCount <= maxPasses then - addon:Print("Sort complete! (%d passes, as predicted)", passCount) - else - addon:Print("Sort complete! (%d passes, %d more than estimated)", passCount, passCount - maxPasses) - end - - -- Final update - local frame = CreateFrame("Frame") - local elapsed = 0 - frame:SetScript("OnUpdate", function() - elapsed = elapsed + arg1 - if elapsed >= 0.7 then - frame:SetScript("OnUpdate", nil) - BagFrame:Update() - end - end) - end - end - - -- Start the first pass - DoSortPass() end -- Hook bag container buttons to open Guda Bag View diff --git a/UI/BankFrame.lua b/UI/BankFrame.lua index 61c9afd..b8c15c7 100644 --- a/UI/BankFrame.lua +++ b/UI/BankFrame.lua @@ -596,73 +596,28 @@ function Guda_BankFrame_ClearSearch() end end --- Sort button handler with auto-repeat and smart pass calculation +-- Bank button handler function Guda_BankFrame_Sort() - if isReadOnlyMode or currentViewChar then - addon:Print("Cannot sort in read-only mode!") - return - end + if isReadOnlyMode or currentViewChar then + addon:Print("Cannot sort in read-only mode!") + return + end - -- Analyze bank to determine how many passes are needed - local analysis = addon.Modules.SortEngine:AnalyzeBank() + if not addon.Modules.BankScanner:IsBankOpen() then + addon:Print("Bank must be open to sort!") + return + end - -- Check if already sorted - if analysis.alreadySorted then - addon:Print("Bank is already sorted!") - return - end + local success, message = addon.Modules.SortEngine:ExecuteSort( + function() return addon.Modules.SortEngine:SortBank() end, + function() return addon.Modules.SortEngine:AnalyzeBank() end, + function() BankFrame:Update() end, + "bank" + ) - -- Print analysis results - addon:Print("Sorting bank... (%d/%d items need sorting, estimated %d passes)", - analysis.itemsOutOfPlace, analysis.totalItems, analysis.passes) - - local passCount = 0 - local maxPasses = math.max(analysis.passes, 1) -- Use estimated passes, minimum 1 - local safetyLimit = maxPasses + 3 -- Add 3 extra passes as safety margin - - local function DoSortPass() - passCount = passCount + 1 - - -- Perform one sort pass - local moveCount = addon.Modules.SortEngine:SortBank() - - -- If items were moved and we haven't hit the limit, do another pass - if moveCount > 0 and passCount < safetyLimit then - -- Wait for items to settle, then sort again - local frame = CreateFrame("Frame") - local elapsed = 0 - frame:SetScript("OnUpdate", function() - elapsed = elapsed + arg1 - if elapsed >= 0.7 then - frame:SetScript("OnUpdate", nil) - DoSortPass() -- Recursive call for next pass - end - end) - else - -- Sorting complete - if passCount >= safetyLimit then - addon:Print("Bank sort complete! (reached safety limit after %d passes)", passCount) - elseif passCount <= maxPasses then - addon:Print("Bank sort complete! (%d passes, as predicted)", passCount) - else - addon:Print("Bank sort complete! (%d passes, %d more than estimated)", passCount, passCount - maxPasses) - end - - -- Final update - local frame = CreateFrame("Frame") - local elapsed = 0 - frame:SetScript("OnUpdate", function() - elapsed = elapsed + arg1 - if elapsed >= 0.7 then - frame:SetScript("OnUpdate", nil) - BankFrame:Update() - end - end) - end - end - - -- Start the first pass - DoSortPass() + if not success and message == "already sorted" then + addon:Print("Bank is already sorted!") + end end -- Switch to Blizzard bank UI