From e83fb53daeb6c96c64b764afc21e1326c6415736 Mon Sep 17 00:00:00 2001 From: Salikh Gurgenidze Date: Sat, 22 Nov 2025 02:20:17 +0400 Subject: [PATCH] feat: unusable item status indicator --- Core/Tooltip.lua | 7 ++- Core/Utils.lua | 108 ++++++++++++++++++++++++++++++++++++++++++++++ UI/ItemButton.lua | 66 ++++++++++++++++++++++++++++ 3 files changed, 180 insertions(+), 1 deletion(-) diff --git a/Core/Tooltip.lua b/Core/Tooltip.lua index 03b3db0..0107a5c 100644 --- a/Core/Tooltip.lua +++ b/Core/Tooltip.lua @@ -164,6 +164,9 @@ local function GetClassColor(classToken) return 1.0, 1.0, 1.0 end +-- Add armor usability info (red type indicator when current character can't equip) +-- Removed: Usability info line in tooltip per user request + function Tooltip:AddInventoryInfo(tooltip, link) -- Check if database is properly initialized and has the expected structure if not Guda_DB or type(Guda_DB) ~= "table" then @@ -217,7 +220,9 @@ function Tooltip:AddInventoryInfo(tooltip, link) local totalCount = totalBags + totalBank + totalEquipped - if hasAnyItems then + -- Usability indicator removed per user request + + if hasAnyItems then -- Top padding above the Inventory block (~10-12px visually) tooltip:AddLine(" ") diff --git a/Core/Utils.lua b/Core/Utils.lua index 59d535b..19a086e 100644 --- a/Core/Utils.lua +++ b/Core/Utils.lua @@ -433,4 +433,112 @@ function Utils:IsEquipment(itemLink) end return false +end + +-- Determine if an armor item is usable by the current player (class/level restrictions) +-- Returns two values: +-- isArmor (boolean): true if the item is in the Armor category +-- canUse (boolean): whether the current character can equip this armor type right now +function Utils:IsArmorUsableByPlayer(itemLink) + if not itemLink then return false, false end + + local itemID = self:ExtractItemID(itemLink) + if not itemID then return false, false end + + local itemName, _, itemRarity, itemLevel, itemCategory, itemType, itemStackCount, itemSubType = self:GetItemInfoSafe(itemID) + if not itemCategory or string.lower(itemCategory) ~= "armor" then + return false, false + end + + -- Normalize subtype (e.g., "Cloth", "Leather", "Mail", "Plate", "Shield") + local subtype = itemSubType or "" + + -- Player info + local _, class = UnitClass("player") + local level = UnitLevel and UnitLevel("player") or 1 + + -- Rules based on Vanilla/TurtleWoW standards + -- Before level 40, some classes only use lighter armor; after 40, upgrades apply + local function isAllowedArmorForClass() + if class == "MAGE" or class == "PRIEST" or class == "WARLOCK" then + return subtype == "Cloth" + elseif class == "ROGUE" or class == "DRUID" then + return subtype == "Leather" + elseif class == "HUNTER" or class == "SHAMAN" then + if level >= 40 then + return subtype == "Mail" + else + return subtype == "Leather" + end + elseif class == "PALADIN" or class == "WARRIOR" then + if level >= 40 then + return subtype == "Plate" + else + return subtype == "Mail" + end + end + return false + end + + -- Shields usability + local function isAllowedShield() + if subtype ~= "Shield" then return false end + return class == "WARRIOR" or class == "PALADIN" or class == "SHAMAN" + end + + local canUse = isAllowedArmorForClass() or isAllowedShield() + return true, canUse +end + +-- Determine if an armor item is permanently unusable by the player's class +-- Ignores temporary level-based proficiency (e.g., Hunter <40 with Mail is considered usable eventually) +-- Returns two values: +-- isArmor (boolean): true if the item is in the Armor category +-- isPermanentlyUnusable (boolean): true if the player's class can never equip this armor subtype at any level +function Utils:IsArmorPermanentlyUnusableByClass(itemLink) + if not itemLink then return false, false end + + local itemID = self:ExtractItemID(itemLink) + if not itemID then return false, false end + + local _, _, _, _, itemCategory, _, _, itemSubType = self:GetItemInfoSafe(itemID) + if not itemCategory or string.lower(itemCategory) ~= "armor" then + return false, false + end + + local subtype = itemSubType or "" + + -- Class info + local _, class = UnitClass("player") + + -- Special case: Shields + if subtype == "Shield" then + local shieldAllowed = (class == "WARRIOR" or class == "PALADIN" or class == "SHAMAN") + return true, not shieldAllowed + end + + -- Armor hierarchy + local rank = { Cloth = 1, Leather = 2, Mail = 3, Plate = 4 } + local itemRank = rank[subtype] + if not itemRank then + -- Unknown subtype within Armor + return true, true + end + + -- Determine maximum armor rank per class (eventual, ignoring current level) + local maxRankByClass = { + MAGE = 1, PRIEST = 1, WARLOCK = 1, + ROGUE = 2, DRUID = 2, + HUNTER = 3, SHAMAN = 3, + PALADIN = 4, WARRIOR = 4, + } + + local maxRank = maxRankByClass[class] + if not maxRank then + -- Unknown class: be safe and mark as unusable + return true, true + end + + local permanentlyUnusable = itemRank > maxRank + return true, permanentlyUnusable end \ No newline at end of file diff --git a/UI/ItemButton.lua b/UI/ItemButton.lua index 3229455..4acf9f3 100644 --- a/UI/ItemButton.lua +++ b/UI/ItemButton.lua @@ -109,6 +109,15 @@ function Guda_ItemButton_OnLoad(self) self.questIcon = iconFrame end + -- Create unusable overlay (semi-transparent red fill over the icon) + if not self.unusableOverlay then + local overlay = self:CreateTexture(nil, "OVERLAY") + overlay:SetTexture("Interface\\Buttons\\WHITE8X8") + overlay:SetVertexColor(1, 0, 0, 0.35) -- semi-transparent red + overlay:Hide() + self.unusableOverlay = overlay + end + -- Ensure the item button sits above its container backdrop and is mouse-enabled local parent = self:GetParent() if parent and parent.GetFrameLevel then @@ -449,6 +458,24 @@ function Guda_ItemButton_SetItem(self, bagID, slotID, itemData, isBank, otherCha elseif Guda and Guda.Modules and Guda.Modules.Utils and Guda.Modules.Utils.GetQualityColor then r, g, b = Guda.Modules.Utils:GetQualityColor(itemQuality) end + + -- If the item is armor and is permanently unusable by class, force a red border/overlay + -- Only applies to current character's live/readwrite view (not other characters or read-only) + local isArmorUnusable = false + if not self.otherChar and not self.isReadOnly then + local isArmor, permanentlyUnusable + if addon and addon.Modules and addon.Modules.Utils and addon.Modules.Utils.IsArmorPermanentlyUnusableByClass then + isArmor, permanentlyUnusable = addon.Modules.Utils:IsArmorPermanentlyUnusableByClass(itemLink) + elseif Guda and Guda.Modules and Guda.Modules.Utils and Guda.Modules.Utils.IsArmorPermanentlyUnusableByClass then + isArmor, permanentlyUnusable = Guda.Modules.Utils:IsArmorPermanentlyUnusableByClass(itemLink) + end + isArmorUnusable = (isArmor and permanentlyUnusable == true) + end + + if isArmorUnusable then + r, g, b = 1, 0, 0 + end + self.qualityBorder:SetBackdropBorderColor(r, g, b, 1) self.qualityBorder:Show() else @@ -479,12 +506,51 @@ function Guda_ItemButton_SetItem(self, bagID, slotID, itemData, isBank, otherCha end end + -- Show or hide unusable overlay, and keep it aligned to icon + do + local showOverlay = false + if not self.otherChar and not self.isReadOnly and itemLink then + local isArmor, permanentlyUnusable + if addon and addon.Modules and addon.Modules.Utils and addon.Modules.Utils.IsArmorPermanentlyUnusableByClass then + isArmor, permanentlyUnusable = addon.Modules.Utils:IsArmorPermanentlyUnusableByClass(itemLink) + elseif Guda and Guda.Modules and Guda.Modules.Utils and Guda.Modules.Utils.IsArmorPermanentlyUnusableByClass then + isArmor, permanentlyUnusable = Guda.Modules.Utils:IsArmorPermanentlyUnusableByClass(itemLink) + end + showOverlay = (isArmor and permanentlyUnusable == true) + end + + if self.unusableOverlay then + local iconTexture = getglobal(self:GetName().."IconTexture") or self.icon or self.IconTexture + if iconTexture and iconTexture.IsShown and iconTexture:IsShown() then + self.unusableOverlay:ClearAllPoints() + self.unusableOverlay:SetPoint("TOPLEFT", iconTexture, "TOPLEFT", 0, 0) + self.unusableOverlay:SetPoint("BOTTOMRIGHT", iconTexture, "BOTTOMRIGHT", 0, 0) + else + -- Fallback to entire button + self.unusableOverlay:ClearAllPoints() + self.unusableOverlay:SetPoint("TOPLEFT", self, "TOPLEFT", 5, -5) + self.unusableOverlay:SetPoint("BOTTOMRIGHT", self, "BOTTOMRIGHT", -5, 5) + end + + if showOverlay then + self.unusableOverlay:Show() + else + self.unusableOverlay:Hide() + end + end + end + self:Show() else self.hasItem = false -- For empty slots, clear the icon texture SetItemButtonTexture(self, nil) + -- Ensure unusable overlay is hidden when no item + if self.unusableOverlay then + self.unusableOverlay:Hide() + end + -- Hide NormalTexture for empty slots (we use EmptySlotBg instead) self:SetNormalTexture("") local normalBorder = getglobal(self:GetName().."NormalTexture")