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 -- 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 -- the installed DLL doesn't have yet -- the popup names the cause so those
-- errors aren't a mystery. -- 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_LATEST = PFUI_CLASSIC_API_MIN
local PFUI_CLASSIC_API_WEBSITE = "https://github.com/brues-code/ClassicAPI" local PFUI_CLASSIC_API_WEBSITE = "https://github.com/brues-code/ClassicAPI"
local PFUI_CLASSIC_API_LATEST_URL = PFUI_CLASSIC_API_WEBSITE .. "/releases/latest" 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) function pfUI.api.CreateGoldString(money)
if type(money) ~= "number" then return "-" end if type(money) ~= "number" then return "-" end
if _G.GetCoinTextureString then return "|cffffffff" .. _G.GetCoinTextureString(money) .. "|r"
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
end end
-- [ Enable Movable ] -- [ Enable Movable ]
@@ -1353,15 +1340,18 @@ end
-- [ Bar Layout Options ] -- -- [ Bar Layout Options ] --
-- 'barsize' size of bar in number of buttons -- '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) function pfUI.api.BarLayoutOptions(barsize)
assert(barsize > 0 and barsize <= NUM_ACTIONBAR_BUTTONS,"BarLayoutOptions: barsize "..tostring(barsize).." is invalid") assert(barsize > 0 and barsize <= NUM_ACTIONBAR_BUTTONS,"BarLayoutOptions: barsize "..tostring(barsize).." is invalid")
local options, seen = {}, {} local options, seen = {}, {}
local function add(option) local function add(option)
if not seen[option] then table.insert(options, option); seen[option] = true end if not seen[option] then table.insert(options, option); seen[option] = true end
end end
for i,layout in ipairs(pfGridmath[barsize]) do for cols = 1, barsize do
add(string.format("%d x %d",layout[1],layout[2])) if barsize % cols == 0 then
add(string.format("%d x %d", cols, barsize / cols))
end
end end
for _, option in ipairs({ 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", "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 return options
end end
-- [ Bar Layout Formfactor ] -- -- 'formfactor' has to be a "cols x rows" string; the actionbar module resets
-- 'option' string option as used in pfUI_config.bars[bar].option -- a stored layout that isn't one before it lays out a bar
-- 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
local function ResolveBarLayout(barsize, formfactor, uneven) local function ResolveBarLayout(barsize, formfactor, uneven)
local layout = tostring(formfactor or "") local _, _, a, b = string.find(formfactor, "(%d+)%s*x%s*(%d+)")
local _, _, a, b = string.find(layout, "(%d+)%s*x%s*(%d+)") local cols, rows = tonumber(a), tonumber(b)
if a and b then cols = math.min(NUM_ACTIONBAR_BUTTONS, math.max(1, cols))
local cols, rows = tonumber(a), tonumber(b) rows = math.min(NUM_ACTIONBAR_BUTTONS, math.max(1, rows))
cols = math.min(NUM_ACTIONBAR_BUTTONS, math.max(1, cols)) local orientation = string.upper(uneven or "DOWN")
rows = math.min(NUM_ACTIONBAR_BUTTONS, math.max(1, rows)) local mode = (orientation == "LEFT" or orientation == "RIGHT") and "cols" or "rows"
local orientation = string.upper(uneven or "DOWN") if mode == "rows" then
local mode = (orientation == "LEFT" or orientation == "RIGHT") and "cols" or "rows" cols = math.max(1, math.min(cols, barsize))
if mode == "rows" then rows = math.max(1, math.ceil(barsize / cols))
cols = math.max(1, math.min(cols, barsize)) else
rows = math.max(1, math.ceil(barsize / cols)) rows = math.max(1, math.min(rows, barsize))
else cols = math.max(1, math.ceil(barsize / rows))
rows = math.max(1, math.min(rows, barsize))
cols = math.max(1, math.ceil(barsize / rows))
end
return cols, rows, mode, orientation
end end
local index = pfUI.api.BarLayoutFormfactor(layout) return cols, rows, mode, orientation
if not index then return nil end
local cols, rows = unpack(pfGridmath[barsize][index])
return cols, rows, "rows", "DOWN"
end end
-- [ Bar Layout Size ] -- -- [ Bar Layout Size ] --
@@ -1434,7 +1398,6 @@ end
function pfUI.api.BarLayoutSize(bar,barsize,formfactor,iconsize,bordersize,padding,uneven,fillmode) 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") assert(barsize > 0 and barsize <= NUM_ACTIONBAR_BUTTONS,"BarLayoutSize: barsize "..tostring(barsize).." is invalid")
local cols, rows = ResolveBarLayout(barsize, formfactor, uneven) 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 width = (iconsize + bordersize*2+padding) * cols + padding
local height = (iconsize + bordersize*2+padding) * rows + padding local height = (iconsize + bordersize*2+padding) * rows + padding
bar._size = {width,height} bar._size = {width,height}
@@ -1452,9 +1415,6 @@ end
function pfUI.api.BarButtonAnchor(button,basename,buttonindex,barsize,formfactor,iconsize,bordersize,padding,uneven,fillmode) 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") assert(barsize > 0 and barsize <= NUM_ACTIONBAR_BUTTONS,"BarButtonAnchor: barsize "..tostring(barsize).." is invalid")
local cols, rows, mode, orientation = ResolveBarLayout(barsize, formfactor, uneven) 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) -- fillmode: remap buttonindex to slotindex (grid position in native fill order)
-- only needed when fill direction differs from layout mode -- 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", "newitem_color", "1,1,1,1")
pfUI:UpdateConfig("appearance", "bags", "borderlimit", "1") pfUI:UpdateConfig("appearance", "bags", "borderlimit", "1")
pfUI:UpdateConfig("appearance", "bags", "borderonlygear", "0") pfUI:UpdateConfig("appearance", "bags", "borderonlygear", "0")
pfUI:UpdateConfig("appearance", "bags", "fulltext", "1")
pfUI:UpdateConfig("appearance", "bags", "movable", "0") pfUI:UpdateConfig("appearance", "bags", "movable", "0")
pfUI:UpdateConfig("appearance", "bags", "abovechat", "0") pfUI:UpdateConfig("appearance", "bags", "abovechat", "0")
pfUI:UpdateConfig("appearance", "bags", "hidechat", "0") pfUI:UpdateConfig("appearance", "bags", "hidechat", "0")
+36 -74
View File
@@ -3,19 +3,19 @@ setfenv(1, pfUI:GetEnvironment())
do -- statusbars do -- statusbars
local animations = {} local animations = {}
local stepsize, val local stepsize
local width, height, point
-- ClassicAPI before v1.18.0 leaves the UI scale in the size of an anchored -- native StatusBar methods the bar overrides
-- frame, so the bar has to divide it back out local statusbar = CreateFrame("StatusBar")
local scaledsize = CLASSIC_API_VERSION < 11800 local SetValue, SetMinMaxValues = statusbar.SetValue, statusbar.SetMinMaxValues
local SetOrientation, SetFrameLevel = statusbar.SetOrientation, statusbar.SetFrameLevel
local animate = CreateFrame("Frame", "pfStatusBarAnimation", UIParent) local animate = CreateFrame("Frame", "pfStatusBarAnimation", UIParent)
animate:SetScript("OnUpdate", function() animate:SetScript("OnUpdate", function()
stepsize = tonumber(pfUI_config.unitframes.animation_speed) stepsize = tonumber(pfUI_config.unitframes.animation_speed)
for bar in pairs(animations) do 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) bar:DisplayValue(bar.val)
elseif bar.val ~= bar.val_ then elseif bar.val ~= bar.val_ then
bar:DisplayValue(bar.val_ + min((bar.val-bar.val_) / stepsize, max(bar.val-bar.val_, 30 / GetFramerate()))) 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 = { local handlers = {
["DisplayValue"] = function(self, val) ["DisplayValue"] = function(self, val)
val = Clamp(val, self.min, self.max) local smin, smax = self:GetMinMaxValues()
val = Clamp(val, smin, smax)
-- remove animation queue -- remove animation queue
if val == self.val_ then if val == self.val_ then
@@ -34,53 +35,20 @@ do -- statusbars
-- set current visible value -- set current visible value
self.val_ = val self.val_ = val
SetValue(self, val)
if self.mode == "vertical" then -- the background fills the missing part, all of an empty range
height = self:GetHeight() self.bg:SetValue(smax > smin and (smax - val) / (smax - smin) or 1)
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
end, end,
["SetMinMaxValues"] = function(self, smin, smax, smooth) ["SetMinMaxValues"] = function(self, smin, smax, smooth)
-- smoothen the transition by keeping the value at the same percentage as before -- 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 local _, oldmax = self:GetMinMaxValues()
self.val_ = (self.val_ or self.val) / self.max * smax if smooth and oldmax > 0 and smax > 0 and oldmax ~= smax then
self.val_ = (self.val_ or self.val) / oldmax * smax
end end
self.min, self.max = smin, smax SetMinMaxValues(self, smin, smax)
self:DisplayValue(self.val_ or self.val) self:DisplayValue(self.val_ or self.val)
end, end,
@@ -93,41 +61,35 @@ do -- statusbars
end end
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) ["SetStatusBarBackgroundColor"] = function(self, r, g, b, a)
self.bg:SetVertexColor(r, g, b, a) self.bg:SetColorFill(r, g, b, a)
end, end,
["SetOrientation"] = function(self, mode) ["SetOrientation"] = function(self, orientation)
self.mode = strlower(mode) 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, end,
} }
function pfUI.api.CreateStatusBar(name, parent) function pfUI.api.CreateStatusBar(name, parent)
local f = CreateFrame("Button", name, parent) local f = CreateFrame("StatusBar", name, parent)
f:EnableMouse(false) SetMinMaxValues(f, 0, 100)
SetValue(f, 0)
f.val = 0
f.bar = f:CreateTexture(nil, "NORMAL") f.bg = CreateFrame("StatusBar", nil, f)
f.bar:SetPoint("TOPLEFT", f, "TOPLEFT", 0, 0) f.bg:SetFrameLevel(f:GetFrameLevel() - 1)
f.bar:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", 0, 0) f.bg:SetAllPoints(f)
f.bg:SetReverseFill(true)
f.bg = f:CreateTexture(nil, "BACKGROUND") f.bg:SetMinMaxValues(0, 1)
f.bg:SetPoint("TOPLEFT", f, "TOPLEFT", 0, 0) f.bg:SetValue(1)
f.bg:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", 0, 0)
-- set some default values
f.min, f.max, f.val = 0, 100, 0
-- add all handler functions to the object -- add all handler functions to the object
for name, func in pairs(handlers) do 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 end
local aggrodata = { } 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) function pfUI.api.UnitHasAggro(unit)
local now = GetTime() local now = GetTime()
local data = aggrodata[unit] local data = aggrodata[unit]
@@ -250,16 +293,6 @@ function pfUI.api.UnitHasAggro(unit)
data.state = 0 data.state = 0
if UnitExists(unit) and UnitIsFriend(unit, "player") then 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 for i = 1, table.getn(aggroScan) do
local s = aggroScan[i] local s = aggroScan[i]
if UnitExists(s.t) and UnitIsUnit(s.t, unit) and UnitCanAttack(s.u, unit) then 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 -- this runs for every frame five times a second, so only touch the
-- attribute when the unit actually changed -- attribute when the unit actually changed
local unitstr = (self.label or "") .. (self.id or "") local unitstr = self.label .. self.id
local unit = unitstr ~= "" and unitstr or nil if unitstr ~= self.unitstr then
if unit ~= self.unitstr then self.unitstr = unitstr
self.unitstr = unit self:SetAttribute("unit", unitstr)
self:SetAttribute("unit", unit)
end end
-- anything but "show"/"hide" means "show if the unit exists" -- anything but "show"/"hide" means "show if the unit exists"
local visibility = "exists" local visibility = "exists"
@@ -395,9 +427,7 @@ function pfUI.uf:UpdateVisibility()
self:RegisterUnitEvents(visibility ~= "hide" and self.unitstr or nil) self:RegisterUnitEvents(visibility ~= "hide" and self.unitstr or nil)
-- vanilla visibility -- vanilla visibility
if self.unitname then if visibility == "hide" then
self:Show()
elseif visibility == "hide" then
self:Hide() self:Hide()
elseif visibility == "show" then elseif visibility == "show" then
self:Show() self:Show()
@@ -508,6 +538,8 @@ function pfUI.uf:UpdateConfig()
if tonumber(f.config.height) < 0 then f.hp:Hide() end if tonumber(f.config.height) < 0 then f.hp:Hide() end
pfUI.api.CreateBackdrop(f.hp, default_border) 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:SetStatusBarTexture(pfUI.media[f.config.bartexture])
f.hp.bar:SetAllPoints(f.hp) f.hp.bar:SetAllPoints(f.hp)
if f.config.verticalbar == "1" then if f.config.verticalbar == "1" then
@@ -522,7 +554,7 @@ function pfUI.uf:UpdateConfig()
if custombg == "1" then if custombg == "1" then
local cr, cg, cb, ca = GetStringColor(custombgcolor) local cr, cg, cb, ca = GetStringColor(custombgcolor)
cr, cg, cb, ca = tonumber(cr), tonumber(cg), tonumber(cb), tonumber(ca) 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 end
f.power:ClearAllPoints() f.power:ClearAllPoints()
@@ -531,7 +563,7 @@ function pfUI.uf:UpdateConfig()
if tonumber(f.config.pheight) < 0 then f.power:Hide() end if tonumber(f.config.pheight) < 0 then f.power:Hide() end
pfUI.api.CreateBackdrop(f.power, default_border) 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:SetStatusBarTexture(pfUI.media[f.config.pbartexture])
f.power.bar:SetAllPoints(f.power) f.power.bar:SetAllPoints(f.power)
@@ -541,7 +573,7 @@ function pfUI.uf:UpdateConfig()
if custompbg == "1" then if custompbg == "1" then
local cr, cg, cb, ca = GetStringColor(custompbgcolor) local cr, cg, cb, ca = GetStringColor(custompbgcolor)
cr, cg, cb, ca = tonumber(cr), tonumber(cg), tonumber(cb), tonumber(ca) 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 end
local fontname, fontsize, fontstyle local fontname, fontsize, fontstyle
@@ -1019,6 +1051,8 @@ function pfUI.uf.OnEvent()
this.update_pvp = true this.update_pvp = true
elseif event == "UNIT_COMBAT" then elseif event == "UNIT_COMBAT" then
CombatFeedback_OnCombatEvent(arg2, arg3, arg4, arg5) CombatFeedback_OnCombatEvent(arg2, arg3, arg4, arg5)
elseif event == "INCOMING_RESURRECT_CHANGED" then
this.update_indicators = true
else else
this.update_base = true this.update_base = true
end end
@@ -1189,10 +1223,6 @@ function pfUI.uf.OnUpdate()
this.incHeal:Hide() this.incHeal:Hide()
end end
end end
-- update ressurections
local ress = libpredict:UnitHasIncomingResurrection(unit)
this.ressIcon:SetShown(ress and UnitIsDeadOrGhost(unit))
end end
end end
end end
@@ -1244,8 +1274,6 @@ function pfUI.uf.OnUpdate()
end end
end end
if not this.label or this.label == "" then return end
-- update portrait on first visible frame -- update portrait on first visible frame
if this.portrait and this.portrait.model and this.portrait.model.update then if this.portrait and this.portrait.model and this.portrait.model.update then
this.portrait.model.lastUnit = UnitName(this.portrait.model.update) this.portrait.model.lastUnit = UnitName(this.portrait.model.update)
@@ -1272,6 +1300,7 @@ local UNIT_EVENTS = {
"UNIT_PORTRAIT_UPDATE", "UNIT_MODEL_CHANGED", "UNIT_PORTRAIT_UPDATE", "UNIT_MODEL_CHANGED",
"UNIT_FACTION", "UNIT_FACTION",
"UNIT_AURA", -- frame=buff, frame=debuff "UNIT_AURA", -- frame=buff, frame=debuff
"INCOMING_RESURRECT_CHANGED", -- frame=ressIcon
} }
-- Point this frame's unit-event subscriptions at `unitstr`, or drop them when -- Point this frame's unit-event subscriptions at `unitstr`, or drop them when
@@ -1332,11 +1361,7 @@ end
-- ============================================================ -- ============================================================
function pfUI.uf.OnClick() function pfUI.uf.OnClick()
if not this.label and this.unitname then pfUI.uf:ClickAction(arg1)
TargetByName(this.unitname, true)
else
pfUI.uf:ClickAction(arg1)
end
end end
function pfUI.uf:ClickAction(button) 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.config = config or pfUI_config.unitframes.fallback
f.tick = tick 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) CreateBackdropShadow(f)
f.hp = CreateFrame("Frame",nil, 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) f.incHeal = CreateFrame("Frame", nil, f.hp)
-- Texture lives on hp.bar for correct draw order: -- 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 = f.hp.bar:CreateTexture(nil, "BORDER")
f.incHeal.texture:SetAllPoints(f.incHeal) f.incHeal.texture:SetAllPoints(f.incHeal)
-- Override Show/Hide since texture lives on hp.bar, not 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() f:Hide()
end end
if f.label ~= "" then f:SetAttribute("unit", f.label .. f.id)
f:SetAttribute("unit", f.label .. f.id)
end
-- register frame for clique -- register frame for clique
_G.ClickCastFrames = ClickCastFrames or {} _G.ClickCastFrames = ClickCastFrames or {}
@@ -1632,6 +1648,10 @@ function pfUI.uf:RefreshIndicators(unit)
unit.pvpIcon:SetShown(unit.config.showPVP == "1" and UnitIsPVP(unitstr)) unit.pvpIcon:SetShown(unit.config.showPVP == "1" and UnitIsPVP(unitstr))
end end
if unit.ressIcon then -- Resurrection Icon
unit.ressIcon:SetShown(libpredict:UnitHasResurrection(unitstr))
end
if unit.restIcon and unit:GetName() == "pfPlayer" then -- Rest Icon if unit.restIcon and unit:GetName() == "pfPlayer" then -- Rest Icon
unit.restIcon:SetShown(C.unitframes.player.showRest == "1" and UnitIsUnit(unitstr, "player") and IsResting()) unit.restIcon:SetShown(C.unitframes.player.showRest == "1" and UnitIsUnit(unitstr, "player") and IsResting())
end 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 Energy Ticks"] = nil,
["Enable Extended Guild Information"] = nil, ["Enable Extended Guild Information"] = nil,
["Enable Frame Shadow"] = nil, ["Enable Frame Shadow"] = nil,
["Enable Full-Text Search"] = nil,
["Enable Indicators"] = nil, ["Enable Indicators"] = nil,
["Enable Item Quality Color For Equipment Only"] = nil, ["Enable Item Quality Color For Equipment Only"] = nil,
["Enable \"Loot & Spam\" Chat Window"] = nil, ["Enable \"Loot & Spam\" Chat Window"] = nil,
-1
View File
@@ -290,7 +290,6 @@ pfUI_translation["enUS"] = {
["Enable Energy Ticks"] = nil, ["Enable Energy Ticks"] = nil,
["Enable Extended Guild Information"] = nil, ["Enable Extended Guild Information"] = nil,
["Enable Frame Shadow"] = nil, ["Enable Frame Shadow"] = nil,
["Enable Full-Text Search"] = nil,
["Enable Indicators"] = nil, ["Enable Indicators"] = nil,
["Enable Item Quality Color For Equipment Only"] = nil, ["Enable Item Quality Color For Equipment Only"] = nil,
["Enable \"Loot & Spam\" Chat Window"] = 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 Energy Ticks"] = "Activar tics de energía",
["Enable Extended Guild Information"] = "Activar información de hermandad extendida", ["Enable Extended Guild Information"] = "Activar información de hermandad extendida",
["Enable Frame Shadow"] = "Activar sombra del marco", ["Enable Frame Shadow"] = "Activar sombra del marco",
["Enable Full-Text Search"] = nil,
["Enable Indicators"] = "Activar indicadores", ["Enable Indicators"] = "Activar indicadores",
["Enable Item Quality Color For Equipment Only"] = "Mostrar el color de calidad de objetos solo para equipo", ["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\"", ["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 Energy Ticks"] = "Activer la barre de rechargement d'énergie",
["Enable Extended Guild Information"] = "Activer les informations de guilde étendues", ["Enable Extended Guild Information"] = "Activer les informations de guilde étendues",
["Enable Frame Shadow"] = "Activer l'ombre des cadres", ["Enable Frame Shadow"] = "Activer l'ombre des cadres",
["Enable Full-Text Search"] = nil,
["Enable Indicators"] = "Activer les indicateurs", ["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 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\"", ["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 Energy Ticks"] = "Energy Ticks 활성화",
["Enable Extended Guild Information"] = "길드 정보 확장 사용", ["Enable Extended Guild Information"] = "길드 정보 확장 사용",
["Enable Frame Shadow"] = nil, ["Enable Frame Shadow"] = nil,
["Enable Full-Text Search"] = nil,
["Enable Indicators"] = nil, ["Enable Indicators"] = nil,
["Enable Item Quality Color For Equipment Only"] = "착용장비 전용 아이템품질 색상 활성화", ["Enable Item Quality Color For Equipment Only"] = "착용장비 전용 아이템품질 색상 활성화",
["Enable \"Loot & Spam\" Chat Window"] = "\"루팅 & 스팜\"채팅 창 사용", ["Enable \"Loot & Spam\" Chat Window"] = "\"루팅 & 스팜\"채팅 창 사용",
-1
View File
@@ -281,7 +281,6 @@ pfUI_translation["ruRU"] = {
["Enable Energy Ticks"] = "Включить восполнение энергии", ["Enable Energy Ticks"] = "Включить восполнение энергии",
["Enable Extended Guild Information"] = "Включить расширенную информацию о гильдии", ["Enable Extended Guild Information"] = "Включить расширенную информацию о гильдии",
["Enable Frame Shadow"] = "Включить тень окон", ["Enable Frame Shadow"] = "Включить тень окон",
["Enable Full-Text Search"] = nil,
["Enable Indicators"] = "Включить индикаторы", ["Enable Indicators"] = "Включить индикаторы",
["Enable Item Quality Color For Equipment Only"] = "Включить цвет качества предметов только для экипировки", ["Enable Item Quality Color For Equipment Only"] = "Включить цвет качества предметов только для экипировки",
["Enable \"Loot & Spam\" Chat Window"] = "Включить окно чата \"Добыча & Спам\"", ["Enable \"Loot & Spam\" Chat Window"] = "Включить окно чата \"Добыча & Спам\"",
-1
View File
@@ -281,7 +281,6 @@ pfUI_translation["zhCN"] = {
["Enable Energy Ticks"] = "显示能量刻度", ["Enable Energy Ticks"] = "显示能量刻度",
["Enable Extended Guild Information"] = "公会信息扩展显示", ["Enable Extended Guild Information"] = "公会信息扩展显示",
["Enable Frame Shadow"] = "启用框架阴影", ["Enable Frame Shadow"] = "启用框架阴影",
["Enable Full-Text Search"] = "启用全文搜索",
["Enable Indicators"] = "启用指示器", ["Enable Indicators"] = "启用指示器",
["Enable Item Quality Color For Equipment Only"] = "仅在装备上显示品质颜色", ["Enable Item Quality Color For Equipment Only"] = "仅在装备上显示品质颜色",
["Enable \"Loot & Spam\" Chat Window"] = "启用[拾取、综合]聊天窗口", ["Enable \"Loot & Spam\" Chat Window"] = "启用[拾取、综合]聊天窗口",
-1
View File
@@ -281,7 +281,6 @@ pfUI_translation["zhTW"] = {
["Enable Energy Ticks"] = "顯示能量刻度", ["Enable Energy Ticks"] = "顯示能量刻度",
["Enable Extended Guild Information"] = "公會資訊擴展顯示", ["Enable Extended Guild Information"] = "公會資訊擴展顯示",
["Enable Frame Shadow"] = "啟用框架陰影", ["Enable Frame Shadow"] = "啟用框架陰影",
["Enable Full-Text Search"] = nil,
["Enable Indicators"] = nil, ["Enable Indicators"] = nil,
["Enable Item Quality Color For Equipment Only"] = "僅在裝備上顯示品質顏色", ["Enable Item Quality Color For Equipment Only"] = "僅在裝備上顯示品質顏色",
["Enable \"Loot & Spam\" Chat Window"] = "啟用[拾取、綜合]聊天視窗", ["Enable \"Loot & Spam\" Chat Window"] = "啟用[拾取、綜合]聊天視窗",
+1
View File
@@ -1,6 +1,7 @@
<Ui xmlns="http://www.blizzard.com/wow/ui/"> <Ui xmlns="http://www.blizzard.com/wow/ui/">
<Include file="..\api\api.lua"/> <Include file="..\api\api.lua"/>
<Include file="..\api\config.lua"/> <Include file="..\api\config.lua"/>
<Include file="..\api\ui-widgets.xml"/>
<Include file="..\api\ui-widgets.lua"/> <Include file="..\api\ui-widgets.lua"/>
<Include file="..\api\unitframes.lua"/> <Include file="..\api\unitframes.lua"/>
</Ui> </Ui>
-1
View File
@@ -7,7 +7,6 @@
<Include file="..\env\locales_esES.lua"/> <Include file="..\env\locales_esES.lua"/>
<Include file="..\env\locales_ruRU.lua"/> <Include file="..\env\locales_ruRU.lua"/>
<Include file="..\env\tables.lua"/>
<Include file="..\env\profiles.lua"/> <Include file="..\env\profiles.lua"/>
<Include file="..\env\translations_enUS.lua"/> <Include file="..\env\translations_enUS.lua"/>
+58 -167
View File
@@ -2,16 +2,21 @@
setfenv(1, pfUI:GetEnvironment()) setfenv(1, pfUI:GetEnvironment())
--[[ libpredict ]]-- --[[ 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 -- 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. -- 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: -- The API provides function calls similar to later WoW expansions such as:
-- UnitGetIncomingHeals(unit) -- UnitGetIncomingHeals(unit)
-- UnitHasIncomingResurrection(unit) -- UnitHasResurrection(unit)
-- --
-- The library is able to receive and send compatible messages to HealComm -- UnitHasResurrection extends ClassicAPI's UnitHasIncomingResurrection, which
-- including resurrections. It has an option to disable the sending of those -- ends with the cast, to a resurrection that landed and waits for the target
-- messages in case HealComm is already active. -- 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 -- Casts are read from ClassicAPI's UNIT_SPELLCAST_* events and heal amounts
-- from COMBAT_LOG_EVENT_UNFILTERED. GetHotDuration() reads live HoT timers -- 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 -- return instantly when another libpredict is already active
if pfUI.api.libpredict then return end if pfUI.api.libpredict then return end
local heals, ress, events, hots = {}, {}, {}, {} local heals, events, hots = {}, {}, {}
local spell_queue = { "DUMMY", "DUMMYRank 9", "TARGET" } local spell_queue = { "DUMMY", "DUMMYRank 9", "TARGET" }
local player = UnitName("player") local player = UnitName("player")
local cache, gear_string = {}, "" local cache, gear_string = {}, ""
local foreignCache = {} -- [casterName][spellKey] = amount, in-memory cache for other healers local foreignCache = {} -- [casterName][spellKey] = amount, in-memory cache for other healers
local rejuvDuration, renewDuration = 12, 15 --default durations local rejuvDuration, renewDuration = 12, 15 --default durations
local ressGuidToName = {} -- [casterGuid] = casterName, for interrupted-cast cleanup
local healGuidToName = {} -- [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 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 resOffers = {} -- [guid] = expiry of a landed resurrection the target has yet to accept
local ress_timers = {} -- [target][sender] = expiry_timestamp (60s rez window) local RES_OFFER_TIMEOUT = 60 -- the target's resurrect popup declines itself after 60s
local RESS_TIMEOUT = 60 -- Vanilla: rez offer expires after 60s
-- Localized spell names resolved once from canonical rank-1 spellIDs. -- Localized spell names resolved once from canonical rank-1 spellIDs.
-- Every rank shares the same name, so per-rank comparisons elsewhere can -- 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 REGROWTH = C_Spell.GetSpellName(8936) -- Regrowth (Rank 1)
local libpredict = CreateFrame("Frame") local libpredict = CreateFrame("Frame")
libpredict:RegisterEvent("UNIT_HEALTH")
libpredict:RegisterEvent("CHAT_MSG_ADDON") libpredict:RegisterEvent("CHAT_MSG_ADDON")
libpredict:RegisterEvent("PLAYER_TARGET_CHANGED") libpredict:RegisterEvent("PLAYER_TARGET_CHANGED")
libpredict:RegisterEvent("PLAYER_LOGOUT") libpredict:RegisterEvent("PLAYER_LOGOUT")
@@ -55,13 +57,8 @@ libpredict:SetScript("OnEvent", function()
return return
end end
if event == "CHAT_MSG_ADDON" and (arg1 == "HealComm" or arg1 == "CTRA") then if event == "CHAT_MSG_ADDON" and arg1 == "HealComm" then
this:ParseChatMessage(arg4, arg2, arg1) this:ParseChatMessage(arg4, arg2)
elseif event == "UNIT_HEALTH" then
local name = UnitName(arg1)
if name and ress[name] and not UnitIsDeadOrGhost(arg1) then
ress[name] = nil
end
end end
end) end)
@@ -95,12 +92,6 @@ local function cacheRaidNames()
if pguid then guidNameCache[pguid] = UnitName("player") end if pguid then guidNameCache[pguid] = UnitName("player") end
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 -- Build cache on world enter
libpredict:RegisterEvent("PLAYER_ENTERING_WORLD") libpredict:RegisterEvent("PLAYER_ENTERING_WORLD")
libpredict:RegisterEvent("RAID_ROSTER_UPDATE") libpredict:RegisterEvent("RAID_ROSTER_UPDATE")
@@ -131,7 +122,6 @@ local function OnOwnCastStart(spellId, castGUID)
if L["resurrections"][spellName] then if L["resurrections"][spellName] then
if target then if target then
libpredict:Ress(player, target, UnitGUID("player"))
libpredict.sender:SendHealCommMsg("Resurrection/" .. target .. "/start/") libpredict.sender:SendHealCommMsg("Resurrection/" .. target .. "/start/")
libpredict.sender:SendResCommMsg("RES " .. target) libpredict.sender:SendResCommMsg("RES " .. target)
libpredict.sender.resurrecting = true libpredict.sender.resurrecting = true
@@ -212,7 +202,7 @@ local function OnOwnCastStart(spellId, castGUID)
end end
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 function OnOwnCastSucceeded(spellId, castGUID)
local target = sentTargets[castGUID] or spell_queue[3] local target = sentTargets[castGUID] or spell_queue[3]
sentTargets[castGUID] = nil sentTargets[castGUID] = nil
@@ -250,14 +240,10 @@ local function OnOwnCastSucceeded(spellId, castGUID)
libpredict:Hot(player, target, hotType, duration, nil, "UNIT_SPELLCAST_SUCCEEDED", rank) libpredict:Hot(player, target, hotType, duration, nil, "UNIT_SPELLCAST_SUCCEEDED", rank)
libpredict.sender:SendHealCommMsg(hotType .. "/" .. target .. "/" .. duration .. "/" .. rank .. "/") libpredict.sender:SendHealCommMsg(hotType .. "/" .. target .. "/" .. duration .. "/" .. rank .. "/")
end end
if isRezSpell(spellId) then
libpredict:RessSetTimer(player, target)
end
end end
-- Foreign cast-time spell started (heals + rez) -- Foreign cast-time heal started
local function OnOtherCastStart(unit, spellId, castGUID) local function OnOtherCastStart(unit, spellId)
local spellName = C_Spell.GetSpellName(spellId) local spellName = C_Spell.GetSpellName(spellId)
if not spellName then return end if not spellName then return end
@@ -267,13 +253,6 @@ local function OnOtherCastStart(unit, spellId, castGUID)
local targetName = UnitSpellTargetName(unit) local targetName = UnitSpellTargetName(unit)
if not targetName then return end 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 -- Heal cast: look up cached amount from a previous cast by this healer
local _, _, _, startMs, endMs = C_Spell.UnitCastingInfo(unit) local _, _, _, startMs, endMs = C_Spell.UnitCastingInfo(unit)
@@ -324,35 +303,31 @@ local function OnOtherCastStart(unit, spellId, castGUID)
libpredict:Heal(casterName, targetName, amount, castTime, casterGuid) libpredict:Heal(casterName, targetName, amount, castTime, casterGuid)
end end
-- Foreign cast landed: rez timer, or the predicted heal is fulfilled -- Foreign cast landed: the predicted heal is fulfilled
local function OnOtherCastSucceeded(unit, spellId, castGUID) local function OnOtherCastSucceeded(unit)
local casterGuid = UnitGUID(unit) local casterName = resolveNameFromGuid(UnitGUID(unit))
local casterName = resolveNameFromGuid(casterGuid)
if not casterName then return end 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) libpredict:HealStop(casterName)
end end
-- Foreign cast interrupted: remove cancelled resses/heals -- Foreign cast interrupted: remove cancelled heals
local function OnOtherCastInterrupted(unit) local function OnOtherCastInterrupted(unit)
local casterGuid = UnitGUID(unit) local casterGuid = UnitGUID(unit)
if not casterGuid then return end if not casterGuid then return end
local ressName = ressGuidToName[casterGuid]
if ressName then
libpredict:RessStop(ressName)
end
local healName = healGuidToName[casterGuid] local healName = healGuidToName[casterGuid]
if healName then libpredict:HealStop(healName) end if healName then libpredict:HealStop(healName) end
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() libpredict:SetScript("OnUpdate", function()
-- throttle cleanup - no need to check every frame -- throttle cleanup - no need to check every frame
local now = pfUI.uf.now or GetTime() local now = pfUI.uf.now or GetTime()
@@ -366,13 +341,12 @@ libpredict:SetScript("OnUpdate", function()
end end
end end
-- Rez timeout: remove offer after 60s if player doesn't accept -- drop resurrection offers the target accepted or let run out
for target, senders in pairs(ress_timers) do for guid, expiry in pairs(resOffers) do
for sender, expiry in pairs(senders) do local unit = UnitTokenFromGUID(guid)
if now >= expiry then if now >= expiry or not (unit and UnitIsDeadOrGhost(unit)) then
senders[sender] = nil resOffers[guid] = nil
if ress[target] then ress[target][sender] = nil end RefreshResFrames(guid)
end
end end
end end
end) end)
@@ -386,8 +360,6 @@ function libpredict:ParseComm(sender, msg)
if libpredict.debug then if libpredict.debug then
DEFAULT_CHAT_FRAME:AddMessage("|cff00ffff[libpredict RX]|r HealStop from " .. tostring(sender)) DEFAULT_CHAT_FRAME:AddMessage("|cff00ffff[libpredict RX]|r HealStop from " .. tostring(sender))
end end
elseif msg == "Resurrection/stop/" then
msgtype = "RessStop"
elseif msg then elseif msg then
local msgobj = {strsplit("/", msg)} local msgobj = {strsplit("/", msg)}
@@ -397,10 +369,6 @@ function libpredict:ParseComm(sender, msg)
msgtype, time = "Delay", msgobj[2] msgtype, time = "Delay", msgobj[2]
end 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 if msgobj[1] == "Heal" and msgobj[2] then
msgtype, target, heal, time = "Heal", msgobj[2], msgobj[3], msgobj[4] msgtype, target, heal, time = "Heal", msgobj[2], msgobj[3], msgobj[4]
end end
@@ -472,25 +440,12 @@ end
local recentHots = {} local recentHots = {}
local DUPLICATE_WINDOW = 0.5 -- Ignoriere gleiche Nachricht innerhalb 0.5s local DUPLICATE_WINDOW = 0.5 -- Ignoriere gleiche Nachricht innerhalb 0.5s
function libpredict:ParseChatMessage(sender, msg, comm) function libpredict:ParseChatMessage(sender, msg)
local msgtype, target, heal, time, rank local msgtype, target, heal, time, rank = libpredict:ParseComm(sender, msg)
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
if msgtype == "Stop" and sender then if msgtype == "Stop" and sender then
libpredict:HealStop(sender) libpredict:HealStop(sender)
return return
elseif ( msg == "RessStop" or msg == "RESNO" ) and sender then
libpredict:RessStop(sender)
return
elseif msgtype == "Delay" and time then elseif msgtype == "Delay" and time then
libpredict:HealDelay(sender, time) libpredict:HealDelay(sender, time)
elseif msgtype == "Heal" and target and heal and time then elseif msgtype == "Heal" and target and heal and time then
@@ -501,29 +456,6 @@ function libpredict:ParseChatMessage(sender, msg, comm)
else else
libpredict:Heal(sender, target, heal, time) libpredict:Heal(sender, target, heal, time)
end 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 elseif msgtype == "Hot" then
-- Duplicate check: ignore same sender+target+spell within DUPLICATE_WINDOW -- Duplicate check: ignore same sender+target+spell within DUPLICATE_WINDOW
local now = pfUI.uf.now or GetTime() local now = pfUI.uf.now or GetTime()
@@ -672,41 +604,6 @@ function libpredict:HealDelay(sender, delay)
end end
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) function libpredict:UnitGetIncomingHeals(unit)
if not unit then return 0 end if not unit then return 0 end
local name = UnitName(unit) local name = UnitName(unit)
@@ -729,21 +626,10 @@ function libpredict:UnitGetIncomingHeals(unit)
return sumheal return sumheal
end end
function libpredict:UnitHasIncomingResurrection(unit) -- Whether a resurrection is being cast at the unit, or landed and waits for
if not unit then return nil end -- the unit to accept it
local name = UnitName(unit) function libpredict:UnitHasResurrection(unit)
if not name then return nil end return UnitHasIncomingResurrection(unit) or resOffers[UnitGUID(unit)] ~= nil
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
end end
local realm = GetRealmName() local realm = GetRealmName()
@@ -834,7 +720,6 @@ local pendingHots = {}
-- Gather Data by User Actions -- Gather Data by User Actions
hooksecurefunc("CastSpell", function(id, bookType) hooksecurefunc("CastSpell", function(id, bookType)
if not libpredict.sender.enabled then return end
local effect, rank = libspell.GetSpellInfo(id, bookType) local effect, rank = libspell.GetSpellInfo(id, bookType)
if not effect then return end if not effect then return end
spell_queue[1] = effect spell_queue[1] = effect
@@ -887,7 +772,6 @@ hooksecurefunc("CastSpell", function(id, bookType)
end) end)
hooksecurefunc("CastSpellByName", function(effect, target) hooksecurefunc("CastSpellByName", function(effect, target)
if not libpredict.sender.enabled then return end
local effect, rank = libspell.GetSpellInfo(effect) local effect, rank = libspell.GetSpellInfo(effect)
if not effect then return end if not effect then return end
@@ -951,7 +835,6 @@ hooksecurefunc("CastSpellByName", function(effect, target)
end) end)
hooksecurefunc("UseAction", function(slot, target, selfcast) hooksecurefunc("UseAction", function(slot, target, selfcast)
if not libpredict.sender.enabled then return end
if not IsCurrentAction(slot) then return end if not IsCurrentAction(slot) then return end
local kind, id = GetActionInfo(slot) local kind, id = GetActionInfo(slot)
local effect, rank local effect, rank
@@ -1012,8 +895,10 @@ hooksecurefunc("UseAction", function(slot, target, selfcast)
end) end)
libpredict.sender = CreateFrame("Frame", "pfPredictionSender", UIParent) libpredict.sender = CreateFrame("Frame", "pfPredictionSender", UIParent)
-- cleared when a HealComm addon already broadcasts this player's casts
libpredict.sender.enabled = true libpredict.sender.enabled = true
libpredict.sender.SendHealCommMsg = function(self, msg) libpredict.sender.SendHealCommMsg = function(self, msg)
if not self.enabled then return end
-- Smart channel selection: Only send to relevant channel to avoid duplicates -- Smart channel selection: Only send to relevant channel to avoid duplicates
if IsInRaid() then if IsInRaid() then
-- In raid: Only send to RAID (includes all raid members) -- 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 -- BG groups are handled by RAID channel
end end
libpredict.sender.SendResCommMsg = function(self, msg) libpredict.sender.SendResCommMsg = function(self, msg)
if not self.enabled then return end
-- Smart channel selection: Only send to relevant channel to avoid duplicates -- Smart channel selection: Only send to relevant channel to avoid duplicates
if IsInRaid() then if IsInRaid() then
-- In raid: Only send to RAID (includes all raid members) -- 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_SUCCEEDED")
libpredict.sender:RegisterEvent("UNIT_SPELLCAST_FAILED") libpredict.sender:RegisterEvent("UNIT_SPELLCAST_FAILED")
libpredict.sender:RegisterEvent("UNIT_SPELLCAST_INTERRUPTED") libpredict.sender:RegisterEvent("UNIT_SPELLCAST_INTERRUPTED")
libpredict.sender:RegisterEvent("UNIT_SPELLCAST_STOP")
libpredict.sender:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED") libpredict.sender:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED")
-- Shared cleanup helper for failed/interrupted casts -- Shared cleanup helper for failed/interrupted casts
@@ -1083,7 +968,6 @@ local function onCastFailed()
s.healing = nil s.healing = nil
elseif s.resurrecting then elseif s.resurrecting then
local target = s.current_cast_target or spell_queue[3] local target = s.current_cast_target or spell_queue[3]
libpredict:RessStop(UnitName("player"))
s:SendHealCommMsg("Resurrection/stop/") s:SendHealCommMsg("Resurrection/stop/")
s:SendResCommMsg("RESNO " .. (target or "")) s:SendResCommMsg("RESNO " .. (target or ""))
s.resurrecting = nil s.resurrecting = nil
@@ -1134,11 +1018,20 @@ local function OnHeal()
end end
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 local lastOtherCast = {} -- [event] = castGUID of the last other-unit event handled
libpredict.sender:SetScript("OnEvent", function() libpredict.sender:SetScript("OnEvent", function()
if event == "COMBAT_LOG_EVENT_UNFILTERED" then 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 return
end end
@@ -1174,13 +1067,11 @@ libpredict.sender:SetScript("OnEvent", function()
lastOtherCast[event] = arg2 lastOtherCast[event] = arg2
if event == "UNIT_SPELLCAST_START" then if event == "UNIT_SPELLCAST_START" then
OnOtherCastStart(unit, arg3, arg2) OnOtherCastStart(unit, arg3)
elseif event == "UNIT_SPELLCAST_SUCCEEDED" then elseif event == "UNIT_SPELLCAST_SUCCEEDED" then
OnOtherCastSucceeded(unit, arg3, arg2) OnOtherCastSucceeded(unit)
elseif event == "UNIT_SPELLCAST_INTERRUPTED" then elseif event == "UNIT_SPELLCAST_INTERRUPTED" then
OnOtherCastInterrupted(unit) OnOtherCastInterrupted(unit)
elseif event == "UNIT_SPELLCAST_STOP" then
castTargets[arg2] = nil
end end
end) end)
+1 -14
View File
@@ -382,8 +382,6 @@ pfUI:RegisterModule("actionbar", function ()
end end
elseif self.spellID then elseif self.spellID then
GameTooltip:SetSpellByID(self.spellID) GameTooltip:SetSpellByID(self.spellID)
elseif self.spellslot and self.booktype then
GameTooltip:SetSpell(self.spellslot, self.booktype)
else else
GameTooltip:SetAction(self.id) GameTooltip:SetAction(self.id)
end end
@@ -456,11 +454,6 @@ pfUI:RegisterModule("actionbar", function ()
id = self.bar == 1 and self.slot or sid-((self.bar)-1)*12 id = self.bar == 1 and self.slot or sid-((self.bar)-1)*12
end 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 if not self.showempty and self.backdrop and not texture and grid == 0 then
self.backdrop:Hide() self.backdrop:Hide()
self.hide = true self.hide = true
@@ -590,9 +583,6 @@ pfUI:RegisterModule("actionbar", function ()
start, duration, enable = GetShapeshiftFormCooldown(button.id) start, duration, enable = GetShapeshiftFormCooldown(button.id)
elseif button.bar == 12 then elseif button.bar == 12 then
start, duration, enable = GetPetActionCooldown(button.id) start, duration, enable = GetPetActionCooldown(button.id)
elseif button.spellslot and button.booktype then
start, duration = GetSpellCooldown(button.spellslot, button.booktype)
enable = 1
else else
start, duration, enable = GetActionCooldown(button.id) start, duration, enable = GetActionCooldown(button.id)
end end
@@ -1150,8 +1140,6 @@ pfUI:RegisterModule("actionbar", function ()
f.count:SetJustifyH("RIGHT") f.count:SetJustifyH("RIGHT")
f.count:SetJustifyV("BOTTOM") f.count:SetJustifyV("BOTTOM")
f.scanmacro, f.spellslot, f.booktype = nil, nil, nil
-- range glow color -- range glow color
f.rangeColor = GetStringColorObject(C.bars.rangecolor) f.rangeColor = GetStringColorObject(C.bars.rangecolor)
@@ -1218,8 +1206,7 @@ pfUI:RegisterModule("actionbar", function ()
end end
-- the stored layout is invalid, temporary fallback -- the stored layout is invalid, temporary fallback
local _, _, fcols, frows = string.find(tostring(formfactor or ""), "(%d+)%s*x%s*(%d+)") if not fcols2 or not frows2 then
if not ((fcols and frows) or pfGridmath[buttons][BarLayoutFormfactor(formfactor)]) then
formfactor = BarLayoutOptions(buttons)[1] formfactor = BarLayoutOptions(buttons)[1]
end end
+117 -144
View File
@@ -69,12 +69,42 @@ pfUI:RegisterModule("addons", function ()
pfUI.addons:Hide() pfUI.addons:Hide()
end) 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 -- addons profile
local function GetSelectedAddonsList() local function GetSelectedAddonsList()
local selected_addons = {} local selected_addons = {}
for i, frame in ipairs(pfUI.addons.list) do for i, addon in ipairs(addons) do
if frame.input:GetChecked() then if IsAddonEnabled(i) then
table.insert(selected_addons, frame.aname) table.insert(selected_addons, addon.name)
end end
end end
return selected_addons return selected_addons
@@ -90,28 +120,20 @@ pfUI:RegisterModule("addons", function ()
pfUI.addons.profile.dropdown:SetSelectionByText(profile) pfUI.addons.profile.dropdown:SetSelectionByText(profile)
-- load profile -- load profile
local selected_list = pfUI_addon_profiles[profile] local selected = {}
for i=1, GetNumAddOns() do for _, name in ipairs(pfUI_addon_profiles[profile]) do
local active = false selected[name] = true
local f = pfUI.addons.list[i] end
for i_i, n in ipairs(selected_list) do for i, addon in ipairs(addons) do
if f.aname == n then if selected[addon.name] and not IsAddonEnabled(i) then
active = true SetAddonEnabled(i, true)
break elseif not selected[addon.name] and IsAddonEnabled(i) then
end SetAddonEnabled(i, false)
end
if active then
if not f.input:GetChecked() then
f.input:Click()
end
else
if f.input:GetChecked() then
f.input:Click()
end
end end
end end
pfUI.addons.scroll:ReinitializeFrames()
end end
-- addon profiles -- addon profiles
@@ -193,23 +215,6 @@ pfUI:RegisterModule("addons", function ()
return menu return menu
end) 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) local function AddDependencyLines(header, deps)
if not deps or table.getn(deps) == 0 then return end if not deps or table.getn(deps) == 0 then return end
GameTooltip:AddLine(" ") GameTooltip:AddLine(" ")
@@ -225,137 +230,105 @@ pfUI:RegisterModule("addons", function ()
end end
end end
local function AddonOnEnter() local function RowOnEnter(self)
this:SetBackdropBorderColor(1,1,1,.08) local addon = self.addon
self:SetBackdropBorderColor(1,1,1,.08)
GameTooltip:SetOwner(this, "ANCHOR_RIGHT") GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
GameTooltip:SetText(this.atitle) GameTooltip:SetText(addon.title)
if this.aversion then if addon.version then
GameTooltip:AddDoubleLine(T["Version"], this.aversion, 1,1,1, .2,1,.8) GameTooltip:AddDoubleLine(T["Version"], addon.version, 1,1,1, .2,1,.8)
end end
if this.aauthor then if addon.author then
GameTooltip:AddDoubleLine(T["Author"], this.aauthor, 1,1,1, .2,1,.8) GameTooltip:AddDoubleLine(T["Author"], addon.author, 1,1,1, .2,1,.8)
end end
GameTooltip:AddLine(this.anote, .75,.75,.75,1) GameTooltip:AddLine(addon.notes, .75,.75,.75,1)
AddDependencyLines(T["Dependencies"], this.adeps) AddDependencyLines(T["Dependencies"], addon.deps)
AddDependencyLines(T["Optional Dependencies"], this.aoptdeps) AddDependencyLines(T["Optional Dependencies"], addon.optdeps)
GameTooltip:SetWidth(180) GameTooltip:SetWidth(180)
GameTooltip:Show() GameTooltip:Show()
end end
local function AddonOnLeave() local function RowOnLeave(self)
this:SetBackdropBorderColor(1,1,1,.04) self:SetBackdropBorderColor(1,1,1,.04)
GameTooltip:Hide() GameTooltip:Hide()
end end
local function AddonOnClick() local function RowOnClick(self)
this.input:Click() self.input:Click()
end end
local function InputOnClick() local function InputOnClick(self)
if this:GetChecked() then SetAddonEnabled(self:GetParent().addon.index, self:GetChecked())
EnableAddOn(this:GetID())
else
DisableAddOn(this:GetID())
end
pfUI.addons.hasChanged = true
local id, name = pfUI.addons.profile.dropdown:GetSelection() local _, name = pfUI.addons.profile.dropdown:GetSelection()
name = name or T["Current"] name = name or T["Current"]
pfUI_addon_profiles[name] = GetSelectedAddonsList() pfUI_addon_profiles[name] = GetSelectedAddonsList()
end end
pfUI.addons.list:SetScript("OnEvent", function() local function InitRow(row, addon)
for i=1, GetNumAddOns() do -- one-time setup of a pooled row
local aname, atitle, anote = GetAddOnInfo(i) if not row.input then
local aauthor = GetAddOnMetadata(aname, "Author") row:SetBackdrop(pfUI.backdrop_hover)
local aversion = GetAddOnMetadata(aname, "Version") row:SetBackdropBorderColor(1,1,1,.04)
-- basic frame row:EnableMouse(true)
if not pfUI.addons.list[i] then row:SetScript("OnEnter", RowOnEnter)
pfUI.addons.list[i] = CreateFrame("Button", nil, pfUI.addons.list) row:SetScript("OnLeave", RowOnLeave)
local frame = pfUI.addons.list[i] row:SetScript("OnClick", RowOnClick)
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)
frame:SetSize(340, 25) -- caption
frame:SetPoint("TOPLEFT", 5, i * -25 + 20) 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) -- input field
frame:SetBackdropBorderColor(1,1,1,.04) row.input = CreateFrame("CheckButton", nil, row, "UICheckButtonTemplate")
row.input:SetNormalTexture("")
frame:EnableMouse(1) row.input:SetPushedTexture("")
frame:SetScript("OnEnter", AddonOnEnter) row.input:SetHighlightTexture("")
frame:SetScript("OnLeave", AddonOnLeave) CreateBackdrop(row.input, nil, true)
frame:SetScript("OnClick", AddonOnClick) row.input:SetBackdropBorderColor(.3,.3,.3,1)
row.input:SetSize(14, 14)
-- caption row.input:SetPoint("RIGHT" , -5, 1)
frame.caption = frame:CreateFontString("Status", "LOW", "GameFontNormal") row.input:SetScript("OnClick", InputOnClick)
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
end end
end)
-- show only frames in visible range row.addon = addon
function pfUI.addons.scroll:ShowVisibleRange() row.caption:SetText(addon.title)
local top_index, bottom_index, range row.caption:SetAlpha(IsAddOnLoaded(addon.index) and 1 or .5)
range = floor(pfUI.addons.scroll:GetHeight() / 25) + 1 row.input:SetChecked(IsAddonEnabled(addon.index))
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
end end
pfUI.addons.scroll.slider:SetScript("OnValueChanged", function() -- addon list: a ScrollBox keeps rows only for the addons in view
this:GetParent():SetVerticalScroll(this:GetValue()) pfUI.addons.scroll = CreateFrame("Frame", "pfAddonListScroll", pfUI.addons, "WowScrollBoxList")
end) pfUI.addons.scroll:SetSize(360, 410)
pfUI.addons.scroll:SetScript("OnScrollRangeChanged", function() pfUI.addons.scroll:SetPoint("BOTTOM", 0, 10)
this.slider:SetMinMaxValues(1, floor(this:GetVerticalScrollRange() + 25))
end) -- the ScrollBox clips its children, so the backdrop is a sibling
pfUI.addons.scroll:SetScript("OnShow", function() pfUI.addons.scroll.backdrop = CreateFrame("Frame", nil, pfUI.addons)
this.slider:SetValue(floor(this:GetVerticalScroll())) pfUI.addons.scroll.backdrop:SetPoint("TOPLEFT", pfUI.addons.scroll, "TOPLEFT", -5, 5)
pfUI.addons.scroll:ShowVisibleRange() pfUI.addons.scroll.backdrop:SetPoint("BOTTOMRIGHT", pfUI.addons.scroll, "BOTTOMRIGHT", 5, -5)
end) CreateBackdrop(pfUI.addons.scroll.backdrop, nil, true)
pfUI.addons.scroll:SetScript("OnVerticalScroll", function()
pfUI.addons.scroll:ShowVisibleRange() local scrollbar = CreateFrame("EventFrame", nil, pfUI.addons, "pfUIScrollBarTemplate")
this.slider:SetValue(floor(this:GetVerticalScroll())) 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)
end) end)
+10 -11
View File
@@ -29,20 +29,20 @@ pfUI:RegisterModule("afkcam", function ()
overlay:SetFrameStrata("DIALOG") overlay:SetFrameStrata("DIALOG")
overlay:SetAllPoints(WorldFrame) overlay:SetAllPoints(WorldFrame)
overlay:SetScript("OnMouseUp",function() -- /afk toggles, so send it only while AFK is still set: once the client
SendChatMessage("","AFK") -- or the server has cleared it, /afk would set it again
this._parent:stop() local function Wake()
end) if IsChatAFK() then SendChatMessage("", "AFK") end
afkcam:stop()
end
overlay:SetScript("OnMouseUp", Wake)
overlay:SetScript("OnShow", function() overlay:SetScript("OnShow", function()
this:EnableKeyboard(true) this:EnableKeyboard(true)
end) end)
overlay:SetScript("OnHide", function() overlay:SetScript("OnHide", function()
this:EnableKeyboard(false) this:EnableKeyboard(false)
end) end)
overlay:SetScript("OnKeyUp",function() overlay:SetScript("OnKeyUp", Wake)
SendChatMessage("","AFK")
this._parent:stop()
end)
overlay:Hide() overlay:Hide()
local chat = CreateFrame("ScrollingMessageFrame", "pfAFKCamChat", overlay) local chat = CreateFrame("ScrollingMessageFrame", "pfAFKCamChat", overlay)
@@ -82,7 +82,6 @@ pfUI:RegisterModule("afkcam", function ()
end) end)
overlay.chat = chat overlay.chat = chat
overlay._parent = afkcam
afkcam.overlay = overlay afkcam.overlay = overlay
local clock = CreateFrame("Frame", "pfAFKClock", overlay) local clock = CreateFrame("Frame", "pfAFKClock", overlay)
@@ -166,9 +165,9 @@ pfUI:RegisterModule("afkcam", function ()
if event == "PLAYER_FLAGS_CHANGED" then if event == "PLAYER_FLAGS_CHANGED" then
if UnitIsAFK('player') then if UnitIsAFK('player') then
delay:Show() delay:Show()
elseif delay:IsVisible() then else
delay:Hide() delay:Hide()
this:stop() if this._spinning then this:stop() end
end end
else else
if this._spinning then if this._spinning then
+29 -55
View File
@@ -6,8 +6,6 @@ pfUI:RegisterModule("bags", function ()
[1804] = {frame="picklock"}, [1804] = {frame="picklock"},
} }
local scanner = libtipscan:GetScanner("input_search")
local function BagSortOpts() local function BagSortOpts()
return { return {
reverse = C.appearance.bags.sortreverse == "1", reverse = C.appearance.bags.sortreverse == "1",
@@ -95,6 +93,7 @@ pfUI:RegisterModule("bags", function ()
pfUI.bag:RegisterEvent("ITEM_LOCK_CHANGED") pfUI.bag:RegisterEvent("ITEM_LOCK_CHANGED")
pfUI.bag:RegisterEvent("SPELLS_CHANGED") pfUI.bag:RegisterEvent("SPELLS_CHANGED")
pfUI.bag:RegisterEvent("MERCHANT_CLOSED") pfUI.bag:RegisterEvent("MERCHANT_CLOSED")
pfUI.bag:RegisterEvent("INVENTORY_SEARCH_UPDATE")
pfUI.bag.delay = { UpdateBag = {} } pfUI.bag.delay = { UpdateBag = {} }
@@ -122,6 +121,11 @@ pfUI:RegisterModule("bags", function ()
pfUI.bag:UpdateItemLock() pfUI.bag:UpdateItemLock()
end end
if this.delay.UpdateSearch then
this.delay.UpdateSearch = nil
pfUI.bag:UpdateSearch()
end
for bag in pairs(this.delay.UpdateBag) do for bag in pairs(this.delay.UpdateBag) do
this.delay.UpdateBag[bag] = nil this.delay.UpdateBag[bag] = nil
pfUI.bag:UpdateBag(bag) pfUI.bag:UpdateBag(bag)
@@ -159,6 +163,10 @@ pfUI:RegisterModule("bags", function ()
this.delay.UpdateItemLock = true this.delay.UpdateItemLock = true
end end
if event == "INVENTORY_SEARCH_UPDATE" then
this.delay.UpdateSearch = true
end
if event == "PLAYERBANKSLOTS_CHANGED" then if event == "PLAYERBANKSLOTS_CHANGED" then
this.delay.UpdateBag[-1] = true this.delay.UpdateBag[-1] = true
end end
@@ -377,6 +385,21 @@ pfUI:RegisterModule("bags", function ()
end end
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) function pfUI.bag:UpdateSlot(bag, slot)
if not pfUI.bags[bag] then return end if not pfUI.bags[bag] then return end
@@ -465,6 +488,7 @@ pfUI:RegisterModule("bags", function ()
SetItemButtonTexture(pfUI.bags[bag].slots[slot].frame, texture) SetItemButtonTexture(pfUI.bags[bag].slots[slot].frame, texture)
SetItemButtonCount(pfUI.bags[bag].slots[slot].frame, count) SetItemButtonCount(pfUI.bags[bag].slots[slot].frame, count)
SetItemButtonDesaturated(pfUI.bags[bag].slots[slot].frame, locked, 0.5, 0.5, 0.5) 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 local hasItem = texture and 1 or nil
pfUI.bags[bag].slots[slot].frame.hasItem = hasItem pfUI.bags[bag].slots[slot].frame.hasItem = hasItem
@@ -1010,7 +1034,6 @@ pfUI:RegisterModule("bags", function ()
-- bag search -- bag search
if not frame.search then if not frame.search then
frame.search = CreateFrame("Frame", "pfBagSearch", frame) frame.search = CreateFrame("Frame", "pfBagSearch", frame)
frame.search.db = {}
frame.search:SetHeight(12) frame.search:SetHeight(12)
frame.search:SetPoint("TOPLEFT", frame, "TOPLEFT", default_border, -default_border) frame.search:SetPoint("TOPLEFT", frame, "TOPLEFT", default_border, -default_border)
frame.search:SetPoint("TOPRIGHT", frame.sort, "TOPLEFT", -default_border*3, -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() frame.search.edit:SetScript("OnEditFocusLost", function()
this:SetText(T["Search"]) this:SetText(T["Search"])
for bag=-2, 11 do C_Container.SetItemSearch("")
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
end) end)
frame.search.edit:SetScript("OnTextChanged", function() frame.search.edit:SetScript("OnTextChanged", function()
if this:GetText() == T["Search"] then return end if this:GetText() == T["Search"] then return end
for bag=-2, 11 do C_Container.SetItemSearch(this:GetText())
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
end) end)
frame.search:SetScript("OnLeave", function() frame.search:SetScript("OnLeave", function()
@@ -1094,17 +1078,7 @@ pfUI:RegisterModule("bags", function ()
frame.search:SetScript("OnHide", function() frame.search:SetScript("OnHide", function()
frame.search.edit:SetText(T["Search"]) frame.search.edit:SetText(T["Search"])
for bag = -2, 11 do C_Container.SetItemSearch("")
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
end) end)
end end
else 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 -- 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. -- from event handlers (cast start, target/focus change), never per-frame.
local function RefreshBar(cb, rank) local function RefreshBar(cb, rank)
local query = cb.unitstr ~= "" and cb.unitstr or cb.unitname local query = cb.unitstr
if not query or (cb.unitstr ~= "" and not UnitExists(cb.unitstr)) then if not UnitExists(query) then
ClearBar(cb) ClearBar(cb)
return return
end end
@@ -178,7 +178,7 @@ pfUI:RegisterModule("castbar", function ()
end end
end end
local function CreateCastbar(name, parent, unitstr, unitname) local function CreateCastbar(name, parent, unitstr)
local cb = CreateFrame("Frame", name, parent or UIParent) local cb = CreateFrame("Frame", name, parent or UIParent)
cb:SetHeight(C.global.font_size * 1.5) cb:SetHeight(C.global.font_size * 1.5)
@@ -186,7 +186,6 @@ pfUI:RegisterModule("castbar", function ()
cb:SetFrameLevel(8) cb:SetFrameLevel(8)
cb.unitstr = unitstr cb.unitstr = unitstr
cb.unitname = unitname
-- icon -- icon
cb.icon = CreateFrame("Frame", nil, cb) cb.icon = CreateFrame("Frame", nil, cb)
@@ -277,7 +276,7 @@ pfUI:RegisterModule("castbar", function ()
-- Non-player bars: if the unit disappears (target died / detarget), -- Non-player bars: if the unit disappears (target died / detarget),
-- drop the bar immediately. -- 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) ClearBar(this)
return return
end end
@@ -423,13 +422,12 @@ pfUI:RegisterModule("castbar", function ()
-- endMs. delay is a positive magnitude; OnUpdate signs it "+" for casts -- endMs. delay is a positive magnitude; OnUpdate signs it "+" for casts
-- and "-" for channels. Skipped during a tradeskill merge. -- and "-" for channels. Skipped during a tradeskill merge.
if this.endTime and not this.tradeskillTotal then if this.endTime and not this.tradeskillTotal then
local query = this.unitstr ~= "" and this.unitstr or this.unitname
local startMs, endMs local startMs, endMs
if this.isChannel then if this.isChannel then
local _, _, _, s, e = C_Spell.UnitChannelInfo(query) local _, _, _, s, e = C_Spell.UnitChannelInfo(unit)
startMs, endMs = s, e startMs, endMs = s, e
else else
local _, _, _, s, e = C_Spell.UnitCastingInfo(query) local _, _, _, s, e = C_Spell.UnitCastingInfo(unit)
startMs, endMs = s, e startMs, endMs = s, e
end end
if startMs and endMs then if startMs and endMs then
@@ -544,9 +542,6 @@ pfUI:RegisterModule("castbar", function ()
pfUI.castbar.focus:SetHeight(C.castbar.focus.height) pfUI.castbar.focus:SetHeight(C.castbar.focus.height)
end end
-- bind castbar to the "focus" unit token; the GUID resolves at read time
pfUI.castbar.focus.unitstr = "focus"
UpdateMovable(pfUI.castbar.focus) UpdateMovable(pfUI.castbar.focus)
end end
end) end)
+5 -28
View File
@@ -29,38 +29,15 @@ pfUI:RegisterModule("focus", function ()
end) end)
-- /focus and /clearfocus live in ClassicAPI's SlashCommandsRegistry now. -- /focus and /clearfocus live in ClassicAPI's SlashCommandsRegistry now.
-- /focusname is pfUI-specific because the engine has no name→GUID -- `/focus Name` takes the nearest prefix match; /focusname tries the exact
-- lookup for off-screen units — we resolve via a short target-swap. -- name first, so "Bob" still wins over a nearer "Bobby".
pfUI.api.RegisterSlashCommand("PFFOCUSNAME", { '/focusname', '/pffocusname' }, function(msg) pfUI.api.RegisterSlashCommand("PFFOCUSNAME", { '/focusname', '/pffocusname' }, function(msg)
if msg == "" then return end if msg == "" then return end
local prevGUID = UnitGUID("target") local unit = UnitTokenFromName(msg, true) or UnitTokenFromName(msg)
local prevPlayer = UnitIsUnit("target", "player") if unit then
FocusUnit(unit)
-- 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()
end end
end, true) 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["Hide Chat When Bags Are Opened"], C.appearance.bags, "hidechat", "checkbox")
CreateConfig(nil, T["Bagslots Per Row"], C.appearance.bags, "bagrowlength") CreateConfig(nil, T["Bagslots Per Row"], C.appearance.bags, "bagrowlength")
CreateConfig(nil, T["Bankslots Per Row"], C.appearance.bags, "bankrowlength") 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(nil, T["Item Slot Size"], C.appearance.bags, "icon_size")
CreateConfig(Reload, T["Reverse Cooldown Animation"], C.appearance.bags, "cooldown_reverse", "checkbox") CreateConfig(Reload, T["Reverse Cooldown Animation"], C.appearance.bags, "cooldown_reverse", "checkbox")
CreateConfig(nil, T["Auto Sell Grey Items"], C.global, "autosell", "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 do -- FPS & Lag
local widget = CreateFrame("Frame", "pfPanelWidgetLag", UIParent) local widget = CreateFrame("Frame", "pfPanelWidgetLag", UIParent)
local lag, fps, laghex, fpshex, _ local lag, fps, laghex, fpshex, owner, _
widget.Tooltip = function() widget.Tooltip = function(self)
owner = self
local active = 0 local active = 0
GameTooltip:ClearLines() GameTooltip:ClearLines()
GameTooltip_SetDefaultAnchor(GameTooltip, this) GameTooltip_SetDefaultAnchor(GameTooltip, self)
GameTooltip:AddLine("|cff555555" .. T["Systeminfo"]) GameTooltip:AddLine("|cff555555" .. T["Systeminfo"])
for i=1, GetNumAddOns() do for i=1, GetNumAddOns() do
if IsAddOnLoaded(i) then if IsAddOnLoaded(i) then
@@ -128,6 +129,51 @@ pfUI:RegisterModule("panel", function()
GameTooltip:AddLine(" ") GameTooltip:AddLine(" ")
GameTooltip:AddDoubleLine(T["Memory Usage"], "|cffffffff" .. memmb) GameTooltip:AddDoubleLine(T["Memory Usage"], "|cffffffff" .. memmb)
GameTooltip:AddDoubleLine(T["Next Memory Cleanup"], "|cffffffff" .. gcmb) 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:AddLine(" ")
GameTooltip:AddDoubleLine(T["Network Down"], "|cffffffff" .. round(nin,1) .. "KB/s") GameTooltip:AddDoubleLine(T["Network Down"], "|cffffffff" .. round(nin,1) .. "KB/s")
GameTooltip:AddDoubleLine(T["Network Up"], "|cffffffff" .. round(nout,1) .. "KB/s") GameTooltip:AddDoubleLine(T["Network Up"], "|cffffffff" .. round(nout,1) .. "KB/s")
@@ -157,6 +203,10 @@ pfUI:RegisterModule("panel", function()
end end
pfUI.panel:OutputPanel("fps", fps .. " " .. T["fps"] .. " & " .. lag .. " " .. T["ms"], widget.Tooltip, widget.Click) 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)
end end
+2 -14
View File
@@ -1172,21 +1172,9 @@ pfUI:RegisterModule("thirdparty-vanilla", function()
end) end)
local EnableHealComm = function() 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 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 pfUI.api.libpredict.sender.enabled = nil
end end
end end
-15
View File
@@ -497,19 +497,4 @@ function pfUI.SetupCVars()
UIParentLoadAddOn("Blizzard_CombatText") UIParentLoadAddOn("Blizzard_CombatText")
end 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 _G.PLAYER_BUFF_START_ID = -1
+30
View File
@@ -279,6 +279,36 @@ pfUI:RegisterSkin("Character", function ()
SkinCheckbox(ReputationDetailInactiveCheckBox) SkinCheckbox(ReputationDetailInactiveCheckBox)
SkinCheckbox(ReputationDetailMainScreenCheckBox) 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 -- Append "(N)" to each standing label where N is rep remaining to the
-- next reaction. Skip exalted (reaction 8) and effectively-capped bars. -- next reaction. Skip exalted (reaction 8) and effectively-capped bars.
-- Each bar's OnLeave handler (Blizzard's ReputationFrame.xml) restores -- Each bar's OnLeave handler (Blizzard's ReputationFrame.xml) restores