11 Commits

Author SHA1 Message Date
Brues 7bbb8df010 remove pre pfUI 7.6 stuff 2026-09-11 00:27:05 -05:00
Brues 96dbe9aca8 Fail @focus clauses quietly when no focus is set
`[@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.
2026-09-10 17:35:49 -05:00
Brues 1de455f96e Support Blizzard-style bracket groups: [a][b] Spell and [] fall-through
A clause may now carry a leading run of [group] blocks sharing one action;
groups are OR'd, first pass wins, and [] always passes. The existing
;-separated single-block form is unchanged and mixes freely.

The OR loop lives in the two single-clause evaluators, DoWithConditionals
and TestAction, which expand a multi-group clause into one-group variants
and recurse. That covers every ;-split site, /castsequence (where ; is not
a separator and the sequence is keyed by the full args), /cancelaura,
/stopmacro and the editor highlight without rewriting strings, so
action.args, the ParsedMsg/Sequences caches and highlight offsets keep
their identity.

ParseMsg scans the leading group run (quote-aware) instead of the greedy
%[(.+)%], which previously turned [a][b] into the bogus key a][b and made
such clauses fail silently on both the cast and icon paths. The flag
pattern still runs on the whole clause when there are no groups, so !Spell
is byte-identical.

TestAction reports which variant passed and TestForActiveAction stores its
conditionals, so range/usable colouring follows the passing group's @unit
rather than group 1's. /target gains ; and group support; /pfcast injects
the resolved unit into every group, not just the first. The macro checker
validates every leading block, drops the EMPTY_CONDITIONAL error, and
catches a missing action after the last group.
2026-09-10 17:17:08 -05:00
Brues d93d97a469 Delete the write-only buff tracking tables and the overflow fallback
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.
2026-09-10 14:40:16 -05:00
Brues 2a562e0550 Make ClassicAPI the source of truth for non-player aura timing
ValidateAura resolved remote aura timers from libdebuff's allBuffAuras and then
from AllCasterAuraTracking via FindAllCasterAuraByName. Both existed for one
reason: vanilla cannot report a timer for an aura on a unit other than the
player. ClassicAPI's Aura::Source cache does, reconstructing duration and
expirationTime for any unit from the observed SMSG_SPELL_GO -- caster-modified,
so talent extensions and combo-point finisher scaling are already applied, and
Carnage's roll-gated Rip/Rake refresh is handled in the DLL rather than mirrored
in Lua.

Replaced that ~70-line cascade with ResolveUnitAuraViaClassicAPI, a positional
C_UnitAuras.UnitAura scan over the relevant range. Matching stays ours -- by
spellID when the conditional supplied a number, else by lowercased name, since
C_UnitAuras' own by-name lookup is case-sensitive and exact and would miss
[debuff:thunder_clap]. It runs after the existing scans and overrides them, which
is what "source of truth" means here.

Also drops the libdebuff UnitBuff timeleft bug the old comments worked around
(timeleft reporting ~1000s), since nothing reads it on this path any more.

Presence and stacks from C_UnitAuras are reliable. Timing is best-effort by
design: an aura cast before login, or one refreshed at max stacks, yields no
expirationTime, and remaining is left nil so the caller keeps its found-and-0
default. A permanent aura (duration 0) reports -1, matching the existing
convention.

Still on the old machinery and not yet migrated: the non-player overflow-slot
buff path (AllCasterAuraTracking, guarded by a debuff-slot check) and the player
branches. AllCasterAuraTracking still has 56 references across 5 files, mostly
OverflowBuffFrame and Conditionals.
2026-09-10 14:40:16 -05:00
Brues daaab2fd11 Retire the hasPfUI76 flag and the pfUI 7.6 branches it gated
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.
2026-09-10 14:40:16 -05:00
Brues 43f1ba378a use brues-code/packager@vCAPI for packaging 2026-09-10 14:40:16 -05:00
Brues 828acc0c4b Remove two silently-shadowed code paths
Both were invisible: no error, no warning, just logic that never ran.

Console.lua no longer defines the global RunMacro. Core.lua installs a hook of
the same name and loads first, so this file's name-only wrapper replaced it. What
that cost, whenever SuperMacro was absent: macro *index* arguments stopped
working (Blizzard's RunMacro accepts an index or a name, and ExecuteMacroByName
takes only a name), the delegation to SuperMacro went away, so did the fallback
to the saved Blizzard original when a macro would not resolve, and so did
clearing the stop/skip flags at the start of a top-level run -- which is what
lets /stopmacro, /skipmacro and /firstaction work across parent/child macro
boundaries. With SuperMacro loaded, Compatibility/SuperMacro.lua reassigns the
global afterwards anyway, so this definition only ever did harm.
SlashCmdList.RUNMACRO keeps using ExecuteMacroByName, which is correct there.

Utility.lua drops ClearReactiveProcOnCast and the wrappers it existed for, which
tried to clear a reactive proc when its spell was cast. Three independent bugs
meant it never worked: the wrappers guarded on CleveRoids.Frame, which Core.lua
creates after this file loads, so the guard was always false and neither wrapper
installed; the UNIT_CASTEVENT wrapper tested arg2 == "START" when that signature
is (caster, target, action, spell_id, cast_time), so it compared the target; and
both passed a spell ID to a lookup keyed by spell name. They also allocated an
arg table per call and unpack()'d it. Deleting is behaviour-preserving.

Swept for both patterns: no other duplicate global definitions, and no remaining
file-scope use of CleveRoids.Frame in a file that loads before Core.lua.
2026-09-10 14:40:16 -05:00
Brues f296425c94 Cut per-call allocations in the event and publish paths; merge SPELL_CAST_EVENT
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.
2026-09-10 14:40:16 -05:00
Brues 4dd5326172 Drop the action-bar overrides now that ClassicAPI draws macro buttons
Removes the nine Lua replacements the macro display integration makes
redundant: GameTooltip.SetAction, GetActionCooldown, GetActionCount,
IsConsumableAction, IsUsableAction, IsActionInRange, ActionHasRange,
IsCurrentAction and GetActionTexture. The client now derives all of it from the
value published via C_Macro.SetMacroDisplay, including the drag cursor and macro
window grid that Lua could never reach, and answers range for the macro slot
itself instead of via a borrowed proxy slot. Also drops GetSlotMacroTexture,
which only GetActionTexture used.

Behavior change: a macro with a hand-picked icon now keeps that icon. The
deleted GetActionTexture substituted the active action's icon unconditionally;
the client only substitutes when the macro's own icon is the question mark.
That is the engine's rule, matching 3.3.5's macro icon getter.

Two things the integration guide expected to fall out did not, and are kept:

- GetProxyActionSlot still has six callers unrelated to macro display
  (/startattack, [stopattack], and [channeled] for Attack/Auto Shot/Shoot).
  Only three of its callers lived in the deleted range.
- Hooks.IsCurrentAction and Hooks.OriginalIsUsableAction are used outside the
  overrides they were saved for, so the aliases stay. They now simply name the
  unmodified globals. The auto-attack drift check specifically needs the real
  current-action state, which the old override could not give it.

PickupAction is untouched: it invalidates our per-slot caches on drag, which is
not display and is still needed.
2026-09-10 14:40:16 -05:00
Brues c5ddc6c313 Publish resolved macro actions to ClassicAPI's C_Macro.SetMacroDisplay
Hands ClassicAPI the action this addon resolved for each macro, so the icon,
tooltip, cooldown sweep, range and usable state come from the client instead of
from replaced action-bar globals. That also reaches the drag cursor and the macro
window grid, which Lua cannot touch, and lets the client answer range for a macro
slot directly rather than borrowing a proxy slot.

- CleveRoids.useClassicAPIDisplay feature-detects C_Macro.SetMacroDisplay rather
  than checking CLASSIC_API_VERSION, which reports a dev sentinel while the API is
  unreleased. ClassicAPIMacroDisplay tells ClassicAPI we drive macro display, which
  it otherwise stands down from when it sees this addon.
- macro.actions.macroID back-references the Blizzard index, since the update loop
  walks actions objects rather than macros. nil for SuperMacro macros, which have
  no index and so cannot be published.
- PublishDisplay sends false, not nothing, when no action matched: that claims the
  macro and shows the question mark, where silence would hand it back to
  ClassicAPI's own #showtooltip parser.
- PublishAllDisplays runs once the addon is ready and after every re-parse, since
  ClassicAPI re-evaluates nothing for us. It covers macros that aren't on a bar,
  which is what keeps the macro window grid correct. Deferred past load because
  SetMacroDisplay returns false until the player is in the world.
- ReleaseDisplays hands every macro back on DisableAddon.

The per-slot ACTIONBAR_SLOT_CHANGED fan-out is kept for the non-integrated path.
Publishing repaints every slot holding the macro through the client's own notifier,
so it is redundant when the API is present, but removing it outright would leave
buttons never repainting on a ClassicAPI build without the API.
2026-09-10 14:40:16 -05:00
9 changed files with 63 additions and 119 deletions
+6 -19
View File
@@ -3,16 +3,11 @@
ClassicAPI is a client mod (sibling to Nampower/SuperWoW) that backports the
modern C_* API into the 1.12.1 Lua environment. It is a HARD REQUIREMENT of
this addon (ClassicAPI v1.15.0+, which added frame:RegisterUnitEvent), so the
wrappers below call the API directly — no fallbacks. The load-time requirement
check (Core.lua) uses IsAvailable() to warn when the DLL is missing and
HasMinimumVersion() when it's too old; users who don't want ClassicAPI should
run the upstream addon.
The minimum is not advisory: Utility.lua calls frame:RegisterUnitEvent at file
scope, so an older ClassicAPI aborts that chunk and leaves most of the addon
undefined. Raise the Core.lua minimum in step with any new API adopted at file
scope.
this addon (ClassicAPI v1.12.1+, which added the positional
C_UnitAuras.UnitAura), so the wrappers below call the API directly — no
fallbacks. The load-time requirement check (Core.lua) uses IsAvailable() to
warn when the DLL is missing and HasMinimumVersion() when it's too old; users
who don't want ClassicAPI should run the upstream addon.
Detection: the global CLASSIC_API_VERSION is defined once the client has
booted, encoded as X*10000 + Y*100 + Z for a vX.Y.Z tag (untagged dev builds
@@ -42,14 +37,6 @@ function API.GetVersionNumber()
return CLASSIC_API_VERSION or 0
end
-- Returns the loaded version as major, minor, patch (0, 0, 0 if absent).
function API.GetVersion()
local v = CLASSIC_API_VERSION or 0
local major = math.floor(v / 10000)
local minor = math.floor(v / 100) - major * 100
return major, minor, v - math.floor(v / 100) * 100
end
-- True if the ClassicAPI client mod is loaded at all.
function API.IsAvailable()
return CLASSIC_API_VERSION ~= nil
@@ -69,7 +56,7 @@ end
-- Scan one aura range of `unit` (filter = "HELPFUL" or "HARMFUL") for an aura
-- matching the dispel type. Uses the positional C_UnitAuras.UnitAura (added in
-- ClassicAPI v1.12.1, below this addon's minimum) -- no table allocated per slot, with
-- ClassicAPI v1.12.1, this addon's minimum) -- no table allocated per slot, with
-- dispelName as the 4th return. The filtered index self-terminates at the end of
-- the range (nil name); 48 is a backstop over vanilla's 32 helpful / 16 harmful slots.
local function scanDispel(unit, filter, dispelType, wantAny)
+4 -4
View File
@@ -745,7 +745,7 @@ function Extension.OnLoad()
Extension.RegisterEvent("SPELLCAST_FAILED", "OnSpellcastFailed")
Extension.RegisterEvent("SPELLCAST_INTERRUPTED", "OnSpellcastInterrupted")
Extension.RegisterEvent("PLAYER_TARGET_CHANGED", "OnTargetChanged")
Extension.RegisterUnitEvent("UNIT_AURA", "OnUnitAura", "target", "player")
Extension.RegisterEvent("UNIT_AURA", "OnUnitAura")
Extension.RegisterEvent("PLAYER_COMBO_POINTS", "OnComboPointsChanged")
-- PERFORMANCE OPTIMIZATION: Removed OnUpdate polling for combo points
@@ -765,10 +765,10 @@ function Extension.OnTargetChanged()
CleveRoids.UpdateComboPoints()
end
-- Registered as a unit event for target and player, so arg1 is always one of
-- those two -- no token check needed.
function Extension.OnUnitAura()
CleveRoids.UpdateComboPoints()
if arg1 == "target" or arg1 == "player" then
CleveRoids.UpdateComboPoints()
end
end
function Extension.OnComboPointsChanged()
+31 -54
View File
@@ -198,12 +198,10 @@ requirementCheckFrame:SetScript("OnEvent", function()
local hasNampower30 = hasNampower and CleveRoids.NampowerAPI
and CleveRoids.NampowerAPI.HasMinimumVersion(3, 0, 0)
local hasClassicAPI = CleveRoids.ClassicAPI and CleveRoids.ClassicAPI.IsAvailable()
-- v1.15.0 added frame:RegisterUnitEvent, which Utility.lua calls at file scope.
-- On an older build that call raises, aborting the rest of the chunk -- so this
-- is not a degraded-features warning, it's "the addon did not finish loading".
local hasClassicAPI1150 = hasClassicAPI and CleveRoids.ClassicAPI.HasMinimumVersion(1, 15, 0)
-- v1.12.1 added the positional C_UnitAuras.UnitAura the dispel conditionals use.
local hasClassicAPI1121 = hasClassicAPI and CleveRoids.ClassicAPI.HasMinimumVersion(1, 12, 1)
if not hasNampower30 or not hasUnitXP or not hasClassicAPI or not hasClassicAPI1150 then
if not hasNampower30 or not hasUnitXP or not hasClassicAPI or not hasClassicAPI1121 then
-- Show warnings (don't disable — tearing down a partially-initialized addon causes hangs)
if not hasNampower then
CleveRoids.Print("|cFFFF9900WARNING:|r |cFF00FFFFAvitasia's Nampower v3.0.0+|r is required:")
@@ -223,11 +221,10 @@ requirementCheckFrame:SetScript("OnEvent", function()
CleveRoids.Print("|cFFFF9900WARNING:|r |cFF00FFFFClassicAPI|r is required:")
CleveRoids.Print("https://github.com/brues-code/ClassicAPI")
CleveRoids.Print("Dispel-type and movement conditionals will be unavailable without it.")
elseif not hasClassicAPI1150 then
local major, minor, patch = CleveRoids.ClassicAPI.GetVersion()
CleveRoids.Print(format("|cFFFF9900WARNING:|r |cFF00FFFFClassicAPI v1.15.0+|r is required (you have v%d.%d.%d):", major, minor, patch))
elseif not hasClassicAPI1121 then
CleveRoids.Print("|cFFFF9900WARNING:|r |cFF00FFFFClassicAPI v1.12.1+|r is required:")
CleveRoids.Print("https://github.com/brues-code/ClassicAPI")
CleveRoids.Print("The addon cannot finish loading on this version -- update ClassicAPI.")
CleveRoids.Print("Dispel-type conditionals will be unavailable with this older version.")
end
end
@@ -594,10 +591,6 @@ frame:SetScript("OnEvent", function()
if type(CleveRoidMacros.macrocheck) ~= "number" then
CleveRoidMacros.macrocheck = 1 -- enabled by default
end
-- The saved realtime setting is only readable now; the unit streams were
-- registered at load assuming event-driven mode.
CleveRoids.ApplyUnitStreamEvents()
end)
-- Queues a full update of all action bars.
@@ -1142,7 +1135,7 @@ local publishedDisplay = {}
-- nothing matched" and shows the question mark. Skipping the call instead would hand
-- the macro back to ClassicAPI's own #showtooltip parser.
function CleveRoids.PublishDisplay(actions)
if not CleveRoids.ClassicAPIMacroDisplay then return end
if not CleveRoids.useClassicAPIDisplay then return end
local macroID = actions and actions.macroID
if not macroID then return end -- SuperMacro macros have no Blizzard index
@@ -1157,7 +1150,7 @@ end
-- ClassicAPI re-evaluates nothing for us. Covers macros that aren't on a bar too,
-- which is what keeps the macro window grid's icons correct.
function CleveRoids.PublishAllDisplays()
if not CleveRoids.ClassicAPIMacroDisplay then return end
if not CleveRoids.useClassicAPIDisplay then return end
-- Forget what we published so every macro republishes once. Callers reach here
-- after login and after a re-parse, where a cached value could otherwise
-- suppress the publish a freshly rebuilt macro still needs.
@@ -1172,16 +1165,13 @@ end
-- Hand every macro back to ClassicAPI's own parser and stop claiming ownership.
function CleveRoids.ReleaseDisplays()
if not CleveRoids.ClassicAPIMacroDisplay then return end
-- Sweep the whole index space rather than the macros we know we published:
-- PublishAllDisplays clears that record on every re-parse, so a macro claimed
-- before one and gone after it would keep our last value forever. Releasing a
-- slot we never claimed costs nothing, and this runs once.
for i = 1, CleveRoids.MAX_MACRO_SLOTS do
if not CleveRoids.useClassicAPIDisplay then return end
for i = 1, 36 do
C_Macro.SetMacroDisplay(i, nil)
end
publishedDisplay = {}
CleveRoids.ClassicAPIMacroDisplay = false
CleveRoids.useClassicAPIDisplay = false
end
-- PERFORMANCE: Static buffer references for hot path
@@ -1227,10 +1217,17 @@ function CleveRoids.TestForAllActiveActions()
local slots = actionsToSlots[actions]
local stateChanged = CleveRoids.TestForActiveAction(actions)
if stateChanged then
-- Publishing repaints every slot holding this macro through the client's
-- own notifier, so there is no per-slot fan-out to do here. It no-ops once
-- ReleaseDisplays has handed the macros back.
CleveRoids.PublishDisplay(actions)
if CleveRoids.useClassicAPIDisplay then
-- Publishing repaints every slot holding this macro through the
-- client's own notifier, so the per-slot fan-out below is redundant.
CleveRoids.PublishDisplay(actions)
else
-- Send event to ALL slots that use this macro
local count = slots._count
for j = 1, count do
CleveRoids.SendEventForAction(slots[j], "ACTIONBAR_SLOT_CHANGED", slots[j])
end
end
end
-- Clear for reuse (reset count and clear buffer reference)
for j = 1, slots._count do
@@ -4467,36 +4464,18 @@ if type(C_LossOfControl) == "table" then
CleveRoids.Frame:RegisterEvent("LOSS_OF_CONTROL_ADDED")
CleveRoids.Frame:RegisterEvent("LOSS_OF_CONTROL_UPDATE")
end
-- The unit state streams that drive icon refresh: GUID events when Nampower
-- provides them (v2.39+, one event per unit change rather than one per token),
-- else the stock per-token events. These cannot become RegisterUnitEvent calls
-- -- the handlers ignore the unit and refresh every macro, because a conditional
-- may name any unit ([@party3,hp:<50], @focus, @mouseover), so narrowing the
-- token set would leave those icons stale.
local unitStreamEvents
-- Use GUID events when available (v2.39+), fall back to standard per-token events
if CleveRoids.NampowerAPI.features.hasUnitGuidEvents then
unitStreamEvents = { "UNIT_AURA_GUID", "UNIT_HEALTH_GUID", "UNIT_MANA_GUID", "UNIT_RAGE_GUID", "UNIT_ENERGY_GUID" }
CleveRoids.Frame:RegisterEvent("UNIT_AURA_GUID")
CleveRoids.Frame:RegisterEvent("UNIT_HEALTH_GUID")
CleveRoids.Frame:RegisterEvent("UNIT_MANA_GUID")
CleveRoids.Frame:RegisterEvent("UNIT_RAGE_GUID")
CleveRoids.Frame:RegisterEvent("UNIT_ENERGY_GUID")
else
unitStreamEvents = { "UNIT_AURA", "UNIT_HEALTH", "UNIT_POWER" }
CleveRoids.Frame:RegisterEvent("UNIT_AURA")
CleveRoids.Frame:RegisterEvent("UNIT_HEALTH")
CleveRoids.Frame:RegisterEvent("UNIT_POWER")
end
-- They do fire continuously for every unit in range, and in realtime mode their
-- handlers do nothing at all: the OnUpdate refreshes on every throttled tick and
-- QueueActionUpdate no-ops. Rather than pay a Lua dispatch per event to return
-- early, drop the registrations entirely while realtime is on. Re-applied at
-- VARIABLES_LOADED (when the saved value is first known) and whenever
-- `/cleveroid realtime` flips it.
function CleveRoids.ApplyUnitStreamEvents()
local eventDriven = not CleveRoidMacros or CleveRoidMacros.realtime == 0
for i = 1, table.getn(unitStreamEvents) do
if eventDriven then
CleveRoids.Frame:RegisterEvent(unitStreamEvents[i])
else
CleveRoids.Frame:UnregisterEvent(unitStreamEvents[i])
end
end
end
CleveRoids.ApplyUnitStreamEvents()
if CleveRoids.hasSuperwow then
CleveRoids.Frame:RegisterEvent("UNIT_CASTEVENT")
end
@@ -5829,8 +5808,6 @@ SlashCmdList["CLEVEROID"] = function(msg)
local num = tonumber(val)
if num == 0 or num == 1 then
CleveRoidMacros.realtime = num
-- The unit streams are only worth receiving in event-driven mode.
CleveRoids.ApplyUnitStreamEvents()
CleveRoids.Print("realtime set to " .. num)
else
CleveRoids.Print("Usage: /cleveroid realtime 0 or 1 - Force realtime updates rather than event based updates (Default: 0. 1 = on, increases CPU load.)")
+1 -1
View File
@@ -1180,7 +1180,7 @@ local function ReportAllMacroErrors()
-- Collect body (syntax) errors per macro. Macro names are no longer
-- restricted (slot/index-based identification), so no name checks here.
for i = 1, CleveRoids.MAX_MACRO_SLOTS do
for i = 1, 36 do
local nameOk, name = pcall(GetMacroInfo, i)
if nameOk and name and name ~= "" then
local errors = {}
-18
View File
@@ -66,10 +66,6 @@ function CleveRoids.RegisterExtension(name)
CleveRoids.RegisterEvent(name, eventName, callbackName)
end
extension.RegisterUnitEvent = function(eventName, callbackName, ...)
CleveRoids.RegisterUnitEvent(name, eventName, callbackName, unpack(arg))
end
extension.Hook = function(functionName, callbackName, dontCallOriginal)
CleveRoids.RegisterHook(name, functionName, callbackName, dontCallOriginal)
end
@@ -150,20 +146,6 @@ function CleveRoids.RegisterEvent(extensionName, eventName, callbackName)
extension.internal.frame:RegisterEvent(eventName)
end
-- Registers a callback for a UNIT_* event, filtered to the given unit tokens.
-- The callback then only runs for those units: the client drops every other
-- unit's copy, instead of all of them reaching Lua to be compared away. Use
-- this over RegisterEvent whenever the handler starts by testing arg1.
-- extensionName: The name of the extension trying to register the callback
-- eventName: The UNIT_* event to register
-- callbackName: The name of the callback that gets called when the event fires
-- ...: the unit tokens to accept (e.g. "player", "target")
function CleveRoids.RegisterUnitEvent(extensionName, eventName, callbackName, ...)
local extension = CleveRoids.Extensions[extensionName]
extension.internal.eventHandlers[eventName] = callbackName
extension.internal.frame:RegisterUnitEvent(eventName, unpack(arg))
end
-- Hooks the given function by it's name
-- extensionName: The name of the extension trying to register the callback
-- functionName: The name of the function that'll be hooked
+7 -19
View File
@@ -19,15 +19,6 @@ CleveRoids.mouseOverResolvers = {}
CleveRoids.mouseoverUnit = CleveRoids.mouseoverUnit or nil
CleveRoids.mouseOverUnit = nil
-- Every macro slot the client can hold: 18 account-wide (1-18) followed by 18
-- character-specific (19-36). This is the index space GetMacroInfo and
-- C_Macro.SetMacroDisplay address, and it is fixed -- GetNumMacros() returns how
-- many of each tab are *used*, which cannot be summed into a range, because the
-- character block starts at 19 no matter how few account macros exist. Blizzard's
-- own MAX_MACROS is no help either: it lives in the load-on-demand Blizzard_MacroUI
-- and is nil until the player opens the macro window.
CleveRoids.MAX_MACRO_SLOTS = 36
-- Environment flags
CleveRoids.hasSuperwow = SetAutoloot and true or false
CleveRoids.hasTurtle = (type(_G.TURTLE_WOW_VERSION) ~= "nil")
@@ -38,17 +29,14 @@ CleveRoids.supported = CleveRoids.hasTurtle
-- tooltip, cooldown sweep, range and usable state all come from the client -- including
-- the drag cursor and the macro window grid, which Lua cannot reach.
--
-- ClassicAPI stands down from macro display entirely when it sees this addon loaded;
-- ClassicAPIMacroDisplay is what tells it we drive it instead, and ReleaseDisplays
-- clears it to hand every macro back. A fork that leaves the flag unset keeps the old
-- behavior -- both must never drive the same buttons. It doubles as the internal
-- "may we call C_Macro.SetMacroDisplay" guard, so the two can never disagree.
--
-- Feature-detect rather than version-check: SetMacroDisplay ships in ClassicAPI
-- v1.15.0, this addon's minimum, so a nil here means the client mod is missing
-- outright -- the case Core.lua's requirement check warns about but keeps running.
CleveRoids.ClassicAPIMacroDisplay =
-- Feature-detect rather than version-check: the API is unreleased, so
-- CLASSIC_API_VERSION reports the dev sentinel. ClassicAPI stands down from macro
-- display entirely when it sees this addon loaded; ClassicAPIMacroDisplay is what
-- tells it we drive it instead. A fork that leaves the flag unset keeps the old
-- behavior -- both must never drive the same buttons.
CleveRoids.useClassicAPIDisplay =
(type(C_Macro) == "table" and C_Macro.SetMacroDisplay ~= nil) and true or false
CleveRoids.ClassicAPIMacroDisplay = CleveRoids.useClassicAPIDisplay
CleveRoids.ParsedMsg = {}
CleveRoids.ExpandedGroups = {}
+11 -1
View File
@@ -1096,7 +1096,17 @@ function CleveRoids.ValidateAllMacros()
local results = {}
local totalErrors = 0
for i = 1, CleveRoids.MAX_MACRO_SLOTS do
-- Account-wide macros are indexed from 1 up to GetNumMacros().
-- Character-specific macros occupy the slots immediately following the account-wide ones.
-- In Classic clients, the macro UI has 18 General (Account) slots and 18 Character-Specific slots.
local numAccountMacros = GetNumMacros()
-- The WoW API GetMacroInfo(index) supports indexing up to 36 (1-18 for General, 19-36 for Character)
-- in Classic clients, even though the total is GetNumMacros() + GetNumCharacterMacros() in Retail.
-- To ensure we check all 36 possible slots:
local totalSlots = 36
for i = 1, totalSlots do
local nameSuccess, name = pcall(GetMacroInfo, i)
-- Check if GetMacroInfo returned a name (i.e., the slot is used)
+1 -1
View File
@@ -10,7 +10,7 @@ Enhanced macro addon for World of Warcraft 1.12.1 (Vanilla/Turtle WoW) with dyna
|-----|:--------:|---------|
| [Nampower](https://github.com/brues-code/nampower/releases) (v3.0.0+) | ✅ | Spell queueing, DBC data, auto-attack events |
| [UnitXP_SP3](https://codeberg.org/konaka/UnitXP_SP3/releases) | ✅ | Distance checks, `[multiscan]` enemy scanning |
| [ClassicAPI](https://github.com/brues-code/ClassicAPI/releases) (v1.15.0+) | ✅ | Modern `C_*` API: dispel-type conditionals (`[magic]`, `[curse]`, …), `[moving]` speed, unit-filtered events |
| [ClassicAPI](https://github.com/brues-code/ClassicAPI/releases) | ✅ | Modern `C_*` API: dispel-type conditionals (`[magic]`, `[curse]`, …), `[moving]` speed |
## Installation
+2 -2
View File
@@ -4115,7 +4115,7 @@ end)
local ev = CreateFrame("Frame", "CleveRoidsLibDebuffFrame", UIParent)
ev:RegisterEvent("PLAYER_TARGET_CHANGED")
ev:RegisterUnitEvent("UNIT_AURA", "target")
ev:RegisterEvent("UNIT_AURA")
ev:RegisterEvent("ADDON_LOADED") -- For pfUI integration initialization
ev:RegisterEvent("ZONE_CHANGED_NEW_AREA") -- Clear known enemy GUIDs on zone change
@@ -4214,7 +4214,7 @@ ev:SetScript("OnEvent", function()
end
SeedUnit("target")
elseif event == "UNIT_AURA" then
elseif event == "UNIT_AURA" and arg1 == "target" then
SeedUnit("target")
elseif event == "UNIT_CASTEVENT" then