14 Commits

Author SHA1 Message Date
Brues cc8b8b476b Removed macrotweak 2026-09-09 23:05:10 -05:00
Brues bd2b6c3105 no need to load macrotweak if ClassicAPI 1.15 is found 2026-09-09 21:48:13 -05:00
Brues 5bede33f00 energytick: sweep the real regen clock, and stop it stuttering
The mana spark modelled a tick clock that does not exist. Spending mana
restarted the sweep with a five-second span, then it cycled at two seconds
until the next spend -- so every cast reset the phase, and the spark drew a
period the server never runs. There is one regen timer for every power,
REGEN_TIME_FULL (2s), re-armed with `+=` in Player::RegenerateAll and never
touched by casting. The five-second rule is a separate countdown that
SetLastManaUse arms on any mana-costing cast; it changes what a tick pays,
never when ticks land. Inside the window the tick still fires and still pays
mp5 plus whatever share of spirit the player keeps.

That share is common and not computable here. The amounts are readable now
(C_Spell.GetSpellEffectInfo), and the talent, racial and buff sources could be
enumerated -- but the Casting Regen item ladder is equip auras that never
appear in the buff list, and m_modManaRegenInterrupt is never sent. A floor
that is wrong whenever the player wears such an item is worse than showing
what happens: the spark starts an FSR window dim and goes solid on the first
tick that lands inside it.

The sweep is a free-running phase lock on that one clock. Every positive mana
delta used to re-anchor it, and most are not the tick -- Illumination refunds,
Judgement of Wisdom, potions, a Mana Spring totem on its own 2s clock at
another phase -- so the spark snapped back to the edge mid-sweep several times
a cycle. A gain re-anchors only if it lands within 250ms of the predicted
boundary. Two rejected gains exactly one period apart are the real clock and
move the lock, which is how a wrong first lock heals. Rollover advances by
whole periods from the anchor instead of restarting from now, so frame timing
no longer accumulates into drift the lock then has to chase.

Even a correct tick used to hitch the spark: re-anchoring to arrival time
moved it by the jitter, 20-80ms, right at the wrap. Inside an 80ms band the
tick now confirms the sweep and leaves the anchor alone; only the excess is
pulled in. With no self-drift left there is nothing systematic in that band.

The rule is armed from UNIT_SPELLCAST_SUCCEEDED filtered through
C_Spell.GetSpellPowerCost, mirroring Spell::TakePower's condition, rather than
from a mana decrease -- Mana Burn lowers mana without arming the rule.
UNIT_SPELLCAST_CHANNEL_STOP re-arms it, since Unit::Update will not expire it
while the spending spell is still channeling.

The energy period no longer names a spell. Player::RegenerateAll sums
SPELL_AURA_MOD_ENERGY_REGEN_TIME and takes amount * agility / 10 off the timer
in milliseconds; everything in that sum is readable here. The player's own
spells carrying aura 217 come from C_SpellBook.GetPlayerSpellsByAura, passives
count while known (current rank only), anything else while it is up in the
buff list, and each amount is basePoints + baseDice from the effect data. So
Blade Rush is found without GetTalentInfo(2, 16) -- an ordinal into the talent
grid that does not fail when the tree changes but reads another talent's rank
(index 16 is Vigor one tab over) -- and a retune or a second source needs no
edit. The sum is cached and dropped on SPELLS_CHANGED and
PLAYER_AURAS_CHANGED, the two events its two halves move on, so a tick costs
no spellbook walk; agility stays live, it is one call.

The CHAT_MSG_SPELL_SELF_BUFF "You gain ... Energy from" filter is gone. The
phase lock rejects Relentless Strikes and Thistle Tea the same way it rejects
a paladin's refunds, it was English-only, and its ignore-the-next-gain logic
could eat a real tick if events reordered.
2026-09-09 13:24:17 -05:00
Brues a286843b18 nameplates: gate the per-plate update before classifying it
The central loop calls the per-plate update for every visible plate ~100 times
a second, and the plate's own throttle sat at the bottom of the function. Above
it ran the whole classification: a GetAlpha call, a castbar IsShown, a cast
lookup, and one or two libthrottle:Get resolutions -- all to work out which
throttle applied, on plates throttled to 10fps that were going to return
anyway. Forty plates makes that four thousand times a second.

Add a cheap gate first, against the floor across all four throttle categories.
Nothing that would have updated can be turned away by it -- a plate past its
real throttle is necessarily past the minimum -- and the category-specific gate
still runs after the classification, unchanged. Event flags are read before
both and bypass both, as they did before.

libthrottle:Get is not a lookup: it walks the saved variables, a defaults
fallback and a preset table, and can build a "<category>_custom" key. Resolve
the four categories in CacheConfig instead, where config changes already land,
and read them from cfg on the hot path. That covers the two calls in the
castbar branch as well.

The castbar hide moves below the cheap gate, which is safe rather than merely
tolerable: a plate showing a castbar sets isCastingNonTarget, which selects the
castbar throttle -- one of the four the floor is derived from -- so it always
clears the first gate and reaches the hide at its normal rate.
2026-09-09 13:24:17 -05:00
Brues 39840c10fd unitframes: let UpdateVisibility own the event subscriptions
Every unit frame registered fifteen unit events globally and then sorted them
out per event:

  arg1 == this.label .. this.id or (UnitGUID and arg1 == UnitGUID(this.label .. this.id))

A string concat -- and on a miss a second concat plus a UnitGUID call -- for
every frame, for every unit event fired by anything in the world. Forty-five
frames in a raid, on every health tick of every raider, to conclude "not mine".

UpdateVisibility is the single place a frame's label and id are ever assigned,
and it already pushed the result to SetAttribute("unit", ...). It now caches
the unitstr and points the subscriptions at it, so there is one hook rather
than a set of transitions to catch, and visibilityscan re-running it five
times a second makes it self-healing. A frame that is not in use drops its
unit events entirely.

EnableEvents no longer registers them: a registration keeps its kind, so
plain-registering first would make every later RegisterUnitEvent a no-op.

UNIT_PET and UNIT_HAPPINESS stay plain deliberately. Their branches key on the
frame's label, and UNIT_PET's arg1 is the pet's OWNER -- "player" for a "pet"
frame -- so filtering them by the frame's own unit would drop them.

The GUID alternative is gone. These events fire once per token that resolves
to the unit AND once with the raw GUID, so the token form always arrives and
the GUID clause was only ever a duplicate wake. It dates from b7a0912e, before
ClassicAPI's TokenObserver made synthetic tokens first-class event sources.

Two teardown paths needed help. UnregisterAllEvents drops the filters with the
registrations, so the cached unit has to be cleared or the next
UpdateVisibility believes they are still in place. And PLAYER_LOGOUT silences
the frame while visibilityscan keeps ticking, which would re-register
everything it just dropped -- the crash 132 that branch exists to prevent -- so
the frame now takes itself off the scan list.
2026-09-09 13:24:17 -05:00
Brues ab98bb7ab5 nameplates: let each plate watch its own unit
The central handler registered UNIT_AURA, UNIT_FLAGS and the four
UNIT_SPELLCAST_* globally, so every unit event in the world woke it for a
"^nameplate" prefix test, and each of the four branches that survived the test
then resolved the plate back out of arg1 -- UnitGUID + plateByGuid, or
GetNamePlateForUnit.

Register them per plate instead, on the plate's own frame against its own
token. The token is the subscription, so the event only reaches the plate it
concerns and `this` is already that plate: the aura branch is now one
assignment where it used to be a prefix test, an API call and a table lookup.

This is also what makes the approach viable at all. A central listener would
have had to name every token it might care about, and nameplate slots have no
cap -- Events.cpp keeps them in a vector that grows by push_back, with only a
"<80 even in AV-scale scenes" comment for sizing. Any nameplate1..N list would
have been a guess that fails silently in exactly the crowded scenes where
plates matter.

Lifecycle: NAME_PLATE_UNIT_ADDED points the registrations at the new token,
replacing the previous unit rather than stacking, since RegisterUnitEvent over
an already-filtered registration swaps the units. NAME_PLATE_UNIT_REMOVED
unregisters, which is required -- freed slots are reused, and a stale token
would feed the frame another unit's events.

PLAYER_LOGOUT now tears down the plates too. It silenced only the central
frame before; leaving forty plate frames dispatching through logout is the
crash 132 that branch exists to prevent.
2026-09-09 13:24:17 -05:00
Brues 73f4999004 marktracking: filter to the mark tokens and idle when solo
UNIT_HEALTH/UNIT_MAXHEALTH register through RegisterUnitEvent for mark1
through mark8, so the scanner stops waking for every unit in the world. Eight
tokens is past the four other clients accept; ClassicAPI takes as many as it
is given, so this file is fork-only.

That makes arg1 always one of eight known strings, so the handler looks the
row index up in a table built alongside markerTokens, instead of running
string.match + tonumber -- which allocated a capture and parsed it on every
health tick of every marked unit.

The fallback poll was the larger cost and the filtering did not touch it: a
full eight-row rebuild once a second for the whole session, whether or not a
marker existed anywhere. Raid markers are a group feature, so the scanner now
watches PARTY_MEMBERS_CHANGED / RAID_ROSTER_UPDATE and creates or cancels the
ticker with group membership -- outside a group no timer is queued at all,
rather than one waking every second to return early. It is deliberately not
keyed on a mark being visible: a marker set on a unit that is out of range
shows no row, and that is precisely the case the poll exists to catch.

The mark-token capability probe at the top goes too. ClassicAPI is a hard
dependency, so the tokens are always there to resolve.
2026-09-09 13:24:17 -05:00
Brues b7c3fe5333 Filter unit events with RegisterUnitEvent
ClassicAPI's RegisterUnitEvent registers for an event but only delivers it
when arg1 is one of the given units, so a handler for one unit stops waking
for every other one in the world. Convert the 26 registrations whose unit set
is fixed and known at registration time.

The rule throughout is register the superset and keep the handler's own
check: the filter narrows what arrives, it does not decide what to act on.
castbar is the case that matters -- the player's own casts only ever fire
arg1=="player", never "target"/"focus", so the target and focus bars must
register "player" too, and their UnitIsUnit test is what still rejects a
"player" event while you are targeting a mob.

Two that are not one-line swaps:

  actionbar's two unit events are keys in tables that also drive dispatch, so
  they cannot leave the tables -- and they cannot be plain-registered first,
  because a registration keeps its kind and RegisterUnitEvent over a plain
  one stays plain. They route through a small event_units map instead.

  swingtimer's UNIT_DIED carries a GUID, not a token. The filter is a plain
  case-insensitive string compare with no GUID resolution, so the player's
  own GUID -- fixed for the session -- filters it exactly.

Guards that are now unreachable stay put: the filter applies only when arg1
is a string, so an event that ever fires with a number or no argument is
delivered as if plainly registered, and the handler's own test is what still
rejects it.

Left alone: api/unitframes.lua (the frame's unit changes at runtime and the
handler also matches on GUID), nameplates' six nameplateN events, libdebuff
and libpredict's UNIT_HEALTH (genuinely any unit), and raid.lua's UNIT_PET
(any raid member can own the pet).
2026-09-09 13:24:17 -05:00
Brues 8e109a585a eqcompare: place the compare after the tooltip settles when Guda is loaded
Guda anchors its item tooltips with ANCHOR_NONE and its own SetPoint, and
the tooltip module then relocates every ANCHOR_NONE tooltip to its
configured spot. eqcompare picked left or right from GetLeft/GetRight
inside the Set* call -- against the position the tooltip was about to
leave -- so for an item in the left half of the screen the shopping
tooltips anchored to the right edge and rode the relocation off-screen
(vatichild/guda#41). Guda's own inventory block also widens the tooltip
after our hook has run.

With Guda present, resolve the link in the hook but hand the placement to
RunNextFrame, skipping it if the tooltip has hidden since. Guda lists
pfUI as a dependency, so it loads after us: gate on DoesAddOnExist and
flip the flag from ContinueOnAddOnLoaded. Without Guda the hook is
unchanged.
2026-09-09 13:23:53 -05:00
Brues 83f89be14a unitframes: compare indicator auras without case folding
The indicator scan lowercased the aura's name and icon on every aura of every
unit, and SetupBuffIndicators lowercased the other side to match. Both sides
come out of the same DBC records byte for byte, so the comparison already held
without any of it: the aura's name and icon are Spell.dbc's localized name and
SpellIcon.dbc's path (aura/Data.cpp fills one struct and emits it as either the
AuraData table or the positional UnitAura tuple), and the indicator record
reads those same two fields through C_Spell.GetSpellName / GetSpellTexture.

So this was two string allocations per aura per scan to reach a result it
already had -- and on Lua 5.0 that is an allocate-and-hash each time, since the
VM interns even when the string exists. RefreshUnit runs this for every unit
frame, which in a raid is 40 frames against up to 32 auras each.

Checked every rank of all 65 indicator spells in Spell.dbc: name and icon are
byte-identical across a spell's whole ladder, no case or spelling drift, so
nothing depended on the fold.

It also drops a crash path. aura/Data.cpp yields nil for the icon when a
spell's SpellIconID is 0, and icon:lower() would have errored on that; name was
guarded on the line above but icon never was. A raw compare is just false.

The equality is now load-bearing, so AddIndicator's comment says to feed it
spell ids and never a hand-written name or icon path -- a literal
"interface\icons\foo" in that table would silently match nothing.
2026-09-08 16:35:31 -05:00
Brues e59158764c unitframes: identify buff indicators by spell id, not icon path
Spell.dbc reuses icons freely across unrelated spells, so matching an aura on
its icon alone lit the indicator for the wrong buff. Blessing of Sanctuary
fired on Lightning Shield and Shadowguard, Blessing of Kings on Mage Armor and
Commanding Shout, and Totemic Power on the Blessed Sunfruit food buff -- the
last of which the code already carried a comment about. Fixes #55.

