9 Commits
Author SHA1 Message Date
Brues 74fbbb6154 Use PFUI_CLASS_COLORS in loothistory 2026-08-12 14:33:05 -05:00
Brues af6893c10c 40-y rangecheck on by default 2026-08-12 14:25:37 -05:00
Brues be6b2ea036 Support binding a click to a named macro
Accept "macro:<name>" in a click-cast action to run a saved macro by
name via the secure `macro` attribute (leading space after the colon is
trimmed). ClassicAPI dispatches it through RunMacro, so a conditional
addon still evaluates the macro body.
2026-08-12 00:33:49 -05:00
roby-brok afdb6c3862 unitframes: stop UnitHasAggro rescanning and reallocating on every call
Two costs on the hot path. It concatenated '<u>target' and '<u>targettarget'
per call per unit, though pfValidUnits never changes after load -- now built
once into a static triple list. And only positive results were cached, so the
common case (nothing has aggro) rescanned the entire unit table on every call;
negative results now cache for 0.3s, short enough that aggro still appears
promptly.
2026-08-11 02:06:28 -05:00
Brues b230a45b92 Show spell and unit IDs on tooltips
Add "Show Spell IDs" and "Show Unit IDs" options alongside the existing
item-ID line. Spell IDs come from GameTooltip:GetSpell() on spell
tooltips and from the aura's spellId on unit-aura tooltips; unit IDs
come from C_CreatureInfo.GetCreatureID on the mouseover unit's GUID
(skipped for players and guarded when the GUID carries no entry).
2026-08-11 00:38:28 -05:00
Brues ef1fa47539 Dock GreedMeter into the chat panel
Register GreedMeter's primary window on the meter dock's damage slot,
mirroring the ShaguDPS integration. GreedMeter builds its windows lazily
and supports several, so frames are resolved at call time.

