371 lines
12 KiB
Lua
371 lines
12 KiB
Lua
-- 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
|
|
-- Uses shared tooltip from Utils module
|
|
--=====================================================
|
|
|
|
-- Scan tooltip and return all text lines
|
|
local function ScanTooltipLines(bagID, slotID, itemLink)
|
|
-- Use shared tooltip from Utils module
|
|
local tooltip, tooltipName = addon.Modules.Utils:GetScanTooltip()
|
|
|
|
-- 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
|