18 Commits
Author SHA1 Message Date
Brues 26c55bddb0 Drive the rez icon from ClassicAPI's incoming resurrections
ClassicAPI's UnitHasIncomingResurrection sees every resurrection cast in
view from SMSG_SPELL_START and ends it with the cast, and
INCOMING_RESURRECT_CHANGED signals each change. libpredict's own
tracking goes: the ress tables and 60s timers, parsing Resurrection and
RES/RESNO messages, clearing on UNIT_HEALTH, and the rez branches of the
cast handlers, along with UNIT_SPELLCAST_STOP and the foreign cast
targets only rez used. libpredict still announces the player's own
resurrections to HealComm and CTRA for clients without ClassicAPI.

The native state ends with the cast, so a rez that landed would vanish
before the target accepts it. libpredict records the SPELL_RESURRECT
combat log event per GUID and keeps the offer until the target is alive
again or the 60s accept popup runs out, unlike Grid2, which cannot tell
a landed rez from a cancelled one. UnitHasResurrection answers both.

The unit frames show the icon in RefreshIndicators, refreshed by
INCOMING_RESURRECT_CHANGED on the frame's own unit and by libpredict for
offers, instead of polling libpredict every 0.1s.
2026-10-29 23:49:28 -05:00
Brues 8c22fdb850 Show the addon list in a ClassicAPI ScrollBox
The list built one row per addon in a ScrollFrame, hid the rows out of
view by hand and drove pfUI's own slider. A WowScrollBoxList keeps rows
only for the addons in view and recycles them while scrolling.

pfUIScrollBarTemplate (api/ui-widgets.xml) is a ScrollBarMixin bar with
the old slider's look: a thin track and a flat teal thumb that hides when
nothing scrolls. Its steppers exist for the mixin but stay hidden.

Rows read the addon's enabled state, so profiles enable and disable
addons directly instead of clicking every row's checkbox. A checkbox
now shows whether an addon is enabled; it showed whether it was loaded.
2026-10-29 23:04:47 -05:00
Brues 2d275c53fa Clear AFK on wake only while it is still set
/afk toggles. The overlay sent it on every click and key press, so when
the client or the server had already cleared AFK it set it again. Check
ClassicAPI's IsChatAFK first. It follows the client's /afk state at
once, where UnitIsAFK waits for the server's flag update.
2026-10-29 22:40:30 -05:00
Brues ba752056ad Stop the AFK camera when AFK clears while it spins
62e0d4eb stopped the camera only while the start delay was shown, but the
delay hides itself once the camera starts. AFK cleared during the spin
left the overlay up, and the next key press sent /afk and set AFK again.
Stop when the camera spins instead, which also keeps stop() and its
SetView(4) from running when AFK clears before the camera started.
2026-10-29 22:40:26 -05:00
Brues 1ccf87c61a Drop CreateGoldString's fallback for clients without coin icons
ClassicAPI always provides GetCoinTextureString, so the presence check
and the hand-built "g/s/c" string after it never ran.
2026-10-29 22:39:01 -05:00
Brues dc89f01dda Refresh the FPS & Lag panel tooltip while it is shown
The tooltip was built once on enter, so its memory, network and addon
figures stayed at the values from that moment. The widget's one-second
ticker now rebuilds it while the panel still owns GameTooltip. The
tooltip takes its owner as self, not this, so the ticker can call it.
2026-10-29 22:24:36 -05:00
Brues a917a70b1c Add an expand/collapse-all button to the Reputation tab
ClassicAPI 1.18's ExpandAllFactionHeaders and CollapseAllFactionHeaders
set every header in one call. The button sits on the "Faction" label's
row, in the headers' +/- column, and shows "+" while any header is
collapsed.
2026-10-29 22:24:36 -05:00
Brues 03ea3d5aa5 Clear cancelled resurrections announced over HealComm and CTRA
ParseChatMessage tested the raw message for "RessStop", which no
HealComm message is, so ParseComm's Resurrection/stop/ result was never
acted on. CTRA's RESNO was matched against the whole message, but it
carries the target's name, and the CTRA parser only recognised RES. A
cancelled rez from another player left the icon on the target's frame
until they were revived. RessStop still keeps a rez whose cast landed
and whose accept window is open.
2026-10-29 18:58:15 -05:00
Brues 2b4d33d46a Keep libpredict tracking casts when HealComm is loaded
With HealComm-1.0 present (HealComm, LunaUnitFrames, NotGrid), the
compat hook unregistered every event on libpredict.sender to stop pfUI
broadcasting a duplicate of the player's casts. Since cast detection
moved onto that frame's UNIT_SPELLCAST_* handlers, that also blinded
libpredict to every cast, so the player's own resurrections never put
the rez icon on the target's frame: ParseChatMessage drops the player's
own Resurrection echo, expecting the cast events to have tracked it.

The compat hook now only mutes the outgoing HealComm and CTRA messages.
The HealComm.SendAddonMessage hook that parsed the player's own messages
while solo goes too, since libpredict tracks its own casts directly.
2026-10-29 18:56:51 -05:00
Brues 94dcfb3dad Compute bar layout options instead of tabulating pfGridmath
pfGridmath listed every exact cols x rows grid per bar size; the only
live use was seeding the layout dropdown, which a divisor loop now does.

Everything else that read it was unreachable: every layout option is a
"cols x rows" string, so ResolveBarLayout's BarLayoutFormfactor lookup
never matched, and the actionbar module resets a stored layout that
isn't one before BarLayoutSize/BarButtonAnchor run, so their fallbacks
never fired. BarLayoutFormfactor and env/tables.lua go with it.
2026-10-29 14:16:43 -05:00
Brues 0890905eb6 Drop named-unit frames and the pfValidUnits global
CreateUnitFrame and CreateCastbar could take a character name instead of
a unit token, but every caller passes a token, so that mode and the
empty-label guards around it were dead. The OnClick TargetByName branch
could never run anyway: label was always a string, and "" is truthy.

With that gone, the aggro scan was the only reader of pfValidUnits, so
its unit list now lives in unitframes.lua and is built once at load.