Each entry is now a spell id, resolved once into the aura's name plus its icon,
and a match needs both. Name alone is no better than icon alone: creature and
item auras reuse player spell names, so a mob's "Renew" or a trinket's
"Rejuvenation" would light a HoT indicator. Every rank of a spell carries the
same name and icon, so one id covers the whole rank ladder without listing it.

HOT_INDICATORS folds away with this. It existed to name-check exactly the three
HoTs whose icons were known to collide; that check is now what every entry
does, and the table's only remaining job -- naming a HoT's libpredict key -- is
an argument on the Renew, Regrowth and Rejuvenation entries.

Deriving the name from an id also picks up this client's renames for free. The
paladin blessings are "Hand of Freedom", "Hand of Protection" and "Greater
Blessing of Sacrifice" here, which hardcoded English names would have missed.

All 65 ids were checked against the client's Spell.dbc, SpellIcon.dbc and
SkillLineAbility.dbc: each resolves to the intended aura, with its full rank
ladder sitting on the icon the old list matched.

That check also turned up five entries matching no spell at all on this
client. ability_hunter_misdirection, spell_holy_prayerofmendingtga and
inv_misc_herb_felblossom are TBC leftovers and are gone.
spell_nature_giftofthewild was never right -- Gift of the Wild uses Mark of the
Wild's icon -- and is now covered by id 21849. ability_warrior_rallyingcry
hosts no Commanding Shout either, so that entry now points at the real spell
(45580) and works for the first time.

Unrelated to the above, this file's string.fn(s, ...) calls become s:fn(...).
2026-09-08 16:28:51 -05:00
Brues 863d99647d Draw the loot history and map overlays from real pools
Both modules had already grown a pool by hand. loothistory kept usedPlayers /
freePlayers tables with a recycle loop and a table.remove acquire; mapreveal
kept pfOverlays keyed by index, a pfOverlayMax high-water mark, and a
hide-the-tail loop. Replace both with ClassicAPI's Pools.lua backport, which
is what modules/tooltip.lua already uses for its buff icons.

The map explore icons change shape rather than just plumbing. Everything
constant -- size, scripts, mouse, frame level, the magnifying glass texture --
moves into the pool's creator instead of being re-set on every overlay on
every map update, and the acquire moves inside the visibility test. The old
loop built and configured an icon for every overlay in the zone and then hid
most of them again; with "mapexploration" off it built the whole set and hid
all of them. Now nothing is acquired for an icon that will not be shown.

The tile textures lose their pfRevealN names, since pools create anonymous
regions. They were already unique, so nothing was being clobbered -- this
only costs their labels in /fstack.
2026-09-07 18:41:54 -05:00
Brues 06b3ae8d22 cooldown: stop shadowing the global time()
`local time = time()` bound the timestamp over the top of the time function
inside that branch, so any later call in the same scope would have indexed a
number. Nothing did, but the name is a trap. Call it currentTime.
2026-09-07 18:39:28 -05:00
Brues 879d7afe83 cooldown: throttle before the work, not after
The 0.1s gate sat below the hidden-cooldown check, so every frame, for every
ticking cooldown, the update built "<parent>Cooldown" twice and did two _G
lookups with it. Lua 5.0 interns on every concat -- it allocates and hashes
even when the string already exists -- so this was allocating garbage at the
frame rate times the number of live cooldowns. A profiler run had it at 24s
of accumulated CPU.

Move the gate to the top so a non-tick frame costs one GetTime() and a
compare. The name lookup goes away entirely: pfCreateCoolDown already has the
cooldown frame, so it stashes the reference instead. That is also more
correct than deriving it from the parent's name, which silently skipped the
check for any cooldown not named "<parent>Cooldown".

The text frame and its fontstring were both created with a fixed name, so
every one of the hundreds in a UI clobbered _G.pfCooldownFrame and left it
pointing at whichever was made last. Name them after the cooldown they render
for, falling back to a counter for anonymous ones.

