`[@focus,...] X` with no focus set printed "Invalid target" before falling
through to the next clause, which the new `[@mouseover][@focus][] X` idiom
turned into a message on every press. The noise came from a pre-ClassicAPI
name-based fallback in DoWithConditionals: with no native focus token,
TryTargetFocus resolved UnitName('focus') to nil and could never succeed,
so the branch only ever reached the error print. GetFocusUnitId's own
contract already says @focus should fall through silently.
Both evaluators now fail the clause quietly when GetFocusUnitId is nil,
the same way @mouseover does, so display and execution agree (TestAction
previously evaluated against "target" in that case). GetFocusName,
TryTargetFocus and pfUI's FocusNameHook were only reachable through that
path and are removed.
Following the aura data source to ClassicAPI exposed a chain of machinery that
nothing consumed any more.
ValidateAura's non-player overflow fallback is gone. It existed because a buff
can occupy server slots 33-48 with no client slot, so it read presence and
duration out of AllCasterAuraTracking behind a UnitDebuff slot scan that stopped
[buff:Name] matching a debuff. ClassicAPI settles this at the source: its
HELPFUL/HARMFUL filters select on each aura's real polarity flag rather than the
slot range it happens to sit in, so per docs/API.md "a debuff parked in a buff
slot still reads harmful" -- and an overflowed buff still reads helpful. The
ClassicAPI resolution already covers the case, and classifies it more accurately
than the slot-range guard did.
That removed the last caller of FindAllCasterAuraByName (67 lines), and the
player-side allBuffAuras timing lookup went with it: for the player, C_Spell...
C_UnitAuras reads expirationTime out of the engine's own player-buff table, so it
is the more authoritative source rather than a fallback.
With no readers left, three tables turn out to be pure overhead -- populated on
every buff aura event, swept periodically, cleared on removal and death, and
never read for aura state:
- lib.allBuffAuras [guid][name][caster] = {startTime, duration, rank}
- lib.ownBuffCasts [guid][name] = {startTime, duration, spellId, ...}
- lib.pendingBuffCasts [guid][spellId] = {casterGuid, duration, name, time}
pendingBuffCasts only existed to correlate AURA_CAST_ON_OTHER with
BUFF_ADDED_OTHER so the other two could be filled, so removing them emptied the
BUFF_ADDED_OTHER handler entirely. That event is no longer registered, and pfUI
compat no longer unregisters an event we never ask for.
Fixes a regression from the previous commit in this series: removing the
allBuffAuras/ownBuffCasts blocks from BUFF_REMOVED_SELF and BUFF_REMOVED_OTHER
also deleted their `local spellName = C_Spell.GetSpellName(spellId)`, leaving
later `if spellName` guards reading a nil global. The OverflowBuffsByName and
AllCasterAuraTracking prunes in those branches had silently stopped running.
Both declarations are restored.
AllCasterAuraTracking itself stays: OverflowBuffFrame and the aura-tracking
writers still use it.
Assume a modern pfUI, and drop the version-tiered branching entirely: 31 call
sites across five files, plus the flag and its detector.
Note which side was actually dead. HasPfUI76() required four things -- pfUI
version >= 7.6, Nampower >= 2.40, pfUI.libdebuff_objects_guid, and
pfUI.libdebuff_casts. The last one does not exist: pfUI exports 28 libdebuff_*
tables and libdebuff_casts is not among them (libdebuff_recent_casts is a
dedup table keyed [targetGuid][spellName][casterGuid], a different shape). So
the detector returned false at any version, and it was the hasPfUI76-TRUE
branches that never ran, not the fallbacks. Removing those is behaviour-
preserving; keeping SCRM as the owner of castTracking is what the code has
always actually done.
Removed accordingly:
- lib:HasPfUI76(), both flag declarations, and the block that would have aliased
castTracking to pfUI.libdebuff_casts and re-pointed lib.objects/iconCache.
- HookPfUILibdebuff's "7.6 handles durations internally" early return, so the
GetDuration/AddEffect hooks below it are visibly the live path again.
- The SPELL_START_OTHER / SPELL_FAILED_OTHER unregister arm, the pfUI branch of
GetAuraTrackingData, the pfUI76 arm of the libdebuff zone/death cleanup, the
pfUI backing table in the /cleveroid aura dump, and two status-string suffixes.
- SyncComboDurationToPfUI (77 lines) with its two call sites: it returned
immediately whenever hasPfUI76, and is inert once the tier split is gone.
Unconditionalised the paths that were gated on `not hasPfUI76`: the castTracking
populate/clear/sweep, InitPfUIIntegration on login, and ten libdebuff duration
lookups.
Verified no hasPfUI76 reference remains, and that per-file block balance is
unchanged from HEAD.
Chasing high memory churn (~188 kb/s with pfUI). Four allocation sources and one
correctness bug found along the way.
The recurring cause: in 1.12's Lua 5.0 a function declared `function(...)` builds
a fresh `arg` table on every call. Three handlers were declared that way and none
of them read the vararg.
- Compatibility/pfUI.lua: the registered action handler. This was the dominant
one. UpdateAllManagedCooldowns fans ACTIONBAR_UPDATE_COOLDOWN across every
managed slot (up to 120) on each SPELL_UPDATE_COOLDOWN, so every GCD and
cooldown tick allocated ~120 tables that the handler then discarded, because
its whole body only ever applied to ACTIONBAR_SLOT_CHANGED. Dropped the vararg
and added an early return before any work. Bongos' and UltimaMacros' handlers
were already vararg-free.
- Core.lua SendEventForAction: dropped the vararg (every caller passes exactly
one extra value) and replaced the inline "arg" .. i concatenations -- 30 per
call across three loops -- with a prebuilt name table. The eight-branch arg.n
fan-out collapses to one loop.
- Core.lua Frame OnEvent dispatcher: dropped the vararg. It fires for all ~48
registered events, including the UNIT_HEALTH / UNIT_AURA / UNIT_*_GUID streams.
PublishDisplay now skips no-op publishes. Publishing is not free: the client
repaints holders of the macro through its own notifier, which returns as
ACTIONBAR_SLOT_CHANGED -> ClearAction + IndexActionSlot -> TestForActiveAction ->
publish. Publishing an unchanged value therefore tore down and re-resolved slots
for nothing and fed itself, since GetAction and IndexActionSlot both publish on
paths that fire constantly. This was the C_Macro.SetMacroDisplay regression.
Also merges the two same-named SPELL_CAST_EVENT handlers. Lua assigns in file
order, so the later definition silently replaced the earlier one and its half
never ran: channel-start detection, spell_tracking cleanup, and cast-sequence
advancement. AdvanceSequence has only two callers and the other is inside
UNIT_CASTEVENT, which is registered only under SuperWoW -- so /castsequence had
no way to advance on a successful cast for Nampower-only users.
Replace hand-rolled ADDON_LOADED/PLAYER_LOGIN handlers with
EventUtil.ContinueOnAddOnLoaded / ContinueOnPlayerLogin, which fire
immediately if the event already happened -- removing the "we loaded before
the target addon and missed its ADDON_LOADED" workarounds.
- pfUI compat: ContinueOnAddOnLoaded("pfUI") + ContinueOnPlayerLogin; drops
the missed-event fallback (login path still re-runs SetupCompatibility).
- MacroErrorUI / MacroLengthWarn: ContinueOnAddOnLoaded("Blizzard_MacroUI"),
folding their manual "already loaded" checks.
- 9 Mouseover extensions (ag_UnitFrames, CT_RaidAssist, CT_UnitFrames,
DiscordUnitFrames, Grid, NotGrid, Cursive, sRaidFrames, PerfectRaid):
ContinueOnAddOnLoaded("<AddonName>", OnLoad). Since immediate-fire passes no
event args, the old `arg1 == "X"` checks are replaced by the addon-name gate
(global guards kept where present); also removes the buggy
UnregisterEvent("ADDON_LOADED", "Onload") no-ops that never fired.
Addon names match file names; drops support for renamed folders (e.g. -master).
Swap 166 call sites from GetSpellRecField(id, "name") to
C_Spell.GetSpellName(id) and GetSpellRecField(id, "rank") to
C_Spell.GetSpellSubtext(id) across Core, Conditionals, Utility,
ComboPointTracker, CursiveCustomSpells, pfUI, OverflowBuffFrame, and
Generic. Guard forms and the _GetSpellRecField alias calls collapse to the
direct C_Spell call.
GetSpellRecField stays for fields with no C_Spell equivalent (school,
spellIconID, mechanic, effectMechanic, effectApplyAuraName, stackAmount,
rangeIndex) and inside the NampowerAPI wrapper layer.
- ScheduleTimer now wraps C_Timer.After, removing the dedicated timer
frame + per-frame OnUpdate loop + timers queue.
- Aura-tracking cleanup -> C_Timer.NewTicker(5) (_auraCleanupTicker);
drop the UnitXP arm, the named global CleveRoids_AuraTrackingCleanupTimer,
and the inline "not hasUnitXP" fallback in OnAuraCastOther.
- Libdebuff cleanup -> C_Timer.NewTicker(30) (_cleanupTicker); drop the
UnitXP arm and CleveRoids_LibDebuffCleanupTimer.
- Shutdown cancels the tickers instead of UnitXP timer disarm.
Cleanup now always runs (was gated on hasUnitXP) and no longer depends
on UnitXP threaded timers or named global callbacks. Tickers self-guard
on isShuttingDown and are cancelled on shutdown.
GetActionInfo:
- Add ClassicAPI.GetActionInfo wrapper; rewrite GetActionButtonInfo
(Generic.lua) to use it and resolve names via GetSpellRecField /
GetItem / GetMacroInfo, replacing the per-slot GameTooltip scan +
texture heuristic. Powers reactive-ability slot detection.
- Delete the dead SuperWoW-GetActionText copy of GetActionButtonInfo.
Spell IDs in the index:
- IndexSpells now uses ClassicAPI GetSpellInfo(slot, bookType), whose
10th return is the spellID, so every Spells entry carries .id (one
call also replaces GetSpellName + GetSpellTexture).
Dynamic pfUI action-button icons (pairs with the pfUI fork change):
- GameTooltip.SetAction hook renders spells via ClassicAPI
SetSpellByID(spell.id) instead of SetSpell(spellSlot, bookType) +
manual rank text. Items stay location-based (instance data).
- Drop the redundant GetActionSpellSlot -> GetSpellCooldown cooldown
shim in the pfUI handler; pfUI now routes through the hooked
GetActionCooldown when it defers macro scanning to us.
Don't clear the confirmed flag after first use since pfUI may call
AddEffect multiple times for the same spell. Instead, rely on the
time-based expiry (0.5s timeout) to prevent stale tracking data.
This fixes the issue where the second AddEffect call would see
unconfirmed tracking and ignore the correct duration.
CRITICAL FIX: pfUI's libdebuff uses completely different data structures:
- Uses unit NAME (not GUID)
- Uses unit LEVEL as a key
- Uses spell NAME (effect, not ID)
- Storage: pflib.objects[unitName][unitLevel][effectName]
Fixed SyncComboDurationToPfUI:
- Convert GUID to unit name (check target, fallback to guidToName map)
- Get unit level from target or default to 0
- Convert spell ID to spell name using SpellInfo()
- Remove rank from spell name to match pfUI's format
- Search both specific level and level 0 (pfUI's fallback)
Fixed AddEffect hook:
- Updated parameters to match pfUI signature: (unit, unitlevel, effect, duration, caster)
- Check spell by name instead of ID
- Map spell name back to ID to find tracking data
This ensures pfUI's cooldown displays show correct combo durations.
After analyzing pfUI's cooldown module, we need to directly sync
combo durations to pfUI's libdebuff.objects storage:
- Added SyncComboDurationToPfUI function to force-update pfUI's
stored debuff duration after tracking combo spells
- Called from UNIT_CASTEVENT handler after AddEffect
- This ensures pfUI's cooldown display shows the correct combo
duration (e.g., 28s for 5 CP Rip) instead of base 12s
pfUI's cooldown module is just a display layer - it shows durations
from pfUI.api.libdebuff.objects. By directly updating this storage,
we ensure the correct duration is displayed regardless of timing.
Hook into pfUI.api.libdebuff to inject combo-based durations:
- Hook GetDuration: Returns learned combo durations for combo spells
- Hook AddEffect: Injects tracked combo durations when debuffs are applied
This ensures pfUI's cooldown module displays the correct duration
(e.g., 28s for 5 CP Rip instead of base 12s).
Debug messages show when pfUI hooks are triggered in debug mode.