From dfbeeb454a1517476e570510465b9363eeec22d5 Mon Sep 17 00:00:00 2001 From: Brues <5278969+brues-code@users.noreply.github.com> Date: Wed, 1 Jul 2026 16:56:07 -0500 Subject: [PATCH] eqcompare: use C_Item.GetItemStatDelta for the comparison math MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces the twin-tooltip text extraction/comparison pair (ExtractAttributes + CompareAttributes) with a single delta pull from ClassicAPI's C_Item.GetItemStatDelta(equippedLink, newLink). Base stats (Str/Agi/Sta/Int/Spi/Mana/Health + Armor + resistances) stay annotated inline on their existing tooltip line: iterate the tooltip's FontString regions (no more _G["...TextLeft"..i] name lookup), match the "+N Foo" prefix, look up the trailing noun in a label→key map, append (+delta)/(-delta) from the ClassicAPI delta table. Extended stats (attack power / ranged AP / spell damage/healing / crit ratings / hit ratings / mana regen / defense / DPS) don't emit their own line — vanilla mixes them into equip-spell descriptions ("Equip: Increases your critical strike chance by 1%") — so aggregate them into a "Compared to equipped:" block at the bottom via AddDoubleLine. New `tooltip.compare.extendedstats` config knob (default 1) gates that block; it depends on `basestats` being on (its GUI checkbox grays out otherwise). DPS rounded to one decimal — ClassicAPI derives it from damage/delay so it lands as a raw float. Random-suffix bonuses ("of the Bear" etc.) now count correctly since GetItemStatDelta walks item-record + equip-spell auras + suffix enchants server-side. --- api/config.lua | 1 + modules/eqcompare.lua | 198 ++++++++++++++++++++++++------------------ modules/gui.lua | 16 +++- 3 files changed, 131 insertions(+), 84 deletions(-) diff --git a/api/config.lua b/api/config.lua index f17ad9ed..3dddb1fe 100644 --- a/api/config.lua +++ b/api/config.lua @@ -752,6 +752,7 @@ function pfUI:LoadConfig() pfUI:UpdateConfig("tooltip", nil, "alpha", "0.8") pfUI:UpdateConfig("tooltip", nil, "alwaysperc", "0") pfUI:UpdateConfig("tooltip", "compare", "basestats", "1") + pfUI:UpdateConfig("tooltip", "compare", "extendedstats", "1") pfUI:UpdateConfig("tooltip", "compare", "showalways", "0") pfUI:UpdateConfig("tooltip", "vendor", "showalways", "1") pfUI:UpdateConfig("tooltip", "questitem", "showquest", "1") diff --git a/modules/eqcompare.lua b/modules/eqcompare.lua index 09463aec..b4c59a9c 100644 --- a/modules/eqcompare.lua +++ b/modules/eqcompare.lua @@ -74,38 +74,88 @@ pfUI:RegisterModule("eqcompare", function () [28] = { "RangedSlot" }, -- relic } - local function startsWith(str, start) - return string.sub(str, 1, string.len(start)) == start + -- 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 = {} + 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" }, + } + 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 + 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 + end end - local function ExtractAttributes(tooltip) - local name = tooltip:GetName() + -- 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", + } - -- get the name/header of the last set comparison tooltip - local comparetooltip = pfUI.eqcompare.tooltip:GetName() - local iname = _G[comparetooltip .. "TextLeft1"] and _G[comparetooltip .. "TextLeft1"]:GetText() - - -- only run once per item - if tooltip.pfCompLastName == iname then return end - - tooltip.pfCompData = {} - tooltip.pfCompLastName = iname - - for i=1,30 do - local widget = _G[name.."TextLeft"..i] - if widget and widget:GetObjectType() == "FontString" then - local text = widget:GetText() - if text and not string.find(text, "-", 1, true) then - local start = 1 - if startsWith(text, "\+") or startsWith(text, "\(") then start = 2 end - - local space = string.find(text, " ", 1, true) - if space then - local value = tonumber(string.sub(text, start, space-1)) - if value and text then - -- we've found an attr - local attr = string.sub(text, space, string.len(text)) - tooltip.pfCompData[attr] = { value = tonumber(value), widget = widget } + local function AnnotateBaseStats(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+") + if endIdx then + local rest = string.sub(text, endIdx + 1) + 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") end end end @@ -113,47 +163,26 @@ pfUI:RegisterModule("eqcompare", function () end end - local function CompareAttributes(data, targetData) - if not data then return end - - for attr,v in pairs(data) do - if targetData then - local target = targetData[attr] - if target then - if v.value ~= target.value and v.widget:GetText() then - if v.value > target.value then - if not strfind(v.widget:GetText(), "|cff88ff88") and not strfind(v.widget:GetText(), "|cffff8888") then - v.widget:SetText(v.widget:GetText() .. "|cff88ff88 (+" .. round(v.value - target.value, 1) .. ")") - end - elseif not v.widget.compSet then - if not strfind(v.widget:GetText(), "|cff88ff88") and not strfind(v.widget:GetText(), "|cffff8888") then - v.widget:SetText(v.widget:GetText() .. "|cffff8888 (-" .. round(target.value - v.value, 1) .. ")") - end - end - target.processed = true - else - target.processed = true - end - else - -- this attribute doesnt exist in target - if v.widget and v.widget:GetText() then - if not strfind(v.widget:GetText(), "|cff88ff88") and not strfind(v.widget:GetText(), "|cffff8888") then - v.widget:SetText(v.widget:GetText() .. "|cff88ff88 (+" .. v.value .. ")") - end - end - end - end - end - - for _,target in pairs(targetData) do - if target and not target.processed then - -- we are an extra value - local text = target.widget:GetText() - if text and not strfind(text, "|cff88ff88") and not strfind(text, "|cffff8888") then - target.widget:SetText(text .. "|cff88ff88 (+" .. target.value .. ")") + local function AppendExtendedSummary(tooltip, delta) + local first = true + for _, key in ipairs(EXTENDED_STAT_ORDER) do + local v = delta[key] + if v and v ~= 0 then + if first then + tooltip:AddLine(" ") + 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") end end + if not first then tooltip:Show() end -- re-measure after adding lines end pfUI.eqcompare = {} @@ -207,27 +236,30 @@ pfUI:RegisterModule("eqcompare", function () ShoppingTooltip2:Show() 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 + end + end + end return true end -- add HookScript method if not already existing GameTooltip.HookScript = GameTooltip.HookScript or HookScript - ShoppingTooltip1.HookScript = ShoppingTooltip1.HookScript or HookScript - ShoppingTooltip2.HookScript = ShoppingTooltip2.HookScript or HookScript - pfUI.eqcompare.ShoppingTooltipShow = function() - -- abort if no comparison tooltip has been set - if not pfUI.eqcompare.tooltip then return end - - ExtractAttributes(this) - ExtractAttributes(pfUI.eqcompare.tooltip) - CompareAttributes(pfUI.eqcompare.tooltip.pfCompData, this.pfCompData) - end - - -- Add Gametooltip Hooks + -- Add Gametooltip Hook GameTooltip:HookScript("OnShow", pfUI.eqcompare.GameTooltipShow) - if C.tooltip.compare.basestats == "1" then - ShoppingTooltip1:HookScript("OnShow", pfUI.eqcompare.ShoppingTooltipShow) - ShoppingTooltip2:HookScript("OnShow", pfUI.eqcompare.ShoppingTooltipShow) - end end) diff --git a/modules/gui.lua b/modules/gui.lua index 6f1df601..8d1fe188 100644 --- a/modules/gui.lua +++ b/modules/gui.lua @@ -2748,7 +2748,21 @@ pfUI:RegisterModule("gui", function () CreateConfig(nil, T["Show Movement Speed"], C.tooltip, "movespeed", "checkbox") CreateConfig(nil, T["Custom Transparency"], C.tooltip, "alpha") CreateConfig(nil, T["Status Bar Texture"], C.tooltip.statusbar, "texture", "dropdown", pfUI.gui.dropdowns.uf_bartexture) - CreateConfig(nil, T["Compare Item Base Stats"], C.tooltip.compare, "basestats", "checkbox") + 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) 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")