diff --git a/modules/eqcompare.lua b/modules/eqcompare.lua index b4c59a9c..f5370c82 100644 --- a/modules/eqcompare.lua +++ b/modules/eqcompare.lua @@ -74,88 +74,106 @@ pfUI:RegisterModule("eqcompare", function () [28] = { "RangedSlot" }, -- relic } - -- Base stats are annotated INLINE on their tooltip stat line. We match - -- by label (the trailing noun after the "N " value prefix), so we need - -- the localized noun for each ClassicAPI key. Primary stats come from - -- vanilla's "%c%d Stat" format strings (with the noun stripped out); - -- resistances are already plain nouns. - local BASE_STAT_LABEL_TO_KEY = {} + local BASE_STAT_KEYS = { + "ITEM_MOD_DAMAGE_PER_SECOND_SHORT", "ITEM_MOD_BLOCK_VALUE", + "ITEM_MOD_STRENGTH_SHORT", "ITEM_MOD_AGILITY_SHORT", + "ITEM_MOD_STAMINA_SHORT", "ITEM_MOD_INTELLECT_SHORT", + "ITEM_MOD_SPIRIT_SHORT", "ITEM_MOD_MANA_SHORT", + "ITEM_MOD_HEALTH_SHORT", + "RESISTANCE0_NAME", "RESISTANCE1_NAME", "RESISTANCE2_NAME", + "RESISTANCE3_NAME", "RESISTANCE4_NAME", "RESISTANCE5_NAME", + "RESISTANCE6_NAME", + } + local EXTENDED_STAT_KEYS = { + "ITEM_MOD_ATTACK_POWER_SHORT", + "ITEM_MOD_RANGED_ATTACK_POWER_SHORT", + "ITEM_MOD_SPELL_DAMAGE_DONE_SHORT", + "ITEM_MOD_SPELL_HEALING_DONE_SHORT", + "ITEM_MOD_CRIT_SPELL_RATING", + "ITEM_MOD_CRIT_MELEE_RATING", "ITEM_MOD_CRIT_RANGED_RATING", + "ITEM_MOD_HIT_MELEE_RATING", "ITEM_MOD_HIT_RANGED_RATING", + "ITEM_MOD_HIT_SPELL_RATING", + "ITEM_MOD_MANA_REGENERATION", "ITEM_MOD_DEFENSE_SKILL_RATING", + "ITEM_MOD_DODGE_RATING", "ITEM_MOD_PARRY_RATING", + "ITEM_MOD_BLOCK_RATING", + } + local STAT_LABELS = {} + local BASE_STAT_LABEL_TO_KEY = {} -- reverse map for inline base-stat match do local function noun(fmt) return fmt and (string.gsub(string.gsub(fmt, "^%%c?%%d ", ""), "^%s+", "")) or nil end local baseFmt = { - { key = "ITEM_MOD_STRENGTH_SHORT", fmt = "ITEM_MOD_STRENGTH" }, - { key = "ITEM_MOD_AGILITY_SHORT", fmt = "ITEM_MOD_AGILITY" }, - { key = "ITEM_MOD_STAMINA_SHORT", fmt = "ITEM_MOD_STAMINA" }, - { key = "ITEM_MOD_INTELLECT_SHORT", fmt = "ITEM_MOD_INTELLECT" }, - { key = "ITEM_MOD_SPIRIT_SHORT", fmt = "ITEM_MOD_SPIRIT" }, - { key = "ITEM_MOD_MANA_SHORT", fmt = "ITEM_MOD_MANA" }, - { key = "ITEM_MOD_HEALTH_SHORT", fmt = "ITEM_MOD_HEALTH" }, + ITEM_MOD_STRENGTH_SHORT = "ITEM_MOD_STRENGTH", + ITEM_MOD_AGILITY_SHORT = "ITEM_MOD_AGILITY", + ITEM_MOD_STAMINA_SHORT = "ITEM_MOD_STAMINA", + ITEM_MOD_INTELLECT_SHORT = "ITEM_MOD_INTELLECT", + ITEM_MOD_SPIRIT_SHORT = "ITEM_MOD_SPIRIT", + ITEM_MOD_MANA_SHORT = "ITEM_MOD_MANA", + ITEM_MOD_HEALTH_SHORT = "ITEM_MOD_HEALTH", } - for _, e in ipairs(baseFmt) do - local label = _G[e.key] or noun(_G[e.fmt]) - if label then BASE_STAT_LABEL_TO_KEY[label] = e.key end + for shortKey, fmtKey in pairs(baseFmt) do + STAT_LABELS[shortKey] = _G[shortKey] or noun(_G[fmtKey]) or shortKey end for i = 0, 6 do - local label = _G["RESISTANCE"..i.."_NAME"] - if label then BASE_STAT_LABEL_TO_KEY[label] = "RESISTANCE"..i.."_NAME" end + STAT_LABELS["RESISTANCE"..i.."_NAME"] = _G["RESISTANCE"..i.."_NAME"] + end + STAT_LABELS.ITEM_MOD_ATTACK_POWER_SHORT = _G.ITEM_MOD_ATTACK_POWER_SHORT or _G.ATTACK_POWER_TOOLTIP or "Attack Power" + STAT_LABELS.ITEM_MOD_RANGED_ATTACK_POWER_SHORT = _G.ITEM_MOD_RANGED_ATTACK_POWER_SHORT or _G.RANGED_ATTACK_POWER or "Ranged Attack Power" + STAT_LABELS.ITEM_MOD_SPELL_DAMAGE_DONE_SHORT = _G.ITEM_MOD_SPELL_DAMAGE_DONE_SHORT or "Spell Damage" + STAT_LABELS.ITEM_MOD_SPELL_HEALING_DONE_SHORT = _G.ITEM_MOD_SPELL_HEALING_DONE_SHORT or "Spell Healing" + STAT_LABELS.ITEM_MOD_CRIT_MELEE_RATING = _G.ITEM_MOD_CRIT_MELEE_RATING or "Melee Crit %" + STAT_LABELS.ITEM_MOD_CRIT_RANGED_RATING = _G.ITEM_MOD_CRIT_RANGED_RATING or "Ranged Crit %" + STAT_LABELS.ITEM_MOD_HIT_MELEE_RATING = _G.ITEM_MOD_HIT_MELEE_RATING or "Melee Hit %" + STAT_LABELS.ITEM_MOD_HIT_RANGED_RATING = _G.ITEM_MOD_HIT_RANGED_RATING or "Ranged Hit %" + STAT_LABELS.ITEM_MOD_HIT_SPELL_RATING = _G.ITEM_MOD_HIT_SPELL_RATING or "Spell Hit %" + STAT_LABELS.ITEM_MOD_MANA_REGENERATION = _G.ITEM_MOD_MANA_REGENERATION or "Mana Regen" + STAT_LABELS.ITEM_MOD_DEFENSE_SKILL_RATING = _G.ITEM_MOD_DEFENSE_SKILL_RATING or "Defense" + STAT_LABELS.ITEM_MOD_DAMAGE_PER_SECOND_SHORT = _G.ITEM_MOD_DAMAGE_PER_SECOND_SHORT or "DPS" + STAT_LABELS.ITEM_MOD_CRIT_SPELL_RATING = _G.ITEM_MOD_CRIT_SPELL_RATING or "Spell Crit %" + STAT_LABELS.ITEM_MOD_DODGE_RATING = _G.ITEM_MOD_DODGE_RATING or "Dodge Rating" + STAT_LABELS.ITEM_MOD_PARRY_RATING = _G.ITEM_MOD_PARRY_RATING or "Parry Rating" + STAT_LABELS.ITEM_MOD_BLOCK_RATING = _G.ITEM_MOD_BLOCK_RATING or "Block Rating" + STAT_LABELS.ITEM_MOD_BLOCK_VALUE = _G.ITEM_MOD_BLOCK_VALUE or "Block Value" + + -- Reverse map for base-stat inline matching (label → key) + for _, key in ipairs(BASE_STAT_KEYS) do + local label = STAT_LABELS[key] + if label then BASE_STAT_LABEL_TO_KEY[label] = key end + end + if _G.BLOCK then + BASE_STAT_LABEL_TO_KEY[_G.BLOCK] = "ITEM_MOD_BLOCK_VALUE" + end + if _G.DPS_TEMPLATE then + local n = string.gsub(_G.DPS_TEMPLATE, "%%.-f%s*", "") + n = string.gsub(n, "^[%s%(]+", "") + n = string.gsub(n, "[%s%)]+$", "") + if n ~= "" then BASE_STAT_LABEL_TO_KEY[n] = "ITEM_MOD_DAMAGE_PER_SECOND_SHORT" end end end - -- Extended stats (attack power, crit/hit ratings, spell power, defense, - -- DPS, mana regen) aren't emitted as their own tooltip lines by vanilla - -- in a shape we can reliably match — they're mixed into equip-spell - -- descriptions ("Equip: Increases your critical strike chance by 1%"). - -- Aggregate them into a Blizzard-style summary block at the bottom. - local EXTENDED_STAT_LABELS = { - ITEM_MOD_ATTACK_POWER_SHORT = _G.ITEM_MOD_ATTACK_POWER_SHORT or _G.ATTACK_POWER_TOOLTIP or "Attack Power", - ITEM_MOD_RANGED_ATTACK_POWER_SHORT = _G.ITEM_MOD_RANGED_ATTACK_POWER_SHORT or _G.RANGED_ATTACK_POWER or "Ranged Attack Power", - ITEM_MOD_SPELL_DAMAGE_DONE_SHORT = _G.ITEM_MOD_SPELL_DAMAGE_DONE_SHORT or "Spell Damage", - ITEM_MOD_SPELL_HEALING_DONE_SHORT = _G.ITEM_MOD_SPELL_HEALING_DONE_SHORT or "Spell Healing", - ITEM_MOD_CRIT_MELEE_RATING = _G.ITEM_MOD_CRIT_MELEE_RATING or "Melee Crit %", - ITEM_MOD_CRIT_RANGED_RATING = _G.ITEM_MOD_CRIT_RANGED_RATING or "Ranged Crit %", - ITEM_MOD_HIT_MELEE_RATING = _G.ITEM_MOD_HIT_MELEE_RATING or "Melee Hit %", - ITEM_MOD_HIT_RANGED_RATING = _G.ITEM_MOD_HIT_RANGED_RATING or "Ranged Hit %", - ITEM_MOD_HIT_SPELL_RATING = _G.ITEM_MOD_HIT_SPELL_RATING or "Spell Hit %", - ITEM_MOD_MANA_REGENERATION = _G.ITEM_MOD_MANA_REGENERATION or "Mana Regen", - ITEM_MOD_DEFENSE_SKILL_RATING = _G.ITEM_MOD_DEFENSE_SKILL_RATING or "Defense", - ITEM_MOD_DAMAGE_PER_SECOND_SHORT = _G.ITEM_MOD_DAMAGE_PER_SECOND_SHORT or "DPS", - } - local EXTENDED_STAT_ORDER = { - "ITEM_MOD_DAMAGE_PER_SECOND_SHORT", - "ITEM_MOD_ATTACK_POWER_SHORT", - "ITEM_MOD_RANGED_ATTACK_POWER_SHORT", - "ITEM_MOD_SPELL_DAMAGE_DONE_SHORT", - "ITEM_MOD_SPELL_HEALING_DONE_SHORT", - "ITEM_MOD_CRIT_MELEE_RATING", - "ITEM_MOD_CRIT_RANGED_RATING", - "ITEM_MOD_HIT_MELEE_RATING", - "ITEM_MOD_HIT_RANGED_RATING", - "ITEM_MOD_HIT_SPELL_RATING", - "ITEM_MOD_MANA_REGENERATION", - "ITEM_MOD_DEFENSE_SKILL_RATING", - } - - local function AnnotateBaseStats(tooltip, delta) + local function AnnotateBaseStatsInline(tooltip, delta) for _, region in ipairs({tooltip:GetRegions()}) do if region and region.GetObjectType and region:GetObjectType() == "FontString" then local text = region:GetText() if text and text ~= "" and not strfind(text, "|cff88ff88", 1, true) and not strfind(text, "|cffff8888", 1, true) then - -- Widget text shape we care about: "+5 Strength" / "5 Fire Resistance". - -- Match the "+N " / "N " prefix, then look up the rest verbatim. - local _, endIdx = strfind(text, "^%+?%d+%s+") + -- Match "value noun" tooltip lines. Base stats look like + -- "+5 Strength" / "5 Fire Resistance" / "20 Block"; DPS looks + -- like "(84.5 damage per second)". Same shape once we accept + -- an optional leading "(", an optional "+", and decimals. + local _, endIdx = strfind(text, "^%(?%+?[%d%.]+%s+") if endIdx then local rest = string.sub(text, endIdx + 1) - rest = gsub(rest, "%s+$", "") + rest = gsub(rest, "[%s%)]+$", "") local key = BASE_STAT_LABEL_TO_KEY[rest] local v = key and delta[key] if v and v ~= 0 then - local color = v > 0 and "|cff88ff88" or "|cffff8888" - local sign = v > 0 and "+" or "" - region:SetText(text .. " " .. color .. "(" .. sign .. v .. ")|r") + local shown = (key == "ITEM_MOD_DAMAGE_PER_SECOND_SHORT") and round(v, 1) or v + local color = shown > 0 and "|cff88ff88" or "|cffff8888" + local sign = shown > 0 and "+" or "" + region:SetText(text .. " " .. color .. "(" .. sign .. shown .. ")|r") end end end @@ -163,9 +181,9 @@ pfUI:RegisterModule("eqcompare", function () end end - local function AppendExtendedSummary(tooltip, delta) + local function AppendSummaryBlock(tooltip, delta, keys) local first = true - for _, key in ipairs(EXTENDED_STAT_ORDER) do + for _, key in ipairs(keys) do local v = delta[key] if v and v ~= 0 then if first then @@ -173,29 +191,21 @@ pfUI:RegisterModule("eqcompare", function () tooltip:AddLine(T["Compared to equipped:"] or "Compared to equipped:", 0.6, 0.6, 0.6) first = false end - -- DPS is a float derived from damage/delay in ClassicAPI; every - -- other extended stat is an integer. Round DPS to 1 decimal so - -- the summary shows "+4.3 DPS" instead of "+4.34302…". local shown = (key == "ITEM_MOD_DAMAGE_PER_SECOND_SHORT") and round(v, 1) or v local color = shown > 0 and "|cff88ff88" or "|cffff8888" local sign = shown > 0 and "+" or "" - tooltip:AddDoubleLine(EXTENDED_STAT_LABELS[key] or key, color .. sign .. shown .. "|r") + tooltip:AddDoubleLine(STAT_LABELS[key] or key, color .. sign .. shown .. "|r") end end - if not first then tooltip:Show() end -- re-measure after adding lines end pfUI.eqcompare = {} - pfUI.eqcompare.GameTooltipShow = function() - -- use this tooltip for the next comparison - pfUI.eqcompare.tooltip = this + pfUI.eqcompare.tooltip = GameTooltip + local function AnnotateTooltip(tooltip) if not IsShiftKeyDown() and C.tooltip.compare.showalways ~= "1" then return end - -- Resolve the item's slot numerically instead of scanning tooltip text - -- for a localized INVTYPE_* label. GameTooltip:GetItem() returns - -- (name, link, itemID); the slotTable is keyed by Enum.InventoryType. - local _, _, itemID = this:GetItem() + local _, newLink, itemID = tooltip:GetItem() if not itemID then return end local invType = C_Item.GetItemInventoryTypeByID(itemID) local slots = invType and slotTable[invType] @@ -209,14 +219,14 @@ pfUI:RegisterModule("eqcompare", function () x = x / UIParent:GetEffectiveScale() if x > ltrigger then ltrigger = nil end - -- first tooltip + -- first equipped comparison tooltip local slotID = GetInventorySlotInfo(slots[1]) - ShoppingTooltip1:SetOwner(this, "ANCHOR_NONE") + ShoppingTooltip1:SetOwner(tooltip, "ANCHOR_NONE") ShoppingTooltip1:ClearAllPoints() if ltrigger then - ShoppingTooltip1:SetPoint("BOTTOMLEFT", this, "BOTTOMRIGHT", 0, 0) + ShoppingTooltip1:SetPoint("BOTTOMLEFT", tooltip, "BOTTOMRIGHT", 0, 0) else - ShoppingTooltip1:SetPoint("BOTTOMRIGHT", this, "BOTTOMLEFT", -border*2-1, 0) + ShoppingTooltip1:SetPoint("BOTTOMRIGHT", tooltip, "BOTTOMLEFT", -border*2-1, 0) end ShoppingTooltip1:SetInventoryItem("player", slotID) ShoppingTooltip1:Show() @@ -225,7 +235,7 @@ pfUI:RegisterModule("eqcompare", function () -- second tooltip for pair slots (finger / trinket / 1H weapon) if slots[2] then local slotID_other = GetInventorySlotInfo(slots[2]) - ShoppingTooltip2:SetOwner(this, "ANCHOR_NONE") + ShoppingTooltip2:SetOwner(tooltip, "ANCHOR_NONE") ShoppingTooltip2:ClearAllPoints() if ltrigger then ShoppingTooltip2:SetPoint("BOTTOMLEFT", ShoppingTooltip1, "BOTTOMRIGHT", 0, 0) @@ -237,29 +247,27 @@ pfUI:RegisterModule("eqcompare", function () AddHeader(ShoppingTooltip2) end - -- Fetch the delta once; annotate base stats inline (green +/red - - -- next to their existing tooltip line), then aggregate extended - -- stats (attack power, crit/hit ratings, spell power, DPS, …) into - -- a "Compared to equipped:" block at the bottom. The extended-stats - -- summary is a sub-option of the whole comparison — if base-stat - -- comparison is off, we're not doing comparisons at all. if C.tooltip.compare.basestats == "1" then - local _, newLink = this:GetItem() local equippedLink = GetInventoryItemLink("player", slotID) if newLink and equippedLink then local delta = C_Item.GetItemStatDelta(equippedLink, newLink) if delta then - AnnotateBaseStats(this, delta) - if C.tooltip.compare.extendedstats == "1" then AppendExtendedSummary(this, delta) end + if C.tooltip.compare.extendedstats == "1" then + local keys = {} + for _, k in ipairs(BASE_STAT_KEYS) do table.insert(keys, k) end + for _, k in ipairs(EXTENDED_STAT_KEYS) do table.insert(keys, k) end + AppendSummaryBlock(tooltip, delta, keys) + else + AnnotateBaseStatsInline(tooltip, delta) + end end end end - return true end + pfUI.eqcompare.AnnotateTooltip = AnnotateTooltip - -- add HookScript method if not already existing GameTooltip.HookScript = GameTooltip.HookScript or HookScript + pfUI.eqcompare.GameTooltipShow = function() AnnotateTooltip(this) end - -- Add Gametooltip Hook GameTooltip:HookScript("OnShow", pfUI.eqcompare.GameTooltipShow) end) diff --git a/modules/gui.lua b/modules/gui.lua index 8d1fe188..4d3eb17a 100644 --- a/modules/gui.lua +++ b/modules/gui.lua @@ -1,5 +1,5 @@ pfUI:RegisterModule("gui", function () - local Reload, U, CreateConfig, CreateTabFrame, CreateArea, CreateGUIEntry, EntryUpdate + local Reload, U, CreateConfig, CreateTabFrame, CreateArea, CreateGUIEntry, EntryUpdate, MakeDependent -- "searchDB" gets populated when CreateConfig is called. The table holds -- information about the title, its parent buttons and the frame itself: @@ -594,9 +594,24 @@ pfUI:RegisterModule("gui", function () CreateBackdropShadow(pfUI.gui) table.insert(UISpecialFrames, "pfConfigGUI") + function MakeDependent(child, parent) + local function refresh() + if parent.input:GetChecked() then + child.input:Enable() + child.caption:SetTextColor(1, 1, 1) + else + child.input:Disable() + child.caption:SetTextColor(0.5, 0.5, 0.5) + end + end + refresh() + HookScript(parent.input, "OnClick", refresh) + end + -- make some locals available to thirdparty pfUI.gui.Reload = Reload pfUI.gui.CreateConfig = CreateConfig + pfUI.gui.MakeDependent = MakeDependent pfUI.gui.CreateGUIEntry = CreateGUIEntry pfUI.gui.UpdaterFunctions = U @@ -2750,19 +2765,7 @@ pfUI:RegisterModule("gui", function () CreateConfig(nil, T["Status Bar Texture"], C.tooltip.statusbar, "texture", "dropdown", pfUI.gui.dropdowns.uf_bartexture) local baseCompare = CreateConfig(nil, T["Compare Item Base Stats"], C.tooltip.compare, "basestats", "checkbox") local extCompare = CreateConfig(nil, T["Compare Extended Stats (AP/Crit/etc.)"], C.tooltip.compare, "extendedstats", "checkbox") - -- Extended comparison is a sub-option of the base one — gray it out - -- (and disable interaction) whenever base comparison itself is off. - local function UpdateExtCompareGate() - if baseCompare.input:GetChecked() then - extCompare.input:Enable() - extCompare.caption:SetTextColor(1, 1, 1) - else - extCompare.input:Disable() - extCompare.caption:SetTextColor(0.5, 0.5, 0.5) - end - end - UpdateExtCompareGate() - HookScript(baseCompare.input, "OnClick", UpdateExtCompareGate) + MakeDependent(extCompare, baseCompare) CreateConfig(nil, T["Always Show Item Comparison"], C.tooltip.compare, "showalways", "checkbox") CreateConfig(nil, T["Always Show Extended Vendor Values"], C.tooltip.vendor, "showalways", "checkbox") CreateConfig(U["questitem"], T["Show Related Quest On Questitems"], C.tooltip.questitem, "showquest", "checkbox")