diff --git a/Core/Constants.lua b/Core/Constants.lua index 8fcf178..6123fd1 100644 --- a/Core/Constants.lua +++ b/Core/Constants.lua @@ -81,6 +81,18 @@ C.PROFESSION_TOOL_IDS = { [20824] = true, -- Simple Grinder } +-- Items classified as "Quest" by GetItemInfo but are actually consumables +-- These get reclassified as "Consumable" for category view and sorting +C.QUEST_CATEGORY_EXCLUSIONS = { + [12450] = true, -- Juju Flurry + [12451] = true, -- Juju Power + [12455] = true, -- Juju Ember + [12457] = true, -- Juju Chill + [12458] = true, -- Juju Guile + [12459] = true, -- Juju Escape + [12460] = true, -- Juju Might +} + -- Weapon subtypes that should NOT be marked as junk C.PROFESSION_TOOL_SUBTYPES = { ["Fishing Pole"] = true, diff --git a/Core/ItemDetection.lua b/Core/ItemDetection.lua index 0da65c3..ef94a8b 100644 --- a/Core/ItemDetection.lua +++ b/Core/ItemDetection.lua @@ -172,6 +172,17 @@ local function DetectQuestItem(lines, itemData) local isQuestItem = false local isQuestStarter = false + -- Early exit: exclude known non-quest items misclassified by the API + if itemData and itemData.link and addon.Constants and addon.Constants.QUEST_CATEGORY_EXCLUSIONS then + local _, _, idStr = string.find(itemData.link, "item:(%d+)") + if idStr then + local id = tonumber(idStr) + if id and addon.Constants.QUEST_CATEGORY_EXCLUSIONS[id] then + return false, false + end + end + end + -- Check item category first if itemData and itemData.class == "Quest" then isQuestItem = true diff --git a/Core/Utils.lua b/Core/Utils.lua index 88d9bff..0717830 100644 --- a/Core/Utils.lua +++ b/Core/Utils.lua @@ -841,6 +841,11 @@ function Utils:IsQuestItem(bagID, slotID, itemData, isOtherChar, isBank) end end + -- Early exit: exclude known non-quest items that the API misclassifies as "Quest" + if itemID and addon.Constants and addon.Constants.QUEST_CATEGORY_EXCLUSIONS and addon.Constants.QUEST_CATEGORY_EXCLUSIONS[itemID] then + return false, false + end + -- Check if item is equipment (should not be classified as quest unless explicitly Quest category) local isEquipment = (itemCategory == "Weapon" or itemCategory == "Armor" or itemType == "Weapon" or itemType == "Armor") diff --git a/Data/BagScanner.lua b/Data/BagScanner.lua index 051229b..eee5d37 100644 --- a/Data/BagScanner.lua +++ b/Data/BagScanner.lua @@ -221,6 +221,14 @@ function BagScanner:ScanSlot(bagID, slot) itemData.iLevel = iLevel itemData.type = itemType itemData.class = itemCategory + -- Override misclassified Quest items (e.g. Juju consumables) to Consumable + if itemCategory == "Quest" and itemLink and addon.Constants and addon.Constants.QUEST_CATEGORY_EXCLUSIONS then + local itemID = addon.Modules.Utils:ExtractItemID(itemLink) + if itemID and addon.Constants.QUEST_CATEGORY_EXCLUSIONS[itemID] then + itemData.class = "Consumable" + itemData.type = "Consumable" + end + end itemData.subclass = itemSubType itemData.equipSlot = itemEquipLoc itemData.locked = locked diff --git a/Sorting/SortEngine.lua b/Sorting/SortEngine.lua index 0915610..7ac2ca4 100644 --- a/Sorting/SortEngine.lua +++ b/Sorting/SortEngine.lua @@ -800,7 +800,12 @@ local function AddSortKeys(items) item.sortedClass = 1 -- All equippable gear gets priority class item.equipSlotOrder = EQUIP_SLOT_ORDER[itemSubType] or 999 else - item.sortedClass = CATEGORY_ORDER[itemCategory] or 99 + -- Override misclassified Quest items (e.g. Juju consumables) to Consumable + if itemCategory == "Quest" and (addon.Constants.QUEST_CATEGORY_EXCLUSIONS and addon.Constants.QUEST_CATEGORY_EXCLUSIONS[itemID]) then + item.sortedClass = CATEGORY_ORDER["Consumable"] or 2 + else + item.sortedClass = CATEGORY_ORDER[itemCategory] or 99 + end -- Check if item is a permanent enchant (should NOT be Quest) local itemProps = GetItemProperties(item.bagID, item.slot, item.data.link) @@ -812,9 +817,10 @@ local function AddSortKeys(items) item.sortedClass = 6 -- Same as Tools, comes before Quest (7) -- Heuristic: Detect items that should be in the Quest category (priority 7) -- but aren't categorized as such by the game (e.g. some "Manual" items) - elseif item.sortedClass ~= (CATEGORY_ORDER["Quest"] or 7) then + -- Skip items with a known non-Quest category (e.g. Consumable, Trade Goods) + elseif item.sortedClass ~= (CATEGORY_ORDER["Quest"] or 7) and not CATEGORY_ORDER[itemCategory] then local nameLower = string.lower(item.itemName) - if string.find(nameLower, "manual") or string.find(nameLower, "quest") then + if string.find(nameLower, "manual") then item.sortedClass = CATEGORY_ORDER["Quest"] or 7 elseif IsQuestItemTooltip(item.bagID, item.slot, item.data) then item.sortedClass = CATEGORY_ORDER["Quest"] or 7 @@ -859,8 +865,8 @@ local function AddSortKeys(items) item.isQuestUsable = false 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, item.data) then + if not isPermanentEnchant and not (addon.Constants.QUEST_CATEGORY_EXCLUSIONS and addon.Constants.QUEST_CATEGORY_EXCLUSIONS[itemID]) then + if itemCategory == "Quest" or item.data.class == "Quest" or IsQuestItemTooltip(item.bagID, item.slot, item.data) then item.isQuest = true 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 diff --git a/UI/ItemButton.lua b/UI/ItemButton.lua index 2d2af4f..55c5003 100644 --- a/UI/ItemButton.lua +++ b/UI/ItemButton.lua @@ -84,6 +84,70 @@ local function IsQuestItem(bagID, slotID, isBank, itemData) return false, false end +--===================================================== +-- Inner Shadow (inset quality glow, GudaBags-inspired) +-- 4 gradient textures along edges colored by item quality +--===================================================== +local INNER_SHADOW_SIZE = 4 +local INNER_SHADOW_ALPHA = 0.5 + +-- Create the 4-edge inner shadow textures on a button, anchored to an icon texture +local function CreateInnerShadow(button, anchorTo) + local shadow = {} + -- Top edge + shadow.top = button:CreateTexture(nil, "ARTWORK", nil, 1) + shadow.top:SetPoint("TOPLEFT", anchorTo, "TOPLEFT", 0, 0) + shadow.top:SetPoint("TOPRIGHT", anchorTo, "TOPRIGHT", 0, 0) + shadow.top:SetHeight(INNER_SHADOW_SIZE) + shadow.top:SetTexture("Interface\\ChatFrame\\ChatFrameBackground") + shadow.top:Hide() + -- Bottom edge + shadow.bottom = button:CreateTexture(nil, "ARTWORK", nil, 1) + shadow.bottom:SetPoint("BOTTOMLEFT", anchorTo, "BOTTOMLEFT", 0, 0) + shadow.bottom:SetPoint("BOTTOMRIGHT", anchorTo, "BOTTOMRIGHT", 0, 0) + shadow.bottom:SetHeight(INNER_SHADOW_SIZE) + shadow.bottom:SetTexture("Interface\\ChatFrame\\ChatFrameBackground") + shadow.bottom:Hide() + -- Left edge + shadow.left = button:CreateTexture(nil, "ARTWORK", nil, 1) + shadow.left:SetPoint("TOPLEFT", anchorTo, "TOPLEFT", 0, 0) + shadow.left:SetPoint("BOTTOMLEFT", anchorTo, "BOTTOMLEFT", 0, 0) + shadow.left:SetWidth(INNER_SHADOW_SIZE) + shadow.left:SetTexture("Interface\\ChatFrame\\ChatFrameBackground") + shadow.left:Hide() + -- Right edge + shadow.right = button:CreateTexture(nil, "ARTWORK", nil, 1) + shadow.right:SetPoint("TOPRIGHT", anchorTo, "TOPRIGHT", 0, 0) + shadow.right:SetPoint("BOTTOMRIGHT", anchorTo, "BOTTOMRIGHT", 0, 0) + shadow.right:SetWidth(INNER_SHADOW_SIZE) + shadow.right:SetTexture("Interface\\ChatFrame\\ChatFrameBackground") + shadow.right:Hide() + return shadow +end + +-- Show inner shadow with a given quality color +local function ShowInnerShadow(shadow, r, g, b) + if not shadow then return end + local a = INNER_SHADOW_ALPHA + shadow.top:SetGradientAlpha("VERTICAL", r, g, b, 0, r, g, b, a) + shadow.top:Show() + shadow.bottom:SetGradientAlpha("VERTICAL", r, g, b, a, r, g, b, 0) + shadow.bottom:Show() + shadow.left:SetGradientAlpha("HORIZONTAL", r, g, b, a, r, g, b, 0) + shadow.left:Show() + shadow.right:SetGradientAlpha("HORIZONTAL", r, g, b, 0, r, g, b, a) + shadow.right:Show() +end + +-- Hide inner shadow +local function HideInnerShadow(shadow) + if not shadow then return end + shadow.top:Hide() + shadow.bottom:Hide() + shadow.left:Hide() + shadow.right:Hide() +end + --===================================================== -- Junk Icon Pool (Baganator-inspired memory optimization) -- Uses frame pooling to avoid creating new frames per button @@ -400,6 +464,14 @@ function Guda_ItemButton_OnLoad(self) self.qualityBorder = backdrop end + -- Create inner shadow for quality color glow (anchored to icon texture) + if not self.innerShadow then + local iconTex = getglobal(self:GetName() .. "IconTexture") + if iconTex then + self.innerShadow = CreateInnerShadow(self, iconTex) + end + end + -- Create quest item border (golden, higher priority than quality border) if not self.questBorder then local questBackdrop = CreateFrame("Frame", nil, self) @@ -564,6 +636,7 @@ local function ResetButtonVisualState(self) if self.questBorder then self.questBorder:Hide() end if self.questIcon then self.questIcon:Hide() end if self.qualityBorder then self.qualityBorder:Hide() end + HideInnerShadow(self.innerShadow) if self.unusableOverlay then self.unusableOverlay:Hide() end HideJunkIcon(self) @@ -763,11 +836,13 @@ local function UpdateQualityBorder(self, itemQuality, itemLink, bagID, Utils) -- Special border for keyring items (cyan/blue) self.qualityBorder:SetBackdropBorderColor(0.2, 0.8, 1.0, 1) self.qualityBorder:Show() + ShowInnerShadow(self.innerShadow, 0.2, 0.8, 1.0) return end if not itemQuality then self.qualityBorder:Hide() + HideInnerShadow(self.innerShadow) return end @@ -796,8 +871,10 @@ local function UpdateQualityBorder(self, itemQuality, itemLink, bagID, Utils) end self.qualityBorder:SetBackdropBorderColor(r, g, b, 1) self.qualityBorder:Show() + ShowInnerShadow(self.innerShadow, r, g, b) else self.qualityBorder:Hide() + HideInnerShadow(self.innerShadow) end end @@ -852,8 +929,10 @@ local function ClearItemButton(self, emptySlotBg, countText, bagID) if bagID == -2 then self.qualityBorder:SetBackdropBorderColor(0.2, 0.8, 1.0, 0.5) self.qualityBorder:Show() + ShowInnerShadow(self.innerShadow, 0.2, 0.8, 1.0) else self.qualityBorder:Hide() + HideInnerShadow(self.innerShadow) end end @@ -1176,12 +1255,13 @@ function Guda_ItemButton_SetItem(self, bagID, slotID, itemData, isBank, otherCha countText:Hide() end - -- Set quality border + -- Set quality border and inner shadow if self.qualityBorder then if bagID == -2 then -- Special border for keyring items (cyan/blue) self.qualityBorder:SetBackdropBorderColor(0.2, 0.8, 1.0, 1) self.qualityBorder:Show() + ShowInnerShadow(self.innerShadow, 0.2, 0.8, 1.0) elseif itemQuality then -- Check settings to determine if we should show borders local showEquipmentBorder, showOtherBorder @@ -1215,11 +1295,14 @@ function Guda_ItemButton_SetItem(self, bagID, slotID, itemData, isBank, otherCha end self.qualityBorder:SetBackdropBorderColor(r, g, b, 1) self.qualityBorder:Show() + ShowInnerShadow(self.innerShadow, r, g, b) else self.qualityBorder:Hide() + HideInnerShadow(self.innerShadow) end else self.qualityBorder:Hide() + HideInnerShadow(self.innerShadow) end end @@ -1283,8 +1366,10 @@ function Guda_ItemButton_SetItem(self, bagID, slotID, itemData, isBank, otherCha if bagID == -2 then self.qualityBorder:SetBackdropBorderColor(0.2, 0.8, 1.0, 0.5) -- Dimmer cyan for empty slots self.qualityBorder:Show() + ShowInnerShadow(self.innerShadow, 0.2, 0.8, 1.0) else self.qualityBorder:Hide() + HideInnerShadow(self.innerShadow) end end @@ -1490,6 +1575,33 @@ function Guda_ItemButton_OnEnter(self) GameTooltip:Show() + -- Debug: print item classification info to chat when debug mode is active + if addon.DEBUG and self.hasItem and self.bagID and self.slotID and not self._debugPrinted then + local link = self.itemData and self.itemData.link or GetContainerItemLink(self.bagID, self.slotID) + if link then + local itemID = addon.Modules.Utils:ExtractItemID(link) + if itemID then + local itemName, _, itemRarity, itemLevel, itemCategory, itemType, _, itemSubType = GetItemInfo(itemID) + addon:Debug("Item: %s (ID: %s)", tostring(itemName), tostring(itemID)) + addon:Debug(" Category: %s | Type: %s | SubType: %s", tostring(itemCategory), tostring(itemType), tostring(itemSubType)) + addon:Debug(" Quality: %s | iLvl: %s", tostring(itemRarity), tostring(itemLevel)) + if addon.Modules.ItemDetection then + local props = addon.Modules.ItemDetection:GetItemProperties({link = link}, self.bagID, self.slotID) + local flags = {} + if props.isQuestItem then table.insert(flags, "Quest") end + if props.isQuestStarter then table.insert(flags, "Starter") end + if props.isQuestUsable then table.insert(flags, "Usable") end + if props.isJunk then table.insert(flags, "Junk") end + if props.isPermanentEnchant then table.insert(flags, "Enchant") end + if props.isUnusable then table.insert(flags, "Unusable") end + local flagStr = table.getn(flags) > 0 and table.concat(flags, ", ") or "none" + addon:Debug(" Flags: %s", flagStr) + end + self._debugPrinted = true + end + end + end + -- Handle merchant sell cursor (same approach as BagShui) if MerchantFrame:IsShown() and not self.isBank and not self.otherChar and self.hasItem then ShowContainerSellCursor(self.bagID, self.slotID) @@ -1501,6 +1613,7 @@ end -- OnLeave handler function Guda_ItemButton_OnLeave(self) + self._debugPrinted = nil -- Clear any viewed character hint on the tooltip when leaving if GameTooltip then GameTooltip.GudaViewedCharacter = nil diff --git a/UI/TrackedItemBar.lua b/UI/TrackedItemBar.lua index 74c257e..cc0c901 100644 --- a/UI/TrackedItemBar.lua +++ b/UI/TrackedItemBar.lua @@ -30,7 +30,6 @@ function TrackedItemBar:ScanForTrackedItems() local itemOrder = {} local itemIsQuest = {} local itemIsQuestStarter = {} - local itemQualities = {} -- Scan backpack and 4 bags for bagID = 0, 4 do @@ -51,9 +50,6 @@ function TrackedItemBar:ScanForTrackedItems() local isQuest, isStarter = IsQuestItem(bagID, slotID) itemIsQuest[id] = isQuest itemIsQuestStarter[id] = isStarter - -- Get item quality - local _, _, itemQuality = GetItemInfo(id) - itemQualities[id] = tonumber(itemQuality) table.insert(itemOrder, id) end itemCounts[id] = itemCounts[id] + count @@ -88,7 +84,6 @@ function TrackedItemBar:ScanForTrackedItems() isQuestStarter = itemIsQuestStarter[id], isUnusable = isUnusable, isJunk = isJunk, - quality = itemQualities[id], }) end end @@ -112,7 +107,6 @@ function TrackedItemBar:Update() -- Hide all buttons and their overlays initially for _, btn in ipairs(buttons) do btn:Hide() - if btn.qualityBorder then btn.qualityBorder:Hide() end if btn.unusableOverlay then btn.unusableOverlay:Hide() end if btn.junkIcon then btn.junkIcon:Hide() end end @@ -136,19 +130,6 @@ function TrackedItemBar:Update() questBorder:Hide() button.questBorder = questBorder - -- Create quality border (colored by rarity) - local qualityBorder = CreateFrame("Frame", nil, button) - qualityBorder:SetFrameLevel(button:GetFrameLevel() + 5) - qualityBorder:SetBackdrop({ - bgFile = nil, - edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border", - edgeSize = 12, - insets = {left = 4, right = 4, top = 4, bottom = 4} - }) - qualityBorder:SetBackdropBorderColor(0, 0, 0, 0) - qualityBorder:Hide() - button.qualityBorder = qualityBorder - -- Create quest icon (question mark in corner) local questIcon = CreateFrame("Frame", nil, button) questIcon:SetFrameLevel(button:GetFrameLevel() + 7) @@ -258,23 +239,6 @@ function TrackedItemBar:Update() emptyBg:SetHeight(buttonSize) end - -- Position and show/hide quality border - if button.qualityBorder then - button.qualityBorder:ClearAllPoints() - button.qualityBorder:SetPoint("TOPLEFT", icon, "TOPLEFT", -5, 5) - button.qualityBorder:SetPoint("BOTTOMRIGHT", icon, "BOTTOMRIGHT", 5, -5) - if info.quality and info.quality >= 1 then - local r, g, b = 1, 1, 1 - if addon.Modules.Utils and addon.Modules.Utils.GetQualityColor then - r, g, b = addon.Modules.Utils:GetQualityColor(info.quality) - end - button.qualityBorder:SetBackdropBorderColor(r, g, b, 1) - button.qualityBorder:Show() - else - button.qualityBorder:Hide() - end - end - -- Position and show/hide quest border if button.questBorder then button.questBorder:ClearAllPoints()