Eight commits off classicapi_next.
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. The 26 registrations whose unit set is
fixed at registration time now name it. 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. Guards that look unreachable stay put: the
filter applies only when arg1 is a string, so an event that fires with a
number or no argument is delivered as if plainly registered.
Frames whose unit changes at runtime own their subscriptions instead of
sorting events out per event. unitframes points them at the unitstr
UpdateVisibility already computes -- replacing a string concat, and on a miss
a second concat plus a UnitGUID call, for every frame on every unit event in
the world -- and a frame that is not in use drops its unit events entirely.
nameplates registers per plate against the plate's own token, which is also
the only workable shape: slots have no cap, so any nameplate1..N list would
have been a guess that fails in exactly the crowded scenes where plates
matter. marktracking names mark1 through mark8. A registration keeps its
kind, so none of these can be plain-registered first.
Both teardown paths PLAYER_LOGOUT guards -- the crash 132 -- now cover the
per-frame subscriptions: plates tear down rather than dispatching through
logout, and a unit frame takes itself off the visibility scan so it cannot
re-register what it just dropped.
marktracking also drops its once-a-second full rebuild, which ran for the
whole session whether or not a marker existed anywhere. The ticker is created
and cancelled with group membership. It is deliberately not keyed on a mark
being visible -- a marker on an out-of-range unit shows no row, and that is
the case the poll exists to catch.
nameplates gates the per-plate update against the floor across all four
throttle categories before classifying it, instead of running a GetAlpha, a
castbar IsShown, a cast lookup and up to two libthrottle:Get resolutions on
plates throttled to 10fps that were going to return anyway. Nothing that
would have updated can be turned away by a floor. The four throttles resolve
in CacheConfig, where config changes already land.
energytick sweeps the clock the server actually runs. There is one regen
timer for every power, re-armed every 2s by Player::RegenerateAll and never
touched by casting; the five-second rule changes what a tick pays, not when
it lands. The sweep is a free-running phase lock on that clock, so
Illumination refunds, potions and a Mana Spring totem on its own phase no
longer snap the spark mid-cycle, and an 80ms band keeps a correct tick from
hitching it at the wrap. The FSR window shades rather than predicting a share
of spirit the client cannot compute -- the Casting Regen item ladder is equip
auras absent from the buff list. The energy period is summed from
SPELL_AURA_MOD_ENERGY_REGEN_TIME across the spellbook and buffs, so Blade
Rush is found without GetTalentInfo(2, 16), an ordinal that does not fail
when the tree changes but reads another talent's rank.
macrotweak is gone -- ClassicAPI 1.15 covers it -- with its config entry, its
GUI block, its translations in all eight locales, and actionbar's
ButtonMacroScan, the #showtooltip scanner that fed it.
This change updates several text setters to use `SetFormattedText` instead of passing `string.format(...)` into `SetText`. It also switches a few regex captures from `string.find` to `string.match` for cleaner capture handling, and replaces explicit width/height setters with `SetSize` in the first-run dialog for consistency.
* Migrate player castbar to ClassicAPI UNIT_SPELLCAST_* events
Drive the player cast path off ClassicAPI's synthesized, player-only
UNIT_SPELLCAST_* events instead of the vanilla SPELLCAST_* plus nampower
SPELL_{START,GO,DELAYED}_SELF mix. START/CHANNEL_START poll UnitCastingInfo/
UnitChannelInfo and stamp; STOP/CHANNEL_STOP/FAILED/INTERRUPTED clear;
SUCCEEDED drives tradeskill craft counting; DELAYED/CHANNEL_UPDATE re-poll.
Since UNIT_SPELLCAST_START fires for chained same-spell recasts, the nampower
SELF deps and their RunNextFrame co-hook-ordering workarounds are gone.
Remote target/focus bars keep SPELL_*_OTHER (the new events are player-only).
Fix the cast-start flash: StampBar now primes the fill on the stamp frame,
and ClearBar no longer snaps the bar to full (that snap lingered in the fade
tail and flashed when the next cast stamped).
Add a red flash on a cancelled cast: new appearance.castbar.failcolor,
flashed by ClearBar only when a cast was actually in progress.
Handle pushback Quartz-style: DELAYED/CHANNEL_UPDATE re-poll just the times
and accumulate the endMs shift into this.delay for the +/- indicator (cast
+X, channel -X), rather than a full restamp.
* Route target/focus castbars through remote UNIT_SPELLCAST_*
ClassicAPI now fires UNIT_SPELLCAST_* for remote tokens (target/focus/...)
via PollRemote, so drop the nampower SPELL_*_OTHER path and drive all three
bars off one event model. The gate is now token-based (arg1 == this.unitstr,
plus the arg1=="player" + UnitIsUnit case for target=self); the RunNextFrame
defer is gone since the remote poll fires after UnitCastingInfo is readable.
Cast detection is now nampower-free across every bar.
Use the event's rank payload (arg5): thread it through RefreshBar into
StampBar, which now only calls C_Spell.GetSpellSubtext as a fallback on the
retarget re-poll (PLAYER_TARGET/FOCUS_CHANGED) where no event is in hand.
* Drive nameplate castbars off UNIT_SPELLCAST_*
ClassicAPI now fires UNIT_SPELLCAST_* for nameplate tokens, so populate the
per-GUID castState cache from UNIT_SPELLCAST_{START,CHANNEL_START} (poll
PollCastInfo for the timing the payload omits) and clear it on
{STOP,CHANNEL_STOP} -- the remote poll fires STOP for natural end, interrupt,
and cancel alike. Replaces the nampower SPELL_{START,FAILED}_OTHER path; the
^nameplate arg1 gate ignores the target/focus/party fires of the same events,
and the NAME_PLATE_UNIT_ADDED seed still catches a plate spawning mid-cast.
Nameplate cast detection is now nampower-free.
* We no longer use these features from SuperWoW/Nampower
* Use secure unit attributes; remove mouseover scripts
Switch unitframes to attribute-driven clicks and remove mouseover tooltip handlers. Adds SetAttribute("unit") in UpdateVisibility and sets frame attributes (unit and type1='target') when creating frames. Removes OnEnter/OnLeave functions and their SetScript registrations and eliminates direct TargetUnit calls in ClickAction
* Use SetShown and simplify component default
Replace explicit Show/Hide conditionals in api/unitframes.lua with SetShown(...) for ressIcon, leaderIcon, lootIcon, pvpIcon, and restIcon to reduce branching and improve readability. Also remove the local shadowing of the parameter 'component' by using assignment (component = component or ""). No intended behavior changes.
* Drive unit-frame clicks via secure attributes
Replace the Lua OnClick dispatch (OnClick/ClickAction/RightClickAction) with
secure frame attributes: default type1=target / type2=menu, and EnableClickCast
now writes type/spell/macrotext/menu/target/focus attributes per button+modifier
instead of caching a clickactions table read at click time. Drops the now-dead
clickactions table and buttons list.
Also set a "unit" attribute on each frame (at creation and re-synced in
UpdateVisibility to the live token, so a party shown on the raid grid reports
partyN, not raidN) so the hovered unit resolves from the frame.
Match the target/focus/menu click keywords exactly rather than by prefix, so a
spell whose name starts with "focus"/"target"/"menu" (e.g. Focus Magic) casts
instead of being swallowed as the keyword action.
* Simplify marktracking colors and refresh per mark
Replace the hand-rolled ParseColor plus its default-color table with
GetStringColor -- the raidmarkercolor_* config keys already carry those same
defaults, so the fallback was dead code.
Restructure the refresh off observed mark tokens: UNIT_HEALTH/UNIT_MAXHEALTH
now arrive as arg1 == "markN", so refresh just that one row (UpdateRow) instead
of rescanning all eight on every nearby unit's health tick. A visibility flip
(into range / death / hp crossing 0) re-packs the rows, so UpdateRow hands off
to a full UpdateDisplay; RAID_TARGET_UPDATE / PLAYER_ENTERING_WORLD stay full
refreshes, with the 1s poll as the range-change backstop.
* not true anymore
* Poll marktracking fallback via C_Timer instead of OnUpdate
The 1s range-change safety net ran a per-frame OnUpdate that no-oped ~59 of
every 60 frames. Replace it with C_Timer.NewTicker(FALLBACK_INTERVAL,
UpdateDisplay) -- one wakeup per second off the shared timer driver -- and drop
the elapsed accumulator. The scanner frame is now purely event-driven.
* SuperWoW is now optional
* Update README feature list
Add 'Mouseover Unit Frames' and 'Click-casting' to the main features list. Update the SuperWoW entry to reflect that it tracks party/raid units on the minimap (replacing the prior SetMouseoverUnit note).
* Replace fixed-interval OnUpdate polls with C_Timer/RunNextFrame
Swap hand-rolled per-frame throttles for the modern timer primitives:
- turtle-wow: one-shot next-frame defer (self-hiding OnUpdate frame) -> RunNextFrame
- panel: clock, combat, and fps widgets -> NewTicker(1); guild roster -> NewTicker(60)
- minimap: coordinates text -> NewTicker(0.1)
- addonbuttons: one-shot init -> RunNextFrame; 5s button rescan -> NewTicker(5),
guarded by IsShown() to preserve the old "paused while hidden" behavior
Ticker callbacks reference frames via upvalue since 'this' is unbound outside
OnUpdate. Genuine per-frame work (bar fills, fades, drag) and polls already
coordinated through pfUI.throttle are left as-is.
* update pfQuest link
* Use SetShown/SetSize in panel module
Replace manual Show/Hide toggles with SetShown(not ... ) and replace SetWidth/SetHeight with SetSize in modules/panel.lua. Changes simplify toggle logic (timer, WorldMap, chat hide buttons) and unify sizing calls for timer, frames, textures, and microbutton. No behavior changes intended; purely refactor for conciseness and consistency.
* Bump ClassicAPI min version to 1.9.0
* Drive reagent counter from events instead of a polling OnUpdate
The pfReagentCounter frame ran an OnUpdate that (1) progressively rescanned
all 120 action slots one-per-100ms on any slot change and (2) recounted
reagent inventory on a 1s throttle. Replace both with direct event handling:
- ACTIONBAR_SLOT_CHANGED updates just the changed slot (arg1), full-scanning
only when arg1 is 0/nil, instead of restarting a ~12s rescan on every edit
- BAG_UPDATE_DELAYED recounts tracked reagents directly (it is already
Blizzard's coalesced bag event, so the extra 1s throttle was redundant)
- PLAYER_ENTERING_WORLD seeds the full reagent map once
UpdateSlot now seeds a new reagent's real count via GetItemCount so a freshly
placed reagent spell shows the correct number immediately. The updatecache ->
BarsUpdate render path and the IsReagentAction/GetReagentCount accessors are
unchanged.
commit 70b1e66c2d
Author: Brues <5278969+brues-code@users.noreply.github.com>
Date: Sun Jul 26 18:30:18 2026 -0500
Use SetSize/SetShown and simplify minimap/map
commit 7b6bfe9975
Author: Brues <5278969+brues-code@users.noreply.github.com>
Date: Sat Jul 25 11:27:14 2026 -0500
Include raidpet frames in /pftest test mode
The unit-frame test toggle (showall) only previews frames that exist and
are positioned. Raidpet frames are created when raidpet.visible == "1",
but LayoutPets' collapse mode only positions pets whose raidpet<N> unit
actually exists, so solo/in test mode they stayed hidden.
Add a showall branch to LayoutPets that mirror-lays every pet cell and
shows it (bypassing collapse and the roster gate), still guarded by the
existing showpets check so nothing happens when raidpet is disabled. Call
LayoutPets from the /pftest handler so the grid updates immediately on
toggle-on and restores to the normal layout on toggle-off.
commit 4c65c38647
Author: Brues <5278969+brues-code@users.noreply.github.com>
Date: Sat Jul 25 10:34:33 2026 -0500
Remove unused RangeCache local in UnitInRange
commit 6c96bbbe6c
Author: Brues <5278969+brues-code@users.noreply.github.com>
Date: Sat Jul 25 10:34:33 2026 -0500
Use Clamp() for the two-sided clamps in ui-widgets
Replace the hand-rolled math.min/math.max and if/elseif clamp idioms in the
status bar and scroll frame with Clamp(). Equivalent for every value seen
(min <= max always holds), just clearer.
commit 830e0a0be1
Author: Brues <5278969+brues-code@users.noreply.github.com>
Date: Sat Jul 25 10:34:33 2026 -0500
Drop vanilla GetItemInfo shim for C_Item.GetItemInfo
The compat/vanilla.lua GetItemInfo override reshaped vanilla's tuple into
retail order by inserting nil at slot 4 (itemLevel), but truncated at 8
fields, dropping itemEquipLoc/itemTexture. ClassicAPI's global GetItemInfo
keeps the vanilla shape (its hook only warms the item cache), so the shim
stayed necessary but incomplete.
Remove it and point the three callers at ClassicAPI's C_Item.GetItemInfo,
which returns the full 18-field retail tuple. Positions 1-8 are identical
to the old shim output, so bags/roll are unchanged; character.lua's
itemSlot (equipLoc, field 9) was always nil under the truncating shim and
now resolves correctly for ShaguScore.
commit c0da63657d
Author: Brues <5278969+brues-code@users.noreply.github.com>
Date: Sat Jul 25 10:09:34 2026 -0500
Various cleanup
Removes version compatibility checks for TBC/Vanilla, consolidating code to target a single WoW version. Refactors repetitive SetWidth/SetHeight calls to SetSize for cleaner code. Removes duplicate function definitions from unitxp module and simplifies conditional show/hide operations using SetShown.
commit 11a6302c2d
Author: Brues <5278969+brues-code@users.noreply.github.com>
Date: Sat Jul 25 09:53:37 2026 -0500
Delegate pfUI.api.strsplit to ClassicAPI's strsplit
Replace the Lua pattern-based implementation with a thin wrapper around
ClassicAPI's C-level strsplit. Keeps the pfUI.api.strsplit entry point for
backwards compatibility with addons that call it, while dropping the
redundant reimplementation.
Behavioral note: the old version used ([^delim]+) which silently collapsed
empty fields; delegating to real strsplit now preserves them
("a,,b" -> "a", "", "b"). All in-repo callers split numeric color tuples,
version strings, or build name-keyed lookup tables, none of which are
affected by empty-field preservation.
commit 67c126eac8
Author: Brues <5278969+brues-code@users.noreply.github.com>
Date: Sat Jul 25 01:01:02 2026 -0500
Bump ClassicAPI minimum version to 1.8.0
commit a6cf157518
Author: Brues <5278969+brues-code@users.noreply.github.com>
Date: Sat Jul 25 00:49:50 2026 -0500
read spell rank via C_Spell.GetSpellSubtext
The rank string comes from the spell subtext ("Rank N"), which ClassicAPI's
C_Spell.GetSpellSubtext returns directly -- so drop nampower's
GetSpellRecField(spellId, "rank") here. The existing "Rank " gsub parse is
unchanged.
commit 4b1ba99b4f
Author: Brues <5278969+brues-code@users.noreply.github.com>
Date: Sat Jul 25 00:33:20 2026 -0500
Move player info overlay onto ClassicAPI (drop Nampower)
The haste/spell-power overlay read both values through Nampower and disabled
itself entirely without it. Move both to ClassicAPI so it works on any
ClassicAPI client:
- Spell power: GetSpellPower("net") (nampower) -> GetSpellBonusDamage(i)
(ClassicAPI, same per-school field). Merged the build+scan into one loop;
the default school seeds the tiebreak so equal schools still favor it.
- Haste: GetUnitField("player", "modCastSpeed") -> UnitSpellHaste("player"),
which returns the haste percentage directly (the (1/modCastSpeed-1)*100
conversion is now baked in, off the same UNIT_MOD_CAST_SPEED field, signed).
- Dropped the "if not GetUnitField then return" gate -- UpdateInfoText no
longer touches Nampower, so the overlay runs everywhere.
commit 7df4aa6d50
Author: Brues <5278969+brues-code@users.noreply.github.com>
Date: Sat Jul 25 00:32:44 2026 -0500
Hook the real global _G.UnitHealth for feign death
Inside a RegisterModule body, `function UnitHealth(...)` defines UnitHealth on
the pfUI environment, not the real global -- so the feign-death real-HP fix
only reached callers that resolve UnitHealth through pfUI's env, and missed
_G consumers (Blizzard frames, other addons). Hook _G.UnitHealth explicitly
(and capture oldUnitHealth from _G) so the un-gate applies everywhere.
commit b0bf2fd869
Author: Brues <5278969+brues-code@users.noreply.github.com>
Date: Fri Jul 24 21:10:58 2026 -0500
Refactor nampower module
Simplify reactive spell storage by using spell IDs instead of texture/name tables. Update to use modern C_Spell APIs (GetSpellTexture, GetSpellName) instead of manual texture paths. Consolidate SetWidth/SetHeight calls to SetSize and use print() instead of DEFAULT_CHAT_FRAME:AddMessage().
commit 0b06961333
Author: Brues <5278969+brues-code@users.noreply.github.com>
Date: Fri Jul 24 20:54:23 2026 -0500
Use GetNamePlateForUnit for target lookups; drop dead ScanGuid block
- Replace the three GetNamePlateForGUID(UnitGUID("target")) round-trips with
GetNamePlateForUnit("target"), which resolves the token to a GUID internally
-- no UnitGUID string detour, and it returns nil for no-target/no-plate so
the UnitExists guards collapse. GetNamePlateForGUID is left for raw-GUID cases.
- Remove the dead libunitscan.ScanGuid nameplate block (ScanGuid was deleted
from libunitscan long ago, so the guard was never true) -- it carried the
last GetUnitField("npcFlags") read.
commit 3e6210086e
Author: Brues <5278969+brues-code@users.noreply.github.com>
Date: Fri Jul 24 20:54:23 2026 -0500
Drop Nampower stats system and polling from unit frames
GetUnitStats now reads health/power straight from UnitHealth/UnitPower
(the descriptor fields the server broadcasts) -- for a real unit token
GetUnitField read nothing different, so the whole Nampower-vs-fallback
apparatus was measuring a distinction that no longer exists. Removed:
- The stats system: pfUI.uf.stats, pfUIStatsFrame + UpdateStatsDisplay, the
lastUnitStats change-cache, the /pfuistats slash command, and every counter
increment (event/heartbeat/earlyReturn/throttledSkip/nampower/fallback).
- All GetUnitField health reads (GetUnitStats, heal-prediction, health-gradient
color) -- collapsed to the UnitHealth/UnitHealthMax they already fell back to.
- The heartbeat-polling backstop and its fallbackThrottle/lastEventUpdate deps.
Frames now refresh on events only; eventless frames still use their own tick,
and range/aggro still run in the eventless-actions block.
commit 63001b7b0c
Author: Brues <5278969+brues-code@users.noreply.github.com>
Date: Fri Jul 24 20:09:42 2026 -0500
Move nameplates onto ClassicAPI stable nameplate tokens
ClassicAPI now assigns retail-exact, per-plate-stable "nameplateN" tokens and
fires vanilla UNIT_* events for them, so the nameplate module can key off the
token instead of Nampower/SuperWoW GUID primitives:
- Events: UNIT_AURA_GUID/UNIT_FLAGS_GUID -> UNIT_AURA/UNIT_FLAGS, matched by
the "nameplateN" token (guarded on the token prefix, since these also fire
for target/party/raid). Registered unconditionally -- no GetUnitField gate.
- Health: GetUnitField(guid, "health"/"maxHealth") -> UnitHealth/UnitHealthMax
on the plate's cached token. Same UNIT_FIELD_HEALTH the server broadcasts
(real HP on Turtle; the ~= 100 scaled-vs-real guard is unchanged).
- Combat/target in GetCombatStateColor: GetUnitField("flags") + HasFlag ->
UnitAffectingCombat(token); GetUnitField("target") + SuperWoW "<guid>target"
-> the "nameplateNtarget" chain. Dropped the now-dead HasFlag,
UNIT_FLAG_IN_COMBAT, and guidTargetTokenCache.
- GetCastInfo now takes the token directly; every caller already holds it, so
the UnitTokenFromGUID(guid) round-trip is gone (unused module-wide now).
NAME_PLATE_UNIT_ADDED caches the token as plate.nameplate.unit alongside the
GUID; REMOVED clears both. GUID is retained only as the stable cache key
(debuffCache/threatMemory/combatColorCache). Only GetUnitField("npcFlags")
remains -- it has no token/vanilla equivalent.
commit ce1c49fcbb
Author: Brues <5278969+brues-code@users.noreply.github.com>
Date: Fri Jul 24 18:20:06 2026 -0500
Make focus/focustarget event-driven via ClassicAPI unit events
ClassicAPI now fires UNIT_* (health/mana/aura/...) with arg1 == "focus" and
arg1 == "focustarget", observed per-unit like target/party/raid. Both frames
already registered those events (focus/focustarget are in pfValidUnits) and
their OnEvent already matches arg1 == label, so the 0.2s polling ticks were
pure workarounds for the missing events. Drop both ticks; the frames now
refresh on-event like target, with range/glow still on the shared 0.5s state
pass and PLAYER_FOCUS_CHANGED still driving assign/clear.
pfUI's Lua hooksecurefunc lived in pfUI.env and shadowed ClassicAPI's C
global for all pfUI code. Replace it with a thin pfUI.hooksecurefunc shim
that keeps the missing-target no-op our call sites rely on (ClassicAPI
errors on a nil target) and delegates the actual hook to _G.hooksecurefunc.
Migrated all 70 internal call sites (modules/libs/skins) to
pfUI.hooksecurefunc; bare hooksecurefunc now resolves to ClassicAPI's C
version everywhere. Dropped the unused prepend path and the orphaned
pfUI.hooks table.
Drop the now-vestigial expansion plumbing.
- Delete modules/thirdparty-tbc.lua + its xml Include
- Strip 10 tbc-tagged CreateConfig calls in modules/gui.lua
- Drop the expansion arg from CreateConfig() signature + the disabled-
entry rendering path that depended on it
- Drop the showdisabled GUI toggle + its default
- Simplify pfUI:RegisterModule / pfUI:RegisterSkin to (name, func) only
- Strip the leading version arg ("vanilla:tbc", etc.) from all 114
Register call sites
- Delete the pfUI.expansion variable
Bugfixes:
- Fixed 40-yard range check not working for raid/party frames
- Fixed aggro indicator not displaying properly on raid/party frames
- Improved aggro cache to only cache positive results for instant detection
- Fixed HP/Mana not updating with "Use Raid Frames for group" enabled
- Added SuperWoW nil-check for SpellInfo
- Added missing events: PARTY_MEMBER_ENABLE, PARTY_MEMBER_DISABLE, PLAYER_UPDATE_RESTING
UI Improvements:
- Share/Hoverbind buttons now show warning when module is disabled
This commit breaks alot of compatibility and maybe
other things too. The change was necessary, because
the old framework ran up against its limits.
A lot of new configuration options are now possible
and some of them were already added.