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