diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..13ee2b0 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "nuxt.isNuxtApp": false +} \ No newline at end of file diff --git a/Sorting/SortEngine.lua b/Sorting/SortEngine.lua index 54cc653..28dabc2 100644 --- a/Sorting/SortEngine.lua +++ b/Sorting/SortEngine.lua @@ -1,58 +1,126 @@ -- Guda Sort Engine --- Sorts bags by quality, name, or item type +-- Adapted from Baganator sorting logic for WoW 1.12.1 local addon = Guda local SortEngine = {} addon.Modules.SortEngine = SortEngine --- Sort by quality (descending) -local function SortByQuality(a, b) - if a.quality ~= b.quality then - return a.quality > b.quality - end - if a.name and b.name then - return a.name < b.name - end - return false -end - --- Sort by name -local function SortByName(a, b) - if a.name and b.name then - return a.name < b.name - end - return false -end - --- Sort by type (class/subclass) -local function SortByType(a, b) - if a.class ~= b.class then - return (a.class or "") < (b.class or "") - end - if a.subclass ~= b.subclass then - return (a.subclass or "") < (b.subclass or "") - end - if a.quality ~= b.quality then - return a.quality > b.quality - end - if a.name and b.name then - return a.name < b.name - end - return false -end - --- Get sort function by method -local sortMethods = { - quality = SortByQuality, - name = SortByName, - type = SortByType, +-- Priority items that should always be sorted first +local PriorityItems = { + [6948] = true, -- Hearthstone } +-- Custom ordering for item classes (matches Baganator's logical grouping) +local classOrder = { + [0] = 1, -- Consumable + [6] = 2, -- Projectile (ammo/arrows) + [2] = 3, -- Weapon + [4] = 4, -- Armor + [11] = 5, -- Quiver + [5] = 6, -- Reagent + [7] = 7, -- Trade Goods + [9] = 8, -- Recipe + [1] = 9, -- Container + [12] = 10, -- Quest + [13] = 11, -- Key + [15] = 12, -- Miscellaneous +} + +-- Extract itemID from item link +local function GetItemID(link) + if not link then return 0 end + local _, _, itemID = string.find(link, "item:(%d+)") + return tonumber(itemID) or 0 +end + +-- Get item class ID (numeric) +local function GetItemClassID(link) + if not link then return 99 end + local _, _, _, _, _, _, class = GetItemInfo(link) + -- In 1.12.1, GetItemInfo returns class as string, need to map to ID + -- We'll use a simple hash of the string for ordering + if not class then return 99 end + + -- Map common class names to IDs (1.12.1 compatible) + local classMap = { + ["Consumable"] = 0, + ["Container"] = 1, + ["Weapon"] = 2, + ["Armor"] = 4, + ["Reagent"] = 5, + ["Projectile"] = 6, + ["Trade Goods"] = 7, + ["Recipe"] = 9, + ["Quiver"] = 11, + ["Quest"] = 12, + ["Key"] = 13, + ["Miscellaneous"] = 15, + } + + return classMap[class] or 99 +end + +-- Add sort keys to items +local function AddSortKeys(items) + for _, item in ipairs(items) do + if item.data and item.data.link then + local itemID = GetItemID(item.data.link) + local classID = GetItemClassID(item.data.link) + + -- Priority: Hearthstone = 1, everything else = 1000 + item.priority = PriorityItems[itemID] and 1 or 1000 + + -- Get sorted class order (lower = earlier in bags) + item.sortedClass = classOrder[classID] or 99 + + -- Inverted quality (higher quality = earlier) + item.invertedQuality = -(item.quality or 0) + + -- Item name for alphabetical sorting + item.itemName = item.name or "" + + -- Inverted item ID for consistent sorting + item.invertedItemID = -(itemID) + end + end +end + +-- Sort items using multi-criteria comparison +local function SortItems(items) + AddSortKeys(items) + + table.sort(items, function(a, b) + -- Sort by priority first (Hearthstone) + if a.priority ~= b.priority then + return a.priority < b.priority + end + + -- Then by class (Consumables, Ammo, Weapons, Armor, etc.) + if a.sortedClass ~= b.sortedClass then + return a.sortedClass < b.sortedClass + end + + -- Within same class, sort by quality (Epic > Rare > Uncommon > Common > Poor) + if a.invertedQuality ~= b.invertedQuality then + return a.invertedQuality < b.invertedQuality + end + + -- Then alphabetically by name + if a.itemName ~= b.itemName then + return a.itemName < b.itemName + end + + -- Finally by item ID for consistency + return a.invertedItemID < b.invertedItemID + end) + + return items +end + -- Collect all items from bags function SortEngine:CollectItems(bagIDs) local items = {} - local emptySlots = {} for _, bagID in ipairs(bagIDs) do local numSlots = addon.Modules.Utils:GetBagSlotCount(bagID) @@ -69,87 +137,138 @@ function SortEngine:CollectItems(bagIDs) quality = itemData.quality or 0, name = itemData.name or "", class = itemData.class or "", - subclass = itemData.subclass or "", }) - else - table.insert(emptySlots, {bagID = bagID, slot = slot}) end end end end - return items, emptySlots -end - --- Sort items -function SortEngine:SortItems(items, method) - local sortFunc = sortMethods[method] or sortMethods.quality - - table.sort(items, sortFunc) - return items end --- Apply sorted items back to bags -function SortEngine:ApplySort(bagIDs, method) - addon:Print("Sorting bags by %s...", method or "quality") +-- Build target slot positions (sequential, no gaps) +local function BuildTargetPositions(bagIDs, itemCount) + local positions = {} + local index = 1 - -- Collect items - local items, emptySlots = self:CollectItems(bagIDs) - - -- Sort items - items = self:SortItems(items, method) - - -- Clear all bags first - ClearCursor() - - -- Track moves for undo (not implemented yet, but good to have) - local moves = {} - - -- Place items in sorted order - local targetIndex = 1 for _, bagID in ipairs(bagIDs) do local numSlots = addon.Modules.Utils:GetBagSlotCount(bagID) if addon.Modules.Utils:IsBagValid(bagID) then for slot = 1, numSlots do - if targetIndex <= table.getn(items) then - local item = items[targetIndex] + if index <= itemCount then + positions[index] = {bag = bagID, slot = slot} + index = index + 1 + else + break + end + end + end - -- Move item if it's not already in the right place - if item.bagID ~= bagID or item.slot ~= slot then - -- Use PickupContainerItem to move items - -- Note: This is a simplified version and may need refinement - -- for handling soulbound items, bag type restrictions, etc. + if index > itemCount then + break + end + end - table.insert(moves, { - from = {bag = item.bagID, slot = item.slot}, - to = {bag = bagID, slot = slot}, - }) - end + return positions +end - targetIndex = targetIndex + 1 +-- Apply sorted items back to bags (two-phase move system from Baganator) +function SortEngine:ApplySort(bagIDs, method) + addon:Print("Sorting bags...") + + -- Collect and sort items + local items = self:CollectItems(bagIDs) + + if table.getn(items) == 0 then + addon:Print("No items to sort!") + return + end + + items = SortItems(items) + + -- Build target positions (sequential slots) + local targetPositions = BuildTargetPositions(bagIDs, table.getn(items)) + + -- Clear cursor + ClearCursor() + + -- Two-phase move system (from Baganator) + -- Phase 1: Moves to empty slots + -- Phase 2: Swaps with occupied slots + + local moveQueue0 = {} -- Moves to empty slots + local moveQueue1 = {} -- Swaps with occupied slots + + -- Build move queues + for i, item in ipairs(items) do + local target = targetPositions[i] + + if target then + local sourceBag, sourceSlot = item.bagID, item.slot + local targetBag, targetSlot = target.bag, target.slot + + -- Skip if already in correct position + if sourceBag ~= targetBag or sourceSlot ~= targetSlot then + -- Check if target slot is empty + local targetItem = GetContainerItemLink(targetBag, targetSlot) + + if not targetItem then + -- Target is empty - Phase 1 move + table.insert(moveQueue0, { + sourceBag = sourceBag, + sourceSlot = sourceSlot, + targetBag = targetBag, + targetSlot = targetSlot, + }) + else + -- Target is occupied - Phase 2 swap + table.insert(moveQueue1, { + sourceBag = sourceBag, + sourceSlot = sourceSlot, + targetBag = targetBag, + targetSlot = targetSlot, + }) end end end end - -- Actually perform the moves (simplified - in reality needs more complex logic) - -- For now, just trigger a bag update - addon:Print("Sort complete! (%d items sorted)", table.getn(items)) + -- Execute Phase 1: Move to empty slots first + local moveCount = 0 + for _, move in ipairs(moveQueue0) do + local _, _, locked = GetContainerItemInfo(move.sourceBag, move.sourceSlot) + if not locked then + PickupContainerItem(move.sourceBag, move.sourceSlot) + PickupContainerItem(move.targetBag, move.targetSlot) + ClearCursor() + moveCount = moveCount + 1 + end + end - -- Note: Actual item moving requires careful handling of: - -- - Locked items - -- - Bag type restrictions (soul bags, etc.) - -- - Soulbound items - -- - Quest items - -- This would require a more sophisticated move queue system + -- Execute Phase 2: Swap with occupied slots + for _, move in ipairs(moveQueue1) do + local _, _, sourceLocked = GetContainerItemInfo(move.sourceBag, move.sourceSlot) + local _, _, targetLocked = GetContainerItemInfo(move.targetBag, move.targetSlot) + + if not sourceLocked and not targetLocked then + PickupContainerItem(move.sourceBag, move.sourceSlot) + PickupContainerItem(move.targetBag, move.targetSlot) + ClearCursor() + moveCount = moveCount + 1 + end + end + + if moveCount > 0 then + addon:Print("Sort complete! (%d items moved)", moveCount) + else + addon:Print("Items are already sorted!") + end end -- Sort current bags function SortEngine:SortBags() - local method = addon.Modules.DB:GetSetting("sortMethod") or "quality" - self:ApplySort(addon.Constants.BAGS, method) + self:ApplySort(addon.Constants.BAGS, "type") end -- Sort bank @@ -159,6 +278,5 @@ function SortEngine:SortBank() return end - local method = addon.Modules.DB:GetSetting("sortMethod") or "quality" - self:ApplySort(addon.Constants.BANK_BAGS, method) + self:ApplySort(addon.Constants.BANK_BAGS, "type") end diff --git a/UI/BagFrame.lua b/UI/BagFrame.lua index c73fcab..b2086b7 100644 --- a/UI/BagFrame.lua +++ b/UI/BagFrame.lua @@ -201,13 +201,13 @@ function BagFrame:ResizeFrame(currentRow, currentCol, columns) -- Calculate required dimensions based on columns local containerWidth = (columns * (buttonSize + spacing)) + 20 local containerHeight = (totalRows * (buttonSize + spacing)) + 20 - local frameWidth = containerWidth + 30 + local frameWidth = containerWidth + 20 local frameHeight = containerHeight + 100 -- Title (40) + search (30) + footer (30) -- Minimum sizes if containerWidth < 200 then containerWidth = 200 - frameWidth = 230 + frameWidth = 220 end if containerHeight < 150 then containerHeight = 150 @@ -219,7 +219,7 @@ function BagFrame:ResizeFrame(currentRow, currentCol, columns) -- Maximum sizes if containerWidth > 800 then containerWidth = 800 - frameWidth = 830 + frameWidth = 820 end if containerHeight > 600 then containerHeight = 600 diff --git a/UI/BagFrame.xml b/UI/BagFrame.xml index 8656b64..190b830 100644 --- a/UI/BagFrame.xml +++ b/UI/BagFrame.xml @@ -67,7 +67,7 @@ - + @@ -295,7 +295,7 @@ - + @@ -373,7 +373,7 @@ - +