One behavior change: the hidden-cooldown check is now throttled too, so text
can linger up to 100ms after its cooldown frame hides. It only refreshes at
0.1s anyway, and expiry still runs through the remaining < 0 branch.
2026-09-07 18:14:33 -05:00
31 changed files with 663 additions and 576 deletions
+1 -1
View File
@@ -13,7 +13,7 @@ do
-- on. There pfUI does load, and will throw wherever it reaches for something
-- the installed DLL doesn't have yet -- the popup names the cause so those
-- errors aren't a mystery.
local PFUI_CLASSIC_API_MIN = 11304 -- (X*10000 + Y*100 + Z)
local PFUI_CLASSIC_API_MIN = 11500 -- (X*10000 + Y*100 + Z)
local PFUI_CLASSIC_API_LATEST = PFUI_CLASSIC_API_MIN
local PFUI_CLASSIC_API_WEBSITE = "https://github.com/brues-code/ClassicAPI"
local PFUI_CLASSIC_API_LATEST_URL = PFUI_CLASSIC_API_WEBSITE .. "/releases/latest"
-1
View File
@@ -645,7 +645,6 @@ function pfUI:LoadConfig()
pfUI:UpdateConfig("bars", nil, "animation", "zoomfade")
pfUI:UpdateConfig("bars", nil, "animmode", "keypress")
pfUI:UpdateConfig("bars", nil, "animalways", "0")
pfUI:UpdateConfig("bars", nil, "macroscan", "1")
pfUI:UpdateConfig("bars", nil, "reagents", "1")
pfUI:UpdateConfig("bars", nil, "hunterbar", "0")
pfUI:UpdateConfig("bars", nil, "pagemasteralt", "0")
+193 -168
View File
@@ -35,14 +35,38 @@ pfUI.api.RegisterSlashCommand("PFTEST", { "/pftest", "/pfuftest" }, function()
if pfUI.uf.raid and pfUI.uf.raid.LayoutPets then pfUI.uf.raid:LayoutPets() end
end, true)
-- HoT buff indicators that need name verification because their icons are
-- reused by other spells. Maps icon (lowercased) → expected aura name +
-- libpredict key for the prediction integration.
local HOT_INDICATORS = {
[strlower(C_Spell.GetSpellTexture(774))] = { name = strlower(C_Spell.GetSpellName(774)), predict = "Reju" },
[strlower(C_Spell.GetSpellTexture(139))] = { name = strlower(C_Spell.GetSpellName(139)), predict = "Renew" },
[strlower(C_Spell.GetSpellTexture(8936))] = { name = strlower(C_Spell.GetSpellName(8936)), predict = "Regr" },
}
-- Buff indicators are identified by a spell id, resolved once into the aura's
-- localized name plus its icon. A match needs both to agree.
--
-- Icon alone is ambiguous: Spell.dbc reuses icons across unrelated spells, so
-- an icon-only filter lights the indicator for the wrong buff (Blessing of
-- Sanctuary shares its icon with Lightning Shield and Shadowguard, Blessing of
-- Kings with Mage Armor and Commanding Shout, Totemic Power with the Blessed
-- Sunfruit food buff). Name alone is ambiguous too -- creature and item auras
-- reuse player spell names ("Renew", "Rejuvenation", "Fire Resistance").
--
-- Every rank of a spell carries the same name and icon, so one id per buff
-- covers the whole rank ladder. Ids missing from this client resolve to nil and
-- drop out of the list. 'predict' names the libpredict key of a HoT.
--
-- Both fields are stored raw, and RefreshUnit compares them raw. The aura's
-- name and icon and these come out of the same DBC records byte for byte --
-- Spell.dbc's localized name, SpellIcon.dbc's path -- so case folding either
-- side would only burn a string per aura per scan. Feed this ids, never
-- hand-written names or icon paths, or that equality quietly stops holding.
local indicator_cache = {}
local function AddIndicator(indicators, spellId, predict)
local record = indicator_cache[spellId]
if record == nil then
local name = C_Spell.GetSpellName(spellId)
local icon = name and C_Spell.GetSpellTexture(spellId)
-- cache misses as false, so an absent spell is only looked up once
record = icon and { name = name, icon = icon, predict = predict } or false
indicator_cache[spellId] = record
end
if record then table.insert(indicators, record) end
end
local glow = {
edgeFile = pfUI.media["img:glow"], edgeSize = 8,
@@ -289,9 +313,10 @@ function pfUI.uf:UpdateVisibility()
self._label, self._id = nil, nil
end
local unitstr = string.format("%s%s", self.label or "", self.id or "")
self:SetAttribute("unit", unitstr ~= "" and unitstr or nil)
local visibility = string.format("[target=%s,exists] show; hide", unitstr)
local unitstr = ("%s%s"):format(self.label or "", self.id or "")
self.unitstr = unitstr ~= "" and unitstr or nil
self:SetAttribute("unit", self.unitstr)
local visibility = ("[target=%s,exists] show; hide"):format(unitstr)
-- Group frames are redundant when the group is already shown as a raid grid:
-- either an actual raid, or a party promoted to the raid grid via
@@ -320,6 +345,15 @@ function pfUI.uf:UpdateVisibility()
self.visible = nil
end
-- This is the single place a frame's unit is ever assigned, so it is also the
-- place its subscriptions follow it to: the engine then delivers only this
-- unit's events, and OnEvent compares against the cached string instead of
-- rebuilding label..id -- and calling UnitGUID -- for every unit event fired
-- by anything, anywhere. A frame that is not in use drops them entirely; the
-- roster events that would bring it back are registered plainly, and
-- visibilityscan re-runs this every 0.2s regardless, so it recovers on its own.
self:RegisterUnitEvents(visibility ~= "hide" and self.unitstr or nil)
-- vanilla visibility
if self.unitname then
self:Show()
@@ -600,11 +634,14 @@ function pfUI.uf:UpdateConfig()
f.feedbackText:ClearAllPoints()
f.feedbackText:SetPoint("CENTER", f.portrait, "CENTER")
end
f:RegisterEvent("UNIT_COMBAT")
f.combatfeedback = true
else
f.feedbackText:Hide()
f:UnregisterEvent("UNIT_COMBAT")
f.combatfeedback = nil
end
-- RegisterUnitEvents owns UNIT_COMBAT; clearing the cached unit makes the
-- next UpdateVisibility re-run it against the new combatfeedback state.
f.eventunit = nil
f.hpLeftText:SetFontObject(GameFontWhite)
f.hpLeftText:SetFont(fontname, fontsize, fontstyle)
@@ -894,6 +931,9 @@ function pfUI.uf:UpdateConfig()
f:UpdateFrameSize()
else
f:UnregisterAllEvents()
-- that dropped the unit filters along with the registrations, so the cache
-- has to go too or the next UpdateVisibility believes they are still set
f.eventunit = nil
f:Hide()
end
end
@@ -935,6 +975,10 @@ function pfUI.uf.OnEvent()
this:UnregisterAllEvents()
this:SetScript("OnEvent", nil)
this:SetScript("OnUpdate", nil)
-- visibilityscan is a separate frame and keeps ticking, so leaving this one
-- on its list would have UpdateVisibility re-register the unit events we
-- just dropped -- straight back into the crash 132 this branch prevents
visibilityscan.frames[this] = nil
return
end
@@ -1001,8 +1045,12 @@ function pfUI.uf.OnEvent()
this.update_aura = true
elseif this.label == "pet" and event == "UNIT_HAPPINESS" then
this.update_full = true
-- UNIT_XXX Events
elseif arg1 and (arg1 == this.label .. this.id or (UnitGUID and arg1 == UnitGUID(this.label .. this.id))) then
-- UNIT_XXX Events. RegisterUnitEvents means arg1 can only be this frame's own
-- unit; the compare is kept for the case the filter sits out, which is when
-- arg1 is not a string. The old GUID alternative is gone: these events fire
-- once per token that resolves to the unit AND once with the raw GUID, so the
-- token form always arrives and the GUID form was only ever a duplicate wake.
elseif arg1 and arg1 == this.unitstr then
if event == "UNIT_PORTRAIT_UPDATE" or event == "UNIT_MODEL_CHANGED" then
this.update_portrait = true
elseif event == "UNIT_AURA" then
@@ -1227,25 +1275,56 @@ function pfUI.uf.OnUpdate()
end
end
-- The unit events whose arg1 is the frame's OWN unit -- everything the OnEvent
-- routes through its "UNIT_XXX Events" branch. These are not registered here:
-- UpdateVisibility owns them, because it owns the frame's unit (below).
--
-- UNIT_PET and UNIT_HAPPINESS are deliberately absent. Their branches key on
-- the frame's label, and UNIT_PET's arg1 is the pet's OWNER ("player" for a
-- "pet" frame), so filtering them by the frame's own unit would drop them.
local UNIT_EVENTS = {
"UNIT_DISPLAYPOWER",
"UNIT_HEALTH", "UNIT_MAXHEALTH",
"UNIT_MANA", "UNIT_MAXMANA",
"UNIT_RAGE", "UNIT_MAXRAGE",
"UNIT_ENERGY", "UNIT_MAXENERGY",
"UNIT_FOCUS",
"UNIT_PORTRAIT_UPDATE", "UNIT_MODEL_CHANGED",
"UNIT_FACTION",
"UNIT_AURA", -- frame=buff, frame=debuff
}
-- Point this frame's unit-event subscriptions at `unitstr`, or drop them when
-- the frame has no unit. Cheap to call repeatedly: it no-ops unless the unit
-- actually changed, which matters because visibilityscan runs UpdateVisibility
-- for every frame five times a second.
function pfUI.uf:RegisterUnitEvents(unitstr)
if self.eventunit == unitstr then return end
self.eventunit = unitstr
for i = 1, table.getn(UNIT_EVENTS) do
if unitstr then
self:RegisterUnitEvent(UNIT_EVENTS[i], unitstr)
else
self:UnregisterEvent(UNIT_EVENTS[i])
end
end
-- UNIT_COMBAT rides along only while the frame draws combat feedback text.
-- UpdateConfig clears eventunit when it toggles that, so the next
-- UpdateVisibility re-runs this.
if unitstr and self.combatfeedback then
self:RegisterUnitEvent("UNIT_COMBAT", unitstr)
else
self:UnregisterEvent("UNIT_COMBAT")
end
end
function pfUI.uf:EnableEvents()
local f = self
f:RegisterEvent("PLAYER_ENTERING_WORLD")
f:RegisterEvent("PLAYER_LOGOUT")
f:RegisterEvent("UNIT_DISPLAYPOWER")
f:RegisterEvent("UNIT_HEALTH")
f:RegisterEvent("UNIT_MAXHEALTH")
f:RegisterEvent("UNIT_MANA")
f:RegisterEvent("UNIT_MAXMANA")
f:RegisterEvent("UNIT_RAGE")
f:RegisterEvent("UNIT_MAXRAGE")
f:RegisterEvent("UNIT_ENERGY")
f:RegisterEvent("UNIT_MAXENERGY")
f:RegisterEvent("UNIT_FOCUS")
f:RegisterEvent("UNIT_PORTRAIT_UPDATE")
f:RegisterEvent("UNIT_MODEL_CHANGED")
f:RegisterEvent("UNIT_FACTION")
f:RegisterEvent("UNIT_AURA") -- frame=buff, frame=debuff
f:RegisterEvent("PLAYER_AURAS_CHANGED") -- label=player && frame=buff
f:RegisterEvent("PLAYER_EQUIPMENT_CHANGED") -- label=player && frame=buff (ClassicAPI: weapon-enchant buffs)
f:RegisterEvent("PARTY_MEMBERS_CHANGED") -- label=party, frame=leaderIcon
@@ -1363,6 +1442,7 @@ function pfUI.uf:CreateUnitFrame(unit, id, config, tick)
f.UpdateConfig = pfUI.uf.UpdateConfig
f.EnableScripts = pfUI.uf.EnableScripts
f.EnableEvents = pfUI.uf.EnableEvents
f.RegisterUnitEvents = pfUI.uf.RegisterUnitEvents
f.EnableClickCast = pfUI.uf.EnableClickCast
f.GetColor = pfUI.uf.GetColor
@@ -1479,6 +1559,9 @@ function pfUI.uf:CreateUnitFrame(unit, id, config, tick)
f:UpdateFrameSize()
else
f:UnregisterAllEvents()
-- that dropped the unit filters along with the registrations, so the cache
-- has to go too or the next UpdateVisibility believes they are still set
f.eventunit = nil
f:Hide()
end
@@ -1939,7 +2022,7 @@ function pfUI.uf:RefreshUnit(unit, component)
if not unit.indicator_custom and unit.config.buff_indicator == "1" then
unit.indicator_custom = {}
for k, v in pairs({strsplit("#", unit.config.custom_indicator)}) do
unit.indicator_custom[k] = string.lower(v)
unit.indicator_custom[k] = v:lower()
end
elseif not unit.indicator_custom then
unit.indicator_custom = {}
@@ -1951,17 +2034,12 @@ function pfUI.uf:RefreshUnit(unit, component)
for i=1,n do
local name, icon, count, _, _, expirationTime = C_UnitAuras.UnitAuraBySlot(unitstr, auraSlots[i])
if not name then break end
local texLower = string.lower(icon)
local timeleft = expirationTime > 0 and (expirationTime - GetTime()) or nil
for _, filter in pairs(unit.indicators) do
if filter == texLower then
local hot = HOT_INDICATORS[texLower]
if hot and string.lower(name) ~= hot.name then
break -- texture matches but name disambiguates (e.g. shared icon)
end
if hot then
local start, duration, prediction = libpredict:GetHotDuration(unitstr, hot.predict)
if filter.icon == icon and filter.name == name then
if filter.predict then
local start, duration, prediction = libpredict:GetHotDuration(unitstr, filter.predict)
pfUI.uf:AddIcon(unit, pos, icon, timeleft or prediction, count, tonumber(start), tonumber(duration))
else
pfUI.uf:AddIcon(unit, pos, icon, timeleft, count)
@@ -1979,7 +2057,7 @@ function pfUI.uf:RefreshUnit(unit, component)
local name, icon, count, _, _, expirationTime = C_UnitAuras.UnitAuraBySlot(unitstr, auraSlots[i])
if not name then break end
local timeleft = expirationTime > 0 and (expirationTime - GetTime()) or nil
local lowerName = string.lower(name)
local lowerName = name:lower()
for _, filter in pairs(unit.indicator_custom) do
if filter == lowerName then
pfUI.uf:AddIcon(unit, pos, icon, timeleft, count)
@@ -1996,7 +2074,7 @@ function pfUI.uf:RefreshUnit(unit, component)
if name then
local timeleft = expirationTime > 0 and (expirationTime - GetTime()) or nil
for _, filter in pairs(unit.indicator_custom) do
if filter == string.lower(name) then
if filter == name:lower() then
pfUI.uf:AddIcon(unit, pos, icon, timeleft, count)
pos = pos + 1
break
@@ -2205,17 +2283,17 @@ function pfUI.uf:EnableClickCast()
local action = pfUI_config.unitframes["clickcast"..bconf..mconf]
if action and action ~= "" then
local prefix = modifier ~= "" and (modifier .. "-") or ""
local low = string.lower(action)
local low = action:lower()
if low == "menu" then
self:SetAttribute(prefix .. "type" .. bid, "menu")
elseif low == "target" then
self:SetAttribute(prefix .. "type" .. bid, "target")
elseif low == "focus" then
self:SetAttribute(prefix .. "type" .. bid, "focus")
elseif string.find(low, "^macro:") then
elseif low:find("^macro:") then
self:SetAttribute(prefix .. "type" .. bid, "macro")
self:SetAttribute(prefix .. "macro" .. bid, string.gsub(string.sub(action, 7), "^%s+", ""))
elseif string.find(action, "^/") then
self:SetAttribute(prefix .. "macro" .. bid, action:sub(7):gsub("^%s+", ""))
elseif action:find("^/") then
self:SetAttribute(prefix .. "type" .. bid, "macro")
self:SetAttribute(prefix .. "macrotext" .. bid, action)
else
@@ -2353,175 +2431,122 @@ function pfUI.uf:SetupBuffIndicators(config)
if config.show_buffs == "1" then -- buffs
if myclass == "DRUID" then
-- Mark of the Wild
table.insert(indicators, "interface\\icons\\spell_nature_regeneration")
-- Gift of the Wild
table.insert(indicators, "interface\\icons\\spell_nature_giftofthewild")
-- Thorns
table.insert(indicators, "interface\\icons\\spell_nature_thorns")
AddIndicator(indicators, 1126) -- Mark of the Wild
AddIndicator(indicators, 21849) -- Gift of the Wild
AddIndicator(indicators, 467) -- Thorns
end
if myclass == "PRIEST" then
-- Prayer Of Fortitude"
table.insert(indicators, "interface\\icons\\spell_holy_wordfortitude")
table.insert(indicators, "interface\\icons\\spell_holy_prayeroffortitude")
-- Prayer of Spirit
table.insert(indicators, "interface\\icons\\spell_holy_divinespirit")
table.insert(indicators, "interface\\icons\\spell_holy_prayerofspirit")
-- Shadow Protection
table.insert(indicators, "interface\\icons\\spell_shadow_antishadow")
table.insert(indicators, "interface\\icons\\spell_holy_prayerofshadowprotection")
-- Fear Ward
table.insert(indicators, "interface\\icons\\spell_holy_excorcism")
AddIndicator(indicators, 1243) -- Power Word: Fortitude
AddIndicator(indicators, 21562) -- Prayer of Fortitude
AddIndicator(indicators, 6386) -- Divine Spirit
AddIndicator(indicators, 27681) -- Prayer of Spirit
AddIndicator(indicators, 976) -- Shadow Protection
AddIndicator(indicators, 27683) -- Prayer of Shadow Protection
AddIndicator(indicators, 6346) -- Fear Ward
end
if myclass == "PALADIN" then
-- Blessing of Salvation
table.insert(indicators, "interface\\icons\\spell_holy_greaterblessingofsalvation")
table.insert(indicators, "interface\\icons\\spell_holy_sealofsalvation")
-- Blessing of Wisdom
table.insert(indicators, "interface\\icons\\spell_holy_sealofwisdom")
table.insert(indicators, "interface\\icons\\spell_holy_greaterblessingofwisdom")
-- Blessing of Sanctuary
table.insert(indicators, "interface\\icons\\spell_nature_lightningshield")
table.insert(indicators, "interface\\icons\\spell_holy_greaterblessingofsanctuary")
-- Blessing of Kings
table.insert(indicators, "interface\\icons\\spell_magic_magearmor")
table.insert(indicators, "interface\\icons\\spell_magic_greaterblessingofkings")
-- Blessing of Might
table.insert(indicators, "interface\\icons\\spell_holy_fistofjustice")
table.insert(indicators, "interface\\icons\\spell_holy_greaterblessingofkings")
-- Blessing of Light
table.insert(indicators, "interface\\icons\\spell_holy_prayerofhealing02")
table.insert(indicators, "interface\\icons\\spell_holy_greaterblessingoflight")
-- Blessing of Sacrifice
table.insert(indicators, "interface\\icons\\spell_holy_sealofsacrifice")
-- Blessing of Freedom
table.insert(indicators, "interface\\icons\\spell_holy_sealofvalor")
-- Blessing of Protection
table.insert(indicators, "interface\\icons\\spell_holy_sealofprotection")
AddIndicator(indicators, 1038) -- Blessing of Salvation
AddIndicator(indicators, 25895) -- Greater Blessing of Salvation
AddIndicator(indicators, 19742) -- Blessing of Wisdom
AddIndicator(indicators, 25894) -- Greater Blessing of Wisdom
AddIndicator(indicators, 20204) -- Blessing of Sanctuary
AddIndicator(indicators, 25899) -- Greater Blessing of Sanctuary
AddIndicator(indicators, 20217) -- Blessing of Kings
AddIndicator(indicators, 25898) -- Greater Blessing of Kings
AddIndicator(indicators, 19740) -- Blessing of Might
AddIndicator(indicators, 25782) -- Greater Blessing of Might
AddIndicator(indicators, 19977) -- Blessing of Light
AddIndicator(indicators, 25890) -- Greater Blessing of Light
AddIndicator(indicators, 6940) -- Hand of Sacrifice
AddIndicator(indicators, 45801) -- Greater Blessing of Sacrifice
AddIndicator(indicators, 1044) -- Hand of Freedom
AddIndicator(indicators, 1022) -- Hand of Protection
end
if myclass == "WARLOCK" then
-- Fire Shield
table.insert(indicators, "interface\\icons\\spell_fire_firearmor")
-- Blood Pact
table.insert(indicators, "interface\\icons\\spell_shadow_bloodboil")
-- Soulstone
table.insert(indicators, "interface\\icons\\spell_shadow_soulgem")
-- Unending Breath
table.insert(indicators, "interface\\icons\\spell_shadow_demonbreath")
-- Detect Greater Invisibility or Detect Invisibility
table.insert(indicators, "interface\\icons\\spell_shadow_detectinvisibility")
-- Detect Lesser Invisibility
table.insert(indicators, "interface\\icons\\spell_shadow_detectlesserinvisibility")
-- Paranoia
table.insert(indicators, "interface\\icons\\Spell_Shadow_AuraOfDarkness")
AddIndicator(indicators, 1167) -- Fire Shield
AddIndicator(indicators, 6307) -- Blood Pact
AddIndicator(indicators, 20707) -- Soulstone Resurrection
AddIndicator(indicators, 5697) -- Unending Breath
AddIndicator(indicators, 2970) -- Detect Invisibility
AddIndicator(indicators, 11743) -- Detect Greater Invisibility
AddIndicator(indicators, 132) -- Detect Lesser Invisibility
AddIndicator(indicators, 19480) -- Paranoia
end
if myclass == "WARRIOR" then
-- Battle Shout
table.insert(indicators, "interface\\icons\\ability_warrior_battleshout")
-- Commanding Shout (TBC)
table.insert(indicators, "interface\\icons\\ability_warrior_rallyingcry")
AddIndicator(indicators, 5242) -- Battle Shout
AddIndicator(indicators, 45580) -- Commanding Shout
end
if myclass == "MAGE" then
-- Arcane Intellect
table.insert(indicators, "interface\\icons\\spell_holy_magicalsentry")
table.insert(indicators, "interface\\icons\\spell_holy_arcaneintellect")
-- Dampen Magic
table.insert(indicators, "interface\\icons\\spell_nature_abolishmagic")
-- Amplify Magic
table.insert(indicators, "interface\\icons\\spell_holy_flashheal")
AddIndicator(indicators, 1459) -- Arcane Intellect
AddIndicator(indicators, 23028) -- Arcane Brilliance
AddIndicator(indicators, 604) -- Dampen Magic
AddIndicator(indicators, 1008) -- Amplify Magic
end
if myclass == "HUNTER" then
-- Aspect of the Wild
table.insert(indicators, "interface\\icons\\spell_nature_protectionformnature")
-- Aspect of the Pack
table.insert(indicators, "interface\\icons\\ability_mount_whitetiger")
-- Misdirection (TBC)
table.insert(indicators, "interface\\icons\\ability_hunter_misdirection")
AddIndicator(indicators, 20043) -- Aspect of the Wild
AddIndicator(indicators, 13159) -- Aspect of the Pack
end
if myclass == "SHAMAN" then
-- Earth Shield (TBC)
table.insert(indicators, "interface\\icons\\spell_nature_skinofearth")
AddIndicator(indicators, 45525) -- Earth Shield
end
end
if config.show_procs == "1" then -- procs
if myclass == "SHAMAN" or config.all_procs == "1" then
-- Ancestral Fortitude
table.insert(indicators, "interface\\icons\\spell_nature_undyingstrength")
-- Healing Way
table.insert(indicators, "interface\\icons\\spell_nature_healingway")
-- Totemic Power (known issue: one conflicts with Blessed Sunfruit buff)
table.insert(indicators, "interface\\icons\\spell_holy_spiritualguidence")
table.insert(indicators, "interface\\icons\\spell_holy_devotion")
table.insert(indicators, "interface\\icons\\spell_holy_holynova")
table.insert(indicators, "interface\\icons\\spell_magic_magearmor")
AddIndicator(indicators, 16177) -- Ancestral Fortitude
AddIndicator(indicators, 29202) -- Healing Way
-- Totemic Power is four auras, one per totem school, each with its own icon
AddIndicator(indicators, 28824)
AddIndicator(indicators, 28825)
AddIndicator(indicators, 28826)
AddIndicator(indicators, 28827)
end
if myclass == "PRIEST" or config.all_procs == "1" then
-- Inspiration
table.insert(indicators, "interface\\icons\\inv_shield_06")
AddIndicator(indicators, 14893) -- Inspiration
end
end
if config.show_hots == "1" then -- hots
if myclass == "PRIEST" or config.all_hots == "1" then
-- Renew
table.insert(indicators, "interface\\icons\\spell_holy_renew")
-- Power Word: Shield
table.insert(indicators, "interface\\icons\\spell_holy_powerwordshield")
-- Prayer of Mending (TBC)
table.insert(indicators, "interface\\icons\\spell_holy_prayerofmendingtga")
AddIndicator(indicators, 139, "Renew") -- Renew
AddIndicator(indicators, 17) -- Power Word: Shield
end
if myclass == "DRUID" or config.all_hots == "1" then
-- Regrowth
table.insert(indicators, "interface\\icons\\spell_nature_resistnature")
-- Rejuvenation
table.insert(indicators, "interface\\icons\\spell_nature_rejuvenation")
-- Lifebloom
table.insert(indicators, "interface\\icons\\inv_misc_herb_felblossom")
AddIndicator(indicators, 8936, "Regr") -- Regrowth
AddIndicator(indicators, 774, "Reju") -- Rejuvenation
end
end
if config.show_totems == "1" and myclass == "SHAMAN" then -- totems
-- Strength of Earth Totem
table.insert(indicators, "interface\\icons\\spell_nature_earthbindtotem")
-- Stoneskin Totem
table.insert(indicators, "interface\\icons\\spell_nature_stoneskintotem")
-- Mana Spring Totem
table.insert(indicators, "interface\\icons\\spell_nature_manaregentotem")
-- Mana Tide Totem
table.insert(indicators, "interface\\icons\\spell_frost_summonwaterelemental")
-- Healing Spring Totem
table.insert(indicators, "interface\\icons\\inv_spear_04")
-- Tranquil Air Totem
table.insert(indicators, "interface\\icons\\spell_nature_brilliance")
-- Grace of Air Totem
table.insert(indicators, "interface\\icons\\spell_nature_invisibilitytotem")
-- Grounding Totem
table.insert(indicators, "interface\\icons\\spell_nature_groundingtotem")
-- Nature Resistance Totem
table.insert(indicators, "interface\\icons\\spell_nature_natureresistancetotem")
-- Fire Resistance Totem
table.insert(indicators, "interface\\icons\\spell_fireresistancetotem_01")
-- Frost Resistance Totem
table.insert(indicators, "interface\\icons\\spell_frostresistancetotem_01")
-- the aura each totem applies, not the cast that drops it: they share an
-- icon but the totem's own name carries a " Totem" suffix the aura lacks
AddIndicator(indicators, 8076) -- Strength of Earth
AddIndicator(indicators, 8072) -- Stoneskin
AddIndicator(indicators, 5677) -- Mana Spring
AddIndicator(indicators, 16191) -- Mana Tide
AddIndicator(indicators, 5672) -- Healing Stream
AddIndicator(indicators, 25909) -- Tranquil Air
AddIndicator(indicators, 8836) -- Grace of Air
AddIndicator(indicators, 8177) -- Grounding Totem
AddIndicator(indicators, 10596) -- Nature Resistance
AddIndicator(indicators, 8185) -- Fire Resistance
AddIndicator(indicators, 8182) -- Frost Resistance
end
return indicators
end
local function abbrevname(t)
return string.sub(t,1,1)..". "
return t:sub(1,1)..". "
end
function pfUI.uf:GetNameString(unitstr)
@@ -2531,12 +2556,12 @@ function pfUI.uf:GetNameString(unitstr)
-- first try to only abbreviate the first word
if abbrev and name and strlen(name) > size then
name = string.gsub(name, "^(%S+) ", abbrevname)
name = name:gsub("^(%S+) ", abbrevname)
end
-- abbreviate all if it still doesn't fit
if abbrev and name and strlen(name) > size then
name = string.gsub(name, "(%S+) ", abbrevname)
name = name:gsub("(%S+) ", abbrevname)
end
return name
-1
View File
@@ -670,7 +670,6 @@ pfUI_translation["deDE"] = {
["Scale"] = nil,
["Scale Border On HiDPI Displays"] = nil,
["Scaling"] = nil,
["Scan Macros For Spells"] = nil,
["Screen Edge Glow Intensity"] = nil,
["Screen Resolution"] = nil,
["Screenshot"] = nil,
-1
View File
@@ -683,7 +683,6 @@ pfUI_translation["enUS"] = {
["Scale"] = nil,
["Scale Border On HiDPI Displays"] = nil,
["Scaling"] = nil,
["Scan Macros For Spells"] = nil,
["Screen Edge Glow Intensity"] = nil,
["Screen Resolution"] = nil,
["Screenshot"] = nil,
-1
View File
@@ -670,7 +670,6 @@ pfUI_translation["esES"] = {
["Scale"] = "Escala",
["Scale Border On HiDPI Displays"] = "Escalar los bordes en las pantallas con DPI alto",
["Scaling"] = "Escalada",
["Scan Macros For Spells"] = nil,
["Screen Edge Glow Intensity"] = "Intensidad de brillo en los bordes de la pantalla",
["Screen Resolution"] = "Resolución de pantalla",
["Screenshot"] = "Captura de pantalla",
-1
View File
@@ -670,7 +670,6 @@ pfUI_translation["frFR"] = {
["Scale"] = "Échelle",
["Scale Border On HiDPI Displays"] = "Échelle de bordure sur les écrans HiDPI",
["Scaling"] = "Mise à l'échelle",
["Scan Macros For Spells"] = nil,
["Screen Edge Glow Intensity"] = nil,
["Screen Resolution"] = "Résolution d'écran",
["Screenshot"] = "Imprime écran",
-1
View File
@@ -670,7 +670,6 @@ pfUI_translation["koKR"] = {
["Scale"] = nil,
["Scale Border On HiDPI Displays"] = nil,
["Scaling"] = nil,
["Scan Macros For Spells"] = nil,
["Screen Edge Glow Intensity"] = nil,
["Screen Resolution"] = "화면 해상도",
["Screenshot"] = nil,
-1
View File
@@ -670,7 +670,6 @@ pfUI_translation["ruRU"] = {
["Scale"] = "Масштаб",
["Scale Border On HiDPI Displays"] = "Масштабировать границы на HiDPI мониторах",
["Scaling"] = "Масштаб интерфейса",
["Scan Macros For Spells"] = nil,
["Screen Edge Glow Intensity"] = "Интенсивность свечения на краях экрана",
["Screen Resolution"] = "Разрешение экрана",
["Screenshot"] = "Снимок экрана",
-1
View File
@@ -670,7 +670,6 @@ pfUI_translation["zhCN"] = {
["Scale"] = "比例",
["Scale Border On HiDPI Displays"] = "缩放高DPI显示器上的边框",
["Scaling"] = "UI缩放",
["Scan Macros For Spells"] = "扫描宏命令中的法术",
["Screen Edge Glow Intensity"] = "屏幕边缘发光强度",
["Screen Resolution"] = "屏幕分辨率",
["Screenshot"] = "屏幕截图",
-1
View File
@@ -670,7 +670,6 @@ pfUI_translation["zhTW"] = {
["Scale"] = "比例",
["Scale Border On HiDPI Displays"] = nil,
["Scaling"] = nil,
["Scan Macros For Spells"] = nil,
["Screen Edge Glow Intensity"] = nil,
["Screen Resolution"] = "螢幕解析度",
["Screenshot"] = nil,
-1
View File
@@ -70,7 +70,6 @@
<Include file="..\modules\addoncompat.lua"/>
<Include file="..\modules\energytick.lua"/>
<Include file="..\modules\totems.lua"/>
<Include file="..\modules\macrotweak.lua"/>
<Include file="..\modules\macroicons.lua"/>
<Include file="..\modules\superwow.lua"/>
<Include file="..\modules\innervatecall.lua"/>
+2 -2
View File
@@ -10,8 +10,8 @@ local libhealth = CreateFrame("Frame")
libhealth.enabled = true
libhealth.reqhit = 4
libhealth.reqdmg = 5
libhealth:RegisterEvent("UNIT_HEALTH")
libhealth:RegisterEvent("UNIT_COMBAT")
libhealth:RegisterUnitEvent("UNIT_HEALTH", "target")
libhealth:RegisterUnitEvent("UNIT_COMBAT", "target")
libhealth:RegisterEvent("PLAYER_TARGET_CHANGED")
libhealth:RegisterEvent("PLAYER_ENTERING_WORLD")
libhealth:SetScript("OnEvent", function()
+1 -1
View File
@@ -1134,7 +1134,7 @@ libpredict.sender:RegisterEvent("SPELL_HEAL_BY_SELF")
libpredict.sender:RegisterEvent("SPELL_HEAL_BY_OTHER") -- populates foreignCache for other healers
-- force cache updates
libpredict.sender:RegisterEvent("UNIT_INVENTORY_CHANGED")
libpredict.sender:RegisterUnitEvent("UNIT_INVENTORY_CHANGED", "player")
libpredict.sender:RegisterEvent("SKILL_LINES_CHANGED")
-- Shared cleanup helper for failed/interrupted casts
+23 -73
View File
@@ -360,68 +360,6 @@ pfUI:RegisterModule("actionbar", function ()
end
end
local function ButtonMacroScan(self)
if self.bar > 10 then return end
if not self.scanmacro then return end
if pfUI.bars.skip_macro then return end
-- SuperCleveRoidMacros: for macros it manages, leave spellslot/booktype unset
-- so the button's icon, cooldown, and tooltip flow through the hooked
-- GetActionTexture / GetActionCooldown / GameTooltip:SetAction and follow the
-- active conditional dynamically, instead of being frozen to the first
-- statically-scanned spell.
if CleveRoids and CleveRoids.IsManagedAction and CleveRoids.IsManagedAction(self.id) then
self.spellslot, self.booktype, self.spellID = nil, nil, nil
return
end
local kind, slot = GetActionInfo(self.id)
self.spellslot, self.booktype, self.spellID = nil, nil, nil
if kind == 'macro' then
local name, _, body = GetMacroInfo(slot)
if name and body then
local match
for line in gfind(body, "[^%\n]+") do
_, _, match = string.find(line, '^#showtooltip (.+)')
-- allow the user to disable the scan
if match and strfind(match, "disable") then
return
end
if not match then
-- add support to specify custom tooltips via:
-- /run --showtooltip SPELLNAME
_, _, match = string.find(line, '%-%-showtooltip (.+)')
end
if not match then
_, _, match = string.find(line, '^/cast (.+)')
end
if not match then
_, _, match = string.find(line, '^/pfcast (.+)')
end
if not match then
_, _, match = string.find(line, '^/pfmouse (.+)')
end
if not match then
_, _, match = string.find(line, 'CastSpellByName%(%"(.+)%"%)')
end
if match then
self.spellslot, self.booktype, self.spellID = select(7, libspell.GetSpellInfo(match))
if self.spellslot and self.spellslot > 0 then return end
end
end
end
end
end
local function ButtonEnter(self)
self = self or this
@@ -690,7 +628,6 @@ pfUI:RegisterModule("actionbar", function ()
local function ButtonFullUpdate(button)
if not button then return end
ButtonMacroScan(button)
ButtonSlotUpdate(button)
ButtonRangeUpdate(button)
ButtonUsableUpdate(button)
@@ -807,10 +744,28 @@ pfUI:RegisterModule("actionbar", function ()
-- create the main event and update handler for pfUI actionbars
local bars = CreateFrame("Frame", "pfActionBar", UIParent)
for event in pairs(special_events) do bars:RegisterEvent(event) end
for event in pairs(global_events) do bars:RegisterEvent(event) end
for event in pairs(aura_events) do bars:RegisterEvent(event) end
for event in pairs(pet_events) do bars:RegisterEvent(event) end
-- The only unit events in the tables above; both concern the player alone.
-- A registration keeps its kind, so these have to go in unit-filtered from
-- the start -- RegisterUnitEvent over a plain registration stays plain.
local event_units = {
["UNIT_INVENTORY_CHANGED"] = "player",
["UNIT_PET"] = "player",
}
local function RegisterBarEvent(event)
local unit = event_units[event]
if unit then
bars:RegisterUnitEvent(event, unit)
else
bars:RegisterEvent(event)
end
end
for event in pairs(special_events) do RegisterBarEvent(event) end
for event in pairs(global_events) do RegisterBarEvent(event) end
for event in pairs(aura_events) do RegisterBarEvent(event) end
for event in pairs(pet_events) do RegisterBarEvent(event) end
-- refresh actionbar buttons on event
bars:SetScript("OnEvent", BarsEvent)
@@ -1148,12 +1103,7 @@ pfUI:RegisterModule("actionbar", function ()
f.count:SetJustifyH("RIGHT")
f.count:SetJustifyV("BOTTOM")
-- macro spell scan (disabled when macro addons are loaded)
if C.bars.macroscan == "0" or pfUI:MacroAddonsLoaded() then
f.scanmacro, f.spellslot, f.booktype = nil, nil, nil
else
f.scanmacro = true
end
f.scanmacro, f.spellslot, f.booktype = nil, nil, nil
-- range glow color
f.rangeColor = GetStringColorObject(C.bars.rangecolor)
+1 -1
View File
@@ -164,7 +164,7 @@ pfUI:RegisterModule("buff", function ()
pfUI.buff = CreateFrame("Frame", "pfGlobalBuffFrame", UIParent)
pfUI.buff:RegisterEvent("PLAYER_AURAS_CHANGED")
pfUI.buff:RegisterEvent("PLAYER_EQUIPMENT_CHANGED")
pfUI.buff:RegisterEvent("UNIT_MODEL_CHANGED")
pfUI.buff:RegisterUnitEvent("UNIT_MODEL_CHANGED", "player")
pfUI.buff:RegisterEvent("BUFF_UPDATE_DURATION_SELF")
pfUI.buff:RegisterEvent("DEBUFF_UPDATE_DURATION_SELF")
pfUI.buff:SetScript("OnEvent", function()
+15 -9
View File
@@ -322,15 +322,21 @@ pfUI:RegisterModule("castbar", function ()
-- casts only ever fire arg1=="player" -- when the bar's unit resolves to the
-- player (target=self). PLAYER_TARGET/FOCUS_CHANGED re-polls so a unit
-- already mid-cast when it becomes the target/focus still shows.
cb:RegisterEvent("UNIT_SPELLCAST_START")
cb:RegisterEvent("UNIT_SPELLCAST_STOP")
cb:RegisterEvent("UNIT_SPELLCAST_FAILED")
cb:RegisterEvent("UNIT_SPELLCAST_INTERRUPTED")
cb:RegisterEvent("UNIT_SPELLCAST_DELAYED")
cb:RegisterEvent("UNIT_SPELLCAST_SUCCEEDED")
cb:RegisterEvent("UNIT_SPELLCAST_CHANNEL_START")
cb:RegisterEvent("UNIT_SPELLCAST_CHANNEL_STOP")
cb:RegisterEvent("UNIT_SPELLCAST_CHANNEL_UPDATE")
-- Filter to this bar's unit, plus "player" for the target/focus bars: the
-- player's own casts only ever fire arg1=="player", so a self-targeted cast
-- has to reach them too (nil for the player bar itself, which the filter
-- skips). This only narrows what arrives -- the arg1/UnitIsUnit test below
-- still decides whether the bar acts on it.
local selfunit = unitstr ~= "player" and "player" or nil
cb:RegisterUnitEvent("UNIT_SPELLCAST_START", unitstr, selfunit)
cb:RegisterUnitEvent("UNIT_SPELLCAST_STOP", unitstr, selfunit)
cb:RegisterUnitEvent("UNIT_SPELLCAST_FAILED", unitstr, selfunit)
cb:RegisterUnitEvent("UNIT_SPELLCAST_INTERRUPTED", unitstr, selfunit)
cb:RegisterUnitEvent("UNIT_SPELLCAST_DELAYED", unitstr, selfunit)
cb:RegisterUnitEvent("UNIT_SPELLCAST_SUCCEEDED", unitstr, selfunit)
cb:RegisterUnitEvent("UNIT_SPELLCAST_CHANNEL_START", unitstr, selfunit)
cb:RegisterUnitEvent("UNIT_SPELLCAST_CHANNEL_STOP", unitstr, selfunit)
cb:RegisterUnitEvent("UNIT_SPELLCAST_CHANNEL_UPDATE", unitstr, selfunit)
if unitstr == "target" then
cb:RegisterEvent("PLAYER_TARGET_CHANGED")
elseif unitstr == "focus" then
+1 -1
View File
@@ -52,7 +52,7 @@ pfUI:RegisterModule("combopoints", function ()
-- combo
if class == "DRUID" or class == "ROGUE" then
local combo = CreateFrame("Frame")
combo:RegisterEvent("UNIT_COMBO_POINTS")
combo:RegisterUnitEvent("UNIT_COMBO_POINTS", "player")
combo:RegisterEvent("PLAYER_COMBO_POINTS")
combo:RegisterEvent("PLAYER_TARGET_CHANGED")
combo:RegisterEvent("PLAYER_ENTERING_WORLD")
+28 -18
View File
@@ -6,30 +6,35 @@ pfUI:RegisterModule("cooldown", function ()
-- local hourcolor = {strsplit(",", C.appearance.cd.hourcolor)}
-- local daycolor = {strsplit(",", C.appearance.cd.daycolor)}
local parent, parent_name
local function pfCooldownOnUpdate()
parent = this:GetParent()
-- Throttle FIRST. One of these runs per visible cooldown text, every frame,
-- so anything above this gate is multiplied by the frame rate and by how
-- many cooldowns are ticking.
local now = GetTime()
if (this.tick or 0) > now then return end
this.tick = now + .1
local parent = this:GetParent()
if not parent then this:Hide() return end
parent_name = parent:GetName()
-- avoid to set cooldowns on invalid frames
if parent_name and _G[parent_name .. "Cooldown"] then
if not _G[parent_name .. "Cooldown"]:IsShown() then
this:Hide()
end
-- avoid to set cooldowns on invalid frames. The cooldown frame is stashed
-- at creation: resolving it as _G[parent:GetName() .. "Cooldown"] built and
-- interned that string twice per call, and this is the hottest path in the
-- UI. The stashed reference is also the frame itself rather than a guess
-- from its parent's name, so it holds for cooldowns named anything else.
if this.cooldown and not this.cooldown:IsShown() then
this:Hide()
return
end
-- only run every 0.1 seconds from here on
if ( this.tick or .1) > GetTime() then return else this.tick = GetTime() + .1 end
-- fix own alpha value (should be inherited, but somehow isn't always)
if this:GetAlpha() ~= parent:GetAlpha() then
this:SetAlpha(parent:GetAlpha())
end
if this.start < GetTime() then
if this.start < now then
-- calculating remaining time as it should be
local remaining = this.duration - (GetTime() - this.start)
local remaining = this.duration - (now - this.start)
if remaining >= 0 then
this.text:SetText(GetColoredTimeString(remaining))
else
@@ -38,13 +43,13 @@ pfUI:RegisterModule("cooldown", function ()
else
-- I have absolutely no idea, but it works:
-- https://github.com/Stanzilla/WoWUIBugs/issues/47
local time = time()
local startupTime = time - GetTime()
local currentTime = time()
local startupTime = currentTime - now
-- just a simplification of: ((2^32) - (start * 1000)) / 1000
local cdTime = (2 ^ 32) / 1000 - this.start
local cdStartTime = startupTime - cdTime
local cdEndTime = cdStartTime + this.duration
local remaining = cdEndTime - time
local remaining = cdEndTime - currentTime
if remaining >= 0 then
this.text:SetText(GetColoredTimeString(remaining))
@@ -55,11 +60,16 @@ pfUI:RegisterModule("cooldown", function ()
end
local height, size
local textcount = 0
local function pfCreateCoolDown(cooldown, start, duration)
cooldown.pfCooldownText = CreateFrame("Frame", "pfCooldownFrame", cooldown:GetParent())
textcount = textcount + 1
local name = cooldown.GetName and cooldown:GetName() or "pfCooldown" .. textcount
cooldown.pfCooldownText = CreateFrame("Frame", name .. "Text", cooldown:GetParent())
cooldown.pfCooldownText.cooldown = cooldown
cooldown.pfCooldownText:SetAllPoints(cooldown)
cooldown.pfCooldownText:SetFrameLevel(cooldown:GetParent():GetFrameLevel() + 2)
cooldown.pfCooldownText.text = cooldown.pfCooldownText:CreateFontString("pfCooldownFrameText", "OVERLAY")
cooldown.pfCooldownText.text = cooldown.pfCooldownText:CreateFontString(name .. "TextString", "OVERLAY")
if not cooldown.pfCooldownType then
size = tonumber(C.appearance.cd.font_size_foreign)
+185 -60
View File
@@ -1,20 +1,82 @@
local function getAdjustedTickTimer()
local adjustedEnergyTick = 2
-- One server clock drives every power: Player::RegenerateAll fires every
-- REGEN_TIME_FULL (2s), re-arms with `+=`, and is never reset by casting. The
-- five-second rule (SetLastManaUse on any mana-costing cast) changes what a tick
-- pays, never when it lands; mp5 and the player's MOD_MANA_REGEN_INTERRUPT share
-- still come in. That share can't be computed here -- item sources are equip
-- auras absent from the buff list and m_modManaRegenInterrupt is never sent --
-- so the spark shows it instead: dim through the window until a tick lands.
--
-- The sweep free-runs on that clock and phase-locks to observed gains. A gain
-- mid-sweep (Illumination, Judgement of Wisdom, potions, a Mana Spring totem on
-- its own phase) is not the tick and never moves it.
-- Check rogue talents and compute energy tick timing reduction for Combat spec (1.18.0 Blade Rush Talent)
if UnitClassBase("player") == "ROGUE" then
local _, _, _, _, currRank = GetTalentInfo(2, 16)
local bladeRushRank = currRank or 0
local FIVE_SECOND_RULE = 5
if bladeRushRank > 0 then
local agility = UnitStat("player", 2) -- 2 is agility stat index
local reductionPerAgi = 0.0006 * bladeRushRank -- 0.0006 for rank 1, 0.0012 for rank 2
local totalReduction = agility * reductionPerAgi
adjustedEnergyTick = adjustedEnergyTick - totalReduction
-- gains farther than this from the predicted boundary are not the tick
local TICK_TOLERANCE = .25
-- arrival jitter; a tick inside this band confirms the sweep rather than
-- re-anchoring it, or the spark hitches at every wrap
local TICK_JITTER = .08
-- Player::RegenerateAll:
-- mod = GetTotalAuraModifier(SPELL_AURA_MOD_ENERGY_REGEN_TIME)
-- if mod > 0 then mod = mod * agility / 10 end
-- m_regenTimer += max(1, REGEN_TIME_FULL - mod) -- milliseconds
local REGEN_TIME_FULL = 2
local ENERGY_REGEN_TIME_AURA = 217 -- SPELL_AURA_MOD_ENERGY_REGEN_TIME
-- fixed magnitude is basePoints + baseDice (stored 11 -> 12); a die above 1 is
-- a roll the client can't know, so it counts as nothing rather than a guess
local amountCache = {}
local function auraAmount(spellID)
local amount = amountCache[spellID]
if amount then return amount end
amount = 0
local effects = C_Spell.GetSpellEffectInfo(spellID) -- nil for an id with no record
if effects then
for i = 1, 3 do
local fx = effects[i]
if fx.auraName == ENERGY_REGEN_TIME_AURA and fx.dieSides <= 1 then
amount = fx.basePoints + fx.baseDice
break
end
end
end
amountCache[spellID] = amount
return amount
end
return adjustedEnergyTick
-- A passive is in effect exactly while known (current rank only, never in the
-- buff list); anything castable or cast on us counts only while it is up.
local function getEnergyRegenTimeMod()
local sum = 0
for _, spellID in ipairs(C_SpellBook.GetPlayerSpellsByAura(ENERGY_REGEN_TIME_AURA)) do
if C_Spell.IsSpellPassive(spellID) then
sum = sum + auraAmount(spellID)
end
end
for i = 1, 32 do
local spellID = select(10, C_UnitAuras.UnitAura("player", i, "HELPFUL"))
if not spellID then break end
sum = sum + auraAmount(spellID)
end
return sum
end
-- cleared on SPELLS_CHANGED (passives) and PLAYER_AURAS_CHANGED (buffs), and
-- recomputed by the next tick that asks. Agility stays live: it's one call.
local energyRegenTimeMod
local function getAdjustedTickTimer()
if not energyRegenTimeMod then
energyRegenTimeMod = getEnergyRegenTimeMod()
end
if energyRegenTimeMod == 0 then return REGEN_TIME_FULL end
-- ms on the server, seconds here; the 1ms floor is the server's and this is a divisor
local reduction = energyRegenTimeMod * UnitStat("player", 2) / 10000
return math.max(0.001, REGEN_TIME_FULL - reduction)
end
pfUI:RegisterModule("energytick", function()
@@ -22,14 +84,53 @@ pfUI:RegisterModule("energytick", function()
return
end
-- inside the module body on purpose: C is on pfUI.env, not _G
local function getBarWidth()
return C.unitframes.player.pwidth ~= "-1" and C.unitframes.player.pwidth or C.unitframes.player.width
end
-- was this gain the regen tick? if so, re-anchor the sweep on it
local function lockTick(frame)
local now, period = GetTime(), getAdjustedTickTimer()
if frame.start then
-- signed distance to the nearest predicted boundary
local err = mod(now - frame.start, period)
if err > period / 2 then err = err - period end
if math.abs(err) <= TICK_TOLERANCE then
-- correct only what lies beyond normal jitter
if err > TICK_JITTER then
frame.start = frame.start + (err - TICK_JITTER)
elseif err < -TICK_JITTER then
frame.start = frame.start + (err + TICK_JITTER)
end
frame.max, frame.rejected = period, nil
return true
end
-- two rejected gains one period apart are the real clock: relock to it
local periodic = frame.rejected and math.abs(now - frame.rejected - period) <= TICK_TOLERANCE
if not periodic then
frame.rejected = now
return false
end
end
frame.start, frame.max, frame.rejected = now, period, nil
return true
end
local energytick = CreateFrame("Frame", nil, pfUI.uf.player.power.bar)
energytick:SetAllPoints(pfUI.uf.player.power.bar)
energytick:RegisterEvent("PLAYER_ENTERING_WORLD")
energytick:RegisterEvent("UNIT_DISPLAYPOWER")
energytick:RegisterEvent("UNIT_ENERGY")
energytick:RegisterEvent("UNIT_MANA")
energytick:RegisterEvent("CHAT_MSG_SPELL_SELF_BUFF")
energytick:RegisterEvent("CHAT_MSG_SPELL_PERIODIC_SELF_BUFFS")
energytick:RegisterUnitEvent("UNIT_DISPLAYPOWER", "player")
energytick:RegisterUnitEvent("UNIT_ENERGY", "player")
energytick:RegisterUnitEvent("UNIT_MANA", "player")
energytick:RegisterUnitEvent("UNIT_SPELLCAST_SUCCEEDED", "player")
energytick:RegisterUnitEvent("UNIT_SPELLCAST_CHANNEL_STOP", "player")
energytick:RegisterEvent("SPELLS_CHANGED")
energytick:RegisterEvent("PLAYER_AURAS_CHANGED")
energytick:SetScript("OnEvent", function()
if UnitPowerType("player") == Enum.PowerType.Mana and C.unitframes.player.manatick == "1" then
@@ -42,44 +143,52 @@ pfUI:RegisterModule("energytick", function()
this:Hide()
end
-- Filter nur eigene Energy-Gewinne von Talents/Buffs
if event == "CHAT_MSG_SPELL_SELF_BUFF" or event == "CHAT_MSG_SPELL_PERIODIC_SELF_BUFFS" then
if string.find(arg1, "You gain") and string.find(arg1, "Energy from") then
this.ignoreNextGain = true
end
if event == "SPELLS_CHANGED" or event == "PLAYER_AURAS_CHANGED" then
energyRegenTimeMod = nil
return
end
if event == "PLAYER_ENTERING_WORLD" then
this.lastMana = UnitPower("player")
this.lastPower = UnitPower("player")
end
-- the rule arms on the cast (Spell::TakePower: mana powerType, cost > 0),
-- not on a mana drop -- Mana Burn lowers mana without arming it
if event == "UNIT_SPELLCAST_SUCCEEDED" and arg1 == "player" then
local cost = C_Spell.GetSpellPowerCost(arg3)
cost = cost and cost[1]
if cost and cost.type == Enum.PowerType.Mana and cost.cost > 0 then
this.fsrSpell, this.fsrEnd = arg3, GetTime() + FIVE_SECOND_RULE
this.fsrGain = nil
end
return
end
-- Unit::Update won't expire the rule while the spending spell still channels
if event == "UNIT_SPELLCAST_CHANNEL_STOP" and arg1 == "player" then
if this.fsrSpell and this.fsrSpell == arg3 then
this.fsrEnd, this.fsrGain = GetTime() + FIVE_SECOND_RULE, nil
end
return
end
if (event == "UNIT_MANA" or event == "UNIT_ENERGY") and arg1 == "player" then
this.currentMana = UnitPower("player")
local diff = 0
if this.lastMana then
diff = this.currentMana - this.lastMana
local power = UnitPower("player")
local diff = this.lastPower and (power - this.lastPower) or 0
this.lastPower = power
-- only a gain can be the tick; a spend never touches the phase
if diff > 0 and lockTick(this) then
-- a tick inside the window proves regen continues through it
if this.fsrEnd and this.fsrEnd > GetTime() then
this.fsrGain = true
end
end
if this.mode == "MANA" and diff < 0 then
this.target = 5
elseif this.mode == "MANA" and diff > 0 then
if UnitPower("player") >= UnitPowerMax("player") then
this.start = nil
this.spark:SetAlpha(0)
this:Hide()
elseif this.max ~= 5 and diff > (this.badtick and this.badtick * 1.2 or 5) then
this.target = 2
else
this.badtick = diff
end
elseif this.mode == "ENERGY" and diff >= 0 then
if not this.ignoreNextGain then
this.target = getAdjustedTickTimer()
end
this.ignoreNextGain = false
-- phase is kept while hidden; OnUpdate catches up by whole periods
if this.mode == "MANA" and power >= UnitPowerMax("player") then
this:Hide()
end
this.lastMana = this.currentMana
end
end)
@@ -90,37 +199,53 @@ pfUI:RegisterModule("energytick", function()
end
this.tick = GetTime() + 0.020 -- ~50 FPS
if this.target then
this.start, this.max = GetTime(), this.target
this.target = nil
this.spark:SetAlpha(1)
this:Show()
-- five-second rule drains to nothing
local remaining = this.fsrEnd and (this.fsrEnd - GetTime()) or 0
if this.mode == "MANA" and remaining > 0 then
this.fsrbar:SetWidth(getBarWidth() * remaining / FIVE_SECOND_RULE)
this.fsrbar:Show()
else
this.fsrSpell, this.fsrEnd, this.fsrGain = nil, nil, nil
this.fsrbar:Hide()
end
if not this.start then
this.spark:SetAlpha(0)
return
end
if this.mode == "MANA" and UnitPower("player") >= UnitPowerMax("player") then
this.spark:SetAlpha(0)
return
end
this.current = GetTime() - this.start
-- roll over by whole periods, not from now: restarting bakes frame
-- overshoot into the phase as drift the lock then has to chase
if this.current > this.max then
-- Don't restart tick timer if mana is full
if this.mode == "MANA" and UnitPower("player") >= UnitPowerMax("player") then
this.start = nil
this.spark:SetAlpha(0)
return
end
this.start, this.max, this.current = GetTime(), getAdjustedTickTimer(), 0
this.start = this.start + this.max * math.floor(this.current / this.max)
this.max = getAdjustedTickTimer()
this.current = GetTime() - this.start
end
local pos = (C.unitframes.player.pwidth ~= "-1" and C.unitframes.player.pwidth or C.unitframes.player.width)
* (this.current / this.max)
-- dim while the rule is up and nothing has ticked inside it yet
this.spark:SetAlpha((remaining > 0 and not this.fsrGain) and .4 or 1)
if not C.unitframes.player.pheight then
return
end
local pos = getBarWidth() * (this.current / this.max)
this.spark:SetPoint("LEFT", pos - ((C.unitframes.player.pheight + 5) / 2), 0)
end)
energytick.fsrbar = energytick:CreateTexture(nil, "ARTWORK")
energytick.fsrbar:SetTexture(1, 1, 1, .15)
energytick.fsrbar:SetPoint("TOPLEFT", 0, 0)
energytick.fsrbar:SetPoint("BOTTOMLEFT", 0, 0)
energytick.fsrbar:Hide()
energytick.spark = energytick:CreateTexture(nil, "OVERLAY")
energytick.spark:SetTexture("Interface\\CastingBar\\UI-CastingBar-Spark")
energytick.spark:SetHeight(C.unitframes.player.pheight + 15)
@@ -133,4 +258,4 @@ pfUI:RegisterModule("energytick", function()
energytick.spark:SetWidth(C.unitframes.player.pheight + 5)
hookUpdateConfig(pfUI.uf.player)
end
end)
end)
+20 -1
View File
@@ -130,9 +130,28 @@ pfUI:RegisterModule("eqcompare", function ()
SetTradeTargetItem = GetTradeTargetItemLink
}
-- Guda anchors its item tooltips with ANCHOR_NONE and its own SetPoint, and
-- the tooltip module then moves every ANCHOR_NONE tooltip to its configured
-- spot. Reading the rect inside the Set* call picks the side against the
-- position the tooltip is about to leave, so place the compare a frame later.
local deferred
if C_AddOns.DoesAddOnExist("Guda") then
EventUtil.ContinueOnAddOnLoaded("Guda", function()
deferred = true
end)
end
local function makeHook(getter)
return function(tooltip, arg1, arg2, arg3)
ShowCompareItem(tooltip, getter(arg1, arg2, arg3))
local link = getter(arg1, arg2, arg3)
if not deferred then
return ShowCompareItem(tooltip, link)
end
RunNextFrame(function()
if tooltip:IsShown() then
ShowCompareItem(tooltip, link)
end
end)
end
end
+1 -5
View File
@@ -2556,9 +2556,6 @@ pfUI:RegisterModule("gui", function ()
CreateConfig(U["bars"], T["Button Animation"], C.bars, "animation", "dropdown", pfUI.gui.dropdowns.actionbuttonanimations)
CreateConfig(U["bars"], T["Button Animation Trigger"], C.bars, "animmode", "dropdown", pfUI.gui.dropdowns.animationmode)
CreateConfig(U["bars"], T["Show Animation On Hidden Bars"], C.bars, "animalways", "checkbox")
if not pfUI:MacroAddonsLoaded() then
CreateConfig(U["bars"], T["Scan Macros For Spells"], C.bars, "macroscan", "checkbox", nil, nil, nil, nil)
end
CreateConfig(U["bars"], T["Show Reagent Count"], C.bars, "reagents", "checkbox")
CreateConfig(U["bars"], T["Highlight Equipped Items"], C.bars, "showequipped", "checkbox")
CreateConfig(U["bars"], T["Equipped Item Color"], C.bars, "eqcolor", "color")
@@ -3029,8 +3026,7 @@ pfUI:RegisterModule("gui", function ()
CreateGUIEntry(T["Components"], T["Modules"], function()
table.sort(pfUI.modules)
for i,m in pairs(pfUI.modules) do
-- skip gui and macrotweak when macro addons are loaded
if m ~= "gui" and not (m == "macrotweak" and pfUI:MacroAddonsLoaded()) then
if m ~= "gui" then
-- create disabled entry if not existing and display
pfUI:UpdateConfig("disabled", nil, m, "0")
CreateConfig(nil, T["Disable Module"] .. " " .. m, C.disabled, m, "checkbox")
+6 -17
View File
@@ -104,7 +104,6 @@ pfUI:RegisterModule("loothistory", function ()
-- Frame pools
-- ==========================================================================
local itemFrames = {}
local usedPlayers, freePlayers = {}, {}
local FullUpdate -- forward declaration (toggle handlers call it)
@@ -204,20 +203,10 @@ pfUI:RegisterModule("loothistory", function ()
return f
end
local function RecycleAllPlayers()
for i = 1, table.getn(usedPlayers) do
local pf = usedPlayers[i]
pf:Hide()
table.insert(freePlayers, pf)
end
usedPlayers = {}
end
local function GetPlayerFrame()
local pf = table.remove(freePlayers) or CreatePlayerFrame()
table.insert(usedPlayers, pf)
return pf
end
local playerPool = CreateObjectPool(CreatePlayerFrame, function(_, pf)
pf:Hide()
pf:ClearAllPoints()
end)
local function SetToggleTexture(toggle, isExpanded)
if isExpanded then
@@ -309,7 +298,7 @@ pfUI:RegisterModule("loothistory", function ()
function FullUpdate()
if not pfUI.loothistory:IsShown() then return end
RecycleAllPlayers()
playerPool:ReleaseAll()
local num = C_LootHistory.GetNumItems()
local y = -2
@@ -327,7 +316,7 @@ pfUI:RegisterModule("loothistory", function ()
for p = 1, f.numPlayers do
local name, class, rollType, roll, isWinner, isMe = C_LootHistory.GetPlayerInfo(i, p)
if ShouldDisplayPlayer(f.isDone, roll, isMe) then
local pf = GetPlayerFrame()
local pf = playerPool:Acquire()
RenderPlayerFrame(pf, name, class, rollType, roll, isWinner)
pf:ClearAllPoints()
pf:SetPoint("TOPLEFT", list, "TOPLEFT", 22, y)
-67
View File
@@ -1,67 +0,0 @@
pfUI:RegisterModule("macrotweak", function ()
local conflictAddons = { "Supermacro", "SuperCleveRoidMacros", "UltimaMacros" }
local disabled = false
for _, addon in pairs(conflictAddons) do
local name = addon
EventUtil.ContinueOnAddOnLoaded(name, function()
if not disabled then
DEFAULT_CHAT_FRAME:AddMessage("|cff33ffccpfUI|r: " .. name .. " found, macrotweak disabled.")
end
disabled = true
end)
end
-- do not write macro calls into chat input history
-- (install once: _AddHistoryLine is our backup slot and is nil until we set it)
if not ChatFrameEditBox._AddHistoryLine then
local userinput
ChatFrameEditBox._AddHistoryLine = ChatFrameEditBox.AddHistoryLine
ChatFrameEditBox.AddHistoryLine = function(self, text)
if disabled then return ChatFrameEditBox._AddHistoryLine(self, text) end
if not userinput and text and string.find(text, "^/run(.+)") then return end
if not userinput and string.find(text, "^/script(.+)") then return end
if not userinput and string.find(text, "^/cast(.+)") then return end
ChatFrameEditBox._AddHistoryLine(self, text)
end
local OnEnter = ChatFrameEditBox:GetScript("OnEnterPressed")
ChatFrameEditBox:SetScript("OnEnterPressed", function(a1,a2,a3,a4)
userinput = true
OnEnter(a1,a2,a3,a4)
userinput = nil
end)
end
-- make sure #showtooltip inside macros won't be sent
local hookSendChatMessage = SendChatMessage
function _G.SendChatMessage(msg, ...)
if disabled then return hookSendChatMessage(msg, unpack(arg)) end
if msg and string.find(msg, "^#showtooltip ") then return end
hookSendChatMessage(msg, unpack(arg))
end
-- add /use and /equip to the macro api:
-- https://wowwiki.fandom.com/wiki/Making_a_macro
-- supported arguments:
-- /use <itemname>
-- /use <inventory slot>
-- /use <bag> <slot>
pfUI.api.RegisterSlashCommand("PFUSE", { "/equip" , "/use", "/pfequip", "/pfuse" }, function (msg)
if not msg or msg == "" then return end
local bag, slot, _
if string.find(msg, "%d+%s+%d+") then
_, _, bag, slot = string.find(msg, "(%d+)%s+(%d+)")
elseif string.find(msg, "%d+") then
_, _, slot = string.find(msg, "(%d+)")
else
bag, slot = FindItem(msg)
end
if bag and slot then
UseContainerItem(bag, slot)
elseif not bag and slot then
UseInventoryItem(slot)
end
end)
end)
+42 -44
View File
@@ -30,20 +30,14 @@ pfUI:RegisterModule("mapreveal", function ()
pfUI.mapreveal:UpdateConfig()
end)
local explores = {}
local explorecaches = {}
local alreadyknown = {} -- per-zone accumulator: { [zone] = { [texName] = true } }
-- Own texture pool - separate from Blizzard's WorldMapOverlay textures
local pfOverlays = {}
local pfOverlayMax = 0
local function pfGetOverlay(idx)
if not pfOverlays[idx] then
pfOverlays[idx] = WorldMapDetailFrame:CreateTexture("pfReveal"..idx, "BORDER")
end
return pfOverlays[idx]
end
local overlayPool = CreateTexturePool(WorldMapDetailFrame, "BORDER", nil, nil, function(_, tex)
tex:Hide()
tex:ClearAllPoints()
end)
local exploreEnter = function()
WorldMapTooltip:ClearLines()
@@ -69,14 +63,38 @@ pfUI:RegisterModule("mapreveal", function ()
end
end
-- Magnifying-glass icons for unexplored overlays. Everything constant lives
-- in the creator; the update only anchors and labels what it acquires -- and
-- it only acquires the ones it is going to show, where the old table grew an
-- icon for every overlay in the zone and hid most of them again.
local function CreateExplore()
local explore = CreateFrame("Frame", nil, WorldMapDetailFrame)
explore:SetSize(16, 16)
explore:SetScript("OnEnter", exploreEnter)
explore:SetScript("OnLeave", exploreLeave)
explore:EnableMouse(true)
explore:SetFrameLevel(255)
explore.tex = explore:CreateTexture(nil, "OVERLAY")
explore.tex:SetTexture("Interface\\WorldMap\\WorldMap-MagnifyingGlass")
explore.tex:SetBlendMode("ADD")
explore.tex:SetTexCoord(.08, .92, .08, .92)
explore.tex:SetAllPoints()
return explore
end
local explorePool = CreateObjectPool(CreateExplore, function(_, explore)
explore:Hide()
explore:ClearAllPoints()
end)
local function pfWorldMapFrame_Update()
-- clear stale caches
for k in pairs(explorecaches) do explorecaches[k] = nil end
-- hide all our textures from last frame
for i = 1, pfOverlayMax do
pfOverlays[i]:Hide()
end
overlayPool:ReleaseAll()
local r,g,b,a = GetStringColor(C.appearance.worldmap.mapreveal_color)
local mapFileName = GetMapInfo()
@@ -94,14 +112,13 @@ pfUI:RegisterModule("mapreveal", function ()
local zoneKnown = alreadyknown[mapFileName]
-- hide explore icons
for _, frame in pairs(explores) do frame:Hide() end
explorePool:ReleaseAll()
-- ClassicAPI: full overlay list for the viewed zone (explored + unexplored),
-- read straight from WorldMapOverlay.dbc. Replaces the hand-measured pfMapOverlayData.
local zoneData = C_Map.GetMapOverlays() or {}
local textureCount = 0
for i, overlay in ipairs(zoneData) do
for _, overlay in ipairs(zoneData) do
local name = overlay.textureName -- bare, e.g. "DRYGULCHRAVINE"
local textureName = overlay.texturePath -- full engine path (for SetTexture)
local textureWidth = overlay.textureWidth
@@ -109,30 +126,15 @@ pfUI:RegisterModule("mapreveal", function ()
local offsetX = overlay.offsetX
local offsetY = overlay.offsetY
-- explore magnifying glass icon
explores[i] = explores[i] or CreateFrame("Frame", nil, WorldMapDetailFrame)
local explore = explores[i]
explore:SetWidth(16)
explore:SetHeight(16)
explore:SetPoint("TOPLEFT", "WorldMapDetailFrame", "TOPLEFT", offsetX + textureWidth/2, -offsetY - textureHeight/2)
explore:SetScript("OnEnter", exploreEnter)
explore:SetScript("OnLeave", exploreLeave)
explore:EnableMouse(true)
explore:SetFrameLevel(255)
explore.name = mapFileName .. " (" .. name .. ")"
explore.area = name -- cache key: explorecaches is keyed by the plain area name
explore.tex = explore.tex or explore:CreateTexture("", "OVERLAY")
explore.tex:SetBlendMode("ADD")
explore.tex:SetTexCoord(.08, .92, .08, .92)
explore.tex:SetAllPoints()
-- `alreadyknown` stores the FULL paths GetMapOverlayInfo returns,
-- so compare with the full path, not the bare name.
-- explore magnifying glass icon. `alreadyknown` stores the FULL paths
-- GetMapOverlayInfo returns, so compare with the full path, not the bare
-- name.
if C.appearance.worldmap.mapexploration == "1" and not zoneKnown[string.upper(textureName)] then
explore.tex:SetTexture("Interface\\WorldMap\\WorldMap-MagnifyingGlass")
local explore = explorePool:Acquire()
explore:SetPoint("TOPLEFT", "WorldMapDetailFrame", "TOPLEFT", offsetX + textureWidth/2, -offsetY - textureHeight/2)
explore.name = mapFileName .. " (" .. name .. ")"
explore.area = name -- cache key: explorecaches is keyed by the plain area name
explore:Show()
else
explore:Hide()
end
-- render overlay texture tiles on BORDER draw layer
@@ -147,11 +149,9 @@ pfUI:RegisterModule("mapreveal", function ()
-- exactly what shears quirky overlays (e.g. Icepoint's Kaneq'nuun).
if C.appearance.worldmap.mapreveal == "1" then
for _, tile in ipairs(overlay.tiles) do
textureCount = textureCount + 1
local tex = pfGetOverlay(textureCount)
local tex = overlayPool:Acquire()
tex:SetWidth(tile.width)
tex:SetHeight(tile.height)
tex:SetSize(tile.width, tile.height)
tex:SetTexCoord(0, tile.texCoordX, 0, tile.texCoordY)
tex:ClearAllPoints()
tex:SetPoint("TOPLEFT", "WorldMapDetailFrame", "TOPLEFT", tile.offsetX, -tile.offsetY)
@@ -165,8 +165,6 @@ pfUI:RegisterModule("mapreveal", function ()
end
end
end
pfOverlayMax = math.max(pfOverlayMax, textureCount)
end
-- hook WorldMapFrame_Update
+38 -18
View File
@@ -1,12 +1,9 @@
pfUI:RegisterModule("marktracking", function ()
if not UnitExists("mark1") and not UnitExists("mark8") then
if not pcall(function() UnitExists("mark1") end) then return end
end
local rawborder, border = GetBorderSize()
local markerOrder = { 8, 7, 6, 5, 4, 3, 2, 1 } -- skull, cross, square, moon, triangle, diamond, circle, star
local markerTokens = {}
local markerTokens = {} -- [i] = "markN"
local markerIndex = {} -- ["markN"] = i, for the event handler's arg1
local markerConfigKeys = {
"raidmarkercolor_star",
@@ -22,6 +19,7 @@ pfUI:RegisterModule("marktracking", function ()
local markerColors = {}
for i, markKey in ipairs(markerConfigKeys) do
markerTokens[i] = "mark" .. i
markerIndex[markerTokens[i]] = i
local r, g, b, a = GetStringColor(C.unitframes[markKey])
markerColors[i] = { tonumber(r), tonumber(g), tonumber(b), tonumber(a) }
end
@@ -73,8 +71,7 @@ pfUI.marktracking = CreateFrame("Frame", "pfMarkTracking", UIParent)
else
pfUI.marktracking:SetPoint("TOP", UIParent, "CENTER", 0, 0)
end
pfUI.marktracking:SetWidth(TOTAL_ROW_WIDTH)
pfUI.marktracking:SetHeight(8 * (ROW_HEIGHT + 1) + border * 2 - 1)
pfUI.marktracking:SetSize(TOTAL_ROW_WIDTH, 8 * (ROW_HEIGHT + 1) + border * 2 - 1)
pfUI.marktracking:Hide()
CreateBackdrop(pfUI.marktracking)
@@ -284,27 +281,50 @@ pfUI.marktracking = CreateFrame("Frame", "pfMarkTracking", UIParent)
-- Event-driven scanner frame
local scanner = CreateFrame("Frame")
-- Fallback poll: catches units that come into range AFTER a marker was set
-- (no event fires for that case, so we need this safety net). UpdateDisplay
-- is a full eight-row rebuild, so it exists only while grouped -- raid markers
-- are a group feature and there is nothing to discover alone. The group events
-- below start and cancel it, so outside a group there is no timer queued at
-- all rather than one waking every second to return early.
--
-- Deliberately NOT keyed on a mark being visible: a marker set on a unit that
-- is out of range shows no row, and that is exactly what this poll catches.
local poll
local function UpdatePoll()
local grouped = IsInGroup()
if grouped and not poll then
poll = C_Timer.NewTicker(FALLBACK_INTERVAL, UpdateDisplay)
elseif not grouped and poll then
poll:Cancel()
poll = nil
end
end
-- RAID_TARGET_UPDATE: a raid marker was set/cleared -> full refresh
-- PLAYER_ENTERING_WORLD: login/reload/zone -> full refresh
-- UNIT_HEALTH/UNIT_MAXHEALTH: ClassicAPI fires these per token; with the mark
-- tokens observed they arrive as arg1 == "markN", so we refresh just that
-- one row (UpdateRow) instead of rescanning all eight.
-- PARTY_MEMBERS_CHANGED/RAID_ROSTER_UPDATE: joined or left a group -> the rows
-- can change, and the fallback poll starts or stops with it
-- UNIT_HEALTH/UNIT_MAXHEALTH: filtered to the eight mark tokens, so arg1 is
-- always "markN" and we refresh just that row (UpdateRow) instead of
-- rescanning all eight.
scanner:RegisterEvent("RAID_TARGET_UPDATE")
scanner:RegisterEvent("PLAYER_ENTERING_WORLD")
scanner:RegisterEvent("UNIT_HEALTH")
scanner:RegisterEvent("UNIT_MAXHEALTH")
scanner:RegisterEvent("PARTY_MEMBERS_CHANGED")
scanner:RegisterEvent("RAID_ROSTER_UPDATE")
scanner:RegisterUnitEvent("UNIT_HEALTH", "mark1", "mark2", "mark3", "mark4", "mark5", "mark6", "mark7", "mark8")
scanner:RegisterUnitEvent("UNIT_MAXHEALTH", "mark1", "mark2", "mark3", "mark4", "mark5", "mark6", "mark7", "mark8")
scanner:SetScript("OnEvent", function()
if event == "UNIT_HEALTH" or event == "UNIT_MAXHEALTH" then
-- arg1 is the token; "markN" -> N, nil for any non-mark token.
local i = arg1 and tonumber(string.match(arg1, "^mark(%d)"))
-- arg1 is one of the eight tokens we registered for, so this is a lookup
-- rather than a parse -- string.match would allocate a capture and
-- tonumber would parse it, on every health tick of every marked unit.
local i = arg1 and markerIndex[arg1]
if i then UpdateRow(i) end
return
end
if event ~= "RAID_TARGET_UPDATE" then UpdatePoll() end
UpdateDisplay()
end)
-- Fallback poll: catches units that come into range AFTER a marker was set
-- (no event fires for that case, so we need this safety net)
C_Timer.NewTicker(FALLBACK_INTERVAL, UpdateDisplay)
end)
+1 -1
View File
@@ -225,7 +225,7 @@ pfUI:RegisterModule("minimap", function ()
pfUI.minimap.pvpicon = CreateFrame("Frame", nil, pfUI.minimap)
pfUI.minimap.pvpicon:Hide()
pfUI.minimap.pvpicon:RegisterEvent("UPDATE_FACTION")
pfUI.minimap.pvpicon:RegisterEvent("UNIT_FACTION")
pfUI.minimap.pvpicon:RegisterUnitEvent("UNIT_FACTION", "player")
pfUI.minimap.pvpicon:SetFrameStrata("HIGH")
pfUI.minimap.pvpicon:SetSize(16, 16)
pfUI.minimap.pvpicon:SetAlpha(.5)
+100 -74
View File
@@ -5,10 +5,7 @@ pfUI:RegisterModule("nameplates", function ()
-- Local function references for performance
local GetTime = GetTime
local UnitName = UnitName
local UnitClass = UnitClass
local UnitLevel = UnitLevel
local UnitIsPlayer = UnitIsPlayer
local UnitIsDead = UnitIsDead
local UnitAffectingCombat = UnitAffectingCombat
local UnitIsUnit = UnitIsUnit
local UnitCanAssist = UnitCanAssist
@@ -162,6 +159,24 @@ pfUI:RegisterModule("nameplates", function ()
cfg.debuffanim = tonumber(C.nameplates.debuffanim) or 0
cfg.debufftext = tonumber(C.nameplates.debufftext) or 1
-- Throttle delays, resolved once instead of per plate per tick.
-- libthrottle:Get walks the saved-variable table, a defaults fallback and a
-- preset table, and can build a "<category>_custom" key -- the per-plate
-- OnUpdate was calling it one or two times for every visible plate, a
-- hundred times a second, just to decide it had nothing to do.
--
-- cfg.throttle_min is the floor across all four. No plate can ever be due
-- sooner than that, so the update can bail on it before working out which
-- category it actually belongs to.
cfg.throttle_target = pfUI.throttle:Get("nameplates_target")
cfg.throttle_mass = pfUI.throttle:Get("nameplates_mass")
cfg.throttle_normal = pfUI.throttle:Get("nameplates")
cfg.throttle_castbar = pfUI.throttle:Get("nameplates_castbar")
cfg.throttle_min = cfg.throttle_target
if cfg.throttle_mass < cfg.throttle_min then cfg.throttle_min = cfg.throttle_mass end
if cfg.throttle_normal < cfg.throttle_min then cfg.throttle_min = cfg.throttle_normal end
if cfg.throttle_castbar < cfg.throttle_min then cfg.throttle_min = cfg.throttle_castbar end
-- Rebuild offtanks lookup table
offtanks = {}
for k, v in pairs({strsplit("#", C.nameplates.combatofftanks)}) do
@@ -490,7 +505,7 @@ local nameplates = CreateFrame("Frame", "pfNameplates", UIParent)
nameplates:RegisterEvent("PLAYER_ENTERING_WORLD")
nameplates:RegisterEvent("PLAYER_TARGET_CHANGED")
nameplates:RegisterEvent("PLAYER_LOGOUT")
nameplates:RegisterEvent("UNIT_COMBO_POINTS")
nameplates:RegisterUnitEvent("UNIT_COMBO_POINTS", "player")
nameplates:RegisterEvent("PLAYER_COMBO_POINTS")
nameplates:RegisterEvent("ZONE_CHANGED_NEW_AREA")
nameplates:RegisterEvent("RAID_ROSTER_UPDATE")
@@ -498,13 +513,10 @@ nameplates:RegisterEvent("PARTY_MEMBERS_CHANGED")
nameplates:RegisterEvent("NAME_PLATE_CREATED")
nameplates:RegisterEvent("NAME_PLATE_UNIT_ADDED")
nameplates:RegisterEvent("NAME_PLATE_UNIT_REMOVED")
nameplates:RegisterEvent("UNIT_AURA")
nameplates:RegisterEvent("UNIT_FLAGS")
nameplates:RegisterEvent("UPDATE_MOUSEOVER_UNIT")
nameplates:RegisterEvent("UNIT_SPELLCAST_START")
nameplates:RegisterEvent("UNIT_SPELLCAST_CHANNEL_START")
nameplates:RegisterEvent("UNIT_SPELLCAST_STOP")
nameplates:RegisterEvent("UNIT_SPELLCAST_CHANNEL_STOP")
-- UNIT_AURA / UNIT_FLAGS / UNIT_SPELLCAST_* are registered per plate, on the
-- plate's own frame, against its own token -- see OnCreate and
-- NAME_PLATE_UNIT_ADDED.
nameplates:RegisterEvent("PLAYER_GUILD_UPDATE")
nameplates:SetScript("OnEvent", function()
@@ -516,6 +528,15 @@ nameplates:RegisterEvent("PLAYER_GUILD_UPDATE")
if nameplates.mouselook then
nameplates.mouselook:SetScript("OnUpdate", nil)
end
-- The plates hold their own unit subscriptions now, so silencing this
-- frame alone would leave them dispatching through logout -- exactly what
-- this branch exists to prevent.
for plate in pairs(registry) do
if plate.nameplate then
plate.nameplate:UnregisterAllEvents()
plate.nameplate:SetScript("OnEvent", nil)
end
end
return
elseif event == "PLAYER_GUILD_UPDATE" and arg1 == 'player' then
@@ -591,6 +612,16 @@ nameplates:RegisterEvent("PLAYER_GUILD_UPDATE")
local guid = UnitGUID(arg1)
plate.nameplate.cachedGuid = guid
plate.nameplate.unit = arg1
-- Point this plate's own subscriptions at the token it just took. On a
-- recycled frame these replace the previous unit rather than stacking:
-- RegisterUnitEvent on an already-filtered registration swaps the units.
plate.nameplate:RegisterUnitEvent("UNIT_AURA", arg1)
plate.nameplate:RegisterUnitEvent("UNIT_FLAGS", arg1)
plate.nameplate:RegisterUnitEvent("UNIT_SPELLCAST_START", arg1)
plate.nameplate:RegisterUnitEvent("UNIT_SPELLCAST_CHANNEL_START", arg1)
plate.nameplate:RegisterUnitEvent("UNIT_SPELLCAST_STOP", arg1)
plate.nameplate:RegisterUnitEvent("UNIT_SPELLCAST_CHANNEL_STOP", arg1)
plate.nameplate.creatureType = nil -- recompute for the new unit
plate.nameplate.totemIcon = nil
plate.nameplate.totemSpell = nil
@@ -621,14 +652,9 @@ nameplates:RegisterEvent("PLAYER_GUILD_UPDATE")
if plate and plate.nameplate and plate.nameplate.cachedGuid == guid then
plate.nameplate.cachedGuid = nil
plate.nameplate.unit = nil
end
end
elseif event == "UNIT_FLAGS" then
if arg1 and strfind(arg1, "^nameplate") then
local plate = C_NamePlate.GetNamePlateForUnit(arg1)
if plate and plate.nameplate then
plate.nameplate.eventcache = true
-- Drop the subscriptions with the token: this slot is now free and the
-- next plate to take it would otherwise feed this frame its events.
plate.nameplate:UnregisterAllEvents()
end
end
@@ -643,48 +669,6 @@ nameplates:RegisterEvent("PLAYER_GUILD_UPDATE")
if pn then pn.eventcache = true end
end
elseif event == "UNIT_SPELLCAST_START" or event == "UNIT_SPELLCAST_CHANNEL_START" then
-- ClassicAPI fires UNIT_SPELLCAST_* per unit token, including the caster's
-- "nameplateN". The payload has no timing, so poll it (PollCastInfo picks
-- cast vs channel) and cache -- only for a unit we have a plate for, so
-- the table stays bounded to on-screen casters.
if arg1 and strfind(arg1, "^nameplate") then
local guid = UnitGUID(arg1)
local plate = guid and plateByGuid[guid]
if plate then
castState[guid] = PollCastInfo(arg1)
if castState[guid] then
plate.castUpdate = true -- bypass the throttle so the bar shows now
end
end
end
elseif event == "UNIT_SPELLCAST_STOP" or event == "UNIT_SPELLCAST_CHANNEL_STOP" then
-- Cast/channel ended (natural, interrupted, or cancelled -- the poll fires
-- STOP for all three). Clear the cached cast and refresh its plate.
if arg1 and strfind(arg1, "^nameplate") then
local guid = UnitGUID(arg1)
if guid and castState[guid] then
castState[guid] = nil
local plate = plateByGuid[guid]
if plate then plate.castUpdate = true end
end
end
elseif event == "UNIT_AURA" then
-- ClassicAPI: fires with arg1 == "nameplateN" when a unit's aura set
-- changes (add/remove/modify). Flag the matching plate so OnUpdate does a
-- fresh C_UnitAuras read next tick instead of waiting on the 0.5s
-- throttle -- covers expirations, dispels, refreshes, and stack changes
-- in one event. Guard on the token prefix (UNIT_AURA also fires for
-- target/party/raid).
if arg1 and strfind(arg1, "^nameplate") then
local plate = C_NamePlate.GetNamePlateForUnit(arg1)
if plate and plate.nameplate then
plate.nameplate.auraUpdate = true
end
end
elseif event == "PLAYER_TARGET_CHANGED" then
frameState.targetGuid = UnitGUID('target')
-- Flag the target's plate for update
@@ -766,6 +750,40 @@ nameplates:RegisterEvent("PLAYER_GUILD_UPDATE")
nameplate.cache = {}
nameplate.original = {}
-- Each plate watches its own unit. With RegisterUnitEvent the token IS the
-- subscription, so there is no central listener sifting every unit event in
-- the world for a "^nameplate" prefix and then resolving the plate back out
-- of arg1 -- the event arrives only at the plate it concerns, and `this` is
-- already that plate. NAME_PLATE_UNIT_ADDED points the registration at the
-- new token; _REMOVED drops it, which matters because freed slots are
-- reused and a stale token would feed this frame another unit's events.
nameplate:SetScript("OnEvent", function()
if event == "UNIT_AURA" then
-- a fresh C_UnitAuras read next tick rather than waiting out the 0.5s
-- throttle -- covers expiry, dispels, refreshes and stack changes
this.auraUpdate = true
elseif event == "UNIT_FLAGS" then
this.eventcache = true
elseif event == "UNIT_SPELLCAST_START" or event == "UNIT_SPELLCAST_CHANNEL_START" then
-- the payload carries no timing, so poll it (PollCastInfo picks cast
-- vs channel)
local guid = this.cachedGuid
if guid then
castState[guid] = PollCastInfo(this.unit)
if castState[guid] then
this.castUpdate = true -- bypass the throttle so the bar shows now
end
end
elseif event == "UNIT_SPELLCAST_STOP" or event == "UNIT_SPELLCAST_CHANNEL_STOP" then
-- ended: natural, interrupted or cancelled -- the poll fires STOP for all
local guid = this.cachedGuid
if guid and castState[guid] then
castState[guid] = nil
this.castUpdate = true
end
end
end)
-- create shortcuts for all known elements and disable them
nameplate.original.healthbar, nameplate.original.castbar = parent:GetChildren()
DisableObject(nameplate.original.healthbar)
@@ -1409,6 +1427,16 @@ nameplates:RegisterEvent("PLAYER_GUILD_UPDATE")
-- cachedGuid is maintained by NAME_PLATE_UNIT_ADDED / _REMOVED events.
-- Cheap gate first. The central loop calls this for every visible plate ~100
-- times a second, and classifying the plate below costs two C calls, a cast
-- lookup and a throttle resolution -- all of it wasted on a plate that is
-- throttled to 10fps. cfg.throttle_min is the floor across every category,
-- so nothing that would have updated can be turned away here; the real
-- category-specific throttle is still applied after the classification.
-- Event flags bypass both gates, as before.
local hasEventUpdate = nameplate.eventcache or nameplate.auraUpdate or nameplate.castUpdate or nameplate.targetUpdate or nameplate.comboUpdate
if not hasEventUpdate and (nameplate.lasttick or 0) + cfg.throttle_min > now then return end
-- PERF: Intelligent throttling based on target/castbar status and plate count
-- Use GUID comparison as primary target detection: instant, immune to alpha transitions,
-- and immediately correct on de-target (unlike istarget which updates one tick later)
@@ -1435,25 +1463,24 @@ nameplates:RegisterEvent("PLAYER_GUILD_UPDATE")
end
end
-- Resolved in CacheConfig, so these are table reads rather than a walk
-- through the saved variables and preset tables.
local throttle
if target then
throttle = pfUI.throttle:Get("nameplates_target")
throttle = cfg.throttle_target
elseif visiblePlateCount > 20 then
throttle = pfUI.throttle:Get("nameplates_mass")
throttle = cfg.throttle_mass
else
throttle = pfUI.throttle:Get("nameplates")
throttle = cfg.throttle_normal
end
-- Non-target plates with active castbar use the castbar throttle
if isCastingNonTarget then
local cbThrottle = pfUI.throttle:Get("nameplates_castbar")
if cbThrottle < throttle then throttle = cbThrottle end
if isCastingNonTarget and cfg.throttle_castbar < throttle then
throttle = cfg.throttle_castbar
end
-- Check for pending event updates (these bypass throttle for immediate response)
local hasEventUpdate = nameplate.eventcache or nameplate.auraUpdate or nameplate.castUpdate or nameplate.targetUpdate or nameplate.comboUpdate
-- Event updates bypass throttle
-- The category-specific gate. hasEventUpdate was read above, before the
-- classification, and still bypasses the throttle.
if not hasEventUpdate and (nameplate.lasttick or 0) + throttle > now then return end
nameplate.lasttick = now
@@ -1655,10 +1682,9 @@ nameplates:RegisterEvent("PLAYER_GUILD_UPDATE")
-- engine framerate, decoupled from central loop). Only update non-target castbars here.
local isTargetPlate = target or nameplate.istarget or (nameplate.health and nameplate.health.zoomed)
if cfg.showcastbar and not cfg.targetcastbar and not isTargetPlate then
local cbThrottle = pfUI.throttle:Get("nameplates_castbar")
if visiblePlateCount > 20 then
local massThrottle = pfUI.throttle:Get("nameplates_mass")
if massThrottle > cbThrottle then cbThrottle = massThrottle end
local cbThrottle = cfg.throttle_castbar
if visiblePlateCount > 20 and cfg.throttle_mass > cbThrottle then
cbThrottle = cfg.throttle_mass
end
if (nameplate.castbar_tick or 0) + cbThrottle <= now then
nameplate.castbar_tick = now
+1 -1
View File
@@ -486,7 +486,7 @@ pfUI:RegisterModule("panel", function()
do -- Ammo
local widget = CreateFrame("Frame", "pfPanelWidgetAmmo", UIParent)
widget:RegisterEvent("PLAYER_ENTERING_WORLD")
widget:RegisterEvent("UNIT_INVENTORY_CHANGED")
widget:RegisterUnitEvent("UNIT_INVENTORY_CHANGED", "player")
widget:RegisterEvent("BAG_UPDATE_DELAYED")
widget.Tooltip = function()
if GetInventoryItemQuality("player", 0) then
+1 -1
View File
@@ -816,7 +816,7 @@ pfUI:RegisterModule("swingtimer", function ()
events:RegisterEvent("PLAYER_REGEN_DISABLED")
events:RegisterEvent("PLAYER_REGEN_ENABLED")
events:RegisterEvent("ACTIONBAR_SLOT_CHANGED")
events:RegisterEvent("UNIT_DIED")
events:RegisterUnitEvent("UNIT_DIED", UnitGUID("player"))
events:RegisterEvent("SPELL_QUEUE_EVENT")
events:RegisterEvent("START_AUTOATTACK")
events:RegisterEvent("STOP_AUTOATTACK")
+3 -3
View File
@@ -363,9 +363,9 @@ end
b:EnableMouse(true)
b:RegisterEvent("FACTION_STANDING_CHANGED")
b:RegisterEvent("UNIT_PET")
b:RegisterEvent("UNIT_LEVEL")
b:RegisterEvent("UNIT_PET_EXPERIENCE")
b:RegisterUnitEvent("UNIT_PET", "player")
b:RegisterUnitEvent("UNIT_LEVEL", "player")
b:RegisterUnitEvent("UNIT_PET_EXPERIENCE", "player", "pet")
b:RegisterEvent("PLAYER_ENTERING_WORLD")
b:RegisterEvent("UPDATE_EXHAUSTION")
b:RegisterEvent("PLAYER_XP_UPDATE")