/focusname resolves the name with UnitTokenFromName (exact first, then
prefix) instead of swapping the target and muting UIErrorsFrame.
2026-10-29 14:00:54 -05:00
Brues 6c76a88467 Draw unit frame bars with the native StatusBar
The custom bar sized its textures in Lua from GetWidth(), so it had to
track how ClassicAPI reports the UI scale (#71), and it stretched the bar
texture instead of cropping it. The engine's StatusBar does that layout
itself.

The background of the missing part is a second StatusBar that fills
from the other end (ClassicAPI's SetReverseFill), one frame level below
the fill, so the incoming-heal texture still draws between the two.
SetFrameLevel moves both, and the health and power bars now sit two
levels above their backdrops. SetStatusBarBackgroundTexture becomes
SetStatusBarBackgroundColor, as it only ever took a color.
2026-10-29 11:06:02 -05:00
Brues 35c32f365e Remove scaledsize fix 2026-10-29 10:38:28 -05:00
Brues 6dcb5162be bags: use ClassicAPI's native item search
C_Container.SetItemSearch drives the search box; slots dim from isFiltered on INVENTORY_SEARCH_UPDATE and on each slot update. Matches name, subclass, equip slot and equipment set names. Drops the tooltip-scanning full-text option.
2026-10-29 10:35:28 -05:00
Brues 8afafe71cf Drop RunMacroText fallback and dead actionbar spellslot branches
ClassicAPI 1.18 provides RunMacroText natively; bump CAPI min to 1.18.0. spellslot/booktype/scanmacro were only ever cleared, never set.
2026-10-29 10:35:28 -05:00
Brues 99ed538ee7 bump CAPI min to 1.17.10 2026-10-29 10:35:28 -05:00
Brues f382039f21 panel: show per-addon memory in FPS & Lag tooltip
Use UpdateAddOnMemoryUsage/GetAddOnMemoryUsage like Blizzard's MainMenuBar performance tooltip: total addon memory plus the top 3 consumers, formatted with the TOTAL_MEM/ADDON_MEM global strings. Guarded so the tooltip falls back gracefully on clients without these APIs.
2026-10-29 10:35:28 -05:00
Brues 8307dd757e Bump ClassicAPI minimum 1.17.4 2026-10-29 10:35:28 -05:00
29 changed files with 466 additions and 704 deletions
+1 -1
View File
@@ -13,7 +13,7 @@ do
-- on. There pfUI does load, and will throw wherever it reaches for something
-- the installed DLL doesn't have yet -- the popup names the cause so those
-- errors aren't a mystery.
local PFUI_CLASSIC_API_MIN = 11703 -- (X*10000 + Y*100 + Z)
local PFUI_CLASSIC_API_MIN = 11802 -- (X*10000 + Y*100 + Z)
local PFUI_CLASSIC_API_LATEST = PFUI_CLASSIC_API_MIN
local PFUI_CLASSIC_API_WEBSITE = "https://github.com/brues-code/ClassicAPI"
local PFUI_CLASSIC_API_LATEST_URL = PFUI_CLASSIC_API_WEBSITE .. "/releases/latest"
+22 -62
View File
@@ -722,20 +722,7 @@ end
function pfUI.api.CreateGoldString(money)
if type(money) ~= "number" then return "-" end
if _G.GetCoinTextureString then
return "|cffffffff" .. _G.GetCoinTextureString(money) .. "|r"
end
local gold = floor(money/ 100 / 100)
local silver = floor(mod((money/100),100))
local copper = floor(mod(money,100))
local string = ""
if gold > 0 then string = string .. "|cffffffff" .. gold .. "|cffffd700g" end
if silver > 0 or gold > 0 then string = string .. "|cffffffff " .. silver .. "|cffc7c7cfs" end
string = string .. "|cffffffff " .. copper .. "|cffeda55fc"
return string
return "|cffffffff" .. _G.GetCoinTextureString(money) .. "|r"
end
-- [ Enable Movable ]
@@ -1353,15 +1340,18 @@ end
-- [ Bar Layout Options ] --
-- 'barsize' size of bar in number of buttons
-- returns: array of options as strings for pfUI.gui.bar
-- returns: array of options as strings for pfUI.gui.bar: every grid
-- that fits the bar exactly, plus common uneven ones
function pfUI.api.BarLayoutOptions(barsize)
assert(barsize > 0 and barsize <= NUM_ACTIONBAR_BUTTONS,"BarLayoutOptions: barsize "..tostring(barsize).." is invalid")
local options, seen = {}, {}
local function add(option)
if not seen[option] then table.insert(options, option); seen[option] = true end
end
for i,layout in ipairs(pfGridmath[barsize]) do
add(string.format("%d x %d",layout[1],layout[2]))
for cols = 1, barsize do
if barsize % cols == 0 then
add(string.format("%d x %d", cols, barsize / cols))
end
end
for _, option in ipairs({
"1 x 12", "1 x 10", "2 x 10", "2 x 5", "3 x 10", "3 x 5", "3 x 3",
@@ -1381,49 +1371,23 @@ function pfUI.api.BarLayoutOptions(barsize)
return options
end
-- [ Bar Layout Formfactor ] --
-- 'option' string option as used in pfUI_config.bars[bar].option
-- returns: integer formfactor
local formfactors = {}
setmetatable(formfactors, {__mode = "v"}) -- weak table so values not referenced are collected on next gc
function pfUI.api.BarLayoutFormfactor(option)
if formfactors[option] then
return formfactors[option]
else
for barsize,_ in ipairs(pfGridmath) do
local options = pfUI.api.BarLayoutOptions(barsize)
for i,opt in ipairs(options) do
if opt == option then
formfactors[option] = i
return formfactors[option]
end
end
end
end
end
-- 'formfactor' has to be a "cols x rows" string; the actionbar module resets
-- a stored layout that isn't one before it lays out a bar
local function ResolveBarLayout(barsize, formfactor, uneven)
local layout = tostring(formfactor or "")
local _, _, a, b = string.find(layout, "(%d+)%s*x%s*(%d+)")
if a and b then
local cols, rows = tonumber(a), tonumber(b)
cols = math.min(NUM_ACTIONBAR_BUTTONS, math.max(1, cols))
rows = math.min(NUM_ACTIONBAR_BUTTONS, math.max(1, rows))
local orientation = string.upper(uneven or "DOWN")
local mode = (orientation == "LEFT" or orientation == "RIGHT") and "cols" or "rows"
if mode == "rows" then
cols = math.max(1, math.min(cols, barsize))
rows = math.max(1, math.ceil(barsize / cols))
else
rows = math.max(1, math.min(rows, barsize))
cols = math.max(1, math.ceil(barsize / rows))
end
return cols, rows, mode, orientation
local _, _, a, b = string.find(formfactor, "(%d+)%s*x%s*(%d+)")
local cols, rows = tonumber(a), tonumber(b)
cols = math.min(NUM_ACTIONBAR_BUTTONS, math.max(1, cols))
rows = math.min(NUM_ACTIONBAR_BUTTONS, math.max(1, rows))
local orientation = string.upper(uneven or "DOWN")
local mode = (orientation == "LEFT" or orientation == "RIGHT") and "cols" or "rows"
if mode == "rows" then
cols = math.max(1, math.min(cols, barsize))
rows = math.max(1, math.ceil(barsize / cols))
else
rows = math.max(1, math.min(rows, barsize))
cols = math.max(1, math.ceil(barsize / rows))
end
local index = pfUI.api.BarLayoutFormfactor(layout)
if not index then return nil end
local cols, rows = unpack(pfGridmath[barsize][index])
return cols, rows, "rows", "DOWN"
return cols, rows, mode, orientation
end
-- [ Bar Layout Size ] --
@@ -1434,7 +1398,6 @@ end
function pfUI.api.BarLayoutSize(bar,barsize,formfactor,iconsize,bordersize,padding,uneven,fillmode)
assert(barsize > 0 and barsize <= NUM_ACTIONBAR_BUTTONS,"BarLayoutSize: barsize "..tostring(barsize).." is invalid")
local cols, rows = ResolveBarLayout(barsize, formfactor, uneven)
if not cols or not rows then cols, rows = unpack(pfGridmath[barsize][1]) end
local width = (iconsize + bordersize*2+padding) * cols + padding
local height = (iconsize + bordersize*2+padding) * rows + padding
bar._size = {width,height}
@@ -1452,9 +1415,6 @@ end
function pfUI.api.BarButtonAnchor(button,basename,buttonindex,barsize,formfactor,iconsize,bordersize,padding,uneven,fillmode)
assert(barsize > 0 and barsize <= NUM_ACTIONBAR_BUTTONS,"BarButtonAnchor: barsize "..tostring(barsize).." is invalid")
local cols, rows, mode, orientation = ResolveBarLayout(barsize, formfactor, uneven)
if not cols or not rows then
cols, rows, mode, orientation = unpack(pfGridmath[barsize][1]), "rows", "DOWN"
end
-- fillmode: remap buttonindex to slotindex (grid position in native fill order)
-- only needed when fill direction differs from layout mode
-1
View File
@@ -171,7 +171,6 @@ function pfUI:LoadConfig()
pfUI:UpdateConfig("appearance", "bags", "newitem_color", "1,1,1,1")
pfUI:UpdateConfig("appearance", "bags", "borderlimit", "1")
pfUI:UpdateConfig("appearance", "bags", "borderonlygear", "0")
pfUI:UpdateConfig("appearance", "bags", "fulltext", "1")
pfUI:UpdateConfig("appearance", "bags", "movable", "0")
pfUI:UpdateConfig("appearance", "bags", "abovechat", "0")
pfUI:UpdateConfig("appearance", "bags", "hidechat", "0")
+36 -74
View File
@@ -3,19 +3,19 @@ setfenv(1, pfUI:GetEnvironment())
do -- statusbars
local animations = {}
local stepsize, val
local width, height, point
local stepsize
-- ClassicAPI before v1.18.0 leaves the UI scale in the size of an anchored
-- frame, so the bar has to divide it back out
local scaledsize = CLASSIC_API_VERSION < 11800
-- native StatusBar methods the bar overrides
local statusbar = CreateFrame("StatusBar")
local SetValue, SetMinMaxValues = statusbar.SetValue, statusbar.SetMinMaxValues
local SetOrientation, SetFrameLevel = statusbar.SetOrientation, statusbar.SetFrameLevel
local animate = CreateFrame("Frame", "pfStatusBarAnimation", UIParent)
animate:SetScript("OnUpdate", function()
stepsize = tonumber(pfUI_config.unitframes.animation_speed)
for bar in pairs(animations) do
if not bar.val_ or abs(bar.val_ - bar.val) < stepsize or bar.instant then
if not bar.val_ or abs(bar.val_ - bar.val) < stepsize then
bar:DisplayValue(bar.val)
elseif bar.val ~= bar.val_ then
bar:DisplayValue(bar.val_ + min((bar.val-bar.val_) / stepsize, max(bar.val-bar.val_, 30 / GetFramerate())))
@@ -25,7 +25,8 @@ do -- statusbars
local handlers = {
["DisplayValue"] = function(self, val)
val = Clamp(val, self.min, self.max)
local smin, smax = self:GetMinMaxValues()
val = Clamp(val, smin, smax)
-- remove animation queue
if val == self.val_ then
@@ -34,53 +35,20 @@ do -- statusbars
-- set current visible value
self.val_ = val
SetValue(self, val)
if self.mode == "vertical" then
height = self:GetHeight()
if scaledsize then height = height / self:GetEffectiveScale() end
point = height / (self.max - self.min) * (val - self.min)
-- keep values in limits
point = Clamp(point, 0, height)
-- set point to zero if value and max is zero
if val == 0 then point = 0 end
-- set status bar position/size
self.bar:SetPoint("TOPLEFT", self, "TOPLEFT", 0, - height + point)
self.bar:SetPoint("BOTTOMRIGHT", self, "BOTTOMRIGHT", 0, 0)
-- set background bar position/size
self.bg:SetPoint("TOPLEFT", self, "TOPLEFT", 0, 0)
self.bg:SetPoint("BOTTOMRIGHT", self, "BOTTOMRIGHT", 0, point)
else
width = self:GetWidth()
if scaledsize then width = width / self:GetEffectiveScale() end
point = width / (self.max - self.min) * (val - self.min)
-- keep values in limits
point = Clamp(point, 0, width)
-- set point to zero if value and max is zero
if val == 0 then point = 0 end
-- set status bar position/size
self.bar:SetPoint("TOPLEFT", self, "TOPLEFT", 0, 0)
self.bar:SetPoint("BOTTOMRIGHT", self, "BOTTOMRIGHT", - width + point, 0)
-- set background bar position/size
self.bg:SetPoint("BOTTOMRIGHT", self, "BOTTOMRIGHT", 0, 0)
self.bg:SetPoint("TOPLEFT", self, "TOPLEFT", point, 0)
end
-- the background fills the missing part, all of an empty range
self.bg:SetValue(smax > smin and (smax - val) / (smax - smin) or 1)
end,
["SetMinMaxValues"] = function(self, smin, smax, smooth)
-- smoothen the transition by keeping the value at the same percentage as before
if smooth and self.max and self.max > 0 and smax > 0 and self.max ~= smax then
self.val_ = (self.val_ or self.val) / self.max * smax
local _, oldmax = self:GetMinMaxValues()
if smooth and oldmax > 0 and smax > 0 and oldmax ~= smax then
self.val_ = (self.val_ or self.val) / oldmax * smax
end
self.min, self.max = smin, smax
SetMinMaxValues(self, smin, smax)
self:DisplayValue(self.val_ or self.val)
end,
@@ -93,41 +61,35 @@ do -- statusbars
end
end,
["SetStatusBarTexture"] = function(self, r, g, b, a)
self.bar:SetTexture(r, g, b, a)
end,
["SetStatusBarColor"] = function(self, r, g, b, a)
self.bar:SetVertexColor(r, g, b, a)
end,
["SetStatusBarBackgroundTexture"] = function(self, r, g, b, a)
self.bg:SetTexture(r, g, b, a)
end,
["SetStatusBarBackgroundColor"] = function(self, r, g, b, a)
self.bg:SetVertexColor(r, g, b, a)
self.bg:SetColorFill(r, g, b, a)
end,
["SetOrientation"] = function(self, mode)
self.mode = strlower(mode)
["SetOrientation"] = function(self, orientation)
SetOrientation(self, orientation)
self.bg:SetOrientation(orientation)
end,
-- the background stays one level below the fill, under anything else
-- drawn on the fill's frame
["SetFrameLevel"] = function(self, level)
SetFrameLevel(self, level)
self.bg:SetFrameLevel(level - 1)
end,
}
function pfUI.api.CreateStatusBar(name, parent)
local f = CreateFrame("Button", name, parent)
f:EnableMouse(false)
local f = CreateFrame("StatusBar", name, parent)
SetMinMaxValues(f, 0, 100)
SetValue(f, 0)
f.val = 0
f.bar = f:CreateTexture(nil, "NORMAL")
f.bar:SetPoint("TOPLEFT", f, "TOPLEFT", 0, 0)
f.bar:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", 0, 0)
f.bg = f:CreateTexture(nil, "BACKGROUND")
f.bg:SetPoint("TOPLEFT", f, "TOPLEFT", 0, 0)
f.bg:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", 0, 0)
-- set some default values
f.min, f.max, f.val = 0, 100, 0
f.bg = CreateFrame("StatusBar", nil, f)
f.bg:SetFrameLevel(f:GetFrameLevel() - 1)
f.bg:SetAllPoints(f)
f.bg:SetReverseFill(true)
f.bg:SetMinMaxValues(0, 1)
f.bg:SetValue(1)
-- add all handler functions to the object
for name, func in pairs(handlers) do
+31
View File
@@ -0,0 +1,31 @@
<Ui xmlns="http://www.blizzard.com/wow/ui/">
<!--
pfUI's scroll bar for ClassicAPI's ScrollBox: a thin track with a flat
thumb. ScrollBarMixin needs Back and Forward steppers, so they exist but
stay hidden. Hook it up with ScrollUtil.InitScrollBoxListWithScrollBar.
-->
<EventFrame name="pfUIScrollBarTemplate" inherits="VerticalScrollBarTemplate" virtual="true">
<Size x="7"/>
<KeyValues>
<KeyValue key="hideIfUnscrollable" value="true" type="boolean"/>
</KeyValues>
<Frames>
<Frame parentKey="Track" setAllPoints="true" enableMouse="true">
<Frames>
<EventButton parentKey="Thumb">
<Size x="7"/>
<Layers>
<Layer level="ARTWORK">
<Texture setAllPoints="true">
<Color r=".3" g="1" b=".8" a=".5"/>
</Texture>
</Layer>
</Layers>
</EventButton>
</Frames>
</Frame>
<EventButton parentKey="Back" hidden="true"/>
<EventButton parentKey="Forward" hidden="true"/>
</Frames>
</EventFrame>
</Ui>
+64 -44
View File
@@ -235,7 +235,50 @@ function pfUI.api.GetUnitStats(unitstr)
end
local aggrodata = { }
local aggroScan = nil -- static { u, t, tt } triples, built once so the hot scan allocates nothing
-- every unit whose target or target's target UnitHasAggro checks, as static
-- { u, t, tt } triples so the hot scan allocates nothing
local aggroScan = { }
do
local function add(u)
local t = u .. "target"
table.insert(aggroScan, { u = u, t = t, tt = t .. "target" })
end
add("pet")
add("player")
add("target")
add("mouseover")
add("focus")
add("focustarget")
add("focustargettarget")
add("pettarget")
add("playertarget")
add("targettarget")
add("mouseovertarget")
add("targettargettarget")
for i=1,4 do
add("party" .. i)
add("party" .. i .. "target")
add("partypet" .. i)
add("partypet" .. i .. "target")
end
for i=1,40 do
add("raid" .. i)
add("raid" .. i .. "target")
add("raidpet" .. i)
add("raidpet" .. i .. "target")
add("nameplate" .. i)
add("nameplate" .. i .. "target")
end
end
function pfUI.api.UnitHasAggro(unit)
local now = GetTime()
local data = aggrodata[unit]
@@ -250,16 +293,6 @@ function pfUI.api.UnitHasAggro(unit)
data.state = 0
if UnitExists(unit) and UnitIsFriend(unit, "player") then
-- 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
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
@@ -349,11 +382,10 @@ function pfUI.uf:UpdateVisibility()
-- this runs for every frame five times a second, so only touch the
-- attribute when the unit actually changed
local unitstr = (self.label or "") .. (self.id or "")
local unit = unitstr ~= "" and unitstr or nil
if unit ~= self.unitstr then
self.unitstr = unit
self:SetAttribute("unit", unit)
local unitstr = self.label .. self.id
if unitstr ~= self.unitstr then
self.unitstr = unitstr
self:SetAttribute("unit", unitstr)
end
-- anything but "show"/"hide" means "show if the unit exists"
local visibility = "exists"
@@ -395,9 +427,7 @@ function pfUI.uf:UpdateVisibility()
self:RegisterUnitEvents(visibility ~= "hide" and self.unitstr or nil)
-- vanilla visibility
if self.unitname then
self:Show()
elseif visibility == "hide" then
if visibility == "hide" then
self:Hide()
elseif visibility == "show" then
self:Show()
@@ -508,6 +538,8 @@ function pfUI.uf:UpdateConfig()
if tonumber(f.config.height) < 0 then f.hp:Hide() end
pfUI.api.CreateBackdrop(f.hp, default_border)
-- one level above the backdrop for the bar's background, one more for its fill
f.hp.bar:SetFrameLevel(f.hp.backdrop:GetFrameLevel() + 2)
f.hp.bar:SetStatusBarTexture(pfUI.media[f.config.bartexture])
f.hp.bar:SetAllPoints(f.hp)
if f.config.verticalbar == "1" then
@@ -522,7 +554,7 @@ function pfUI.uf:UpdateConfig()
if custombg == "1" then
local cr, cg, cb, ca = GetStringColor(custombgcolor)
cr, cg, cb, ca = tonumber(cr), tonumber(cg), tonumber(cb), tonumber(ca)
f.hp.bar:SetStatusBarBackgroundTexture(cr,cg,cb,ca)
f.hp.bar:SetStatusBarBackgroundColor(cr,cg,cb,ca)
end
f.power:ClearAllPoints()
@@ -531,7 +563,7 @@ function pfUI.uf:UpdateConfig()
if tonumber(f.config.pheight) < 0 then f.power:Hide() end
pfUI.api.CreateBackdrop(f.power, default_border)
f.power.bar:SetFrameLevel(f.power.backdrop:GetFrameLevel() + 1)
f.power.bar:SetFrameLevel(f.power.backdrop:GetFrameLevel() + 2)
f.power.bar:SetStatusBarTexture(pfUI.media[f.config.pbartexture])
f.power.bar:SetAllPoints(f.power)
@@ -541,7 +573,7 @@ function pfUI.uf:UpdateConfig()
if custompbg == "1" then
local cr, cg, cb, ca = GetStringColor(custompbgcolor)
cr, cg, cb, ca = tonumber(cr), tonumber(cg), tonumber(cb), tonumber(ca)
f.power.bar:SetStatusBarBackgroundTexture(cr,cg,cb,ca)
f.power.bar:SetStatusBarBackgroundColor(cr,cg,cb,ca)
end
local fontname, fontsize, fontstyle
@@ -1019,6 +1051,8 @@ function pfUI.uf.OnEvent()
this.update_pvp = true
elseif event == "UNIT_COMBAT" then
CombatFeedback_OnCombatEvent(arg2, arg3, arg4, arg5)
elseif event == "INCOMING_RESURRECT_CHANGED" then
this.update_indicators = true
else
this.update_base = true
end
@@ -1189,10 +1223,6 @@ function pfUI.uf.OnUpdate()
this.incHeal:Hide()
end
end
-- update ressurections
local ress = libpredict:UnitHasIncomingResurrection(unit)
this.ressIcon:SetShown(ress and UnitIsDeadOrGhost(unit))
end
end
end
@@ -1244,8 +1274,6 @@ function pfUI.uf.OnUpdate()
end
end
if not this.label or this.label == "" then return end
-- update portrait on first visible frame
if this.portrait and this.portrait.model and this.portrait.model.update then
this.portrait.model.lastUnit = UnitName(this.portrait.model.update)
@@ -1272,6 +1300,7 @@ local UNIT_EVENTS = {
"UNIT_PORTRAIT_UPDATE", "UNIT_MODEL_CHANGED",
"UNIT_FACTION",
"UNIT_AURA", -- frame=buff, frame=debuff
"INCOMING_RESURRECT_CHANGED", -- frame=ressIcon
}
-- Point this frame's unit-event subscriptions at `unitstr`, or drop them when
@@ -1332,11 +1361,7 @@ end
-- ============================================================
function pfUI.uf.OnClick()
if not this.label and this.unitname then
TargetByName(this.unitname, true)
else
pfUI.uf:ClickAction(arg1)
end
pfUI.uf:ClickAction(arg1)
end
function pfUI.uf:ClickAction(button)
@@ -1433,13 +1458,6 @@ function pfUI.uf:CreateUnitFrame(unit, id, config, tick)
f.config = config or pfUI_config.unitframes.fallback
f.tick = tick
-- disable events for unknown unitstrings
if not pfValidUnits[strlower(unit) .. id] then
f.unitname = unit
f.label, f.id = "", ""
f.RegisterEvent = function() return end
end
CreateBackdropShadow(f)
f.hp = CreateFrame("Frame",nil, f)
@@ -1490,7 +1508,7 @@ function pfUI.uf:CreateUnitFrame(unit, id, config, tick)
f.incHeal = CreateFrame("Frame", nil, f.hp)
-- Texture lives on hp.bar for correct draw order:
-- hp.bar.bg (BACKGROUND) < incHeal (BORDER) < hp.bar.bar (NORMAL)
-- hp.bar.bg (a level below) < incHeal (BORDER) < hp.bar's fill (ARTWORK)
f.incHeal.texture = f.hp.bar:CreateTexture(nil, "BORDER")
f.incHeal.texture:SetAllPoints(f.incHeal)
-- Override Show/Hide since texture lives on hp.bar, not incHeal
@@ -1556,9 +1574,7 @@ function pfUI.uf:CreateUnitFrame(unit, id, config, tick)
f:Hide()
end
if f.label ~= "" then
f:SetAttribute("unit", f.label .. f.id)
end
f:SetAttribute("unit", f.label .. f.id)
-- register frame for clique
_G.ClickCastFrames = ClickCastFrames or {}
@@ -1632,6 +1648,10 @@ function pfUI.uf:RefreshIndicators(unit)
unit.pvpIcon:SetShown(unit.config.showPVP == "1" and UnitIsPVP(unitstr))
end
if unit.ressIcon then -- Resurrection Icon
unit.ressIcon:SetShown(libpredict:UnitHasResurrection(unitstr))
end
if unit.restIcon and unit:GetName() == "pfPlayer" then -- Rest Icon
unit.restIcon:SetShown(C.unitframes.player.showRest == "1" and UnitIsUnit(unitstr, "player") and IsResting())
end
-50
View File
@@ -1,50 +0,0 @@
-- barlength = {[formfactor]={cols, rows}}
pfGridmath = {
[1] = {{1,1}},
[2] = {{2,1},{1,2}},
[3] = {{3,1},{1,3}},
[4] = {{4,1},{2,2},{1,4}},
[5] = {{5,1},{1,5}},
[6] = {{6,1},{3,2},{2,3},{1,6}},
[7] = {{7,1},{1,7}},
[8] = {{8,1},{4,2},{2,4},{1,8}},
[9] = {{9,1},{3,3},{1,9}},
[10] = {{10,1},{5,2},{2,5},{1,10}},
[11] = {{11,1},{1,11}},
[12] = {{12,1},{6,2},{4,3},{3,4},{2,6},{1,12}}
}
-- list of valid unitstrings
pfValidUnits = {}
pfValidUnits["pet"] = true
pfValidUnits["player"] = true
pfValidUnits["target"] = true
pfValidUnits["mouseover"] = true
pfValidUnits["focus"] = true
pfValidUnits["focustarget"] = true
pfValidUnits["focustargettarget"] = true
pfValidUnits["pettarget"] = true
pfValidUnits["playertarget"] = true
pfValidUnits["targettarget"] = true
pfValidUnits["mouseovertarget"] = true
pfValidUnits["targettargettarget"] = true
for i=1,4 do
pfValidUnits["party" .. i] = true
pfValidUnits["party" .. i .. "target"] = true
pfValidUnits["partypet" .. i] = true
pfValidUnits["partypet" .. i .. "target"] = true
end
for i=1,40 do
pfValidUnits["raid" .. i] = true
pfValidUnits["raid" .. i .. "target"] = true
pfValidUnits["raidpet" .. i] = true
pfValidUnits["raidpet" .. i .. "target"] = true
pfValidUnits["nameplate" .. i] = true
pfValidUnits["nameplate" .. i .. "target"] = true
end
-1
View File
@@ -281,7 +281,6 @@ pfUI_translation["deDE"] = {
["Enable Energy Ticks"] = nil,
["Enable Extended Guild Information"] = nil,
["Enable Frame Shadow"] = nil,
["Enable Full-Text Search"] = nil,
["Enable Indicators"] = nil,
["Enable Item Quality Color For Equipment Only"] = nil,
["Enable \"Loot & Spam\" Chat Window"] = nil,
-1
View File
@@ -290,7 +290,6 @@ pfUI_translation["enUS"] = {
["Enable Energy Ticks"] = nil,
["Enable Extended Guild Information"] = nil,
["Enable Frame Shadow"] = nil,
["Enable Full-Text Search"] = nil,
["Enable Indicators"] = nil,
["Enable Item Quality Color For Equipment Only"] = nil,
["Enable \"Loot & Spam\" Chat Window"] = nil,
-1
View File
@@ -281,7 +281,6 @@ pfUI_translation["esES"] = {
["Enable Energy Ticks"] = "Activar tics de energía",
["Enable Extended Guild Information"] = "Activar información de hermandad extendida",
["Enable Frame Shadow"] = "Activar sombra del marco",
["Enable Full-Text Search"] = nil,
["Enable Indicators"] = "Activar indicadores",
["Enable Item Quality Color For Equipment Only"] = "Mostrar el color de calidad de objetos solo para equipo",
["Enable \"Loot & Spam\" Chat Window"] = "Activar ventana de chat \"Botín y spam\"",
-1
View File
@@ -281,7 +281,6 @@ pfUI_translation["frFR"] = {
["Enable Energy Ticks"] = "Activer la barre de rechargement d'énergie",
["Enable Extended Guild Information"] = "Activer les informations de guilde étendues",
["Enable Frame Shadow"] = "Activer l'ombre des cadres",
["Enable Full-Text Search"] = nil,
["Enable Indicators"] = "Activer les indicateurs",
["Enable Item Quality Color For Equipment Only"] = "Activer la couleur de la qualité des objets seulement sur les pièces d'équipement",
["Enable \"Loot & Spam\" Chat Window"] = "Activer la fenêtre de chat \"Butins & Spam\"",
-1
View File
@@ -281,7 +281,6 @@ pfUI_translation["koKR"] = {
["Enable Energy Ticks"] = "Energy Ticks 활성화",
["Enable Extended Guild Information"] = "길드 정보 확장 사용",
["Enable Frame Shadow"] = nil,
["Enable Full-Text Search"] = nil,
["Enable Indicators"] = nil,
["Enable Item Quality Color For Equipment Only"] = "착용장비 전용 아이템품질 색상 활성화",
["Enable \"Loot & Spam\" Chat Window"] = "\"루팅 & 스팜\"채팅 창 사용",
-1
View File
@@ -281,7 +281,6 @@ pfUI_translation["ruRU"] = {
["Enable Energy Ticks"] = "Включить восполнение энергии",
["Enable Extended Guild Information"] = "Включить расширенную информацию о гильдии",
["Enable Frame Shadow"] = "Включить тень окон",
["Enable Full-Text Search"] = nil,
["Enable Indicators"] = "Включить индикаторы",
["Enable Item Quality Color For Equipment Only"] = "Включить цвет качества предметов только для экипировки",
["Enable \"Loot & Spam\" Chat Window"] = "Включить окно чата \"Добыча & Спам\"",
-1
View File
@@ -281,7 +281,6 @@ pfUI_translation["zhCN"] = {
["Enable Energy Ticks"] = "显示能量刻度",
["Enable Extended Guild Information"] = "公会信息扩展显示",
["Enable Frame Shadow"] = "启用框架阴影",
["Enable Full-Text Search"] = "启用全文搜索",
["Enable Indicators"] = "启用指示器",
["Enable Item Quality Color For Equipment Only"] = "仅在装备上显示品质颜色",
["Enable \"Loot & Spam\" Chat Window"] = "启用[拾取、综合]聊天窗口",
-1
View File
@@ -281,7 +281,6 @@ pfUI_translation["zhTW"] = {
["Enable Energy Ticks"] = "顯示能量刻度",
["Enable Extended Guild Information"] = "公會資訊擴展顯示",
["Enable Frame Shadow"] = "啟用框架陰影",
["Enable Full-Text Search"] = nil,
["Enable Indicators"] = nil,
["Enable Item Quality Color For Equipment Only"] = "僅在裝備上顯示品質顏色",
["Enable \"Loot & Spam\" Chat Window"] = "啟用[拾取、綜合]聊天視窗",
+1
View File
@@ -1,6 +1,7 @@
<Ui xmlns="http://www.blizzard.com/wow/ui/">
<Include file="..\api\api.lua"/>
<Include file="..\api\config.lua"/>
<Include file="..\api\ui-widgets.xml"/>
<Include file="..\api\ui-widgets.lua"/>
<Include file="..\api\unitframes.lua"/>
</Ui>
-1
View File
@@ -7,7 +7,6 @@
<Include file="..\env\locales_esES.lua"/>
<Include file="..\env\locales_ruRU.lua"/>
<Include file="..\env\tables.lua"/>
<Include file="..\env\profiles.lua"/>
<Include file="..\env\translations_enUS.lua"/>
+58 -167
View File
@@ -2,16 +2,21 @@
setfenv(1, pfUI:GetEnvironment())
--[[ libpredict ]]--
-- A pfUI library that detects, receives and sends heal and resurrection predictions.
-- A pfUI library that detects, receives and sends heal predictions.
-- Healing predictions are done by caching the last known "normal" heal value of the
-- spell when last being used. Those chaches are cleared when new talents are detected.
-- The API provides function calls similar to later WoW expansions such as:
-- UnitGetIncomingHeals(unit)
-- UnitHasIncomingResurrection(unit)
-- UnitHasResurrection(unit)
--
-- The library is able to receive and send compatible messages to HealComm
-- including resurrections. It has an option to disable the sending of those
-- messages in case HealComm is already active.
-- UnitHasResurrection extends ClassicAPI's UnitHasIncomingResurrection, which
-- ends with the cast, to a resurrection that landed and waits for the target
-- to accept it.
--
-- The library is able to receive and send compatible messages to HealComm,
-- and announces the player's resurrections to HealComm and CTRA. It has an
-- option to disable the sending of those messages in case HealComm is
-- already active.
--
-- Casts are read from ClassicAPI's UNIT_SPELLCAST_* events and heal amounts
-- from COMBAT_LOG_EVENT_UNFILTERED. GetHotDuration() reads live HoT timers
@@ -20,18 +25,16 @@ setfenv(1, pfUI:GetEnvironment())
-- return instantly when another libpredict is already active
if pfUI.api.libpredict then return end
local heals, ress, events, hots = {}, {}, {}, {}
local heals, events, hots = {}, {}, {}
local spell_queue = { "DUMMY", "DUMMYRank 9", "TARGET" }
local player = UnitName("player")
local cache, gear_string = {}, ""
local foreignCache = {} -- [casterName][spellKey] = amount, in-memory cache for other healers
local rejuvDuration, renewDuration = 12, 15 --default durations
local ressGuidToName = {} -- [casterGuid] = casterName, for interrupted-cast cleanup
local healGuidToName = {} -- [casterGuid] = casterName, for interrupted-cast cleanup
local sentTargets = {} -- [castGUID] = target name of an own cast, from UNIT_SPELLCAST_SENT
local castTargets = {} -- [castGUID] = target name of a foreign cast, from UNIT_SPELLCAST_START
local ress_timers = {} -- [target][sender] = expiry_timestamp (60s rez window)
local RESS_TIMEOUT = 60 -- Vanilla: rez offer expires after 60s
local resOffers = {} -- [guid] = expiry of a landed resurrection the target has yet to accept
local RES_OFFER_TIMEOUT = 60 -- the target's resurrect popup declines itself after 60s
-- Localized spell names resolved once from canonical rank-1 spellIDs.
-- Every rank shares the same name, so per-rank comparisons elsewhere can
@@ -42,7 +45,6 @@ local RENEW = C_Spell.GetSpellName(139) -- Renew (Rank 1)
local REGROWTH = C_Spell.GetSpellName(8936) -- Regrowth (Rank 1)
local libpredict = CreateFrame("Frame")
libpredict:RegisterEvent("UNIT_HEALTH")
libpredict:RegisterEvent("CHAT_MSG_ADDON")
libpredict:RegisterEvent("PLAYER_TARGET_CHANGED")
libpredict:RegisterEvent("PLAYER_LOGOUT")
@@ -55,13 +57,8 @@ libpredict:SetScript("OnEvent", function()
return
end
if event == "CHAT_MSG_ADDON" and (arg1 == "HealComm" or arg1 == "CTRA") then
this:ParseChatMessage(arg4, arg2, arg1)
elseif event == "UNIT_HEALTH" then
local name = UnitName(arg1)
if name and ress[name] and not UnitIsDeadOrGhost(arg1) then
ress[name] = nil
end
if event == "CHAT_MSG_ADDON" and arg1 == "HealComm" then
this:ParseChatMessage(arg4, arg2)
end
end)
@@ -95,12 +92,6 @@ local function cacheRaidNames()
if pguid then guidNameCache[pguid] = UnitName("player") end
end
local function isRezSpell(spellId)
if not L["resurrections"] then return false end
local spellName = C_Spell.GetSpellName(spellId)
return spellName and L["resurrections"][spellName]
end
-- Build cache on world enter
libpredict:RegisterEvent("PLAYER_ENTERING_WORLD")
libpredict:RegisterEvent("RAID_ROSTER_UPDATE")
@@ -131,7 +122,6 @@ local function OnOwnCastStart(spellId, castGUID)
if L["resurrections"][spellName] then
if target then
libpredict:Ress(player, target, UnitGUID("player"))
libpredict.sender:SendHealCommMsg("Resurrection/" .. target .. "/start/")
libpredict.sender:SendResCommMsg("RES " .. target)
libpredict.sender.resurrecting = true
@@ -212,7 +202,7 @@ local function OnOwnCastStart(spellId, castGUID)
end
end
-- Own cast landed: HealStop, Regrowth timer, instant HoTs, own rez timer
-- Own cast landed: HealStop, Regrowth timer, instant HoTs
local function OnOwnCastSucceeded(spellId, castGUID)
local target = sentTargets[castGUID] or spell_queue[3]
sentTargets[castGUID] = nil
@@ -250,14 +240,10 @@ local function OnOwnCastSucceeded(spellId, castGUID)
libpredict:Hot(player, target, hotType, duration, nil, "UNIT_SPELLCAST_SUCCEEDED", rank)
libpredict.sender:SendHealCommMsg(hotType .. "/" .. target .. "/" .. duration .. "/" .. rank .. "/")
end
if isRezSpell(spellId) then
libpredict:RessSetTimer(player, target)
end
end
-- Foreign cast-time spell started (heals + rez)
local function OnOtherCastStart(unit, spellId, castGUID)
-- Foreign cast-time heal started
local function OnOtherCastStart(unit, spellId)
local spellName = C_Spell.GetSpellName(spellId)
if not spellName then return end
@@ -267,13 +253,6 @@ local function OnOtherCastStart(unit, spellId, castGUID)
local targetName = UnitSpellTargetName(unit)
if not targetName then return end
castTargets[castGUID] = targetName
-- Resurrection cast
if L["resurrections"][spellName] then
libpredict:Ress(casterName, targetName, casterGuid)
return
end
-- Heal cast: look up cached amount from a previous cast by this healer
local _, _, _, startMs, endMs = C_Spell.UnitCastingInfo(unit)
@@ -324,35 +303,31 @@ local function OnOtherCastStart(unit, spellId, castGUID)
libpredict:Heal(casterName, targetName, amount, castTime, casterGuid)
end
-- Foreign cast landed: rez timer, or the predicted heal is fulfilled
local function OnOtherCastSucceeded(unit, spellId, castGUID)
local casterGuid = UnitGUID(unit)
local casterName = resolveNameFromGuid(casterGuid)
-- Foreign cast landed: the predicted heal is fulfilled
local function OnOtherCastSucceeded(unit)
local casterName = resolveNameFromGuid(UnitGUID(unit))
if not casterName then return end
if isRezSpell(spellId) then
local targetName = castTargets[castGUID]
if targetName then
libpredict:RessSetTimer(casterName, targetName)
end
return
end
libpredict:HealStop(casterName)
end
-- Foreign cast interrupted: remove cancelled resses/heals
-- Foreign cast interrupted: remove cancelled heals
local function OnOtherCastInterrupted(unit)
local casterGuid = UnitGUID(unit)
if not casterGuid then return end
local ressName = ressGuidToName[casterGuid]
if ressName then
libpredict:RessStop(ressName)
end
local healName = healGuidToName[casterGuid]
if healName then libpredict:HealStop(healName) end
end
-- Flag the unit frames showing `guid` to redraw their resurrection icon
local function RefreshResFrames(guid)
for _, frame in pairs(pfUI.uf.frames) do
if frame.unitstr and UnitGUID(frame.unitstr) == guid then
frame.update_indicators = true
end
end
end
libpredict:SetScript("OnUpdate", function()
-- throttle cleanup - no need to check every frame
local now = pfUI.uf.now or GetTime()
@@ -366,13 +341,12 @@ libpredict:SetScript("OnUpdate", function()
end
end
-- Rez timeout: remove offer after 60s if player doesn't accept
for target, senders in pairs(ress_timers) do
for sender, expiry in pairs(senders) do
if now >= expiry then
senders[sender] = nil
if ress[target] then ress[target][sender] = nil end
end
-- drop resurrection offers the target accepted or let run out
for guid, expiry in pairs(resOffers) do
local unit = UnitTokenFromGUID(guid)
if now >= expiry or not (unit and UnitIsDeadOrGhost(unit)) then
resOffers[guid] = nil
RefreshResFrames(guid)
end
end
end)
@@ -386,8 +360,6 @@ function libpredict:ParseComm(sender, msg)
if libpredict.debug then
DEFAULT_CHAT_FRAME:AddMessage("|cff00ffff[libpredict RX]|r HealStop from " .. tostring(sender))
end
elseif msg == "Resurrection/stop/" then
msgtype = "RessStop"
elseif msg then
local msgobj = {strsplit("/", msg)}
@@ -397,10 +369,6 @@ function libpredict:ParseComm(sender, msg)
msgtype, time = "Delay", msgobj[2]
end
if msgobj[1] and msgobj[1] == "Resurrection" and msgobj[2] then
msgtype, target = "Ress", msgobj[2]
end
if msgobj[1] == "Heal" and msgobj[2] then
msgtype, target, heal, time = "Heal", msgobj[2], msgobj[3], msgobj[4]
end
@@ -472,25 +440,12 @@ end
local recentHots = {}
local DUPLICATE_WINDOW = 0.5 -- Ignoriere gleiche Nachricht innerhalb 0.5s
function libpredict:ParseChatMessage(sender, msg, comm)
local msgtype, target, heal, time, rank
if comm == "HealComm" then
msgtype, target, heal, time, rank = libpredict:ParseComm(sender, msg)
elseif comm == "CTRA" then
local _, _, cmd, ctratarget = string.find(msg, "(%a+)%s?([^#]*)")
if cmd and ctratarget and cmd == "RES" and ctratarget ~= "" and ctratarget ~= UNKNOWN then
msgtype = "Ress"
target = ctratarget
end
end
function libpredict:ParseChatMessage(sender, msg)
local msgtype, target, heal, time, rank = libpredict:ParseComm(sender, msg)
if msgtype == "Stop" and sender then
libpredict:HealStop(sender)
return
elseif ( msg == "RessStop" or msg == "RESNO" ) and sender then
libpredict:RessStop(sender)
return
elseif msgtype == "Delay" and time then
libpredict:HealDelay(sender, time)
elseif msgtype == "Heal" and target and heal and time then
@@ -501,29 +456,6 @@ function libpredict:ParseChatMessage(sender, msg, comm)
else
libpredict:Heal(sender, target, heal, time)
end
elseif msgtype == "Ress" then
-- HealComm: only for other players (own resses tracked via UNIT_SPELLCAST_START)
local playerName = UnitName("player")
if sender ~= playerName then
local senderGuid
for i = 1, GetNumRaidMembers() do
local unit = "raid" .. i
if UnitName(unit) == sender then
senderGuid = UnitGUID(unit)
break
end
end
if not senderGuid then
for i = 1, GetNumPartyMembers() do
local unit = "party" .. i
if UnitName(unit) == sender then
senderGuid = UnitGUID(unit)
break
end
end
end
libpredict:Ress(sender, target, senderGuid)
end
elseif msgtype == "Hot" then
-- Duplicate check: ignore same sender+target+spell within DUPLICATE_WINDOW
local now = pfUI.uf.now or GetTime()
@@ -672,41 +604,6 @@ function libpredict:HealDelay(sender, delay)
end
end
function libpredict:Ress(sender, target, senderGuid)
ress[target] = ress[target] or {}
ress[target][sender] = true
if senderGuid then ressGuidToName[senderGuid] = sender end
-- no timer here - timer is only set once the cast actually completes
end
function libpredict:RessSetTimer(sender, target)
ress_timers[target] = ress_timers[target] or {}
local existing = ress_timers[target][sender]
if not existing or GetTime() >= existing then
ress_timers[target][sender] = GetTime() + RESS_TIMEOUT
else
end
end
function libpredict:RessStop(sender)
local now = GetTime()
for ttarget, t in pairs(ress) do
for tsender in pairs(ress[ttarget]) do
if sender == tsender then
local expiry = ress_timers[ttarget] and ress_timers[ttarget][tsender]
if not expiry or now >= expiry then
ress[ttarget][tsender] = nil
if ress_timers[ttarget] then ress_timers[ttarget][tsender] = nil end
else
end
end
end
end
for guid, name in pairs(ressGuidToName) do
if name == sender then ressGuidToName[guid] = nil end
end
end
function libpredict:UnitGetIncomingHeals(unit)
if not unit then return 0 end
local name = UnitName(unit)
@@ -729,21 +626,10 @@ function libpredict:UnitGetIncomingHeals(unit)
return sumheal
end
function libpredict:UnitHasIncomingResurrection(unit)
if not unit then return nil end
local name = UnitName(unit)
if not name then return nil end
if not ress[name] then
return nil
else
for sender, val in pairs(ress[name]) do
if val == true then
return val
end
end
end
return nil
-- Whether a resurrection is being cast at the unit, or landed and waits for
-- the unit to accept it
function libpredict:UnitHasResurrection(unit)
return UnitHasIncomingResurrection(unit) or resOffers[UnitGUID(unit)] ~= nil
end
local realm = GetRealmName()
@@ -834,7 +720,6 @@ local pendingHots = {}
-- Gather Data by User Actions
hooksecurefunc("CastSpell", function(id, bookType)
if not libpredict.sender.enabled then return end
local effect, rank = libspell.GetSpellInfo(id, bookType)
if not effect then return end
spell_queue[1] = effect
@@ -887,7 +772,6 @@ hooksecurefunc("CastSpell", function(id, bookType)
end)
hooksecurefunc("CastSpellByName", function(effect, target)
if not libpredict.sender.enabled then return end
local effect, rank = libspell.GetSpellInfo(effect)
if not effect then return end
@@ -951,7 +835,6 @@ hooksecurefunc("CastSpellByName", function(effect, target)
end)
hooksecurefunc("UseAction", function(slot, target, selfcast)
if not libpredict.sender.enabled then return end
if not IsCurrentAction(slot) then return end
local kind, id = GetActionInfo(slot)
local effect, rank
@@ -1012,8 +895,10 @@ hooksecurefunc("UseAction", function(slot, target, selfcast)
end)
libpredict.sender = CreateFrame("Frame", "pfPredictionSender", UIParent)
-- cleared when a HealComm addon already broadcasts this player's casts
libpredict.sender.enabled = true
libpredict.sender.SendHealCommMsg = function(self, msg)
if not self.enabled then return end
-- Smart channel selection: Only send to relevant channel to avoid duplicates
if IsInRaid() then
-- In raid: Only send to RAID (includes all raid members)
@@ -1026,6 +911,7 @@ libpredict.sender.SendHealCommMsg = function(self, msg)
-- BG groups are handled by RAID channel
end
libpredict.sender.SendResCommMsg = function(self, msg)
if not self.enabled then return end
-- Smart channel selection: Only send to relevant channel to avoid duplicates
if IsInRaid() then
-- In raid: Only send to RAID (includes all raid members)
@@ -1071,7 +957,6 @@ libpredict.sender:RegisterEvent("UNIT_SPELLCAST_DELAYED")
libpredict.sender:RegisterEvent("UNIT_SPELLCAST_SUCCEEDED")
libpredict.sender:RegisterEvent("UNIT_SPELLCAST_FAILED")
libpredict.sender:RegisterEvent("UNIT_SPELLCAST_INTERRUPTED")
libpredict.sender:RegisterEvent("UNIT_SPELLCAST_STOP")
libpredict.sender:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED")
-- Shared cleanup helper for failed/interrupted casts
@@ -1083,7 +968,6 @@ local function onCastFailed()
s.healing = nil
elseif s.resurrecting then
local target = s.current_cast_target or spell_queue[3]
libpredict:RessStop(UnitName("player"))
s:SendHealCommMsg("Resurrection/stop/")
s:SendResCommMsg("RESNO " .. (target or ""))
s.resurrecting = nil
@@ -1134,11 +1018,20 @@ local function OnHeal()
end
end
-- A landed resurrection: the target has an offer to accept, which outlasts
-- UnitHasIncomingResurrection's end with the cast
local function OnResurrect()
-- arg8=destGUID
resOffers[arg8] = GetTime() + RES_OFFER_TIMEOUT
RefreshResFrames(arg8)
end
local lastOtherCast = {} -- [event] = castGUID of the last other-unit event handled
libpredict.sender:SetScript("OnEvent", function()
if event == "COMBAT_LOG_EVENT_UNFILTERED" then
if arg2 == "SPELL_HEAL" then OnHeal() end
if arg2 == "SPELL_HEAL" then OnHeal()
elseif arg2 == "SPELL_RESURRECT" then OnResurrect() end
return
end
@@ -1174,13 +1067,11 @@ libpredict.sender:SetScript("OnEvent", function()
lastOtherCast[event] = arg2
if event == "UNIT_SPELLCAST_START" then
OnOtherCastStart(unit, arg3, arg2)
OnOtherCastStart(unit, arg3)
elseif event == "UNIT_SPELLCAST_SUCCEEDED" then
OnOtherCastSucceeded(unit, arg3, arg2)
OnOtherCastSucceeded(unit)
elseif event == "UNIT_SPELLCAST_INTERRUPTED" then
OnOtherCastInterrupted(unit)
elseif event == "UNIT_SPELLCAST_STOP" then
castTargets[arg2] = nil
end
end)
+1 -14
View File
@@ -382,8 +382,6 @@ pfUI:RegisterModule("actionbar", function ()
end
elseif self.spellID then
GameTooltip:SetSpellByID(self.spellID)
elseif self.spellslot and self.booktype then
GameTooltip:SetSpell(self.spellslot, self.booktype)
else
GameTooltip:SetAction(self.id)
end
@@ -456,11 +454,6 @@ pfUI:RegisterModule("actionbar", function ()
id = self.bar == 1 and self.slot or sid-((self.bar)-1)*12
end
-- overwrite with spell macro texture where possible
if self.spellslot and self.booktype then
texture = GetSpellTexture(self.spellslot, self.booktype)
end
if not self.showempty and self.backdrop and not texture and grid == 0 then
self.backdrop:Hide()
self.hide = true
@@ -590,9 +583,6 @@ pfUI:RegisterModule("actionbar", function ()
start, duration, enable = GetShapeshiftFormCooldown(button.id)
elseif button.bar == 12 then
start, duration, enable = GetPetActionCooldown(button.id)
elseif button.spellslot and button.booktype then
start, duration = GetSpellCooldown(button.spellslot, button.booktype)
enable = 1
else
start, duration, enable = GetActionCooldown(button.id)
end
@@ -1150,8 +1140,6 @@ pfUI:RegisterModule("actionbar", function ()
f.count:SetJustifyH("RIGHT")
f.count:SetJustifyV("BOTTOM")
f.scanmacro, f.spellslot, f.booktype = nil, nil, nil
-- range glow color
f.rangeColor = GetStringColorObject(C.bars.rangecolor)
@@ -1218,8 +1206,7 @@ pfUI:RegisterModule("actionbar", function ()
end
-- the stored layout is invalid, temporary fallback
local _, _, fcols, frows = string.find(tostring(formfactor or ""), "(%d+)%s*x%s*(%d+)")
if not ((fcols and frows) or pfGridmath[buttons][BarLayoutFormfactor(formfactor)]) then
if not fcols2 or not frows2 then
formfactor = BarLayoutOptions(buttons)[1]
end
+117 -144
View File
@@ -69,12 +69,42 @@ pfUI:RegisterModule("addons", function ()
pfUI.addons:Hide()
end)
-- installed addons, one entry per row of the addon list
local addons = {}
for i = 1, GetNumAddOns() do
local name, title, notes = GetAddOnInfo(i)
addons[i] = {
index = i,
name = name,
title = title,
notes = notes,
author = GetAddOnMetadata(name, "Author"),
version = GetAddOnMetadata(name, "Version"),
deps = { GetAddOnDependencies(i) }, -- required (.toc Dependencies)
optdeps = { C_AddOns.GetAddOnOptionalDependencies(i) }, -- optional (.toc OptionalDeps)
}
end
local function IsAddonEnabled(index)
local _, _, _, enabled = GetAddOnInfo(index)
return enabled
end
local function SetAddonEnabled(index, enabled)
if enabled then
EnableAddOn(index)
else
DisableAddOn(index)
end
pfUI.addons.hasChanged = true
end
-- addons profile
local function GetSelectedAddonsList()
local selected_addons = {}
for i, frame in ipairs(pfUI.addons.list) do
if frame.input:GetChecked() then
table.insert(selected_addons, frame.aname)
for i, addon in ipairs(addons) do
if IsAddonEnabled(i) then
table.insert(selected_addons, addon.name)
end
end
return selected_addons
@@ -90,28 +120,20 @@ pfUI:RegisterModule("addons", function ()
pfUI.addons.profile.dropdown:SetSelectionByText(profile)
-- load profile
local selected_list = pfUI_addon_profiles[profile]
for i=1, GetNumAddOns() do
local active = false
local f = pfUI.addons.list[i]
local selected = {}
for _, name in ipairs(pfUI_addon_profiles[profile]) do
selected[name] = true
end
for i_i, n in ipairs(selected_list) do
if f.aname == n then
active = true
break
end
end
if active then
if not f.input:GetChecked() then
f.input:Click()
end
else
if f.input:GetChecked() then
f.input:Click()
end
for i, addon in ipairs(addons) do
if selected[addon.name] and not IsAddonEnabled(i) then
SetAddonEnabled(i, true)
elseif not selected[addon.name] and IsAddonEnabled(i) then
SetAddonEnabled(i, false)
end
end
pfUI.addons.scroll:ReinitializeFrames()
end
-- addon profiles
@@ -193,23 +215,6 @@ pfUI:RegisterModule("addons", function ()
return menu
end)
-- addon list: scroll frame
pfUI.addons.scroll = CreateScrollFrame("pfAddonListScroll", pfUI.addons)
pfUI.addons.scroll:SetSize(360, 410)
pfUI.addons.scroll:SetPoint("BOTTOM", 0, 10)
pfUI.addons.scroll.backdrop = CreateFrame("Frame", nil, pfUI.addons.scroll)
pfUI.addons.scroll.backdrop:SetFrameLevel(1)
pfUI.addons.scroll.backdrop:SetPoint("TOPLEFT", pfUI.addons.scroll, "TOPLEFT", -5, 5)
pfUI.addons.scroll.backdrop:SetPoint("BOTTOMRIGHT", pfUI.addons.scroll, "BOTTOMRIGHT", 5, -5)
CreateBackdrop(pfUI.addons.scroll.backdrop, nil, true)
-- addon list: scroll parent
pfUI.addons.list = CreateScrollChild("pfAddonList", pfUI.addons.scroll)
pfUI.addons.list:RegisterEvent("ADDON_LOADED")
pfUI.addons.list:RegisterEvent("PLAYER_ENTERING_WORLD")
pfUI.addons.list:SetHeight(GetNumAddOns() * 25 + 26)
local function AddDependencyLines(header, deps)
if not deps or table.getn(deps) == 0 then return end
GameTooltip:AddLine(" ")
@@ -225,137 +230,105 @@ pfUI:RegisterModule("addons", function ()
end
end
local function AddonOnEnter()
this:SetBackdropBorderColor(1,1,1,.08)
local function RowOnEnter(self)
local addon = self.addon
self:SetBackdropBorderColor(1,1,1,.08)
GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
GameTooltip:SetText(this.atitle)
if this.aversion then
GameTooltip:AddDoubleLine(T["Version"], this.aversion, 1,1,1, .2,1,.8)
GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
GameTooltip:SetText(addon.title)
if addon.version then
GameTooltip:AddDoubleLine(T["Version"], addon.version, 1,1,1, .2,1,.8)
end
if this.aauthor then
GameTooltip:AddDoubleLine(T["Author"], this.aauthor, 1,1,1, .2,1,.8)
if addon.author then
GameTooltip:AddDoubleLine(T["Author"], addon.author, 1,1,1, .2,1,.8)
end
GameTooltip:AddLine(this.anote, .75,.75,.75,1)
AddDependencyLines(T["Dependencies"], this.adeps)
AddDependencyLines(T["Optional Dependencies"], this.aoptdeps)
GameTooltip:AddLine(addon.notes, .75,.75,.75,1)
AddDependencyLines(T["Dependencies"], addon.deps)
AddDependencyLines(T["Optional Dependencies"], addon.optdeps)
GameTooltip:SetWidth(180)
GameTooltip:Show()
end
local function AddonOnLeave()
this:SetBackdropBorderColor(1,1,1,.04)
local function RowOnLeave(self)
self:SetBackdropBorderColor(1,1,1,.04)
GameTooltip:Hide()
end
local function AddonOnClick()
this.input:Click()
local function RowOnClick(self)
self.input:Click()
end
local function InputOnClick()
if this:GetChecked() then
EnableAddOn(this:GetID())
else
DisableAddOn(this:GetID())
end
pfUI.addons.hasChanged = true
local function InputOnClick(self)
SetAddonEnabled(self:GetParent().addon.index, self:GetChecked())
local id, name = pfUI.addons.profile.dropdown:GetSelection()
local _, name = pfUI.addons.profile.dropdown:GetSelection()
name = name or T["Current"]
pfUI_addon_profiles[name] = GetSelectedAddonsList()
end
pfUI.addons.list:SetScript("OnEvent", function()
for i=1, GetNumAddOns() do
local aname, atitle, anote = GetAddOnInfo(i)
local aauthor = GetAddOnMetadata(aname, "Author")
local aversion = GetAddOnMetadata(aname, "Version")
local function InitRow(row, addon)
-- one-time setup of a pooled row
if not row.input then
row:SetBackdrop(pfUI.backdrop_hover)
row:SetBackdropBorderColor(1,1,1,.04)
-- basic frame
if not pfUI.addons.list[i] then
pfUI.addons.list[i] = CreateFrame("Button", nil, pfUI.addons.list)
local frame = pfUI.addons.list[i]
frame.aname = aname
frame.atitle = atitle
frame.anote = anote
frame.aauthor = aauthor
frame.aversion = aversion
frame.adeps = { GetAddOnDependencies(i) } -- required (.toc Dependencies)
frame.aoptdeps = { C_AddOns.GetAddOnOptionalDependencies(i) } -- optional (.toc OptionalDeps)
row:EnableMouse(true)
row:SetScript("OnEnter", RowOnEnter)
row:SetScript("OnLeave", RowOnLeave)
row:SetScript("OnClick", RowOnClick)
frame:SetSize(340, 25)
frame:SetPoint("TOPLEFT", 5, i * -25 + 20)
-- caption
row.caption = row:CreateFontString(nil, "LOW", "GameFontNormal")
row.caption:SetFont(pfUI.font_default, C.global.font_size + 2, "OUTLINE")
row.caption:SetPoint("LEFT", row, "LEFT", 2, 0)
row.caption:SetFontObject(GameFontWhite)
row.caption:SetJustifyH("LEFT")
frame:SetBackdrop(pfUI.backdrop_hover)
frame:SetBackdropBorderColor(1,1,1,.04)
frame:EnableMouse(1)
frame:SetScript("OnEnter", AddonOnEnter)
frame:SetScript("OnLeave", AddonOnLeave)
frame:SetScript("OnClick", AddonOnClick)
-- caption
frame.caption = frame:CreateFontString("Status", "LOW", "GameFontNormal")
frame.caption:SetFont(pfUI.font_default, C.global.font_size + 2, "OUTLINE")
frame.caption:SetPoint("LEFT", frame, "LEFT", 2, 0)
frame.caption:SetFontObject(GameFontWhite)
frame.caption:SetJustifyH("LEFT")
frame.caption:SetText(atitle)
-- input field
frame.input = CreateFrame("CheckButton", nil, frame, "UICheckButtonTemplate")
frame.input:SetFrameLevel(3)
frame.input:SetNormalTexture("")
frame.input:SetPushedTexture("")
frame.input:SetHighlightTexture("")
CreateBackdrop(frame.input, nil, true)
frame.input:SetBackdropBorderColor(.3,.3,.3,1)
frame.input:SetWidth(14)
frame.input:SetHeight(14)
frame.input:SetPoint("RIGHT" , -5, 1)
frame.input:SetID(i)
frame.input:SetScript("OnClick", InputOnClick)
end
if IsAddOnLoaded(i) then
pfUI.addons.list[i].input:SetChecked()
pfUI.addons.list[i].caption:SetAlpha(1)
else
pfUI.addons.list[i].caption:SetAlpha(.5)
end
-- input field
row.input = CreateFrame("CheckButton", nil, row, "UICheckButtonTemplate")
row.input:SetNormalTexture("")
row.input:SetPushedTexture("")
row.input:SetHighlightTexture("")
CreateBackdrop(row.input, nil, true)
row.input:SetBackdropBorderColor(.3,.3,.3,1)
row.input:SetSize(14, 14)
row.input:SetPoint("RIGHT" , -5, 1)
row.input:SetScript("OnClick", InputOnClick)
end
end)
-- show only frames in visible range
function pfUI.addons.scroll:ShowVisibleRange()
local top_index, bottom_index, range
range = floor(pfUI.addons.scroll:GetHeight() / 25) + 1
top_index = floor(pfUI.addons.scroll:GetVerticalScroll() / 25) > 0 and floor(pfUI.addons.scroll:GetVerticalScroll() / 25) or 1
bottom_index = top_index + range
for i=1, GetNumAddOns() do
if i >= top_index and i <= bottom_index then
pfUI.addons.list[i]:Show()
else
pfUI.addons.list[i]:Hide()
end
end
row.addon = addon
row.caption:SetText(addon.title)
row.caption:SetAlpha(IsAddOnLoaded(addon.index) and 1 or .5)
row.input:SetChecked(IsAddonEnabled(addon.index))
end
pfUI.addons.scroll.slider:SetScript("OnValueChanged", function()
this:GetParent():SetVerticalScroll(this:GetValue())
end)
pfUI.addons.scroll:SetScript("OnScrollRangeChanged", function()
this.slider:SetMinMaxValues(1, floor(this:GetVerticalScrollRange() + 25))
end)
pfUI.addons.scroll:SetScript("OnShow", function()
this.slider:SetValue(floor(this:GetVerticalScroll()))
pfUI.addons.scroll:ShowVisibleRange()
end)
pfUI.addons.scroll:SetScript("OnVerticalScroll", function()
pfUI.addons.scroll:ShowVisibleRange()
this.slider:SetValue(floor(this:GetVerticalScroll()))
-- addon list: a ScrollBox keeps rows only for the addons in view
pfUI.addons.scroll = CreateFrame("Frame", "pfAddonListScroll", pfUI.addons, "WowScrollBoxList")
pfUI.addons.scroll:SetSize(360, 410)
pfUI.addons.scroll:SetPoint("BOTTOM", 0, 10)
-- the ScrollBox clips its children, so the backdrop is a sibling
pfUI.addons.scroll.backdrop = CreateFrame("Frame", nil, pfUI.addons)
pfUI.addons.scroll.backdrop:SetPoint("TOPLEFT", pfUI.addons.scroll, "TOPLEFT", -5, 5)
pfUI.addons.scroll.backdrop:SetPoint("BOTTOMRIGHT", pfUI.addons.scroll, "BOTTOMRIGHT", 5, -5)
CreateBackdrop(pfUI.addons.scroll.backdrop, nil, true)
local scrollbar = CreateFrame("EventFrame", nil, pfUI.addons, "pfUIScrollBarTemplate")
scrollbar:SetPoint("TOPRIGHT", pfUI.addons.scroll, "TOPRIGHT")
scrollbar:SetPoint("BOTTOMRIGHT", pfUI.addons.scroll, "BOTTOMRIGHT")
local view = CreateScrollBoxListLinearView(5, 5, 5, 15)
view:SetElementExtent(25)
view:SetElementInitializer("Button", InitRow)
ScrollUtil.InitScrollBoxListWithScrollBar(pfUI.addons.scroll, scrollbar, view)
pfUI.addons.scroll:SetDataProvider(CreateDataProvider(addons))
-- a load-on-demand addon that loads brightens its row
pfUI.addons.scroll:RegisterEvent("ADDON_LOADED")
pfUI.addons.scroll:SetScript("OnEvent", function(self)
self:ReinitializeFrames()
end)
end)
+10 -11
View File
@@ -29,20 +29,20 @@ pfUI:RegisterModule("afkcam", function ()
overlay:SetFrameStrata("DIALOG")
overlay:SetAllPoints(WorldFrame)
overlay:SetScript("OnMouseUp",function()
SendChatMessage("","AFK")
this._parent:stop()
end)
-- /afk toggles, so send it only while AFK is still set: once the client
-- or the server has cleared it, /afk would set it again
local function Wake()
if IsChatAFK() then SendChatMessage("", "AFK") end
afkcam:stop()
end
overlay:SetScript("OnMouseUp", Wake)
overlay:SetScript("OnShow", function()
this:EnableKeyboard(true)
end)
overlay:SetScript("OnHide", function()
this:EnableKeyboard(false)
end)
overlay:SetScript("OnKeyUp",function()
SendChatMessage("","AFK")
this._parent:stop()
end)
overlay:SetScript("OnKeyUp", Wake)
overlay:Hide()
local chat = CreateFrame("ScrollingMessageFrame", "pfAFKCamChat", overlay)
@@ -82,7 +82,6 @@ pfUI:RegisterModule("afkcam", function ()
end)
overlay.chat = chat
overlay._parent = afkcam
afkcam.overlay = overlay
local clock = CreateFrame("Frame", "pfAFKClock", overlay)
@@ -166,9 +165,9 @@ pfUI:RegisterModule("afkcam", function ()
if event == "PLAYER_FLAGS_CHANGED" then
if UnitIsAFK('player') then
delay:Show()
elseif delay:IsVisible() then
else
delay:Hide()
this:stop()
if this._spinning then this:stop() end
end
else
if this._spinning then
+29 -55
View File
@@ -6,8 +6,6 @@ pfUI:RegisterModule("bags", function ()
[1804] = {frame="picklock"},
}
local scanner = libtipscan:GetScanner("input_search")
local function BagSortOpts()
return {
reverse = C.appearance.bags.sortreverse == "1",
@@ -95,6 +93,7 @@ pfUI:RegisterModule("bags", function ()
pfUI.bag:RegisterEvent("ITEM_LOCK_CHANGED")
pfUI.bag:RegisterEvent("SPELLS_CHANGED")
pfUI.bag:RegisterEvent("MERCHANT_CLOSED")
pfUI.bag:RegisterEvent("INVENTORY_SEARCH_UPDATE")
pfUI.bag.delay = { UpdateBag = {} }
@@ -122,6 +121,11 @@ pfUI:RegisterModule("bags", function ()
pfUI.bag:UpdateItemLock()
end
if this.delay.UpdateSearch then
this.delay.UpdateSearch = nil
pfUI.bag:UpdateSearch()
end
for bag in pairs(this.delay.UpdateBag) do
this.delay.UpdateBag[bag] = nil
pfUI.bag:UpdateBag(bag)
@@ -159,6 +163,10 @@ pfUI:RegisterModule("bags", function ()
this.delay.UpdateItemLock = true
end
if event == "INVENTORY_SEARCH_UPDATE" then
this.delay.UpdateSearch = true
end
if event == "PLAYERBANKSLOTS_CHANGED" then
this.delay.UpdateBag[-1] = true
end
@@ -377,6 +385,21 @@ pfUI:RegisterModule("bags", function ()
end
end
-- dim the slots that the bag search (C_Container.SetItemSearch) filters out
local searchInfo = {}
function pfUI.bag:UpdateSlotSearch(bag, slot)
local info = C_Container.GetContainerItemInfo(bag, slot, searchInfo)
pfUI.bags[bag].slots[slot].frame:SetAlpha(info and info.isFiltered and .25 or 1)
end
function pfUI.bag:UpdateSearch()
for bag = -2, 11 do
for slot in ipairs(pfUI.bags[bag].slots) do
pfUI.bag:UpdateSlotSearch(bag, slot)
end
end
end
function pfUI.bag:UpdateSlot(bag, slot)
if not pfUI.bags[bag] then return end
@@ -465,6 +488,7 @@ pfUI:RegisterModule("bags", function ()
SetItemButtonTexture(pfUI.bags[bag].slots[slot].frame, texture)
SetItemButtonCount(pfUI.bags[bag].slots[slot].frame, count)
SetItemButtonDesaturated(pfUI.bags[bag].slots[slot].frame, locked, 0.5, 0.5, 0.5)
pfUI.bag:UpdateSlotSearch(bag, slot)
local hasItem = texture and 1 or nil
pfUI.bags[bag].slots[slot].frame.hasItem = hasItem
@@ -1010,7 +1034,6 @@ pfUI:RegisterModule("bags", function ()
-- bag search
if not frame.search then
frame.search = CreateFrame("Frame", "pfBagSearch", frame)
frame.search.db = {}
frame.search:SetHeight(12)
frame.search:SetPoint("TOPLEFT", frame, "TOPLEFT", default_border, -default_border)
frame.search:SetPoint("TOPRIGHT", frame.sort, "TOPLEFT", -default_border*3, -default_border)
@@ -1035,51 +1058,12 @@ pfUI:RegisterModule("bags", function ()
frame.search.edit:SetScript("OnEditFocusLost", function()
this:SetText(T["Search"])
for bag=-2, 11 do
local bagsize = GetContainerNumSlots(bag)
if bag == -2 and pfUI.bag.showKeyring == true then bagsize = GetKeyRingSize() end
for slot=1, bagsize do
pfUI.bags[bag].slots[slot].frame:SetAlpha(1)
end
end
C_Container.SetItemSearch("")
end)
frame.search.edit:SetScript("OnTextChanged", function()
if this:GetText() == T["Search"] then return end
for bag=-2, 11 do
local bagsize = GetContainerNumSlots(bag)
if bag == -2 and pfUI.bag.showKeyring == true then bagsize = GetKeyRingSize() end
for slot=1, bagsize do
pfUI.bags[bag].slots[slot].frame:SetAlpha(.25)
local texture, itemCount, locked, quality, readable = GetContainerItemInfo(bag, slot)
if itemCount then
local itemLink = GetContainerItemLink(bag, slot)
local itemstring = string.sub(itemLink, string.find(itemLink, "%[")+1, string.find(itemLink, "%]")-1)
if C.appearance.bags.fulltext == "1" then
if not frame.search.db[itemLink] then
scanner:SetBagItem(bag, slot)
local text = scanner:Text()
local str = ""
for k, v in pairs(text) do
str = str .. (v[1] or "") .. (v[2] or "")
end
frame.search.db[itemLink] = strlower(str)
end
if strfind(frame.search.db[itemLink], strlower(this:GetText()), 1, true) then
pfUI.bags[bag].slots[slot].frame:SetAlpha(1)
end
end
if strfind(strlower(itemstring), strlower(this:GetText()), 1, true) then
pfUI.bags[bag].slots[slot].frame:SetAlpha(1)
end
end
end
end
C_Container.SetItemSearch(this:GetText())
end)
frame.search:SetScript("OnLeave", function()
@@ -1094,17 +1078,7 @@ pfUI:RegisterModule("bags", function ()
frame.search:SetScript("OnHide", function()
frame.search.edit:SetText(T["Search"])
for bag = -2, 11 do
if pfUI.bags[bag] then
local bagsize = GetContainerNumSlots(bag)
if bag == -2 and pfUI.bag.showKeyring == true then bagsize = GetKeyRingSize() end
for slot = 1, bagsize do
if pfUI.bags[bag] and pfUI.bags[bag].slots[slot] then
pfUI.bags[bag].slots[slot].frame:SetAlpha(1)
end
end
end
end
C_Container.SetItemSearch("")
end)
end
else
+6 -11
View File
@@ -150,8 +150,8 @@ pfUI:RegisterModule("castbar", function ()
-- One-shot poll: read C_Spell for the bar's unit, stamp or clear. Called
-- from event handlers (cast start, target/focus change), never per-frame.
local function RefreshBar(cb, rank)
local query = cb.unitstr ~= "" and cb.unitstr or cb.unitname
if not query or (cb.unitstr ~= "" and not UnitExists(cb.unitstr)) then
local query = cb.unitstr
if not UnitExists(query) then
ClearBar(cb)
return
end
@@ -178,7 +178,7 @@ pfUI:RegisterModule("castbar", function ()
end
end
local function CreateCastbar(name, parent, unitstr, unitname)
local function CreateCastbar(name, parent, unitstr)
local cb = CreateFrame("Frame", name, parent or UIParent)
cb:SetHeight(C.global.font_size * 1.5)
@@ -186,7 +186,6 @@ pfUI:RegisterModule("castbar", function ()
cb:SetFrameLevel(8)
cb.unitstr = unitstr
cb.unitname = unitname
-- icon
cb.icon = CreateFrame("Frame", nil, cb)
@@ -277,7 +276,7 @@ pfUI:RegisterModule("castbar", function ()
-- Non-player bars: if the unit disappears (target died / detarget),
-- drop the bar immediately.
if this.unitstr ~= "" and this.unitstr ~= "player" and not UnitExists(this.unitstr) then
if this.unitstr ~= "player" and not UnitExists(this.unitstr) then
ClearBar(this)
return
end
@@ -423,13 +422,12 @@ pfUI:RegisterModule("castbar", function ()
-- endMs. delay is a positive magnitude; OnUpdate signs it "+" for casts
-- and "-" for channels. Skipped during a tradeskill merge.
if this.endTime and not this.tradeskillTotal then
local query = this.unitstr ~= "" and this.unitstr or this.unitname
local startMs, endMs
if this.isChannel then
local _, _, _, s, e = C_Spell.UnitChannelInfo(query)
local _, _, _, s, e = C_Spell.UnitChannelInfo(unit)
startMs, endMs = s, e
else
local _, _, _, s, e = C_Spell.UnitCastingInfo(query)
local _, _, _, s, e = C_Spell.UnitCastingInfo(unit)
startMs, endMs = s, e
end
if startMs and endMs then
@@ -544,9 +542,6 @@ pfUI:RegisterModule("castbar", function ()
pfUI.castbar.focus:SetHeight(C.castbar.focus.height)
end
-- bind castbar to the "focus" unit token; the GUID resolves at read time
pfUI.castbar.focus.unitstr = "focus"
UpdateMovable(pfUI.castbar.focus)
end
end)
+5 -28
View File
@@ -29,38 +29,15 @@ pfUI:RegisterModule("focus", function ()
end)
-- /focus and /clearfocus live in ClassicAPI's SlashCommandsRegistry now.
-- /focusname is pfUI-specific because the engine has no name→GUID
-- lookup for off-screen units — we resolve via a short target-swap.
-- `/focus Name` takes the nearest prefix match; /focusname tries the exact
-- name first, so "Bob" still wins over a nearer "Bobby".
pfUI.api.RegisterSlashCommand("PFFOCUSNAME", { '/focusname', '/pffocusname' }, function(msg)
if msg == "" then return end
local prevGUID = UnitGUID("target")
local prevPlayer = UnitIsUnit("target", "player")
-- Suppress async "Unknown unit" errors fired by TargetByName misses.
UIErrorsFrame:UnregisterEvent("UI_ERROR_MESSAGE")
-- Try exact match first, then prefix match via /target.
TargetByName(msg, true)
if not UnitExists("target") then
SlashCmdList.TARGET(msg)
end
if UnitExists("target") then
FocusUnit("target")
end
RunNextFrame(function()
UIErrorsFrame:RegisterEvent("UI_ERROR_MESSAGE")
end)
if prevGUID and prevGUID ~= "0x0000000000000000" then
TargetUnit(prevGUID)
elseif prevPlayer then
TargetUnit("player")
else
ClearTarget()
local unit = UnitTokenFromName(msg, true) or UnitTokenFromName(msg)
if unit then
FocusUnit(unit)
end
end, true)
-1
View File
@@ -2459,7 +2459,6 @@ pfUI:RegisterModule("gui", function ()
CreateConfig(nil, T["Hide Chat When Bags Are Opened"], C.appearance.bags, "hidechat", "checkbox")
CreateConfig(nil, T["Bagslots Per Row"], C.appearance.bags, "bagrowlength")
CreateConfig(nil, T["Bankslots Per Row"], C.appearance.bags, "bankrowlength")
CreateConfig(nil, T["Enable Full-Text Search"], C.appearance.bags, "fulltext")
CreateConfig(nil, T["Item Slot Size"], C.appearance.bags, "icon_size")
CreateConfig(Reload, T["Reverse Cooldown Animation"], C.appearance.bags, "cooldown_reverse", "checkbox")
CreateConfig(nil, T["Auto Sell Grey Items"], C.global, "autosell", "checkbox")
+53 -3
View File
@@ -105,11 +105,12 @@ pfUI:RegisterModule("panel", function()
do -- FPS & Lag
local widget = CreateFrame("Frame", "pfPanelWidgetLag", UIParent)
local lag, fps, laghex, fpshex, _
widget.Tooltip = function()
local lag, fps, laghex, fpshex, owner, _
widget.Tooltip = function(self)
owner = self
local active = 0
GameTooltip:ClearLines()
GameTooltip_SetDefaultAnchor(GameTooltip, this)
GameTooltip_SetDefaultAnchor(GameTooltip, self)
GameTooltip:AddLine("|cff555555" .. T["Systeminfo"])
for i=1, GetNumAddOns() do
if IsAddOnLoaded(i) then
@@ -128,6 +129,51 @@ pfUI:RegisterModule("panel", function()
GameTooltip:AddLine(" ")
GameTooltip:AddDoubleLine(T["Memory Usage"], "|cffffffff" .. memmb)
GameTooltip:AddDoubleLine(T["Next Memory Cleanup"], "|cffffffff" .. gcmb)
-- AddOn memory usage (see MainMenuBarPerformanceBarFrame_OnEnter in MainMenuBar.lua)
local ADDON_MEM_KB_ABBR = _G.ADDON_MEM_KB_ABBR or "(%.0f KB) %s";
local ADDON_MEM_MB_ABBR = _G.ADDON_MEM_MB_ABBR or "(%.2f MB) %s";
local TOTAL_MEM_KB_ABBR = _G.TOTAL_MEM_KB_ABBR or "AddOn Memory: %.0f KB";
local TOTAL_MEM_MB_ABBR = _G.TOTAL_MEM_MB_ABBR or "AddOn Memory: %.2f MB";
UpdateAddOnMemoryUsage()
local totalMem = 0
local topAddOns = {}
for i = 1, GetNumAddOns() do
local mem = GetAddOnMemoryUsage(i)
if mem and mem > 0 then
totalMem = totalMem + mem
for j = 1, 3 do
if not topAddOns[j] or mem > topAddOns[j].value then
for k = 3, j + 1, -1 do
topAddOns[k] = topAddOns[k - 1]
end
topAddOns[j] = { value = mem, name = C_AddOns.GetAddOnName(i) }
break
end
end
end
end
if totalMem > 0 then
local str
if totalMem > 1000 then
str = format(TOTAL_MEM_MB_ABBR, totalMem / 1000)
else
str = format(TOTAL_MEM_KB_ABBR, totalMem)
end
GameTooltip:AddLine(" ")
GameTooltip:AddLine(str, 1, 1, 1)
for i = 1, 3 do
if not topAddOns[i] then break end
if topAddOns[i].value > 1000 then
str = format(ADDON_MEM_MB_ABBR, topAddOns[i].value / 1000, topAddOns[i].name)
else
str = format(ADDON_MEM_KB_ABBR, topAddOns[i].value, topAddOns[i].name)
end
GameTooltip:AddLine(str, 1, 1, 1)
end
end
GameTooltip:AddLine(" ")
GameTooltip:AddDoubleLine(T["Network Down"], "|cffffffff" .. round(nin,1) .. "KB/s")
GameTooltip:AddDoubleLine(T["Network Up"], "|cffffffff" .. round(nout,1) .. "KB/s")
@@ -157,6 +203,10 @@ pfUI:RegisterModule("panel", function()
end
pfUI.panel:OutputPanel("fps", fps .. " " .. T["fps"] .. " & " .. lag .. " " .. T["ms"], widget.Tooltip, widget.Click)
if owner and GameTooltip:IsOwned(owner) then
widget.Tooltip(owner)
end
end)
end
+2 -14
View File
@@ -1172,21 +1172,9 @@ pfUI:RegisterModule("thirdparty-vanilla", function()
end)
local EnableHealComm = function()
-- hook healcomm's addon message to parse single-player events
-- HealComm-1.0 already broadcasts this player's casts: keep libpredict
-- tracking them, but stop it sending a second copy
if AceLibrary and AceLibrary:HasInstance("HealComm-1.0") and pfUI.api.libpredict then
local HealComm = AceLibrary("HealComm-1.0")
-- use pfUI frames to draw healComm predictions
local pfHookHealCommSendAddonMessage = HealComm.SendAddonMessage
function HealComm.SendAddonMessage(this, msg)
if not IsInGroup() then
libpredict:ParseChatMessage(UnitName("player"), msg, "HealComm")
end
pfHookHealCommSendAddonMessage(this, msg)
end
-- disable pfUI predictions
pfUI.api.libpredict.sender:UnregisterAllEvents()
pfUI.api.libpredict.sender.enabled = nil
end
end
-15
View File
@@ -497,19 +497,4 @@ function pfUI.SetupCVars()
UIParentLoadAddOn("Blizzard_CombatText")
end
if not RunMacroText then
local obj = setmetatable({ ["GetText"] = function(self) return self.text end }, {
__index = function(tab,key)
local value = function() return end
rawset(tab,key,value)
return value
end
})
function RunMacroText(text)
obj.text = text
ChatEdit_ParseText(obj, 1)
end
end
_G.PLAYER_BUFF_START_ID = -1
+30
View File
@@ -279,6 +279,36 @@ pfUI:RegisterSkin("Character", function ()
SkinCheckbox(ReputationDetailInactiveCheckBox)
SkinCheckbox(ReputationDetailMainScreenCheckBox)
-- Expand or collapse every header at once, as the Skills tab's button
-- does. It shows "+" while any header is collapsed.
local collapseAll = CreateFrame("Button", "pfReputationCollapseAllButton", ReputationFrame, "ReputationHeaderTemplate")
collapseAll:SetText(ALL)
collapseAll:SetWidth(collapseAll:GetTextWidth() + 20)
-- on the "Faction" label's row, in the headers' +/- column
collapseAll:SetPoint("LEFT", ReputationFrameFactionLabel, "LEFT", -43, 0)
collapseAll:SetScript("OnClick", function()
if this.isCollapsed then
ExpandAllFactionHeaders()
else
CollapseAllFactionHeaders()
end
end)
SkinCollapseButton(collapseAll, true)
-- GetFactionInfo, not C_Reputation.GetFactionDataByIndex: the latter
-- returns nil for the Other and Inactive headers.
hooksecurefunc("ReputationFrame_Update", function()
collapseAll.isCollapsed = nil
for i = 1, GetNumFactions() do
local _, _, _, _, _, _, _, _, isHeader, isCollapsed = GetFactionInfo(i)
if isHeader and isCollapsed then
collapseAll.isCollapsed = true
break
end
end
collapseAll:SetNormalTexture(collapseAll.isCollapsed and "Interface\\Buttons\\UI-PlusButton-Up" or "Interface\\Buttons\\UI-MinusButton-Up")
end)
-- Append "(N)" to each standing label where N is rep remaining to the
-- next reaction. Skip exalted (reaction 8) and effectively-capped bars.
-- Each bar's OnLeave handler (Blizzard's ReputationFrame.xml) restores