Item detection issues

This commit is contained in:
Salikh Gurgenidze
2026-01-24 01:34:43 +04:00
parent e4e6e31072
commit cb5a0af096
9 changed files with 618 additions and 265 deletions
+12 -80
View File
@@ -600,7 +600,12 @@ function CategoryManager:EvaluateRule(rule, itemData, bagID, slotID, isOtherChar
return isBoE == ruleValue
elseif ruleType == "isQuestItem" then
-- Use consolidated quest detection from Utils
-- Use centralized ItemDetection for quest item detection
if addon.Modules.ItemDetection then
local props = addon.Modules.ItemDetection:GetItemProperties(itemData, bagID, slotID)
return props.isQuestItem == ruleValue
end
-- Fallback to Utils if ItemDetection not available
local isQuestItem, _ = addon.Modules.Utils:IsQuestItem(bagID, slotID, itemData, isOtherChar, false)
return isQuestItem == ruleValue
@@ -662,86 +667,13 @@ function CategoryManager:EvaluateRule(rule, itemData, bagID, slotID, isOtherChar
return isProfessionTool == ruleValue
elseif ruleType == "isJunk" then
-- Junk items: gray items (quality 0) OR white equippable items (quality 1 + Weapon/Armor)
-- EXCLUDES:
-- 1. Trinkets, Rings, Necklaces (these typically have special effects)
-- 2. Profession tools (skinning knife, mining pick, fishing poles, etc.)
-- 3. Items with yellow description text (Use:, Equip:, Chance on hit: effects)
-- 4. Items with green description text (set bonuses, special properties)
local quality = itemData.quality
local isGray = false
local isWhiteEquip = false
-- Check for gray items (quality 0)
if quality == 0 then
isGray = true
elseif not isOtherChar and addon.Modules.Utils and addon.Modules.Utils.IsItemGrayTooltip then
-- Tooltip fallback for gray detection
isGray = addon.Modules.Utils:IsItemGrayTooltip(bagID, slotID, itemData.link)
-- Use centralized ItemDetection for junk detection
if addon.Modules.ItemDetection then
local props = addon.Modules.ItemDetection:GetItemProperties(itemData, bagID, slotID)
return props.isJunk == ruleValue
end
-- Check for white equippable items (quality 1 + Weapon/Armor)
if quality == 1 then
local itemClass = itemData.class or ""
local itemSubclass = itemData.subclass or ""
if itemClass == "Weapon" or itemClass == "Armor" then
-- In Turtle WoW, equip slot info is in itemSubType, not itemEquipLoc
local itemSubclass = itemData.subclass or ""
addon:Debug("isJunk check: name='%s', subclass='%s', class='%s'",
tostring(itemData.name), tostring(itemSubclass), tostring(itemClass))
-- EXCLUDE: Trinkets, Rings, Necklaces, Tabards, Shirts - these typically have special effects or are cosmetic
-- Check by itemSubType which contains INVTYPE_* values in Turtle WoW
local isSpecialSlot = (itemSubclass == "INVTYPE_TRINKET" or
itemSubclass == "INVTYPE_FINGER" or
itemSubclass == "INVTYPE_NECK" or
itemSubclass == "INVTYPE_TABARD" or
itemSubclass == "INVTYPE_BODY")
if isSpecialSlot then
addon:Debug("isJunk: EXCLUDED (trinket/ring/neck/tabard/shirt) - %s (subclass=%s)", tostring(itemData.name), tostring(itemSubclass))
return false == ruleValue
end
-- Check if this is a profession tool (should NOT be junk)
local isProfessionTool = false
-- Check by item ID
if itemData.link then
local itemID = addon.Modules.Utils:ExtractItemID(itemData.link)
if itemID and addon.Constants.PROFESSION_TOOL_IDS and addon.Constants.PROFESSION_TOOL_IDS[itemID] then
isProfessionTool = true
addon:Debug("isJunk: PROFESSION TOOL (by ID) - %s", tostring(itemData.name))
end
end
-- Check by subtype (e.g., Fishing Pole)
if not isProfessionTool and addon.Constants.PROFESSION_TOOL_SUBTYPES and addon.Constants.PROFESSION_TOOL_SUBTYPES[itemSubclass] then
isProfessionTool = true
addon:Debug("isJunk: PROFESSION TOOL (by subtype) - %s", tostring(itemData.name))
end
-- Check for special tooltip text (yellow/green descriptions)
-- These items have Use:, Equip:, or special effects and should NOT be junk
local hasSpecialText = false
if not isProfessionTool and not isOtherChar then
if addon.Modules.Utils and addon.Modules.Utils.HasSpecialTooltipText then
hasSpecialText = addon.Modules.Utils:HasSpecialTooltipText(bagID, slotID, itemData.link)
if hasSpecialText then
addon:Debug("isJunk: HAS SPECIAL TEXT (Use:/Equip:/green) - %s", tostring(itemData.name))
end
end
end
if not isProfessionTool and not hasSpecialText then
isWhiteEquip = true
addon:Debug("isJunk: WHITE EQUIP DETECTED - %s", tostring(itemData.name))
end
end
end
local isJunk = isGray or isWhiteEquip
return isJunk == ruleValue
-- Fallback: gray items are always junk
return (itemData.quality == 0) == ruleValue
end
return false
+379
View File
@@ -0,0 +1,379 @@
-- Guda Item Detection
-- Centralized item property detection with caching
-- Used by: CategoryManager, SortEngine, QuestItemBar, ItemButton, BagFrame, BankFrame
local addon = Guda
local ItemDetection = {}
addon.Modules.ItemDetection = ItemDetection
--=====================================================
-- Detection Result Caching
-- Caches tooltip scan results to avoid repeated scans
--=====================================================
local detectionCache = {}
local cacheHits = 0
local cacheMisses = 0
-- Clear the detection cache
function ItemDetection:ClearCache()
detectionCache = {}
cacheHits = 0
cacheMisses = 0
end
-- Get cache statistics
function ItemDetection:GetCacheStats()
local total = cacheHits + cacheMisses
local hitRate = total > 0 and (cacheHits / total * 100) or 0
local size = 0
for _ in pairs(detectionCache) do size = size + 1 end
return {
hits = cacheHits,
misses = cacheMisses,
total = total,
hitRate = hitRate,
size = size,
}
end
-- Generate cache key for an item
local function GetCacheKey(itemLink)
if not itemLink then return nil end
return itemLink
end
--=====================================================
-- Tooltip Scanning Helpers
--=====================================================
-- Get or create the scanning tooltip
local scanTooltip = nil
local function GetScanTooltip()
if not scanTooltip then
scanTooltip = CreateFrame("GameTooltip", "GudaItemDetectionTooltip", nil, "GameTooltipTemplate")
scanTooltip:SetOwner(WorldFrame, "ANCHOR_NONE")
end
return scanTooltip
end
-- Scan tooltip and return all text lines
local function ScanTooltipLines(bagID, slotID, itemLink)
local tooltip = GetScanTooltip()
local tooltipName = "GudaItemDetectionTooltip"
-- Ensure tooltip owner is set before each scan
tooltip:SetOwner(WorldFrame, "ANCHOR_NONE")
tooltip:ClearLines()
-- Set tooltip based on what we have
if bagID and slotID then
if bagID == -1 then
-- Bank main bag: use SetInventoryItem (slot 39 + slotID)
if tooltip.SetInventoryItem then
tooltip:SetInventoryItem("player", 39 + slotID)
else
-- Fallback to hyperlink if SetInventoryItem not available
if itemLink then
tooltip:SetHyperlink(itemLink)
else
return {}
end
end
else
-- Regular bags and bank bags (5-11)
tooltip:SetBagItem(bagID, slotID)
end
elseif itemLink then
tooltip:SetHyperlink(itemLink)
else
return {}
end
local lines = {}
local numLines = tooltip:NumLines() or 0
for i = 1, numLines do
local leftLine = getglobal(tooltipName .. "TextLeft" .. i)
local rightLine = getglobal(tooltipName .. "TextRight" .. i)
local leftText = leftLine and leftLine:GetText() or ""
local rightText = rightLine and rightLine:GetText() or ""
-- Get text colors (for detecting yellow/green text)
local lr, lg, lb, la = 1, 1, 1, 1
if leftLine and leftLine.GetTextColor then
lr, lg, lb, la = leftLine:GetTextColor()
end
table.insert(lines, {
left = leftText,
right = rightText,
leftLower = leftText and string.lower(leftText) or "",
rightLower = rightText and string.lower(rightText) or "",
r = lr, g = lg, b = lb,
})
end
return lines
end
--=====================================================
-- Core Detection Functions
--=====================================================
-- Check if item is a permanent enchant (enchanting scroll/vellum)
local function DetectPermanentEnchant(lines)
for _, line in ipairs(lines) do
if string.find(line.leftLower, "permanently") then
return true
end
end
return false
end
-- Check if item is a quest item and/or quest starter
local function DetectQuestItem(lines, itemData)
local isQuestItem = false
local isQuestStarter = false
-- Check item category first
if itemData and itemData.class == "Quest" then
isQuestItem = true
end
-- Scan tooltip for quest-related text
for _, line in ipairs(lines) do
local text = line.leftLower
-- Quest starter patterns (highest priority)
if string.find(text, "quest starter") or
string.find(text, "this item begins a quest") or
string.find(text, "begins a quest") or
string.find(text, "starts a quest") then
isQuestItem = true
isQuestStarter = true
end
-- Quest item patterns
if string.find(text, "quest item") then
isQuestItem = true
end
end
return isQuestItem, isQuestStarter
end
-- Check if item is junk (gray or white equippable without special properties)
local function DetectJunk(lines, itemData)
if not itemData then return false end
-- Ensure quality is a number
local quality = tonumber(itemData.quality)
local itemClass = itemData.class or ""
local itemSubclass = itemData.subclass or ""
local itemName = itemData.name or ""
local itemLink = itemData.link or ""
-- Gray items are always junk
-- Check quality value OR link color (gray = |cff9d9d9d)
if quality == 0 then
return true
end
-- Fallback: check link color for gray items
if itemLink and string.find(itemLink, "|cff9d9d9d") then
return true
end
-- If quality is still nil, try to determine from link color
if quality == nil then
if string.find(itemLink, "|cffffffff") then
quality = 1 -- White
else
return false -- Unknown quality, don't mark as junk
end
end
-- White equippable items might be junk
if quality == 1 and (itemClass == "Weapon" or itemClass == "Armor") then
-- Exclusions: trinkets, rings, necklaces, tabards, shirts
local subLower = string.lower(itemSubclass)
if string.find(subLower, "trinket") or
string.find(subLower, "ring") or
string.find(subLower, "neck") or
string.find(subLower, "tabard") or
string.find(subLower, "shirt") then
return false
end
-- Check for profession tools by common names
local nameLower = string.lower(itemName)
if string.find(nameLower, "mining pick") or
string.find(nameLower, "skinning knife") or
string.find(nameLower, "blacksmith hammer") or
string.find(nameLower, "fishing pole") or
string.find(nameLower, "gnomish army knife") then
return false
end
-- Check for profession tool subtype
if string.find(subLower, "fishing pole") or
string.find(subLower, "mining pick") or
string.find(subLower, "skinning knife") then
return false
end
-- Check tooltip for special text (Use:, Equip:, green text)
for _, line in ipairs(lines) do
local text = line.leftLower
-- Yellow text (Use:, Equip:)
if line.r and line.g and line.b then
local isYellow = (line.r > 0.9 and line.g > 0.75 and line.b < 0.2)
local isGreen = (line.r < 0.2 and line.g > 0.9 and line.b < 0.2)
if isYellow and (string.find(text, "use:") or string.find(text, "equip:")) then
return false
end
if isGreen then
return false
end
end
end
-- White equippable without special properties = junk
return true
end
return false
end
-- Check if item is usable for a quest (has yellow "Use:" text related to quest)
local function DetectQuestUsable(lines)
for _, line in ipairs(lines) do
-- Check for yellow "Use:" text
if line.r and line.g and line.b then
local isYellow = (line.r > 0.9 and line.g > 0.75 and line.b < 0.2)
if isYellow and string.find(line.leftLower, "use:") then
return true
end
end
end
return false
end
--=====================================================
-- Public API - Cached Detection
--=====================================================
-- Get all item properties at once (cached)
-- Returns: { isQuestItem, isQuestStarter, isQuestUsable, isJunk, isPermanentEnchant }
function ItemDetection:GetItemProperties(itemData, bagID, slotID)
if not itemData then
return {
isQuestItem = false,
isQuestStarter = false,
isQuestUsable = false,
isJunk = false,
isPermanentEnchant = false,
}
end
local itemLink = itemData.link
local cacheKey = GetCacheKey(itemLink)
-- Check cache
if cacheKey and detectionCache[cacheKey] then
cacheHits = cacheHits + 1
return detectionCache[cacheKey]
end
cacheMisses = cacheMisses + 1
-- Scan tooltip once
local lines = ScanTooltipLines(bagID, slotID, itemLink)
-- Debug: log if tooltip scan failed
if table.getn(lines) == 0 and addon.DEBUG then
addon:Debug("ItemDetection: No tooltip lines for %s (bag=%s, slot=%s)",
tostring(itemData.name or itemLink), tostring(bagID), tostring(slotID))
end
-- Detect all properties
local isPermanentEnchant = DetectPermanentEnchant(lines)
local isQuestItem, isQuestStarter = DetectQuestItem(lines, itemData)
local isQuestUsable = DetectQuestUsable(lines)
local isJunk = DetectJunk(lines, itemData)
-- Debug: log junk detection for gray items
if addon.DEBUG then
local quality = tonumber(itemData.quality)
local linkHasGray = itemLink and string.find(itemLink, "|cff9d9d9d")
if quality == 0 or linkHasGray then
addon:Debug("ItemDetection JUNK: %s quality=%s linkGray=%s isJunk=%s",
tostring(itemData.name), tostring(quality), tostring(linkHasGray), tostring(isJunk))
end
end
-- Permanent enchants are NOT quest items (even if categorized as Quest)
if isPermanentEnchant then
isQuestItem = false
isQuestStarter = false
isQuestUsable = false
end
local result = {
isQuestItem = isQuestItem,
isQuestStarter = isQuestStarter,
isQuestUsable = isQuestUsable,
isJunk = isJunk,
isPermanentEnchant = isPermanentEnchant,
}
-- Cache result
if cacheKey then
detectionCache[cacheKey] = result
end
return result
end
-- Convenience functions for single property checks
function ItemDetection:IsQuestItem(itemData, bagID, slotID)
local props = self:GetItemProperties(itemData, bagID, slotID)
return props.isQuestItem, props.isQuestStarter
end
function ItemDetection:IsQuestStarter(itemData, bagID, slotID)
local props = self:GetItemProperties(itemData, bagID, slotID)
return props.isQuestStarter
end
function ItemDetection:IsQuestUsable(itemData, bagID, slotID)
local props = self:GetItemProperties(itemData, bagID, slotID)
return props.isQuestUsable
end
function ItemDetection:IsJunk(itemData, bagID, slotID)
local props = self:GetItemProperties(itemData, bagID, slotID)
return props.isJunk
end
function ItemDetection:IsPermanentEnchant(itemData, bagID, slotID)
local props = self:GetItemProperties(itemData, bagID, slotID)
return props.isPermanentEnchant
end
--=====================================================
-- Initialization
--=====================================================
function ItemDetection:Initialize()
-- Clear cache when entering world (character switch)
addon.Modules.Events:Register("PLAYER_ENTERING_WORLD", function()
self:ClearCache()
end, "ItemDetection")
addon:Debug("ItemDetection module initialized")
end
+15
View File
@@ -15,6 +15,11 @@ function Main:Initialize()
-- Initialize database
addon.Modules.DB:Initialize()
-- Initialize item detection (before scanners, as they may use it)
if addon.Modules.ItemDetection then
addon.Modules.ItemDetection:Initialize()
end
-- Initialize scanners
addon.Modules.BagScanner:Initialize()
addon.Modules.BankScanner:Initialize()
@@ -178,6 +183,12 @@ function Main:SetupSlashCommands()
addon:Print("Tooltip Cache: %d hits, %d misses (%.1f%% hit rate)",
stats.hits, stats.misses, stats.hitRate)
end
-- Show item detection cache stats
if addon.Modules.ItemDetection and addon.Modules.ItemDetection.GetCacheStats then
local stats = addon.Modules.ItemDetection:GetCacheStats()
addon:Print("ItemDetection Cache: %d hits, %d misses (%.1f%% hit rate, %d items)",
stats.hits, stats.misses, stats.hitRate, stats.size)
end
elseif msg == "perfreset" then
-- Reset performance statistics
@@ -193,6 +204,10 @@ function Main:SetupSlashCommands()
if addon.Modules.Utils and addon.Modules.Utils.ClearTooltipCache then
addon.Modules.Utils:ClearTooltipCache()
end
-- Clear item detection cache
if addon.Modules.ItemDetection and addon.Modules.ItemDetection.ClearCache then
addon.Modules.ItemDetection:ClearCache()
end
elseif msg == "help" then
-- Show help
+8
View File
@@ -78,6 +78,14 @@ function BankScanner:GetBankData()
end
end
dirtySlots = {}
-- Check for invalidated bags (nil entries) and rescan them
for _, bagID in ipairs(addon.Constants.BANK_BAGS) do
if bankCache[bagID] == nil then
bankCache[bagID] = self:ScanBankBag(bagID)
end
end
return bankCache
end
+1
View File
@@ -14,6 +14,7 @@ Core\Database.lua
DB\QuestItems.lua
Core\Events.lua
Core\Utils.lua
Core\ItemDetection.lua
Core\CategoryManager.lua
Core\Tooltip.lua
+130 -90
View File
@@ -236,24 +236,45 @@ local function GetItemProperties(bagID, slotID, itemLink)
end
-- Check if an item is a quest item by scanning its tooltip
local function IsQuestItemTooltip(bagID, slotID)
-- Uses centralized ItemDetection when available
local function IsQuestItemTooltip(bagID, slotID, itemData)
if not bagID or not slotID then return false end
-- Use ItemDetection if available
if addon.Modules.ItemDetection and itemData then
local props = addon.Modules.ItemDetection:GetItemProperties(itemData, bagID, slotID)
return props.isQuestItem
end
-- Fallback to local GetItemProperties
local link = GetContainerItemLink(bagID, slotID)
local props = GetItemProperties(bagID, slotID, link)
return props and props.isQuest or false
end
-- Check if a quest item is usable (has 'Use:' or click to text)
local function IsQuestItemUsable(bagID, slotID)
-- Uses centralized ItemDetection when available
local function IsQuestItemUsable(bagID, slotID, itemData)
if not bagID or not slotID then return false end
-- Use ItemDetection if available
if addon.Modules.ItemDetection and itemData then
local props = addon.Modules.ItemDetection:GetItemProperties(itemData, bagID, slotID)
return props.isQuestUsable
end
-- Fallback to local GetItemProperties
local link = GetContainerItemLink(bagID, slotID)
local props = GetItemProperties(bagID, slotID, link)
return props and props.isQuestUsable or false
end
-- Check if item is a quest starter (explicit 'Starts a Quest' or 'This Item Begins a Quest')
local function IsQuestItemStarter(bagID, slotID)
-- Uses centralized ItemDetection when available
local function IsQuestItemStarter(bagID, slotID, itemData)
if not bagID or not slotID then return false end
-- Use ItemDetection if available
if addon.Modules.ItemDetection and itemData then
local props = addon.Modules.ItemDetection:GetItemProperties(itemData, bagID, slotID)
return props.isQuestStarter
end
-- Fallback to local GetItemProperties
local link = GetContainerItemLink(bagID, slotID)
local props = GetItemProperties(bagID, slotID, link)
return props and props.isQuestStarter or false
@@ -635,52 +656,42 @@ local function AddSortKeys(items)
item.isMount = isMount
-- Class and slot ordering
-- Check for items that should be treated as junk:
-- 1. Gray items (quality 0)
-- 2. Items with gray tooltip
-- 3. White equippable items (quality 1 Weapon/Armor) - vendor trash
-- EXCLUDES from junk:
-- - Trinkets, Rings, Necklaces (these typically have special effects)
-- - Profession tools (skinning knife, mining pick, fishing poles, etc.)
-- - Items with yellow description text (Use:, Equip:, Chance on hit: effects)
-- - Items with green description text (set bonuses, special properties)
local isGrayItem = itemRarity == 0 or IsItemGrayTooltip(item.bagID, item.slot, item.data.link)
local isWhiteEquip = false
-- Check for items that should be treated as junk
-- Use centralized ItemDetection when available
local isJunkItem = false
if addon.Modules.ItemDetection then
local props = addon.Modules.ItemDetection:GetItemProperties(item.data, item.bagID, item.slot)
isJunkItem = props.isJunk
else
-- Fallback: manual junk detection
local isGrayItem = itemRarity == 0 or IsItemGrayTooltip(item.bagID, item.slot, item.data.link)
local isWhiteEquip = false
if itemRarity == 1 and (itemCategory == "Weapon" or itemCategory == "Armor") then
-- In Turtle WoW, equip slot info is in itemSubType, not itemEquipLoc
addon:Debug("SortEngine isJunk: name='%s', subclass='%s', class='%s'",
tostring(itemName), tostring(itemSubType), tostring(itemCategory))
if itemRarity == 1 and (itemCategory == "Weapon" or itemCategory == "Armor") then
addon:Debug("SortEngine isJunk: name='%s', subclass='%s', class='%s'",
tostring(itemName), tostring(itemSubType), tostring(itemCategory))
-- EXCLUDE: Trinkets, Rings, Necklaces, Tabards, Shirts - these typically have special effects or are cosmetic
-- Check by itemSubType which contains INVTYPE_* values in Turtle WoW
local isSpecialSlot = (itemSubType == "INVTYPE_TRINKET" or
itemSubType == "INVTYPE_FINGER" or
itemSubType == "INVTYPE_NECK" or
itemSubType == "INVTYPE_TABARD" or
itemSubType == "INVTYPE_BODY")
local isSpecialSlot = (itemSubType == "INVTYPE_TRINKET" or
itemSubType == "INVTYPE_FINGER" or
itemSubType == "INVTYPE_NECK" or
itemSubType == "INVTYPE_TABARD" or
itemSubType == "INVTYPE_BODY")
if isSpecialSlot then
addon:Debug("SortEngine: EXCLUDED (special slot) - %s (subclass=%s)", tostring(itemName), tostring(itemSubType))
end
if not isSpecialSlot then
-- Check exclusions for white equippable items
local isProfTool = IsProfessionTool(item.data.link, itemSubType)
local hasSpecialText = false
if not isProfTool then
hasSpecialText = HasSpecialTooltipText(item.bagID, item.slot, item.data.link)
end
-- Only mark as junk if NOT a profession tool AND NOT has special text
if not isProfTool and not hasSpecialText then
isWhiteEquip = true
if not isSpecialSlot then
local isProfTool = IsProfessionTool(item.data.link, itemSubType)
local hasSpecialText = false
if not isProfTool then
hasSpecialText = HasSpecialTooltipText(item.bagID, item.slot, item.data.link)
end
if not isProfTool and not hasSpecialText then
isWhiteEquip = true
end
end
end
isJunkItem = isGrayItem or isWhiteEquip
end
if isGrayItem or isWhiteEquip then
if isJunkItem then
item.sortedClass = CATEGORY_ORDER["Junk"] or 99
item.equipSlotOrder = 999
item.isEquippable = false -- Treat junk gear as junk, not gear
@@ -704,7 +715,7 @@ local function AddSortKeys(items)
local nameLower = string.lower(item.itemName)
if string.find(nameLower, "manual") or string.find(nameLower, "quest") then
item.sortedClass = CATEGORY_ORDER["Quest"] or 7
elseif IsQuestItemTooltip(item.bagID, item.slot) then
elseif IsQuestItemTooltip(item.bagID, item.slot, item.data) then
item.sortedClass = CATEGORY_ORDER["Quest"] or 7
elseif addon.IsQuestItemByID then
-- Check QuestItemsDB for faction-specific quest items
@@ -739,10 +750,10 @@ local function AddSortKeys(items)
item.isPermanentEnchant = isPermanentEnchant -- Store for potential use in sorting
local nameLower = string.lower(item.itemName)
if not isPermanentEnchant then
if itemCategory == "Quest" or string.find(nameLower, "quest") or item.data.class == "Quest" or IsQuestItemTooltip(item.bagID, item.slot) then
if itemCategory == "Quest" or string.find(nameLower, "quest") or item.data.class == "Quest" or IsQuestItemTooltip(item.bagID, item.slot, item.data) then
item.isQuest = true
if IsQuestItemStarter(item.bagID, item.slot) then item.isQuestStarter = true end
if IsQuestItemUsable(item.bagID, item.slot) then item.isQuestUsable = true end
if IsQuestItemStarter(item.bagID, item.slot, item.data) then item.isQuestStarter = true end
if IsQuestItemUsable(item.bagID, item.slot, item.data) then item.isQuestUsable = true end
elseif addon.IsQuestItemByID then
-- Check QuestItemsDB for faction-specific quest items
local playerFaction = UnitFactionGroup("player")
@@ -1167,57 +1178,47 @@ end
-- Split a list of collected items into non-junk and junk items
-- Junk includes: gray items (quality 0), gray tooltip items, white equippable items (quality 1 Weapon/Armor)
-- EXCLUDES from junk:
-- 1. Trinkets, Rings, Necklaces (these typically have special effects)
-- 2. Profession tools (skinning knife, mining pick, fishing poles, etc.)
-- 3. Items with yellow description text (Use:, Equip:, Chance on hit: effects)
-- 4. Items with green description text (set bonuses, special properties)
-- Uses centralized ItemDetection when available
local function SplitGreyItems(items)
local nonGreys, greys = {}, {}
for _, item in ipairs(items) do
-- Use same logic as AddSortKeys for determining Junk status (stability)
local quality = tonumber(item.quality or 0)
local isGray = quality == 0 or IsItemGrayTooltip(item.bagID, item.slot, item.data.link)
-- White equippable items (Weapon/Armor) are also treated as junk
local itemClass = item.class or ""
local itemSubclass = item.data and item.data.subclass or ""
local itemLink = item.data and item.data.link
local isWhiteEquip = false
local isJunk = false
if quality == 1 and (itemClass == "Weapon" or itemClass == "Armor") then
-- In Turtle WoW, equip slot info is in itemSubType (stored as subclass), not equipLoc
addon:Debug("SplitGreyItems isJunk: name='%s', subclass='%s', class='%s'",
tostring(item.data and item.data.name), tostring(itemSubclass), tostring(itemClass))
-- Use centralized ItemDetection when available
if addon.Modules.ItemDetection and item.data then
local props = addon.Modules.ItemDetection:GetItemProperties(item.data, item.bagID, item.slot)
isJunk = props.isJunk
else
-- Fallback: manual junk detection
local quality = tonumber(item.quality or 0)
local isGray = quality == 0 or IsItemGrayTooltip(item.bagID, item.slot, item.data and item.data.link)
local itemClass = item.class or ""
local itemSubclass = item.data and item.data.subclass or ""
local itemLink = item.data and item.data.link
local isWhiteEquip = false
-- EXCLUDE: Trinkets, Rings, Necklaces, Tabards, Shirts - these typically have special effects or are cosmetic
-- Check by subclass which contains INVTYPE_* values in Turtle WoW
local isSpecialSlot = (itemSubclass == "INVTYPE_TRINKET" or
itemSubclass == "INVTYPE_FINGER" or
itemSubclass == "INVTYPE_NECK" or
itemSubclass == "INVTYPE_TABARD" or
itemSubclass == "INVTYPE_BODY")
if quality == 1 and (itemClass == "Weapon" or itemClass == "Armor") then
local isSpecialSlot = (itemSubclass == "INVTYPE_TRINKET" or
itemSubclass == "INVTYPE_FINGER" or
itemSubclass == "INVTYPE_NECK" or
itemSubclass == "INVTYPE_TABARD" or
itemSubclass == "INVTYPE_BODY")
if isSpecialSlot then
addon:Debug("SplitGreyItems: EXCLUDED (special slot) - %s (subclass='%s')", tostring(item.data and item.data.name), tostring(itemSubclass))
end
if not isSpecialSlot then
-- Check exclusions for white equippable items
local isProfTool = IsProfessionTool(itemLink, itemSubclass)
local hasSpecialText = false
if not isProfTool then
hasSpecialText = HasSpecialTooltipText(item.bagID, item.slot, itemLink)
end
-- Only mark as junk if NOT a profession tool AND NOT has special text
if not isProfTool and not hasSpecialText then
isWhiteEquip = true
if not isSpecialSlot then
local isProfTool = IsProfessionTool(itemLink, itemSubclass)
local hasSpecialText = false
if not isProfTool then
hasSpecialText = HasSpecialTooltipText(item.bagID, item.slot, itemLink)
end
if not isProfTool and not hasSpecialText then
isWhiteEquip = true
end
end
end
isJunk = isGray or isWhiteEquip
end
if isGray or isWhiteEquip then
if isJunk then
table.insert(greys, item)
else
table.insert(nonGreys, item)
@@ -1464,6 +1465,12 @@ function SortEngine:SortBags()
end
end
-- Clear caches after sorting to ensure fresh detection on next UI update
addon.Modules.BagScanner:ClearCache()
if addon.Modules.ItemDetection then
addon.Modules.ItemDetection:ClearCache()
end
-- Return total moves made
return routeCount + consolidateCount + specializedMoves + regularMoves
end
@@ -1595,6 +1602,12 @@ function SortEngine:SortBank()
end
end
-- Clear caches after sorting to ensure fresh detection on next UI update
addon.Modules.BankScanner:ClearCache()
if addon.Modules.ItemDetection then
addon.Modules.ItemDetection:ClearCache()
end
-- Return total moves made
return routeCount + consolidateCount + specializedMoves + regularMoves
end
@@ -1651,7 +1664,7 @@ function SortEngine:ExecuteSort(sortFunction, analyzeFunction, updateFrame, sort
-- Sorting is complete!
addon:DebugSort("%s sort complete! (%d passes, %d total moves)", sortType, passCount, totalMoves)
-- Final update
-- Final update (cache clearing happens after delay)
local frame = CreateFrame("Frame")
local startTime = GetTime()
frame:SetScript("OnUpdate", function()
@@ -1659,6 +1672,15 @@ function SortEngine:ExecuteSort(sortFunction, analyzeFunction, updateFrame, sort
frame:SetScript("OnUpdate", nil)
SortEngine.sortingInProgress = false
SortEngine:UpdateSortButtonState(false)
-- Clear caches after sorting to ensure fresh detection
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)
@@ -1667,7 +1689,7 @@ function SortEngine:ExecuteSort(sortFunction, analyzeFunction, updateFrame, sort
addon:DebugSort("%s sort stopped at safety limit! (%d/%d items still need sorting after %d passes)",
sortType, currentAnalysis.itemsOutOfPlace, currentAnalysis.totalItems, passCount)
-- Final update
-- Final update (cache clearing happens after delay)
local frame = CreateFrame("Frame")
local startTime = GetTime()
frame:SetScript("OnUpdate", function()
@@ -1675,6 +1697,15 @@ function SortEngine:ExecuteSort(sortFunction, analyzeFunction, updateFrame, sort
frame:SetScript("OnUpdate", nil)
SortEngine.sortingInProgress = false
SortEngine:UpdateSortButtonState(false)
-- Clear caches after sorting to ensure fresh detection
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)
@@ -1690,7 +1721,7 @@ function SortEngine:ExecuteSort(sortFunction, analyzeFunction, updateFrame, sort
addon:DebugSort("%s sort stopped due to no progress after %d passes (items remaining: %d/%d)",
sortType, passCount, currentAnalysis.itemsOutOfPlace, currentAnalysis.totalItems)
-- Final update
-- Final update (cache clearing happens after delay)
local frame = CreateFrame("Frame")
local startTime = GetTime()
frame:SetScript("OnUpdate", function()
@@ -1698,6 +1729,15 @@ function SortEngine:ExecuteSort(sortFunction, analyzeFunction, updateFrame, sort
frame:SetScript("OnUpdate", nil)
SortEngine.sortingInProgress = false
SortEngine:UpdateSortButtonState(false)
-- Clear caches after sorting to ensure fresh detection
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)
+17 -4
View File
@@ -1630,13 +1630,22 @@ function BankFrame:Initialize()
if currentViewChar then return end
if event == "PLAYERBANKSLOTS_CHANGED" and arg1 then
-- Mark specific slot as dirty in main bank (bagID = -1)
addon.Modules.BankScanner:MarkSlotDirty(-1, arg1)
-- Invalidate the entire main bank bag to ensure fresh data
-- (MarkSlotDirty was causing timing issues with item data)
addon.Modules.BankScanner:InvalidateBag(-1)
-- Clear ItemDetection cache to ensure fresh detection after item swap
if addon.Modules.ItemDetection then
addon.Modules.ItemDetection:ClearCache()
end
elseif event == "BAG_UPDATE" and arg1 then
-- Check if this is a bank bag (5-10)
if arg1 >= 5 and arg1 <= 10 then
-- Invalidate the specific bank bag (we don't know which slot)
-- Invalidate the specific bank bag
addon.Modules.BankScanner:InvalidateBag(arg1)
-- Clear ItemDetection cache to ensure fresh detection after item swap
if addon.Modules.ItemDetection then
addon.Modules.ItemDetection:ClearCache()
end
else
-- Not a bank bag, ignore for bank frame
return
@@ -1645,9 +1654,13 @@ function BankFrame:Initialize()
-- Bank container slot changed (bag added/removed)
-- Must clear entire cache since bag structure changed
addon.Modules.BankScanner:ClearCache()
if addon.Modules.ItemDetection then
addon.Modules.ItemDetection:ClearCache()
end
end
ScheduleBankFrameUpdate(0.1)
-- Slightly longer delay to ensure WoW API has updated
ScheduleBankFrameUpdate(0.15)
end)
end
+37 -4
View File
@@ -9,8 +9,14 @@ local scanTooltip = CreateFrame("GameTooltip", "Guda_QuestScanTooltip", nil, "Ga
scanTooltip:SetOwner(WorldFrame, "ANCHOR_NONE")
-- Helper function to check if an item is a quest item
-- Delegates to consolidated Utils:IsQuestItem() function
local function IsQuestItem(bagID, slotID, isBank)
-- Uses centralized ItemDetection module
local function IsQuestItem(bagID, slotID, isBank, itemData)
-- Use ItemDetection if available
if addon and addon.Modules and addon.Modules.ItemDetection then
local props = addon.Modules.ItemDetection:GetItemProperties(itemData, bagID, slotID)
return props.isQuestItem, props.isQuestStarter
end
-- Fallback to Utils
if addon and addon.Modules and addon.Modules.Utils and addon.Modules.Utils.IsQuestItem then
return addon.Modules.Utils:IsQuestItem(bagID, slotID, nil, false, isBank)
end
@@ -363,7 +369,7 @@ function Guda_ItemButton_OnLoad(self)
if link and addon and addon.Modules and addon.Modules.Utils then
local itemID = addon.Modules.Utils:ExtractItemID(link)
if itemID then
local isQuest = IsQuestItem(this.bagID, this.slotID, this.isBank)
local isQuest = IsQuestItem(this.bagID, this.slotID, this.isBank, this.itemData)
local isUnique = addon.Modules.Utils:IsUniqueItem(this.bagID, this.slotID, link)
-- Only pin to QuestItemBar if it's a unique quest item
@@ -958,6 +964,33 @@ function Guda_ItemButton_SetItem(self, bagID, slotID, itemData, isBank, otherCha
itemLink = GetContainerItemLink(bagID, slotID)
itemQuality = quality
isLocked = locked
-- Ensure itemData is populated for live items (needed for ItemDetection)
if itemLink then
local itemName, _, itemRarity, itemLevel, itemMinLevel, itemType, itemSubType, itemStackCount, itemEquipLoc, itemTexture = GetItemInfo(itemLink)
if not itemData then
-- Create new itemData
if itemName then
itemData = {
link = itemLink,
name = itemName,
quality = itemRarity or quality or 0,
class = itemType,
subclass = itemSubType,
texture = itemTexture,
count = 1,
}
self.itemData = itemData
end
else
-- Update existing itemData with missing fields
if not itemData.link then itemData.link = itemLink end
if itemData.quality == nil then itemData.quality = itemRarity or quality or 0 end
if not itemData.class and itemType then itemData.class = itemType end
if not itemData.subclass and itemSubType then itemData.subclass = itemSubType end
if not itemData.name and itemName then itemData.name = itemName end
end
end
elseif itemData then
-- Use cached metadata from database
itemQuality = itemData.quality
@@ -1071,7 +1104,7 @@ function Guda_ItemButton_SetItem(self, bagID, slotID, itemData, isBank, otherCha
end
if not self.otherChar and not self.isReadOnly then
local isQuest, isQuestStarter = IsQuestItem(bagID, slotID)
local isQuest, isQuestStarter = IsQuestItem(bagID, slotID, self.isBank, itemData)
-- Update quest icon with the appropriate texture
Guda_ItemButton_UpdateQuestIcon(self, isQuest, isQuestStarter)
if self.questBorder then
+19 -87
View File
@@ -14,103 +14,35 @@ local questItems = {}
local flyoutButtons = {}
local flyoutFrame
-- Create a hidden tooltip for scanning
local scanTooltip
local function GetScanTooltip()
if not scanTooltip then
scanTooltip = CreateFrame("GameTooltip", "Guda_QuestBarScanTooltip", nil, "GameTooltipTemplate")
scanTooltip:SetOwner(WorldFrame, "ANCHOR_NONE")
end
return scanTooltip
end
--=====================================================
-- Quest Item Detection (using centralized ItemDetection)
--=====================================================
-- Combined function to check if an item is a quest item AND usable in ONE tooltip scan
-- This avoids the expensive double-scan that was causing lag
-- Combined function to check if an item is a quest item AND usable
-- Uses centralized ItemDetection module for consistent detection
function QuestItemBar:CheckQuestItemUsable(bagID, slotID)
if not bagID or not slotID then return false, false, false end
local tooltip = GetScanTooltip()
tooltip:ClearLines()
tooltip:SetBagItem(bagID, slotID)
local isQuestItem = false
local isQuestStarter = false
local isUsable = false
local isPermanentEnchant = false -- Track enchanting scrolls/vellums
for i = 1, tooltip:NumLines() do
local line = getglobal("Guda_QuestBarScanTooltipTextLeft" .. i)
if line then
local text = line:GetText()
if text then
local tl = string.lower(text)
-- Check for quest item indicators
if string.find(text, "Quest Starter") or
string.find(text, "This Item Begins a Quest") or
string.find(text, "Use: Starts a Quest") then
isQuestItem = true
isQuestStarter = true
isUsable = true -- Quest starters are always usable
elseif string.find(text, "Quest Item") then
isQuestItem = true
end
-- Check for usability (case-insensitive)
if string.find(tl, "use:") or string.find(tl, "begins a quest") or string.find(tl, "starts a quest") then
isUsable = true
end
-- Check for permanent enchant items (should NOT be quest items)
-- Just check for "permanently" anywhere (green text doesn't have "Use:" prefix)
if string.find(tl, "permanently") then
isPermanentEnchant = true
end
end
end
-- Get item data for ItemDetection
local itemData = nil
if addon.Modules.BagScanner then
itemData = addon.Modules.BagScanner:ScanSlot(bagID, slotID)
end
-- Permanent enchant items are NOT quest items, even if categorized as Quest
if isPermanentEnchant then
return false, false, false
-- Use centralized ItemDetection
if addon.Modules.ItemDetection and itemData then
local props = addon.Modules.ItemDetection:GetItemProperties(itemData, bagID, slotID)
return props.isQuestItem, props.isQuestStarter, props.isQuestUsable
end
-- Fallback check for quest category if not detected from tooltip
local link = GetContainerItemLink(bagID, slotID)
local itemID
local itemCategory, itemType
if link and addon.Modules.Utils and addon.Modules.Utils.ExtractItemID and addon.Modules.Utils.GetItemInfoSafe then
itemID = addon.Modules.Utils:ExtractItemID(link)
if itemID then
_, _, _, _, itemCategory, itemType = addon.Modules.Utils:GetItemInfoSafe(itemID)
end
-- Fallback to Utils if ItemDetection not available
if addon.Modules.Utils and addon.Modules.Utils.IsQuestItem then
local isQuestItem, isQuestStarter = addon.Modules.Utils:IsQuestItem(bagID, slotID, nil, false, false)
-- For usability fallback, check if it's a quest item (assume usable)
return isQuestItem, isQuestStarter, isQuestItem
end
-- If it's a Weapon or Armor, it shouldn't be a QuestItem unless it's specifically categorized as Quest
-- This avoids "Use:" equipment showing up in the quest bar
if itemCategory == "Weapon" or itemCategory == "Armor" or itemType == "Weapon" or itemType == "Armor" then
if itemCategory ~= "Quest" and itemType ~= "Quest" then
isQuestItem = false
isQuestStarter = false
end
end
if not isQuestItem then
if itemCategory == "Quest" or itemType == "Quest" then
isQuestItem = true
end
end
-- Check the QuestItemsDB for known faction-specific quest items
if not isQuestItem then
if itemID and addon.IsQuestItemByID then
local playerFaction = UnitFactionGroup("player")
local isDBQuestItem = addon:IsQuestItemByID(itemID, playerFaction)
if isDBQuestItem then
isQuestItem = true
end
end
end
return isQuestItem, isQuestStarter, isUsable
return false, false, false
end
-- Scan bags for quest items (optimized: single tooltip scan per item)