A second GreedMeter window docks into the panel's other half: pfUI's
dock decides fill-vs-split by whether the threat slot is filled, so hook
GreedMeter's window add/remove to reconcile that slot and re-run the
resize. Only the first two windows fit the panel; any beyond that float.
2026-08-10 23:52:52 -05:00
Brues ab0ad7dcf9 Group tooltip settings into sections 2026-08-10 15:07:01 -05:00
Brues c82813df5b Use GetRGB() for NORMAL_FONT_COLOR 2026-08-10 15:05:48 -05:00
Brues 6e1f8095a2 Allow whitespace-only macro names
strtrim collapsed a name like " " to "" and the empty guard then
rejected the save, but a whitespace-only name is valid in vanilla and a
common way to make a name-less macro. Save the raw text and only reject
a truly empty box.
2026-08-10 09:53:34 -05:00
9 changed files with 178 additions and 54 deletions
+4 -1
View File
@@ -223,7 +223,7 @@ function pfUI:LoadConfig()
pfUI:UpdateConfig("unitframes", nil, "always2dportrait", "0")
pfUI:UpdateConfig("unitframes", nil, "portraittexture", "1")
pfUI:UpdateConfig("unitframes", nil, "layout", "default")
pfUI:UpdateConfig("unitframes", nil, "rangecheck", "0")
pfUI:UpdateConfig("unitframes", nil, "rangecheck", "1")
pfUI:UpdateConfig("unitframes", nil, "rangecheck_mode", "vanilla")
pfUI:UpdateConfig("unitframes", nil, "rangecheck_distance", "40")
pfUI:UpdateConfig("unitframes", nil, "buffdetect", "0")
@@ -785,6 +785,8 @@ function pfUI:LoadConfig()
pfUI:UpdateConfig("tooltip", nil, "itemid", "0")
pfUI:UpdateConfig("tooltip", nil, "movespeed", "0")
pfUI:UpdateConfig("tooltip", nil, "aurasource", "0")
pfUI:UpdateConfig("tooltip", nil, "spellid", "0")
pfUI:UpdateConfig("tooltip", nil, "unitid", "0")
pfUI:UpdateConfig("tooltip", nil, "showbuffs", "0")
pfUI:UpdateConfig("tooltip", nil, "alpha", "0.8")
pfUI:UpdateConfig("tooltip", nil, "alwaysperc", "0")
@@ -964,6 +966,7 @@ function pfUI:LoadConfig()
pfUI:UpdateConfig("thirdparty", "dpsmate", "dock", "0")
pfUI:UpdateConfig("thirdparty", "shagudps", "skin", "0")
pfUI:UpdateConfig("thirdparty", "shagudps", "dock", "0")
pfUI:UpdateConfig("thirdparty", "greedmeter", "dock", "0")
pfUI:UpdateConfig("thirdparty", "swstats", "skin", "0")
pfUI:UpdateConfig("thirdparty", "swstats", "dock", "0")
pfUI:UpdateConfig("thirdparty", "ktm", "skin", "0")
+29 -15
View File
@@ -140,32 +140,43 @@ function pfUI.api.GetUnitStats(unitstr)
end
local aggrodata = { }
local aggroScan = nil -- static { u, t, tt } triples, built once so the hot scan allocates nothing
function pfUI.api.UnitHasAggro(unit)
-- Only cache positive results to allow instant detection when aggro changes
if aggrodata[unit] and aggrodata[unit].state > 0 and GetTime() < aggrodata[unit].check + 1 then
return aggrodata[unit].state
local now = GetTime()
local data = aggrodata[unit]
-- Cache positive results 1s (so aggro clears fast) AND negative results 0.3s
-- (so we don't rescan the whole unit table on every call while nothing has aggro).
if data and now < data.check + (data.state > 0 and 1 or 0.3) then
return data.state
end
aggrodata[unit] = aggrodata[unit] or { }
aggrodata[unit].check = GetTime()
aggrodata[unit].state = 0
if not data then data = { }; aggrodata[unit] = data end
data.check = now
data.state = 0
if UnitExists(unit) and UnitIsFriend(unit, "player") then
for u in pairs(pfValidUnits) do
local t = u .. "target"
local tt = t .. "target"
if UnitExists(t) and UnitIsUnit(t, unit) and UnitCanAttack(u, unit) then
aggrodata[unit].state = aggrodata[unit].state + 1
-- pfValidUnits never changes after load, so precompute the "<u>target" /
-- "<u>targettarget" token strings once instead of concatenating per call.
if not aggroScan then
aggroScan = {}
for u in pairs(pfValidUnits) do
local t = u .. "target"
table.insert(aggroScan, { u = u, t = t, tt = t .. "target" })
end
end
if UnitExists(tt) and UnitIsUnit(tt, unit) and UnitCanAttack(t, unit) then
aggrodata[unit].state = aggrodata[unit].state + 1
for i = 1, table.getn(aggroScan) do
local s = aggroScan[i]
if UnitExists(s.t) and UnitIsUnit(s.t, unit) and UnitCanAttack(s.u, unit) then
data.state = data.state + 1
end
if UnitExists(s.tt) and UnitIsUnit(s.tt, unit) and UnitCanAttack(s.t, unit) then
data.state = data.state + 1
end
end
end
return aggrodata[unit].state
return data.state
end
pfUI.uf.glow = CreateFrame("Frame", nil, UIParent)
@@ -2141,6 +2152,9 @@ function pfUI.uf:EnableClickCast()
self:SetAttribute(prefix .. "type" .. bid, "target")
elseif low == "focus" then
self:SetAttribute(prefix .. "type" .. bid, "focus")
elseif string.find(low, "^macro:") then
self:SetAttribute(prefix .. "type" .. bid, "macro")
self:SetAttribute(prefix .. "macro" .. bid, string.gsub(string.sub(action, 7), "^%s+", ""))
elseif string.find(action, "^/") then
self:SetAttribute(prefix .. "type" .. bid, "macro")
self:SetAttribute(prefix .. "macrotext" .. bid, action)
+16 -6
View File
@@ -2763,23 +2763,32 @@ pfUI:RegisterModule("gui", function ()
end)
CreateGUIEntry(T["Tooltip"], nil, function()
CreateConfig(nil, T["General"], nil, nil, "header")
CreateConfig(nil, T["Tooltip Position"], C.tooltip, "position", "dropdown", pfUI.gui.dropdowns.tooltip_position)
CreateConfig(nil, T["Tooltip Text Font"], C.tooltip, "font_tooltip", "dropdown", pfUI.gui.dropdowns.fonts)
CreateConfig(nil, T["Tooltip Text Font Size"], C.tooltip, "font_tooltip_size")
CreateConfig(nil, T["Cursor Tooltip Align"], C.tooltip, "cursoralign", "dropdown", pfUI.gui.dropdowns.tooltip_align)
CreateConfig(nil, T["Cursor Tooltip Offset"], C.tooltip, "cursoroffset")
CreateConfig(nil, T["Enable Extended Guild Information"], C.tooltip, "extguild", "checkbox")
CreateConfig(nil, T["Always Show Health In Percent"], C.tooltip, "alwaysperc", "checkbox")
CreateConfig(nil, T["Show Item IDs"], C.tooltip, "itemid", "checkbox")
CreateConfig(nil, T["Show Movement Speed"], C.tooltip, "movespeed", "checkbox")
CreateConfig(nil, T["Show Aura Caster"], C.tooltip, "aurasource", "checkbox")
CreateConfig(nil, T["Show Unit Buffs"], C.tooltip, "showbuffs", "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["Items"], nil, nil, "header")
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")
CreateConfig(U["questitem"], T["Show Required Questitem Count"], C.tooltip.questitem, "showcount", "checkbox")
CreateConfig(nil, T["Show Item IDs"], C.tooltip, "itemid", "checkbox")
CreateConfig(nil, T["Spells"], nil, nil, "header")
CreateConfig(nil, T["Show Aura Caster"], C.tooltip, "aurasource", "checkbox")
CreateConfig(nil, T["Show Spell IDs"], C.tooltip, "spellid", "checkbox")
CreateConfig(nil, T["Units"], nil, nil, "header")
CreateConfig(nil, T["Enable Extended Guild Information"], C.tooltip, "extguild", "checkbox")
CreateConfig(nil, T["Always Show Health In Percent"], C.tooltip, "alwaysperc", "checkbox")
CreateConfig(nil, T["Show Movement Speed"], C.tooltip, "movespeed", "checkbox")
CreateConfig(nil, T["Show Unit Buffs"], C.tooltip, "showbuffs", "checkbox")
CreateConfig(nil, T["Show Unit IDs"], C.tooltip, "unitid", "checkbox")
end)
CreateGUIEntry(T["Castbar"], T["General"], function()
@@ -2990,6 +2999,7 @@ pfUI:RegisterModule("gui", function ()
CreateConfig(nil, T["Use Chat Colors for Meters"], C.thirdparty, "chatbg", "checkbox")
CreateConfig(nil, "ShaguDPS (" .. T["Skin"] .. ")", C.thirdparty.shagudps, "skin", "checkbox")
CreateConfig(nil, "ShaguDPS (" .. T["Dock"] .. ")", C.thirdparty.shagudps, "dock", "checkbox")
CreateConfig(nil, "GreedMeter (" .. T["Dock"] .. ")", C.thirdparty.greedmeter, "dock", "checkbox")
CreateConfig(nil, "DPSMate (" .. T["Skin"] .. ")", C.thirdparty.dpsmate, "skin", "checkbox")
CreateConfig(nil, "DPSMate (" .. T["Dock"] .. ")", C.thirdparty.dpsmate, "dock", "checkbox")
CreateConfig(nil, "SWStats (" .. T["Skin"] .. ")", C.thirdparty.swstats, "skin", "checkbox")
+5 -15
View File
@@ -77,9 +77,7 @@ pfUI:RegisterModule("loot", function ()
if pfUI.loot.my_index or pfUI.loot.disenchanter_index or pfUI.loot.banker_index then
info = wipe(info)
info.text = T["Special Recipient"]
info.textR = NORMAL_FONT_COLOR.r
info.textG = NORMAL_FONT_COLOR.g
info.textB = NORMAL_FONT_COLOR.b
info.textR, info.textG, info.textB = NORMAL_FONT_COLOR:GetRGB()
info.textHeight = 12
info.hasArrow = 1
info.notCheckable = 1
@@ -158,9 +156,7 @@ pfUI:RegisterModule("loot", function ()
if level == 1 then
info = wipe(info)
info.text = T["Random"]
info.textR = NORMAL_FONT_COLOR.r
info.textG = NORMAL_FONT_COLOR.g
info.textB = NORMAL_FONT_COLOR.b
info.textR, info.textG, info.textB = NORMAL_FONT_COLOR:GetRGB()
info.value = "PFRANDOM"
info.textHeight = 12
info.notCheckable = 1
@@ -170,9 +166,7 @@ pfUI:RegisterModule("loot", function ()
info = wipe(info)
info.text = T["Request Rolls"]
info.textR = NORMAL_FONT_COLOR.r
info.textG = NORMAL_FONT_COLOR.g
info.textB = NORMAL_FONT_COLOR.b
info.textR, info.textG, info.textB = NORMAL_FONT_COLOR:GetRGB()
info.value = "PFROLLS"
info.textHeight = 12
info.hasArrow = 1
@@ -183,9 +177,7 @@ pfUI:RegisterModule("loot", function ()
if UIDROPDOWNMENU_MENU_VALUE == "PFROLLS" then
info = wipe(info)
info.text = T["Clear Rolls"]
info.textR = NORMAL_FONT_COLOR.r
info.textG = NORMAL_FONT_COLOR.g
info.textB = NORMAL_FONT_COLOR.b
info.textR, info.textG, info.textB = NORMAL_FONT_COLOR:GetRGB()
info.value = "PFCLEARROLLS"
info.notCheckable = 1
info.func = pfUI.loot.ClearRolls
@@ -193,9 +185,7 @@ pfUI:RegisterModule("loot", function ()
info = wipe(info)
info.text = T["Reroll Ties"]
info.textR = NORMAL_FONT_COLOR.r
info.textG = NORMAL_FONT_COLOR.g
info.textB = NORMAL_FONT_COLOR.b
info.textR, info.textG, info.textB = NORMAL_FONT_COLOR:GetRGB()
info.value = "PFTIEROLL"
info.notCheckable = 1
info.arg1 = pfUI.loot.rollers_sorted
+2 -8
View File
@@ -16,12 +16,6 @@ pfUI:RegisterModule("loothistory", function ()
local WINMARK = "Interface\\Buttons\\UI-CheckBox-Check"
local QUESTIONMARK = "Interface\\Icons\\INV_Misc_QuestionMark"
local function ClassColor(class)
local c = class and RAID_CLASS_COLORS[class]
if c then return c.r, c.g, c.b end
return 1, 1, 1
end
-- Paint an item row's icon/name/quality from a loaded Item mixin.
local function RenderItemVisual(f, item)
local r, g, b = 1, 1, 1
@@ -277,7 +271,7 @@ pfUI:RegisterModule("loothistory", function ()
if wroll and wroll > 0 then f.winroll:SetText(wroll) else f.winroll:SetText("") end
f.winroll:Show()
f.winname:SetText(wname or UNKNOWN)
f.winname:SetTextColor(ClassColor(wclass))
f.winname:SetTextColor(PFUI_CLASS_COLORS[wclass]:GetRGB())
f.winname:Show()
else
-- nobody won: everyone passed
@@ -298,7 +292,7 @@ pfUI:RegisterModule("loothistory", function ()
local function RenderPlayerFrame(pf, name, class, rollType, roll, isWinner)
pf.name:SetText(name or UNKNOWN)
pf.name:SetTextColor(ClassColor(class))
pf.name:SetTextColor(PFUI_CLASS_COLORS[class]:GetRGB())
pf.rollicon:SetTexture(ROLL_TEX[rollType] or ROLL_TEX[ROLL_PASS])
if roll and roll > 0 then pf.rolltext:SetText(roll) else pf.rolltext:SetText("") end
+1 -1
View File
@@ -72,7 +72,7 @@ pfUI:RegisterModule("macroicons", function ()
end
local function SaveMacroPopup()
local text = strtrim(picker.editbox:GetText())
local text = picker.editbox:GetText()
if text == "" then return end
local icon = iconPicker.GetIcon()
+1 -1
View File
@@ -142,7 +142,7 @@ pfUI:RegisterModule("socialmod", function ()
-- filename is the class token, so no L["class"] reversal is needed
local class = info.filename
_G["WhoFrameButton"..i.."Name"]:SetTextColor(NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b)
_G["WhoFrameButton"..i.."Name"]:SetTextColor(NORMAL_FONT_COLOR:GetRGB())
if (UIDropDownMenu_GetSelectedID(WhoFrameDropDown) == 1) then
if (info.area == playerzone) then
+97
View File
@@ -277,6 +277,103 @@ pfUI:RegisterModule("thirdparty", function()
end
end)
-- GreedMeter Damage Meter
-- Vanilla: https://github.com/iGreed/GreedMeter
-- GreedMeter builds its windows lazily and supports several of them, so the
-- frames are resolved at call time. The primary window takes the "damage"
-- dock slot; a second window (GreedMeter.UI.frames[2]) takes the "threat"
-- slot so the two tile the panel side-by-side. Docking triggers a
-- LayoutBars/RefreshFrame so the bars refit the new width.
HookAddonOrVariable("GreedMeter", function()
local UI = GreedMeter.UI
local function frameAt(i)
return UI and UI.frames and UI.frames[i]
end
local function primary()
return (UI and UI.mainFrame) or getglobal("GreedMeterFrame1")
end
local function relayout(frame)
if not UI then return end
if UI.LayoutBars then UI:LayoutBars(frame) end
if UI.RefreshFrame then UI:RefreshFrame(frame) end
end
-- Builds a docktable for a window resolved through getframe(). left picks
-- the left half in split mode; the primary window uses the right half.
local function BuildDock(name, getframe, left)
return { "greedmeter", "GreedMeter", name,
function() -- single: fill the panel
local frame = getframe()
if not frame then return end
frame:ClearAllPoints()
frame:SetAllPoints(pfUI.chat.right)
frame:SetWidth(pfUI.chat.right:GetWidth())
relayout(frame)
end,
function() -- dual: take one half
local frame = getframe()
if not frame then return end
frame:ClearAllPoints()
if left then
frame:SetPoint("TOPLEFT", pfUI.chat.right, "TOPLEFT", 0, 0)
frame:SetPoint("BOTTOMRIGHT", pfUI.chat.right, "BOTTOM", 0, 0)
else
frame:SetPoint("TOPLEFT", pfUI.chat.right, "TOP", 0, 0)
frame:SetPoint("BOTTOMRIGHT", pfUI.chat.right, "BOTTOMRIGHT", 0, 0)
end
frame:SetWidth(pfUI.chat.right:GetWidth() / 2)
relayout(frame)
end,
function() -- show
local frame = getframe()
if frame then frame:Show() relayout(frame) end
end,
function() -- hide
local frame = getframe()
if frame then frame:Hide() end
end
}
end
local threatdock = BuildDock("GreedMeterFrame2", function() return frameAt(2) end, true)
pfUI.thirdparty.meters:RegisterMeter("damage", BuildDock("GreedMeterFrame1", primary, false))
-- pfUI's dock decides fill-vs-split by whether the threat slot is filled,
-- not by counting windows, so reconcile that slot whenever GreedMeter adds
-- or removes one. Only the first two windows fit; any beyond that float.
local function SyncSecond()
if not pfUI.thirdparty.meters.damage then return end -- dock disabled
local has2 = frameAt(2) ~= nil
if has2 and not pfUI.thirdparty.meters.threat then
pfUI.thirdparty.meters.threat = threatdock
elseif not has2 and pfUI.thirdparty.meters.threat == threatdock then
pfUI.thirdparty.meters.threat = nil
end
pfUI.thirdparty.meters:Resize()
-- match the second window's visibility to the panel (the primary tracks it)
local f1, f2 = primary(), frameAt(2)
if f2 then
if f1 and f1:IsShown() then f2:Show() relayout(f2) else f2:Hide() end
end
end
-- AddFrame and the login-time layout restore both go through
-- CreateMeterFrame; RemoveFrame handles closing a window.
local origCreate = UI.CreateMeterFrame
UI.CreateMeterFrame = function(self, isPrimary, copyFrom)
local frame = origCreate(self, isPrimary, copyFrom)
SyncSecond()
return frame
end
local origRemove = UI.RemoveFrame
UI.RemoveFrame = function(self, frame)
origRemove(self, frame)
SyncSecond()
end
end)
-- DPSMate Damage Meter
-- Vanilla: https://github.com/Geigerkind/DPSMate
-- TBC: https://github.com/Geigerkind/DPSMateTBC
+23 -7
View File
@@ -273,11 +273,14 @@ pfUI:RegisterModule("tooltip", function ()
local unit = pfUI.tooltip:GetUnit()
pfUI.tooltip:UpdateBuffs(unit)
if unit == "none" then
-- process item tooltips
if C.tooltip.itemid == "1" and GameTooltip:HasItem() then
local _, _, itemID = GameTooltip:GetItem()
GameTooltip:AddLine(T["ItemID"] .. ": " .. itemID, .25,.5,1)
GameTooltip:Show()
elseif C.tooltip.spellid == "1" and GameTooltip:HasSpell() then
local _, _, spellID = GameTooltip:GetSpell()
GameTooltip:AddLine(T["SpellID"] .. ": " .. spellID, .25,.5,1)
GameTooltip:Show()
end
return
@@ -344,6 +347,13 @@ pfUI:RegisterModule("tooltip", function ()
end
end
if C.tooltip.unitid == "1" and not UnitIsPlayer(unit) then
local npcID = C_CreatureInfo.GetCreatureID(UnitGUID(unit))
if npcID then
GameTooltip:AddLine(T["UnitID"] .. ": " .. npcID, .25,.5,1)
end
end
if hp and hpm then
if hp >= 1000 then hp = round(hp / 1000, 1) .. "k" end
if hpm >= 1000 then hpm = round(hpm / 1000, 1) .. "k" end
@@ -352,16 +362,22 @@ pfUI:RegisterModule("tooltip", function ()
GameTooltip:Show()
end
if C.tooltip.aurasource == "1" then
if C.tooltip.aurasource == "1" or C.tooltip.spellid == "1" then
hooksecurefunc(GameTooltip, "SetUnitAura", function(self, ...)
local aura = C_UnitAuras.GetAuraDataByIndex(unpack(arg))
if not aura then return end
local caster = aura.sourceUnit and UnitName(aura.sourceUnit)
if not caster and aura.sourceGUID then
caster = UnitNameFromGUID(aura.sourceGUID)
if C.tooltip.aurasource == "1" then
local caster = aura.sourceUnit and UnitName(aura.sourceUnit)
if not caster and aura.sourceGUID then
caster = UnitNameFromGUID(aura.sourceGUID)
end
if caster and caster ~= "" then
self:AddLine(T["Cast by"] .. ": " .. caster, .25,.5,1)
end
end
if C.tooltip.spellid == "1" then
self:AddLine(T["SpellID"] .. ": " .. aura.spellId, .25,.5,1)
end
if not caster or caster == "" then return end
self:AddLine(T["Cast by"] .. ": " .. caster, .7, .7, 1)
self:Show()
end)
end