Merge pull request #36 from vatichild/performance/improvements-for-specific-cases
Performance/improvements for specific cases
This commit is contained in:
+95
-3
@@ -15,13 +15,39 @@ local detectionCache = {}
|
|||||||
local cacheHits = 0
|
local cacheHits = 0
|
||||||
local cacheMisses = 0
|
local cacheMisses = 0
|
||||||
|
|
||||||
-- Clear the detection cache
|
-- Clear the entire detection cache (use sparingly - only for major events)
|
||||||
|
-- For simple item moves, use InvalidateItem() or don't invalidate at all
|
||||||
function ItemDetection:ClearCache()
|
function ItemDetection:ClearCache()
|
||||||
detectionCache = {}
|
detectionCache = {}
|
||||||
cacheHits = 0
|
cacheHits = 0
|
||||||
cacheMisses = 0
|
cacheMisses = 0
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Invalidate a specific item from cache (by itemLink)
|
||||||
|
-- Use this when a specific item's properties might have changed
|
||||||
|
function ItemDetection:InvalidateItem(itemLink)
|
||||||
|
if itemLink then
|
||||||
|
detectionCache[itemLink] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Invalidate multiple items from cache
|
||||||
|
-- Use this for batch operations
|
||||||
|
function ItemDetection:InvalidateItems(itemLinks)
|
||||||
|
if itemLinks then
|
||||||
|
for _, link in ipairs(itemLinks) do
|
||||||
|
if link then
|
||||||
|
detectionCache[link] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Check if we have cached data for an item (useful for debugging)
|
||||||
|
function ItemDetection:IsCached(itemLink)
|
||||||
|
return itemLink and detectionCache[itemLink] ~= nil
|
||||||
|
end
|
||||||
|
|
||||||
-- Get cache statistics
|
-- Get cache statistics
|
||||||
function ItemDetection:GetCacheStats()
|
function ItemDetection:GetCacheStats()
|
||||||
local total = cacheHits + cacheMisses
|
local total = cacheHits + cacheMisses
|
||||||
@@ -91,18 +117,25 @@ local function ScanTooltipLines(bagID, slotID, itemLink)
|
|||||||
local leftText = leftLine and leftLine:GetText() or ""
|
local leftText = leftLine and leftLine:GetText() or ""
|
||||||
local rightText = rightLine and rightLine:GetText() or ""
|
local rightText = rightLine and rightLine:GetText() or ""
|
||||||
|
|
||||||
-- Get text colors (for detecting yellow/green text)
|
-- Get left text colors (for detecting yellow/green/red text)
|
||||||
local lr, lg, lb, la = 1, 1, 1, 1
|
local lr, lg, lb, la = 1, 1, 1, 1
|
||||||
if leftLine and leftLine.GetTextColor then
|
if leftLine and leftLine.GetTextColor then
|
||||||
lr, lg, lb, la = leftLine:GetTextColor()
|
lr, lg, lb, la = leftLine:GetTextColor()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Get right text colors (for detecting red requirements)
|
||||||
|
local rr, rg, rb, ra = 1, 1, 1, 1
|
||||||
|
if rightLine and rightLine.GetTextColor then
|
||||||
|
rr, rg, rb, ra = rightLine:GetTextColor()
|
||||||
|
end
|
||||||
|
|
||||||
table.insert(lines, {
|
table.insert(lines, {
|
||||||
left = leftText,
|
left = leftText,
|
||||||
right = rightText,
|
right = rightText,
|
||||||
leftLower = leftText and string.lower(leftText) or "",
|
leftLower = leftText and string.lower(leftText) or "",
|
||||||
rightLower = rightText and string.lower(rightText) or "",
|
rightLower = rightText and string.lower(rightText) or "",
|
||||||
r = lr, g = lg, b = lb,
|
r = lr, g = lg, b = lb,
|
||||||
|
rightR = rr, rightG = rg, rightB = rb,
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -255,12 +288,63 @@ local function DetectQuestUsable(lines)
|
|||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Durability pattern for filtering out broken item red text
|
||||||
|
local durabilityPattern = DURABILITY_TEMPLATE and string.gsub(DURABILITY_TEMPLATE, "%%d", "%%d+") or nil
|
||||||
|
|
||||||
|
-- Check if text color is red (unusable requirement)
|
||||||
|
local function IsRedColor(r, g, b)
|
||||||
|
if not r or not g or not b then return false end
|
||||||
|
-- RED_FONT_COLOR is typically (1.0, 0.1, 0.1)
|
||||||
|
local dr = math.abs(r - 1.0)
|
||||||
|
local dg = math.abs(g - 0.125)
|
||||||
|
local db = math.abs(b - 0.125)
|
||||||
|
return (dr < 0.15 and dg < 0.15 and db < 0.15)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Check if item is unusable (has red text indicating unmet requirements)
|
||||||
|
-- Excludes durability lines (broken items show red durability)
|
||||||
|
local function DetectUnusable(lines)
|
||||||
|
for _, line in ipairs(lines) do
|
||||||
|
if line.r and line.g and line.b then
|
||||||
|
-- Check for red text (requirements not met)
|
||||||
|
local isRed = IsRedColor(line.r, line.g, line.b) or
|
||||||
|
(line.r > 0.85 and line.g < 0.3 and line.b < 0.3)
|
||||||
|
|
||||||
|
if isRed then
|
||||||
|
local text = line.left or ""
|
||||||
|
-- Ignore durability lines (broken items)
|
||||||
|
if durabilityPattern and string.find(text, durabilityPattern) then
|
||||||
|
-- skip durability
|
||||||
|
else
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Also check right column (some requirements appear there)
|
||||||
|
if line.rightR and line.rightG and line.rightB then
|
||||||
|
local isRed = IsRedColor(line.rightR, line.rightG, line.rightB) or
|
||||||
|
(line.rightR > 0.85 and line.rightG < 0.3 and line.rightB < 0.3)
|
||||||
|
|
||||||
|
if isRed then
|
||||||
|
local text = line.right or ""
|
||||||
|
if durabilityPattern and string.find(text, durabilityPattern) then
|
||||||
|
-- skip durability
|
||||||
|
else
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
--=====================================================
|
--=====================================================
|
||||||
-- Public API - Cached Detection
|
-- Public API - Cached Detection
|
||||||
--=====================================================
|
--=====================================================
|
||||||
|
|
||||||
-- Get all item properties at once (cached)
|
-- Get all item properties at once (cached)
|
||||||
-- Returns: { isQuestItem, isQuestStarter, isQuestUsable, isJunk, isPermanentEnchant }
|
-- Returns: { isQuestItem, isQuestStarter, isQuestUsable, isJunk, isPermanentEnchant, isUnusable }
|
||||||
function ItemDetection:GetItemProperties(itemData, bagID, slotID)
|
function ItemDetection:GetItemProperties(itemData, bagID, slotID)
|
||||||
if not itemData then
|
if not itemData then
|
||||||
return {
|
return {
|
||||||
@@ -269,6 +353,7 @@ function ItemDetection:GetItemProperties(itemData, bagID, slotID)
|
|||||||
isQuestUsable = false,
|
isQuestUsable = false,
|
||||||
isJunk = false,
|
isJunk = false,
|
||||||
isPermanentEnchant = false,
|
isPermanentEnchant = false,
|
||||||
|
isUnusable = false,
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -296,6 +381,7 @@ function ItemDetection:GetItemProperties(itemData, bagID, slotID)
|
|||||||
local isQuestItem, isQuestStarter = DetectQuestItem(lines, itemData)
|
local isQuestItem, isQuestStarter = DetectQuestItem(lines, itemData)
|
||||||
local isQuestUsable = DetectQuestUsable(lines)
|
local isQuestUsable = DetectQuestUsable(lines)
|
||||||
local isJunk = DetectJunk(lines, itemData)
|
local isJunk = DetectJunk(lines, itemData)
|
||||||
|
local isUnusable = DetectUnusable(lines)
|
||||||
|
|
||||||
-- Debug: log junk detection for gray items
|
-- Debug: log junk detection for gray items
|
||||||
if addon.DEBUG then
|
if addon.DEBUG then
|
||||||
@@ -320,6 +406,7 @@ function ItemDetection:GetItemProperties(itemData, bagID, slotID)
|
|||||||
isQuestUsable = isQuestUsable,
|
isQuestUsable = isQuestUsable,
|
||||||
isJunk = isJunk,
|
isJunk = isJunk,
|
||||||
isPermanentEnchant = isPermanentEnchant,
|
isPermanentEnchant = isPermanentEnchant,
|
||||||
|
isUnusable = isUnusable,
|
||||||
}
|
}
|
||||||
|
|
||||||
-- Cache result
|
-- Cache result
|
||||||
@@ -356,6 +443,11 @@ function ItemDetection:IsPermanentEnchant(itemData, bagID, slotID)
|
|||||||
return props.isPermanentEnchant
|
return props.isPermanentEnchant
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function ItemDetection:IsUnusable(itemData, bagID, slotID)
|
||||||
|
local props = self:GetItemProperties(itemData, bagID, slotID)
|
||||||
|
return props.isUnusable
|
||||||
|
end
|
||||||
|
|
||||||
--=====================================================
|
--=====================================================
|
||||||
-- Initialization
|
-- Initialization
|
||||||
--=====================================================
|
--=====================================================
|
||||||
|
|||||||
+4
-10
@@ -615,16 +615,10 @@ function Tooltip:Initialize()
|
|||||||
if event == "BAG_UPDATE" then
|
if event == "BAG_UPDATE" then
|
||||||
if cacheClearPending then return end
|
if cacheClearPending then return end
|
||||||
cacheClearPending = true
|
cacheClearPending = true
|
||||||
-- Debounce: batch rapid BAG_UPDATE events
|
-- Debounce: batch rapid BAG_UPDATE events (uses pooled timer)
|
||||||
local debounceFrame = CreateFrame("Frame")
|
Guda_ScheduleTimer(0.2, function()
|
||||||
debounceFrame.elapsed = 0
|
cacheClearPending = false
|
||||||
debounceFrame:SetScript("OnUpdate", function()
|
Tooltip:ClearCache()
|
||||||
this.elapsed = this.elapsed + arg1
|
|
||||||
if this.elapsed >= 0.2 then
|
|
||||||
this:SetScript("OnUpdate", nil)
|
|
||||||
cacheClearPending = false
|
|
||||||
Tooltip:ClearCache()
|
|
||||||
end
|
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|||||||
@@ -5,6 +5,39 @@ local addon = Guda
|
|||||||
local Utils = {}
|
local Utils = {}
|
||||||
addon.Modules.Utils = Utils
|
addon.Modules.Utils = Utils
|
||||||
|
|
||||||
|
--=============================================================================
|
||||||
|
-- Timer Frame Pool (prevents memory leaks from temporary timers)
|
||||||
|
-- Global function so all modules can use it
|
||||||
|
--=============================================================================
|
||||||
|
local timerPool = {}
|
||||||
|
local TIMER_POOL_MAX = 10 -- Limit pool size to prevent unbounded growth
|
||||||
|
|
||||||
|
function Guda_ScheduleTimer(delay, callback)
|
||||||
|
-- Try to get a frame from the pool
|
||||||
|
local frame = table.remove(timerPool)
|
||||||
|
if not frame then
|
||||||
|
frame = CreateFrame("Frame")
|
||||||
|
end
|
||||||
|
|
||||||
|
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:Hide()
|
||||||
|
-- Return frame to pool if not full
|
||||||
|
if table.getn(timerPool) < TIMER_POOL_MAX then
|
||||||
|
table.insert(timerPool, this)
|
||||||
|
end
|
||||||
|
-- Execute callback
|
||||||
|
this.callback()
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
frame:Show()
|
||||||
|
end
|
||||||
|
|
||||||
--=============================================================================
|
--=============================================================================
|
||||||
-- Tooltip Scan Caching
|
-- Tooltip Scan Caching
|
||||||
-- Caches results of expensive tooltip scanning operations
|
-- Caches results of expensive tooltip scanning operations
|
||||||
|
|||||||
@@ -188,15 +188,9 @@ function BankScanner:Initialize()
|
|||||||
BankScanner:ClearCache() -- Clear cache on open
|
BankScanner:ClearCache() -- Clear cache on open
|
||||||
addon:Debug("Bank opened")
|
addon:Debug("Bank opened")
|
||||||
|
|
||||||
-- Delay scan to ensure bank is fully loaded
|
-- Delay scan to ensure bank is fully loaded (uses pooled timer)
|
||||||
local frame = CreateFrame("Frame")
|
Guda_ScheduleTimer(0.5, function()
|
||||||
local elapsed = 0
|
BankScanner:SaveToDatabase()
|
||||||
frame:SetScript("OnUpdate", function()
|
|
||||||
elapsed = elapsed + arg1
|
|
||||||
if elapsed >= 0.5 then
|
|
||||||
frame:SetScript("OnUpdate", nil)
|
|
||||||
BankScanner:SaveToDatabase()
|
|
||||||
end
|
|
||||||
end)
|
end)
|
||||||
end, "BankScanner")
|
end, "BankScanner")
|
||||||
|
|
||||||
|
|||||||
@@ -91,16 +91,10 @@ function EquipmentScanner:Initialize()
|
|||||||
playerLoggedIn = true
|
playerLoggedIn = true
|
||||||
addon:Print("Scanning equipped items...")
|
addon:Print("Scanning equipped items...")
|
||||||
|
|
||||||
-- Delay scan to ensure character is fully loaded
|
-- Delay scan to ensure character is fully loaded (uses pooled timer)
|
||||||
local frame = CreateFrame("Frame")
|
Guda_ScheduleTimer(2.0, function()
|
||||||
local elapsed = 0
|
EquipmentScanner:SaveToDatabase()
|
||||||
frame:SetScript("OnUpdate", function()
|
addon:Print("Equipped items scanned and saved!")
|
||||||
elapsed = elapsed + arg1
|
|
||||||
if elapsed >= 2.0 then
|
|
||||||
frame:SetScript("OnUpdate", nil)
|
|
||||||
EquipmentScanner:SaveToDatabase()
|
|
||||||
addon:Print("Equipped items scanned and saved!")
|
|
||||||
end
|
|
||||||
end)
|
end)
|
||||||
end, "EquipmentScanner")
|
end, "EquipmentScanner")
|
||||||
|
|
||||||
|
|||||||
+4
-10
@@ -36,17 +36,11 @@ function MoneyTracker:Initialize()
|
|||||||
MoneyTracker:Update()
|
MoneyTracker:Update()
|
||||||
end, "MoneyTracker")
|
end, "MoneyTracker")
|
||||||
|
|
||||||
-- Initial update and save on login
|
-- Initial update and save on login (uses pooled timer)
|
||||||
addon.Modules.Events:OnPlayerLogin(function()
|
addon.Modules.Events:OnPlayerLogin(function()
|
||||||
local frame = CreateFrame("Frame")
|
Guda_ScheduleTimer(1, function()
|
||||||
local elapsed = 0
|
MoneyTracker:Update()
|
||||||
frame:SetScript("OnUpdate", function()
|
addon:Debug("Initial money saved")
|
||||||
elapsed = elapsed + arg1
|
|
||||||
if elapsed >= 1 then
|
|
||||||
frame:SetScript("OnUpdate", nil)
|
|
||||||
MoneyTracker:Update()
|
|
||||||
addon:Debug("Initial money saved")
|
|
||||||
end
|
|
||||||
end)
|
end)
|
||||||
end, "MoneyTracker")
|
end, "MoneyTracker")
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
## Title: Guda
|
## Title: Guda
|
||||||
## Notes: All-in-one bag and bank addon for World of Warcraft 1.12.1 (Turtle WoW)
|
## Notes: All-in-one bag and bank addon for World of Warcraft 1.12.1 (Turtle WoW)
|
||||||
## Author: Vati
|
## Author: Vati
|
||||||
## Version: 1.6.8
|
## Version: 1.6.9
|
||||||
## SavedVariables: Guda_DB
|
## SavedVariables: Guda_DB
|
||||||
## SavedVariablesPerCharacter: Guda_CharDB
|
## SavedVariablesPerCharacter: Guda_CharDB
|
||||||
|
|
||||||
|
|||||||
+92
-101
@@ -12,8 +12,9 @@ SortEngine.sortingInProgress = false
|
|||||||
-- Performance: Max items to move per cycle
|
-- Performance: Max items to move per cycle
|
||||||
-- Baganator uses 5 for manual transfers, but sorting needs more for smooth operation
|
-- Baganator uses 5 for manual transfers, but sorting needs more for smooth operation
|
||||||
local MAX_MOVES_PER_CYCLE = 20
|
local MAX_MOVES_PER_CYCLE = 20
|
||||||
-- Bank uses fewer moves per cycle to avoid lock conflicts (bank ops are slower)
|
-- Bank needs more moves per cycle due to larger capacity (240 vs 160 slots)
|
||||||
local MAX_BANK_MOVES_PER_CYCLE = 15
|
-- But not too many to avoid server-side lock conflicts
|
||||||
|
local MAX_BANK_MOVES_PER_CYCLE = 25
|
||||||
|
|
||||||
-- Current sort context (set by ExecuteSort, used by ApplySort)
|
-- Current sort context (set by ExecuteSort, used by ApplySort)
|
||||||
local currentSortType = "bags"
|
local currentSortType = "bags"
|
||||||
@@ -577,10 +578,12 @@ local function ConsolidateStacks(bagIDs)
|
|||||||
if maxStack > 1 then
|
if maxStack > 1 then
|
||||||
-- Sort stacks: higher priority bags first, then larger stacks
|
-- Sort stacks: higher priority bags first, then larger stacks
|
||||||
table.sort(group.stacks, function(a, b)
|
table.sort(group.stacks, function(a, b)
|
||||||
if a.priority ~= b.priority then
|
if not a then return false end
|
||||||
return a.priority > b.priority
|
if not b then return true end
|
||||||
|
if (a.priority or 0) ~= (b.priority or 0) then
|
||||||
|
return (a.priority or 0) > (b.priority or 0)
|
||||||
end
|
end
|
||||||
return a.count > b.count
|
return (a.count or 0) > (b.count or 0)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
-- Greedy consolidation: fill stacks from left to right
|
-- Greedy consolidation: fill stacks from left to right
|
||||||
@@ -825,9 +828,13 @@ local function SortItems(items)
|
|||||||
end
|
end
|
||||||
|
|
||||||
table.sort(items, function(a, b)
|
table.sort(items, function(a, b)
|
||||||
|
-- Guard against nil entries
|
||||||
|
if not a then return false end
|
||||||
|
if not b then return true end
|
||||||
|
|
||||||
-- 1. Priority items first (Hearthstone, etc.)
|
-- 1. Priority items first (Hearthstone, etc.)
|
||||||
if a.priority ~= b.priority then
|
if (a.priority or 0) ~= (b.priority or 0) then
|
||||||
return a.priority < b.priority
|
return (a.priority or 0) < (b.priority or 0)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- 2. Equippable items always come before non-equippable items
|
-- 2. Equippable items always come before non-equippable items
|
||||||
@@ -1017,10 +1024,12 @@ local function BuildTargetPositions(bagIDs, itemCount)
|
|||||||
-- Only sort if we have more than one element
|
-- Only sort if we have more than one element
|
||||||
if table.getn(sortedBags) > 1 then
|
if table.getn(sortedBags) > 1 then
|
||||||
table.sort(sortedBags, function(a, b)
|
table.sort(sortedBags, function(a, b)
|
||||||
if a.priority ~= b.priority then
|
if not a then return false end
|
||||||
return a.priority > b.priority
|
if not b then return true end
|
||||||
|
if (a.priority or 0) ~= (b.priority or 0) then
|
||||||
|
return (a.priority or 0) > (b.priority or 0)
|
||||||
end
|
end
|
||||||
return a.bagID < b.bagID
|
return (a.bagID or 0) < (b.bagID or 0)
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -1117,22 +1126,35 @@ local function ApplySort(bagIDs, items, targetPositions)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- Execute swaps with occupied slots (if we haven't hit the limit)
|
-- Execute swaps with occupied slots (if we haven't hit the limit)
|
||||||
|
-- Be conservative with swaps - they can cause chain reactions
|
||||||
|
-- Limit swaps to half of max moves to leave room for next pass corrections
|
||||||
|
local maxSwaps = math.floor(maxMoves / 2)
|
||||||
|
local swapCount = 0
|
||||||
|
|
||||||
for _, move in ipairs(swapOccupied) do
|
for _, move in ipairs(swapOccupied) do
|
||||||
-- Limit moves per cycle
|
-- Limit total moves and swaps separately
|
||||||
if moveCount >= maxMoves then
|
if moveCount >= maxMoves or swapCount >= maxSwaps then
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
|
|
||||||
local _, _, sourceLocked = GetContainerItemInfo(move.sourceBag, move.sourceSlot)
|
-- Verify source item is still there (previous swaps may have moved it)
|
||||||
local _, _, targetLocked = GetContainerItemInfo(move.targetBag, move.targetSlot)
|
local sourceLink = GetContainerItemLink(move.sourceBag, move.sourceSlot)
|
||||||
|
if not sourceLink then
|
||||||
if not sourceLocked and not targetLocked then
|
-- Source slot is now empty, skip this swap (will be handled next pass)
|
||||||
PickupContainerItem(move.sourceBag, move.sourceSlot)
|
addon:DebugSort("Swap skipped: source slot now empty (%d:%d)", move.sourceBag, move.sourceSlot)
|
||||||
PickupContainerItem(move.targetBag, move.targetSlot)
|
|
||||||
ClearCursor()
|
|
||||||
moveCount = moveCount + 1
|
|
||||||
else
|
else
|
||||||
lockedCount = lockedCount + 1
|
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
|
||||||
|
swapCount = swapCount + 1
|
||||||
|
else
|
||||||
|
lockedCount = lockedCount + 1
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -1175,10 +1197,12 @@ local function BuildGreyTailPositions(bagIDs, greyCount)
|
|||||||
-- Only sort if we have more than one element
|
-- Only sort if we have more than one element
|
||||||
if table.getn(ordered) > 1 then
|
if table.getn(ordered) > 1 then
|
||||||
table.sort(ordered, function(a, b)
|
table.sort(ordered, function(a, b)
|
||||||
if a.priority ~= b.priority then
|
if not a then return false end
|
||||||
return a.priority < b.priority -- lowest first
|
if not b then return true end
|
||||||
|
if (a.priority or 0) ~= (b.priority or 0) then
|
||||||
|
return (a.priority or 0) < (b.priority or 0) -- lowest first
|
||||||
end
|
end
|
||||||
return a.bagID > b.bagID -- higher bagID later (treated as further to the right)
|
return (a.bagID or 0) > (b.bagID or 0) -- higher bagID later (treated as further to the right)
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -1200,15 +1224,17 @@ local function BuildGreyTailPositions(bagIDs, greyCount)
|
|||||||
-- Ascending order: Priority DESC, BagID ASC, Slot ASC (matching BuildTargetPositions)
|
-- Ascending order: Priority DESC, BagID ASC, Slot ASC (matching BuildTargetPositions)
|
||||||
if table.getn(tailSlots) > 1 then
|
if table.getn(tailSlots) > 1 then
|
||||||
table.sort(tailSlots, function(a, b)
|
table.sort(tailSlots, function(a, b)
|
||||||
|
if not a then return false end
|
||||||
|
if not b then return true end
|
||||||
local aPrio = tonumber(addon.Modules.Utils:GetContainerPriority(a.bag)) or 0
|
local aPrio = tonumber(addon.Modules.Utils:GetContainerPriority(a.bag)) or 0
|
||||||
local bPrio = tonumber(addon.Modules.Utils:GetContainerPriority(b.bag)) or 0
|
local bPrio = tonumber(addon.Modules.Utils:GetContainerPriority(b.bag)) or 0
|
||||||
if aPrio ~= bPrio then
|
if aPrio ~= bPrio then
|
||||||
return aPrio > bPrio
|
return aPrio > bPrio
|
||||||
end
|
end
|
||||||
if a.bag ~= b.bag then
|
if (a.bag or 0) ~= (b.bag or 0) then
|
||||||
return a.bag < b.bag
|
return (a.bag or 0) < (b.bag or 0)
|
||||||
end
|
end
|
||||||
return a.slot < b.slot
|
return (a.slot or 0) < (b.slot or 0)
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -1504,11 +1530,9 @@ function SortEngine:SortBags()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Clear caches after sorting to ensure fresh detection on next UI update
|
-- Clear bag position cache after sorting to ensure fresh slot data on next UI update
|
||||||
|
-- NOTE: Don't clear ItemDetection cache - item properties don't change when items move
|
||||||
addon.Modules.BagScanner:ClearCache()
|
addon.Modules.BagScanner:ClearCache()
|
||||||
if addon.Modules.ItemDetection then
|
|
||||||
addon.Modules.ItemDetection:ClearCache()
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Return total moves made
|
-- Return total moves made
|
||||||
return routeCount + consolidateCount + specializedMoves + regularMoves
|
return routeCount + consolidateCount + specializedMoves + regularMoves
|
||||||
@@ -1641,11 +1665,9 @@ function SortEngine:SortBank()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Clear caches after sorting to ensure fresh detection on next UI update
|
-- Clear bag position cache after sorting to ensure fresh slot data on next UI update
|
||||||
|
-- NOTE: Don't clear ItemDetection cache - item properties don't change when items move
|
||||||
addon.Modules.BankScanner:ClearCache()
|
addon.Modules.BankScanner:ClearCache()
|
||||||
if addon.Modules.ItemDetection then
|
|
||||||
addon.Modules.ItemDetection:ClearCache()
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Return total moves made
|
-- Return total moves made
|
||||||
return routeCount + consolidateCount + specializedMoves + regularMoves
|
return routeCount + consolidateCount + specializedMoves + regularMoves
|
||||||
@@ -1708,52 +1730,36 @@ function SortEngine:ExecuteSort(sortFunction, analyzeFunction, updateFrame, sort
|
|||||||
-- Sorting is complete!
|
-- Sorting is complete!
|
||||||
addon:DebugSort("%s sort complete! (%d passes, %d total moves)", sortType, passCount, totalMoves)
|
addon:DebugSort("%s sort complete! (%d passes, %d total moves)", sortType, passCount, totalMoves)
|
||||||
|
|
||||||
-- Final update (cache clearing happens after delay)
|
-- Final update (cache clearing happens after delay, uses pooled timer)
|
||||||
local frame = CreateFrame("Frame")
|
Guda_ScheduleTimer(0.3, function()
|
||||||
local startTime = GetTime()
|
SortEngine.sortingInProgress = false
|
||||||
frame:SetScript("OnUpdate", function()
|
SortEngine:UpdateSortButtonState(false)
|
||||||
if GetTime() - startTime >= 0.3 then
|
currentSortType = "bags" -- Reset sort context
|
||||||
frame:SetScript("OnUpdate", nil)
|
-- Clear bag position cache - item properties don't change on move
|
||||||
SortEngine.sortingInProgress = false
|
if sortType == "bank" then
|
||||||
SortEngine:UpdateSortButtonState(false)
|
addon.Modules.BankScanner:ClearCache()
|
||||||
currentSortType = "bags" -- Reset sort context
|
else
|
||||||
-- Clear caches after sorting to ensure fresh detection
|
addon.Modules.BagScanner:ClearCache()
|
||||||
if sortType == "bank" then
|
|
||||||
addon.Modules.BankScanner:ClearCache()
|
|
||||||
else
|
|
||||||
addon.Modules.BagScanner:ClearCache()
|
|
||||||
end
|
|
||||||
if addon.Modules.ItemDetection then
|
|
||||||
addon.Modules.ItemDetection:ClearCache()
|
|
||||||
end
|
|
||||||
updateFrame()
|
|
||||||
end
|
end
|
||||||
|
updateFrame()
|
||||||
end)
|
end)
|
||||||
elseif passCount >= safetyLimit then
|
elseif passCount >= safetyLimit then
|
||||||
-- Hit safety limit but not fully sorted
|
-- Hit safety limit but not fully sorted
|
||||||
addon:DebugSort("%s sort stopped at safety limit! (%d/%d items still need sorting after %d passes)",
|
addon:DebugSort("%s sort stopped at safety limit! (%d/%d items still need sorting after %d passes)",
|
||||||
sortType, currentAnalysis.itemsOutOfPlace, currentAnalysis.totalItems, passCount)
|
sortType, currentAnalysis.itemsOutOfPlace, currentAnalysis.totalItems, passCount)
|
||||||
|
|
||||||
-- Final update (cache clearing happens after delay)
|
-- Final update (cache clearing happens after delay, uses pooled timer)
|
||||||
local frame = CreateFrame("Frame")
|
Guda_ScheduleTimer(0.3, function()
|
||||||
local startTime = GetTime()
|
SortEngine.sortingInProgress = false
|
||||||
frame:SetScript("OnUpdate", function()
|
SortEngine:UpdateSortButtonState(false)
|
||||||
if GetTime() - startTime >= 0.3 then
|
currentSortType = "bags" -- Reset sort context
|
||||||
frame:SetScript("OnUpdate", nil)
|
-- Clear bag position cache - item properties don't change on move
|
||||||
SortEngine.sortingInProgress = false
|
if sortType == "bank" then
|
||||||
SortEngine:UpdateSortButtonState(false)
|
addon.Modules.BankScanner:ClearCache()
|
||||||
currentSortType = "bags" -- Reset sort context
|
else
|
||||||
-- Clear caches after sorting to ensure fresh detection
|
addon.Modules.BagScanner:ClearCache()
|
||||||
if sortType == "bank" then
|
|
||||||
addon.Modules.BankScanner:ClearCache()
|
|
||||||
else
|
|
||||||
addon.Modules.BagScanner:ClearCache()
|
|
||||||
end
|
|
||||||
if addon.Modules.ItemDetection then
|
|
||||||
addon.Modules.ItemDetection:ClearCache()
|
|
||||||
end
|
|
||||||
updateFrame()
|
|
||||||
end
|
end
|
||||||
|
updateFrame()
|
||||||
end)
|
end)
|
||||||
else
|
else
|
||||||
-- No progress guard: stop if we make no moves repeatedly
|
-- No progress guard: stop if we make no moves repeatedly
|
||||||
@@ -1767,26 +1773,18 @@ function SortEngine:ExecuteSort(sortFunction, analyzeFunction, updateFrame, sort
|
|||||||
addon:DebugSort("%s sort stopped due to no progress after %d passes (items remaining: %d/%d)",
|
addon:DebugSort("%s sort stopped due to no progress after %d passes (items remaining: %d/%d)",
|
||||||
sortType, passCount, currentAnalysis.itemsOutOfPlace, currentAnalysis.totalItems)
|
sortType, passCount, currentAnalysis.itemsOutOfPlace, currentAnalysis.totalItems)
|
||||||
|
|
||||||
-- Final update (cache clearing happens after delay)
|
-- Final update (cache clearing happens after delay, uses pooled timer)
|
||||||
local frame = CreateFrame("Frame")
|
Guda_ScheduleTimer(0.3, function()
|
||||||
local startTime = GetTime()
|
SortEngine.sortingInProgress = false
|
||||||
frame:SetScript("OnUpdate", function()
|
SortEngine:UpdateSortButtonState(false)
|
||||||
if GetTime() - startTime >= 0.3 then
|
currentSortType = "bags" -- Reset sort context
|
||||||
frame:SetScript("OnUpdate", nil)
|
-- Clear bag position cache - item properties don't change on move
|
||||||
SortEngine.sortingInProgress = false
|
if sortType == "bank" then
|
||||||
SortEngine:UpdateSortButtonState(false)
|
addon.Modules.BankScanner:ClearCache()
|
||||||
currentSortType = "bags" -- Reset sort context
|
else
|
||||||
-- Clear caches after sorting to ensure fresh detection
|
addon.Modules.BagScanner:ClearCache()
|
||||||
if sortType == "bank" then
|
|
||||||
addon.Modules.BankScanner:ClearCache()
|
|
||||||
else
|
|
||||||
addon.Modules.BagScanner:ClearCache()
|
|
||||||
end
|
|
||||||
if addon.Modules.ItemDetection then
|
|
||||||
addon.Modules.ItemDetection:ClearCache()
|
|
||||||
end
|
|
||||||
updateFrame()
|
|
||||||
end
|
end
|
||||||
|
updateFrame()
|
||||||
end)
|
end)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
@@ -1805,15 +1803,8 @@ function SortEngine:ExecuteSort(sortFunction, analyzeFunction, updateFrame, sort
|
|||||||
|
|
||||||
addon:DebugSort("Waiting %.1f seconds before next pass...", totalDelay)
|
addon:DebugSort("Waiting %.1f seconds before next pass...", totalDelay)
|
||||||
|
|
||||||
-- Wait with progressive delay, then sort again
|
-- Wait with progressive delay, then sort again (uses pooled timer)
|
||||||
local frame = CreateFrame("Frame")
|
Guda_ScheduleTimer(totalDelay, DoSortPass)
|
||||||
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
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
+30
-29
@@ -21,6 +21,7 @@ function BagFrame:GetCurrentViewChar()
|
|||||||
end
|
end
|
||||||
local searchText = ""
|
local searchText = ""
|
||||||
local itemButtons = {}
|
local itemButtons = {}
|
||||||
|
local slotToButton = {} -- Fast O(1) lookup: slotToButton[bagID][slotID] = button
|
||||||
local showKeyring = false -- Toggle for keyring display
|
local showKeyring = false -- Toggle for keyring display
|
||||||
local hiddenBags = {} -- Track which bags are hidden (bagID -> true/false)
|
local hiddenBags = {} -- Track which bags are hidden (bagID -> true/false)
|
||||||
local bagParents = {} -- Per-bag parent frames to carry bagID for Blizzard item button templates
|
local bagParents = {} -- Per-bag parent frames to carry bagID for Blizzard item button templates
|
||||||
@@ -243,16 +244,13 @@ function BagFrame:UpdateChangedSlots(bagID)
|
|||||||
local numSlots = GetContainerNumSlots(bagID)
|
local numSlots = GetContainerNumSlots(bagID)
|
||||||
if not numSlots or numSlots == 0 then return -1 end
|
if not numSlots or numSlots == 0 then return -1 end
|
||||||
|
|
||||||
|
-- Check if we have the slot lookup table for this bag
|
||||||
|
if not slotToButton[bagID] then return -1 end
|
||||||
|
|
||||||
local updatedCount = 0
|
local updatedCount = 0
|
||||||
for slotID = 1, numSlots do
|
for slotID = 1, numSlots do
|
||||||
-- Find button for this slot
|
-- O(1) button lookup using hash table
|
||||||
local targetButton = nil
|
local targetButton = slotToButton[bagID][slotID]
|
||||||
for _, button in ipairs(itemButtons) do
|
|
||||||
if button.bagID == bagID and button.slotID == slotID then
|
|
||||||
targetButton = button
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if not targetButton then
|
if not targetButton then
|
||||||
-- Button not found, need full redraw
|
-- Button not found, need full redraw
|
||||||
@@ -498,10 +496,13 @@ function BagFrame:Update()
|
|||||||
-- Display items
|
-- Display items
|
||||||
local viewType = addon.Modules.DB:GetSetting("bagViewType") or "single"
|
local viewType = addon.Modules.DB:GetSetting("bagViewType") or "single"
|
||||||
|
|
||||||
-- Clear itemButtons table before rebuilding (prevents stale references)
|
-- Clear itemButtons table and slot lookup before rebuilding (prevents stale references)
|
||||||
for k in pairs(itemButtons) do
|
for k in pairs(itemButtons) do
|
||||||
itemButtons[k] = nil
|
itemButtons[k] = nil
|
||||||
end
|
end
|
||||||
|
for k in pairs(slotToButton) do
|
||||||
|
slotToButton[k] = nil
|
||||||
|
end
|
||||||
|
|
||||||
-- Reset all section headers before displaying items
|
-- Reset all section headers before displaying items
|
||||||
local i = 1
|
local i = 1
|
||||||
@@ -704,6 +705,9 @@ function BagFrame:DisplayItemsByCategory(bagData, isOtherChar, charName)
|
|||||||
Guda_ItemButton_SetItem(button, bagID, slot, itemData, false, isOtherChar and charName or nil, matchesFilter, isOtherChar)
|
Guda_ItemButton_SetItem(button, bagID, slot, itemData, false, isOtherChar and charName or nil, matchesFilter, isOtherChar)
|
||||||
button.inUse = true
|
button.inUse = true
|
||||||
table.insert(itemButtons, button)
|
table.insert(itemButtons, button)
|
||||||
|
-- Populate slot lookup for O(1) access
|
||||||
|
if not slotToButton[bagID] then slotToButton[bagID] = {} end
|
||||||
|
slotToButton[bagID][slot] = button
|
||||||
|
|
||||||
col = col + 1
|
col = col + 1
|
||||||
if col >= blockCols then
|
if col >= blockCols then
|
||||||
@@ -766,8 +770,11 @@ function BagFrame:DisplayItemsByCategory(bagData, isOtherChar, charName)
|
|||||||
if numItems > 0 then
|
if numItems > 0 then
|
||||||
if sec.name == "Tools" then
|
if sec.name == "Tools" then
|
||||||
table.sort(items, function(a, b)
|
table.sort(items, function(a, b)
|
||||||
if a.itemData.quality ~= b.itemData.quality then
|
-- Guard against nil entries
|
||||||
return a.itemData.quality > b.itemData.quality
|
if not a or not a.itemData then return false end
|
||||||
|
if not b or not b.itemData then return true end
|
||||||
|
if (a.itemData.quality or 0) ~= (b.itemData.quality or 0) then
|
||||||
|
return (a.itemData.quality or 0) > (b.itemData.quality or 0)
|
||||||
end
|
end
|
||||||
return (a.itemData.name or "") < (b.itemData.name or "")
|
return (a.itemData.name or "") < (b.itemData.name or "")
|
||||||
end)
|
end)
|
||||||
@@ -830,6 +837,9 @@ function BagFrame:DisplayItemsByCategory(bagData, isOtherChar, charName)
|
|||||||
Guda_ItemButton_SetItem(button, item.bagID, item.slotID, item.itemData, false, isOtherChar and charName or nil, self:PassesSearchFilter(item.itemData), isOtherChar)
|
Guda_ItemButton_SetItem(button, item.bagID, item.slotID, item.itemData, false, isOtherChar and charName or nil, self:PassesSearchFilter(item.itemData), isOtherChar)
|
||||||
button.inUse = true
|
button.inUse = true
|
||||||
table.insert(itemButtons, button)
|
table.insert(itemButtons, button)
|
||||||
|
-- Populate slot lookup for O(1) access
|
||||||
|
if not slotToButton[item.bagID] then slotToButton[item.bagID] = {} end
|
||||||
|
slotToButton[item.bagID][item.slotID] = button
|
||||||
|
|
||||||
sCol = sCol + 1
|
sCol = sCol + 1
|
||||||
if sCol >= blockCols then
|
if sCol >= blockCols then
|
||||||
@@ -1005,6 +1015,9 @@ function BagFrame:DisplayItems(bagData, isOtherChar, charName)
|
|||||||
Guda_ItemButton_SetItem(button, bagID, slot, itemData, false, isOtherChar and charName or nil, matchesFilter, isOtherChar)
|
Guda_ItemButton_SetItem(button, bagID, slot, itemData, false, isOtherChar and charName or nil, matchesFilter, isOtherChar)
|
||||||
|
|
||||||
table.insert(itemButtons, button)
|
table.insert(itemButtons, button)
|
||||||
|
-- Populate slot lookup for O(1) access
|
||||||
|
if not slotToButton[bagID] then slotToButton[bagID] = {} end
|
||||||
|
slotToButton[bagID][slot] = button
|
||||||
|
|
||||||
-- Advance position
|
-- Advance position
|
||||||
col = col + 1
|
col = col + 1
|
||||||
@@ -1746,7 +1759,9 @@ function Guda_BagFrame_MergeStacks()
|
|||||||
if table.getn(group.stacks) > 1 then
|
if table.getn(group.stacks) > 1 then
|
||||||
-- Sort stacks: larger stacks first (targets), smaller stacks last (sources)
|
-- Sort stacks: larger stacks first (targets), smaller stacks last (sources)
|
||||||
table.sort(group.stacks, function(a, b)
|
table.sort(group.stacks, function(a, b)
|
||||||
return a.count > b.count
|
if not a then return false end
|
||||||
|
if not b then return true end
|
||||||
|
return (a.count or 0) > (b.count or 0)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
local sourceLoopStart = table.getn(group.stacks)
|
local sourceLoopStart = table.getn(group.stacks)
|
||||||
@@ -1849,20 +1864,8 @@ function Guda_BagFrame_MergeStacks()
|
|||||||
ProcessNextMove()
|
ProcessNextMove()
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Simple timer helper
|
-- NOTE: Guda_ScheduleTimer is defined in Core/Utils.lua (loaded earlier)
|
||||||
function Guda_ScheduleTimer(delay, callback)
|
-- to ensure it's available for all modules that need timers
|
||||||
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
|
-- Hook bag container buttons to open Guda Bag View
|
||||||
local function HookBagContainers()
|
local function HookBagContainers()
|
||||||
@@ -2700,10 +2703,8 @@ function BagFrame:Initialize()
|
|||||||
|
|
||||||
if not isSorting then
|
if not isSorting then
|
||||||
-- Try incremental update for manual item moves
|
-- Try incremental update for manual item moves
|
||||||
|
-- NOTE: Don't clear ItemDetection cache - item properties don't change on move
|
||||||
addon.Modules.BagScanner:InvalidateBag(arg1)
|
addon.Modules.BagScanner:InvalidateBag(arg1)
|
||||||
if addon.Modules.ItemDetection then
|
|
||||||
addon.Modules.ItemDetection:ClearCache()
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Try to update only changed slots in this bag
|
-- Try to update only changed slots in this bag
|
||||||
local result = BagFrame:UpdateChangedSlots(arg1)
|
local result = BagFrame:UpdateChangedSlots(arg1)
|
||||||
|
|||||||
+59
-83
@@ -14,6 +14,7 @@ local searchText = ""
|
|||||||
local isReadOnlyMode = false -- Track if viewing saved bank (read-only) or live bank (interactive)
|
local isReadOnlyMode = false -- Track if viewing saved bank (read-only) or live bank (interactive)
|
||||||
local hiddenBankBags = {} -- Track which bank bags are hidden (bagID -> true/false)
|
local hiddenBankBags = {} -- Track which bank bags are hidden (bagID -> true/false)
|
||||||
local bankBagParents = {} -- Parent frames per bank bag (same approach as BagFrame)
|
local bankBagParents = {} -- Parent frames per bank bag (same approach as BagFrame)
|
||||||
|
local bankSlotToButton = {} -- Fast O(1) lookup: bankSlotToButton[bagID][slotID] = button
|
||||||
-- Global click catcher for clearing bank search focus
|
-- Global click catcher for clearing bank search focus
|
||||||
local bankClickCatcher = nil
|
local bankClickCatcher = nil
|
||||||
|
|
||||||
@@ -135,18 +136,9 @@ function BankFrame:UpdateSingleSlot(bagID, slotID)
|
|||||||
if not Guda_BankFrame:IsShown() then return false end
|
if not Guda_BankFrame:IsShown() then return false end
|
||||||
if currentViewChar then return false end -- Can't do single-slot for other characters
|
if currentViewChar then return false end -- Can't do single-slot for other characters
|
||||||
|
|
||||||
-- Find the button for this slot
|
-- O(1) button lookup using hash table
|
||||||
local bankBagParent = bankBagParents[bagID]
|
if not bankSlotToButton[bagID] then return false end
|
||||||
if not bankBagParent or not bankBagParent.itemButtons then return false end
|
local targetButton = bankSlotToButton[bagID][slotID]
|
||||||
|
|
||||||
local targetButton = nil
|
|
||||||
for button in pairs(bankBagParent.itemButtons) do
|
|
||||||
if button.bagID == bagID and button.slotID == slotID then
|
|
||||||
targetButton = button
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if not targetButton then return false end
|
if not targetButton then return false end
|
||||||
|
|
||||||
-- Get fresh item data for this slot
|
-- Get fresh item data for this slot
|
||||||
@@ -190,22 +182,16 @@ function BankFrame:UpdateChangedSlots(bagID)
|
|||||||
if not Guda_BankFrame:IsShown() then return -1 end
|
if not Guda_BankFrame:IsShown() then return -1 end
|
||||||
if currentViewChar then return -1 end
|
if currentViewChar then return -1 end
|
||||||
|
|
||||||
local bankBagParent = bankBagParents[bagID]
|
-- Check if we have the slot lookup table for this bag
|
||||||
if not bankBagParent or not bankBagParent.itemButtons then return -1 end
|
if not bankSlotToButton[bagID] then return -1 end
|
||||||
|
|
||||||
local numSlots = addon.Modules.Utils:GetBagSlotCount(bagID)
|
local numSlots = addon.Modules.Utils:GetBagSlotCount(bagID)
|
||||||
if not numSlots or numSlots == 0 then return -1 end
|
if not numSlots or numSlots == 0 then return -1 end
|
||||||
|
|
||||||
local updatedCount = 0
|
local updatedCount = 0
|
||||||
for slotID = 1, numSlots do
|
for slotID = 1, numSlots do
|
||||||
-- Find button for this slot
|
-- O(1) button lookup using hash table
|
||||||
local targetButton = nil
|
local targetButton = bankSlotToButton[bagID][slotID]
|
||||||
for button in pairs(bankBagParent.itemButtons) do
|
|
||||||
if button.bagID == bagID and button.slotID == slotID then
|
|
||||||
targetButton = button
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if not targetButton then
|
if not targetButton then
|
||||||
-- Button not found, need full redraw
|
-- Button not found, need full redraw
|
||||||
@@ -302,7 +288,7 @@ function BankFrame:Update()
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- Mark all existing buttons as not in use (we'll mark active ones during display)
|
-- Mark all existing buttons as not in use (we'll mark active ones during display)
|
||||||
-- Mark all existing buttons as not in use (we'll mark active ones during display)
|
-- Also clear the slot lookup table for fresh rebuild
|
||||||
for _, bankBagParent in pairs(bankBagParents) do
|
for _, bankBagParent in pairs(bankBagParents) do
|
||||||
if bankBagParent and bankBagParent.itemButtons then
|
if bankBagParent and bankBagParent.itemButtons then
|
||||||
for button in pairs(bankBagParent.itemButtons) do
|
for button in pairs(bankBagParent.itemButtons) do
|
||||||
@@ -312,6 +298,9 @@ function BankFrame:Update()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
for k in pairs(bankSlotToButton) do
|
||||||
|
bankSlotToButton[k] = nil
|
||||||
|
end
|
||||||
|
|
||||||
-- Determine if we're in read-only mode:
|
-- Determine if we're in read-only mode:
|
||||||
-- - If viewing another character → read-only
|
-- - If viewing another character → read-only
|
||||||
@@ -522,6 +511,9 @@ function BankFrame:DisplayItemsByCategory(bankData, isOtherChar, charName)
|
|||||||
local matchesFilter = self:PassesSearchFilter(itemData)
|
local matchesFilter = self:PassesSearchFilter(itemData)
|
||||||
Guda_ItemButton_SetItem(button, bagID, slot, itemData, true, isOtherChar and charName or nil, matchesFilter, isOtherChar or isReadOnlyMode)
|
Guda_ItemButton_SetItem(button, bagID, slot, itemData, true, isOtherChar and charName or nil, matchesFilter, isOtherChar or isReadOnlyMode)
|
||||||
button.inUse = true
|
button.inUse = true
|
||||||
|
-- Populate slot lookup for O(1) access
|
||||||
|
if not bankSlotToButton[bagID] then bankSlotToButton[bagID] = {} end
|
||||||
|
bankSlotToButton[bagID][slot] = button
|
||||||
|
|
||||||
col = col + 1
|
col = col + 1
|
||||||
if col >= blockCols then
|
if col >= blockCols then
|
||||||
@@ -580,8 +572,11 @@ function BankFrame:DisplayItemsByCategory(bankData, isOtherChar, charName)
|
|||||||
if numItems > 0 then
|
if numItems > 0 then
|
||||||
if sec.name == "Tools" then
|
if sec.name == "Tools" then
|
||||||
table.sort(items, function(a, b)
|
table.sort(items, function(a, b)
|
||||||
if a.itemData.quality ~= b.itemData.quality then
|
-- Guard against nil entries
|
||||||
return a.itemData.quality > b.itemData.quality
|
if not a or not a.itemData then return false end
|
||||||
|
if not b or not b.itemData then return true end
|
||||||
|
if (a.itemData.quality or 0) ~= (b.itemData.quality or 0) then
|
||||||
|
return (a.itemData.quality or 0) > (b.itemData.quality or 0)
|
||||||
end
|
end
|
||||||
return (a.itemData.name or "") < (b.itemData.name or "")
|
return (a.itemData.name or "") < (b.itemData.name or "")
|
||||||
end)
|
end)
|
||||||
@@ -642,6 +637,9 @@ function BankFrame:DisplayItemsByCategory(bankData, isOtherChar, charName)
|
|||||||
button:Show()
|
button:Show()
|
||||||
Guda_ItemButton_SetItem(button, item.bagID, item.slotID, item.itemData, true, isOtherChar and charName or nil, self:PassesSearchFilter(item.itemData), isOtherChar or isReadOnlyMode)
|
Guda_ItemButton_SetItem(button, item.bagID, item.slotID, item.itemData, true, isOtherChar and charName or nil, self:PassesSearchFilter(item.itemData), isOtherChar or isReadOnlyMode)
|
||||||
button.inUse = true
|
button.inUse = true
|
||||||
|
-- Populate slot lookup for O(1) access
|
||||||
|
if not bankSlotToButton[item.bagID] then bankSlotToButton[item.bagID] = {} end
|
||||||
|
bankSlotToButton[item.bagID][item.slotID] = button
|
||||||
|
|
||||||
sCol = sCol + 1
|
sCol = sCol + 1
|
||||||
if sCol >= blockCols then
|
if sCol >= blockCols then
|
||||||
@@ -795,6 +793,9 @@ function BankFrame:DisplayItems(bankData, isOtherChar, charName)
|
|||||||
button:SetPoint("TOPLEFT", itemContainer, "TOPLEFT", xPos, yPos)
|
button:SetPoint("TOPLEFT", itemContainer, "TOPLEFT", xPos, yPos)
|
||||||
|
|
||||||
Guda_ItemButton_SetItem(button, bagID, slot, itemData, true, isOtherChar and charName or nil, matchesFilter, isReadOnlyMode)
|
Guda_ItemButton_SetItem(button, bagID, slot, itemData, true, isOtherChar and charName or nil, matchesFilter, isReadOnlyMode)
|
||||||
|
-- Populate slot lookup for O(1) access
|
||||||
|
if not bankSlotToButton[bagID] then bankSlotToButton[bagID] = {} end
|
||||||
|
bankSlotToButton[bagID][slot] = button
|
||||||
|
|
||||||
col = col + 1
|
col = col + 1
|
||||||
if col >= perRow then
|
if col >= perRow then
|
||||||
@@ -1229,7 +1230,9 @@ function Guda_BankFrame_MergeStacks()
|
|||||||
if table.getn(group.stacks) > 1 then
|
if table.getn(group.stacks) > 1 then
|
||||||
-- Sort stacks: larger stacks first (targets), smaller stacks last (sources)
|
-- Sort stacks: larger stacks first (targets), smaller stacks last (sources)
|
||||||
table.sort(group.stacks, function(a, b)
|
table.sort(group.stacks, function(a, b)
|
||||||
return a.count > b.count
|
if not a then return false end
|
||||||
|
if not b then return true end
|
||||||
|
return (a.count or 0) > (b.count or 0)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
local sourceLoopStart = table.getn(group.stacks)
|
local sourceLoopStart = table.getn(group.stacks)
|
||||||
@@ -1611,40 +1614,28 @@ function BankFrame:Initialize()
|
|||||||
|
|
||||||
-- Update when bank is opened
|
-- Update when bank is opened
|
||||||
addon.Modules.Events:OnBankOpen(function()
|
addon.Modules.Events:OnBankOpen(function()
|
||||||
-- Delay showing custom bank to let TransmogUI finish processing
|
-- Delay showing custom bank to let TransmogUI finish processing (uses pooled timer)
|
||||||
local frame = CreateFrame("Frame")
|
Guda_ScheduleTimer(0.2, function()
|
||||||
local elapsed = 0
|
-- Show current character's bank in interactive mode
|
||||||
frame:SetScript("OnUpdate", function()
|
currentViewChar = nil
|
||||||
elapsed = elapsed + arg1
|
|
||||||
if elapsed >= 0.2 then
|
|
||||||
frame:SetScript("OnUpdate", nil)
|
|
||||||
|
|
||||||
-- Show current character's bank in interactive mode
|
-- Do not auto-hide BagFrame when opening BankFrame
|
||||||
currentViewChar = nil
|
-- Users may want both frames visible simultaneously; layout issues, if any,
|
||||||
|
-- should be addressed via positioning rather than auto-hiding.
|
||||||
|
|
||||||
-- Do not auto-hide BagFrame when opening BankFrame
|
-- Show and update custom bank frame
|
||||||
-- Previously, we hid BagFrame to prevent button overlap:
|
local customBankFrame = getglobal("Guda_BankFrame")
|
||||||
-- local bagFrame = getglobal("Guda_BagFrame")
|
if customBankFrame then
|
||||||
-- if bagFrame and bagFrame:IsShown() then
|
customBankFrame:Show()
|
||||||
-- bagFrame:Hide()
|
|
||||||
-- end
|
|
||||||
-- Users may want both frames visible simultaneously; layout issues, if any,
|
|
||||||
-- should be addressed via positioning rather than auto-hiding.
|
|
||||||
|
|
||||||
-- Show and update custom bank frame
|
|
||||||
local customBankFrame = getglobal("Guda_BankFrame")
|
|
||||||
if customBankFrame then
|
|
||||||
customBankFrame:Show()
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Force disable pfUI banks if enabled (pfUI uses pfBank for its bank frame)
|
|
||||||
if pfUI and pfUI.bag and pfUI.bag.left and pfUI.bag.left.Hide then
|
|
||||||
pfUI.bag.left:Hide()
|
|
||||||
end
|
|
||||||
|
|
||||||
addon.Modules.BankFrame:EnsureBagButtonsInitialized()
|
|
||||||
addon.Modules.BankFrame:Update()
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Force disable pfUI banks if enabled (pfUI uses pfBank for its bank frame)
|
||||||
|
if pfUI and pfUI.bag and pfUI.bag.left and pfUI.bag.left.Hide then
|
||||||
|
pfUI.bag.left:Hide()
|
||||||
|
end
|
||||||
|
|
||||||
|
addon.Modules.BankFrame:EnsureBagButtonsInitialized()
|
||||||
|
addon.Modules.BankFrame:Update()
|
||||||
end)
|
end)
|
||||||
end, "BankFrameUI")
|
end, "BankFrameUI")
|
||||||
|
|
||||||
@@ -1714,11 +1705,9 @@ function BankFrame:Initialize()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
addon.Modules.Events:OnBagUpdate(function()
|
-- NOTE: BAG_UPDATE for bank bags (5-10) is handled by the updateFrame below
|
||||||
if addon.Modules.BankScanner:IsBankOpen() and not currentViewChar then
|
-- which provides incremental update logic. We don't need a separate OnBagUpdate
|
||||||
ScheduleBankFrameUpdate(0.1)
|
-- handler here as it would cause duplicate processing.
|
||||||
end
|
|
||||||
end, "BankFrameUI")
|
|
||||||
|
|
||||||
-- Update when items get locked/unlocked (debounced for trading, mailing, etc.)
|
-- Update when items get locked/unlocked (debounced for trading, mailing, etc.)
|
||||||
addon.Modules.Events:Register("ITEM_LOCK_CHANGED", function()
|
addon.Modules.Events:Register("ITEM_LOCK_CHANGED", function()
|
||||||
@@ -1744,11 +1733,9 @@ function BankFrame:Initialize()
|
|||||||
-- arg1 is the slot number (1-28 for main bank)
|
-- arg1 is the slot number (1-28 for main bank)
|
||||||
-- Try single-slot update if not sorting
|
-- Try single-slot update if not sorting
|
||||||
if not isSorting then
|
if not isSorting then
|
||||||
-- Invalidate cache for fresh data
|
-- Invalidate bag scanner cache for fresh slot data
|
||||||
|
-- NOTE: Don't clear ItemDetection cache - item properties don't change on move
|
||||||
addon.Modules.BankScanner:InvalidateBag(-1)
|
addon.Modules.BankScanner:InvalidateBag(-1)
|
||||||
if addon.Modules.ItemDetection then
|
|
||||||
addon.Modules.ItemDetection:ClearCache()
|
|
||||||
end
|
|
||||||
-- Try single-slot update
|
-- Try single-slot update
|
||||||
if BankFrame:UpdateSingleSlot(-1, arg1) then
|
if BankFrame:UpdateSingleSlot(-1, arg1) then
|
||||||
return -- Success, no full redraw needed
|
return -- Success, no full redraw needed
|
||||||
@@ -1756,30 +1743,21 @@ function BankFrame:Initialize()
|
|||||||
end
|
end
|
||||||
-- Fallback: full redraw (sorting or single-slot failed)
|
-- Fallback: full redraw (sorting or single-slot failed)
|
||||||
addon.Modules.BankScanner:InvalidateBag(-1)
|
addon.Modules.BankScanner:InvalidateBag(-1)
|
||||||
if addon.Modules.ItemDetection then
|
|
||||||
addon.Modules.ItemDetection:ClearCache()
|
|
||||||
end
|
|
||||||
elseif event == "BAG_UPDATE" and arg1 then
|
elseif event == "BAG_UPDATE" and arg1 then
|
||||||
-- Check if this is a bank bag (5-10)
|
-- Check if this is a bank bag (5-10)
|
||||||
if arg1 >= 5 and arg1 <= 10 then
|
if arg1 >= 5 and arg1 <= 10 then
|
||||||
|
-- Invalidate bag scanner cache for fresh slot data
|
||||||
|
-- NOTE: Don't clear ItemDetection cache - item properties don't change on move
|
||||||
|
addon.Modules.BankScanner:InvalidateBag(arg1)
|
||||||
|
|
||||||
-- Try incremental update if not sorting
|
-- Try incremental update if not sorting
|
||||||
if not isSorting then
|
if not isSorting then
|
||||||
addon.Modules.BankScanner:InvalidateBag(arg1)
|
|
||||||
if addon.Modules.ItemDetection then
|
|
||||||
addon.Modules.ItemDetection:ClearCache()
|
|
||||||
end
|
|
||||||
-- Try to update only changed slots
|
-- Try to update only changed slots
|
||||||
local result = BankFrame:UpdateChangedSlots(arg1)
|
local result = BankFrame:UpdateChangedSlots(arg1)
|
||||||
if result >= 0 then
|
if result >= 0 then
|
||||||
return -- Success, no full redraw needed
|
return -- Success, no full redraw needed
|
||||||
end
|
end
|
||||||
-- Fall through to full redraw
|
-- Fall through to full redraw
|
||||||
else
|
|
||||||
-- Sorting in progress - invalidate for full redraw
|
|
||||||
addon.Modules.BankScanner:InvalidateBag(arg1)
|
|
||||||
if addon.Modules.ItemDetection then
|
|
||||||
addon.Modules.ItemDetection:ClearCache()
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
-- Not a bank bag, ignore for bank frame
|
-- Not a bank bag, ignore for bank frame
|
||||||
@@ -1787,11 +1765,9 @@ function BankFrame:Initialize()
|
|||||||
end
|
end
|
||||||
elseif event == "PLAYERBANKBAGSLOTS_CHANGED" then
|
elseif event == "PLAYERBANKBAGSLOTS_CHANGED" then
|
||||||
-- Bank container slot changed (bag added/removed)
|
-- Bank container slot changed (bag added/removed)
|
||||||
-- Must clear entire cache since bag structure changed
|
-- Clear bag scanner cache since structure changed
|
||||||
|
-- NOTE: Don't clear ItemDetection cache - item properties don't change
|
||||||
addon.Modules.BankScanner:ClearCache()
|
addon.Modules.BankScanner:ClearCache()
|
||||||
if addon.Modules.ItemDetection then
|
|
||||||
addon.Modules.ItemDetection:ClearCache()
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Slightly longer delay to ensure WoW API has updated
|
-- Slightly longer delay to ensure WoW API has updated
|
||||||
|
|||||||
+11
-4
@@ -210,7 +210,14 @@ end
|
|||||||
|
|
||||||
-- Sort items within a category
|
-- Sort items within a category
|
||||||
function Guda_SortCategoryItems(items)
|
function Guda_SortCategoryItems(items)
|
||||||
|
if not items then return end
|
||||||
table.sort(items, function(a, b)
|
table.sort(items, function(a, b)
|
||||||
|
-- Guard against nil entries
|
||||||
|
if not a then return false end
|
||||||
|
if not b then return true end
|
||||||
|
if not a.itemData then return false end
|
||||||
|
if not b.itemData then return true end
|
||||||
|
|
||||||
-- Rank Trade Goods: meat (name ends with 'meat') = 2, egg (contains 'egg') = 1, others = 0
|
-- Rank Trade Goods: meat (name ends with 'meat') = 2, egg (contains 'egg') = 1, others = 0
|
||||||
local function tgRank(d)
|
local function tgRank(d)
|
||||||
if not d or not d.name then return 0 end
|
if not d or not d.name then return 0 end
|
||||||
@@ -227,8 +234,8 @@ function Guda_SortCategoryItems(items)
|
|||||||
return ra > rb
|
return ra > rb
|
||||||
end
|
end
|
||||||
-- Priority: consumable restore tags (eat > drink > restore > nil)
|
-- Priority: consumable restore tags (eat > drink > restore > nil)
|
||||||
local pa = a.itemData and a.itemData.restoreTag or nil
|
local pa = a.itemData.restoreTag
|
||||||
local pb = b.itemData and b.itemData.restoreTag or nil
|
local pb = b.itemData.restoreTag
|
||||||
local function pr(t)
|
local function pr(t)
|
||||||
if t == "eat" then return 3 end
|
if t == "eat" then return 3 end
|
||||||
if t == "drink" then return 2 end
|
if t == "drink" then return 2 end
|
||||||
@@ -242,8 +249,8 @@ function Guda_SortCategoryItems(items)
|
|||||||
if a.itemData.subclass ~= b.itemData.subclass then
|
if a.itemData.subclass ~= b.itemData.subclass then
|
||||||
return (a.itemData.subclass or "") < (b.itemData.subclass or "")
|
return (a.itemData.subclass or "") < (b.itemData.subclass or "")
|
||||||
end
|
end
|
||||||
if a.itemData.quality ~= b.itemData.quality then
|
if (a.itemData.quality or 0) ~= (b.itemData.quality or 0) then
|
||||||
return a.itemData.quality > b.itemData.quality
|
return (a.itemData.quality or 0) > (b.itemData.quality or 0)
|
||||||
end
|
end
|
||||||
return (a.itemData.name or "") < (b.itemData.name or "")
|
return (a.itemData.name or "") < (b.itemData.name or "")
|
||||||
end)
|
end)
|
||||||
|
|||||||
+9
-86
@@ -110,91 +110,10 @@ local function Guda_GetUnusableColor()
|
|||||||
return 0.9, 0.2, 0.2, 1.0
|
return 0.9, 0.2, 0.2, 1.0
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Build durability match pattern based on the client template
|
-- NOTE: IsItemUnusable detection is now handled by ItemDetection:IsUnusable()
|
||||||
local durabilityPattern
|
-- which uses cached tooltip scanning to avoid duplicate scans per item.
|
||||||
if DURABILITY_TEMPLATE then
|
-- The old IsItemUnusable, IsRedColor, and durabilityPattern have been removed
|
||||||
-- e.g. "Durability %d / %d" -> "Durability (.+)"
|
-- to prevent redundant tooltip scanning - all detection is now centralized.
|
||||||
durabilityPattern = string.gsub(DURABILITY_TEMPLATE, "%%[^%s]+", "(.+)")
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Tiny helper to compare font color to Blizzard's RED_FONT_COLOR
|
|
||||||
local function IsRedColor(r, g, b)
|
|
||||||
if not r or not g or not b or not RED_FONT_COLOR then return false end
|
|
||||||
local dr = math.abs(r - RED_FONT_COLOR.r)
|
|
||||||
local dg = math.abs(g - RED_FONT_COLOR.g)
|
|
||||||
local db = math.abs(b - RED_FONT_COLOR.b)
|
|
||||||
return (dr < 0.08 and dg < 0.08 and db < 0.08)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Scan tooltip for red text that is NOT a durability line
|
|
||||||
-- Uses shared tooltip from Utils module
|
|
||||||
local function IsItemUnusable(bagID, slotID, isBank)
|
|
||||||
bagID = tonumber(bagID)
|
|
||||||
slotID = tonumber(slotID)
|
|
||||||
if not bagID or not slotID then return false end
|
|
||||||
|
|
||||||
-- Get shared tooltip from Utils
|
|
||||||
local scanTooltip, tooltipName = addon.Modules.Utils:GetScanTooltip()
|
|
||||||
|
|
||||||
-- Some clients require SetOwner before every SetBagItem/SetInventoryItem to populate lines
|
|
||||||
if scanTooltip.SetOwner then
|
|
||||||
scanTooltip:SetOwner(UIParent or WorldFrame, "ANCHOR_NONE")
|
|
||||||
end
|
|
||||||
scanTooltip:ClearLines()
|
|
||||||
|
|
||||||
if isBank and bagID == -1 then
|
|
||||||
-- Bank frame item buttons map slots 1.. to inventory slots 40.. (39 + slot)
|
|
||||||
if scanTooltip.SetInventoryItem then
|
|
||||||
scanTooltip:SetInventoryItem("player", 39 + slotID)
|
|
||||||
else
|
|
||||||
-- Fallback to bag scan if API missing
|
|
||||||
scanTooltip:SetBagItem(bagID, slotID)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
scanTooltip:SetBagItem(bagID, slotID)
|
|
||||||
end
|
|
||||||
|
|
||||||
if scanTooltip.Show then scanTooltip:Show() end
|
|
||||||
|
|
||||||
local num = scanTooltip:NumLines() or 0
|
|
||||||
for i = 1, num do
|
|
||||||
-- Scan LEFT column
|
|
||||||
local left = getglobal(tooltipName .. "TextLeft" .. i)
|
|
||||||
if left and left:IsShown() then
|
|
||||||
local text = left:GetText()
|
|
||||||
local r, g, b = left:GetTextColor()
|
|
||||||
-- Be tolerant with red detection in case client colors differ slightly
|
|
||||||
local isRed = IsRedColor(r, g, b) or (r and g and b and r > 0.85 and g < 0.3 and b < 0.3)
|
|
||||||
if text and isRed then
|
|
||||||
-- Ignore red durability (broken) lines
|
|
||||||
if durabilityPattern and string.find(text, durabilityPattern, 1) then
|
|
||||||
-- skip durability
|
|
||||||
else
|
|
||||||
if scanTooltip.Hide then scanTooltip:Hide() end
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Scan RIGHT column as well (required level etc can appear here on some clients)
|
|
||||||
local right = getglobal(tooltipName .. "TextRight" .. i)
|
|
||||||
if right and right:IsShown() then
|
|
||||||
local text = right:GetText()
|
|
||||||
local r, g, b = right:GetTextColor()
|
|
||||||
local isRed = IsRedColor(r, g, b) or (r and g and b and r > 0.85 and g < 0.3 and b < 0.3)
|
|
||||||
if text and isRed then
|
|
||||||
if durabilityPattern and string.find(text, durabilityPattern, 1) then
|
|
||||||
-- skip durability
|
|
||||||
else
|
|
||||||
if scanTooltip.Hide then scanTooltip:Hide() end
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Apply/remove red tint on item texture for unusable items
|
-- Apply/remove red tint on item texture for unusable items
|
||||||
local function Guda_ItemButton_UpdateUsableTint(self)
|
local function Guda_ItemButton_UpdateUsableTint(self)
|
||||||
@@ -225,7 +144,11 @@ local function Guda_ItemButton_UpdateUsableTint(self)
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local unusable = IsItemUnusable(self.bagID, self.slotID, self.isBank)
|
-- Use cached detection from ItemDetection module (avoids duplicate tooltip scans)
|
||||||
|
local unusable = false
|
||||||
|
if self.itemData and addon.Modules.ItemDetection then
|
||||||
|
unusable = addon.Modules.ItemDetection:IsUnusable(self.itemData, self.bagID, self.slotID)
|
||||||
|
end
|
||||||
|
|
||||||
-- Ensure overlay exists (created in OnLoad, but be defensive)
|
-- Ensure overlay exists (created in OnLoad, but be defensive)
|
||||||
if not self.unusableOverlay then
|
if not self.unusableOverlay then
|
||||||
|
|||||||
+4
-10
@@ -649,16 +649,10 @@ function QuestItemBar:Initialize()
|
|||||||
addon.Modules.Events:Register("BAG_UPDATE", function()
|
addon.Modules.Events:Register("BAG_UPDATE", function()
|
||||||
if bagUpdatePending then return end
|
if bagUpdatePending then return end
|
||||||
bagUpdatePending = true
|
bagUpdatePending = true
|
||||||
-- Debounce: wait 0.15 seconds before updating to batch rapid events
|
-- Debounce: wait 0.15 seconds before updating (uses pooled timer)
|
||||||
local debounceFrame = CreateFrame("Frame")
|
Guda_ScheduleTimer(0.15, function()
|
||||||
debounceFrame.elapsed = 0
|
bagUpdatePending = false
|
||||||
debounceFrame:SetScript("OnUpdate", function()
|
QuestItemBar:Update()
|
||||||
this.elapsed = this.elapsed + arg1
|
|
||||||
if this.elapsed >= 0.15 then
|
|
||||||
this:SetScript("OnUpdate", nil)
|
|
||||||
bagUpdatePending = false
|
|
||||||
QuestItemBar:Update()
|
|
||||||
end
|
|
||||||
end)
|
end)
|
||||||
end, "QuestItemBar")
|
end, "QuestItemBar")
|
||||||
|
|
||||||
|
|||||||
+4
-10
@@ -312,16 +312,10 @@ function TrackedItemBar:Initialize()
|
|||||||
addon.Modules.Events:Register("BAG_UPDATE", function()
|
addon.Modules.Events:Register("BAG_UPDATE", function()
|
||||||
if bagUpdatePending then return end
|
if bagUpdatePending then return end
|
||||||
bagUpdatePending = true
|
bagUpdatePending = true
|
||||||
-- Debounce: wait 0.15 seconds before updating to batch rapid events
|
-- Debounce: wait 0.15 seconds before updating (uses pooled timer)
|
||||||
local debounceFrame = CreateFrame("Frame")
|
Guda_ScheduleTimer(0.15, function()
|
||||||
debounceFrame.elapsed = 0
|
bagUpdatePending = false
|
||||||
debounceFrame:SetScript("OnUpdate", function()
|
TrackedItemBar:Update()
|
||||||
this.elapsed = this.elapsed + arg1
|
|
||||||
if this.elapsed >= 0.15 then
|
|
||||||
this:SetScript("OnUpdate", nil)
|
|
||||||
bagUpdatePending = false
|
|
||||||
TrackedItemBar:Update()
|
|
||||||
end
|
|
||||||
end)
|
end)
|
||||||
end, "TrackedItemBar")
|
end, "TrackedItemBar")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user