From 6edd37a76f8b7491b8165a0364a60849260be0cb Mon Sep 17 00:00:00 2001 From: Brues <5278969+brues-code@users.noreply.github.com> Date: Fri, 11 Sep 2026 13:44:09 -0500 Subject: [PATCH] Require ClassicAPI v1.15.0 An out-of-date ClassicAPI did not degrade the addon, it decapitated it. Utility.lua calls ev:RegisterUnitEvent at file scope (v1.15.0), so on an older build that call raises and Lua abandons the rest of the chunk -- every definition below line 4118 of a 10040-line file, some 6000 lines, silently never happens. What the user sees is the first one a common path reaches: PLAYER_TARGET_CHANGED calling a nil ClearResistState, once per target swap, forever. The requirement check still said v1.12.1 (the positional C_UnitAuras.UnitAura), so the affected client sailed through it and the warning it did print promised only that "dispel-type conditionals will be unavailable" -- describing a graceful degradation that was not happening. The gate is now v1.15.0 and says the addon cannot finish loading, with the installed version in the message the way the Nampower branch does it. ClassicAPI.lua gains GetVersion() to decode CLASSIC_API_VERSION, keeping the X*10000 + Y*100 + Z encoding inside that module, and its header notes that the minimum has to rise with any ClassicAPI call adopted at file scope -- that is the property that turns a version mismatch into a silent half-load rather than a missing feature. C_Macro.SetMacroDisplay also ships in v1.15.0, which retires the last pre-ClassicAPI display path: useClassicAPIDisplay folds into ClassicAPIMacroDisplay. The two were always equal -- set together at load, cleared together in ReleaseDisplays -- and one of them is a handshake ClassicAPI reads, so a second name for the same state could only ever drift. TestForAllActiveActions loses the per-slot ACTIONBAR_SLOT_CHANGED fan-out. Publishing repaints every slot holding the macro through the client's own notifier; the fan-out was what ran when SetMacroDisplay was unavailable, and its one remaining reachable state was after DisableAddon, where ReleaseDisplays has already handed all 36 macros back and ClassicAPI repaints them itself. The load-time feature detect stays, with a new reason: below the minimum the addon is already broken, so a nil SetMacroDisplay now means ClassicAPI is absent outright -- the case the requirement check warns about and then keeps running. --- ClassicAPI.lua | 25 +++++++++++++++++++------ Core.lua | 37 ++++++++++++++++--------------------- Init.lua | 17 ++++++++++------- README.md | 2 +- 4 files changed, 46 insertions(+), 35 deletions(-) diff --git a/ClassicAPI.lua b/ClassicAPI.lua index b0897b6..a2cf514 100644 --- a/ClassicAPI.lua +++ b/ClassicAPI.lua @@ -3,11 +3,16 @@ 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.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. + 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. 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 @@ -37,6 +42,14 @@ 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 @@ -56,7 +69,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, this addon's minimum) -- no table allocated per slot, with +-- ClassicAPI v1.12.1, below 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) diff --git a/Core.lua b/Core.lua index 91de01d..27251b6 100644 --- a/Core.lua +++ b/Core.lua @@ -198,10 +198,12 @@ 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.12.1 added the positional C_UnitAuras.UnitAura the dispel conditionals use. - local hasClassicAPI1121 = hasClassicAPI and CleveRoids.ClassicAPI.HasMinimumVersion(1, 12, 1) + -- 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) - if not hasNampower30 or not hasUnitXP or not hasClassicAPI or not hasClassicAPI1121 then + if not hasNampower30 or not hasUnitXP or not hasClassicAPI or not hasClassicAPI1150 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:") @@ -221,10 +223,11 @@ 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 hasClassicAPI1121 then - CleveRoids.Print("|cFFFF9900WARNING:|r |cFF00FFFFClassicAPI v1.12.1+|r is required:") + 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)) CleveRoids.Print("https://github.com/brues-code/ClassicAPI") - CleveRoids.Print("Dispel-type conditionals will be unavailable with this older version.") + CleveRoids.Print("The addon cannot finish loading on this version -- update ClassicAPI.") end end @@ -1139,7 +1142,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.useClassicAPIDisplay then return end + if not CleveRoids.ClassicAPIMacroDisplay then return end local macroID = actions and actions.macroID if not macroID then return end -- SuperMacro macros have no Blizzard index @@ -1154,7 +1157,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.useClassicAPIDisplay then return end + if not CleveRoids.ClassicAPIMacroDisplay 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. @@ -1169,13 +1172,12 @@ end -- Hand every macro back to ClassicAPI's own parser and stop claiming ownership. function CleveRoids.ReleaseDisplays() - if not CleveRoids.useClassicAPIDisplay then return end + if not CleveRoids.ClassicAPIMacroDisplay 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 @@ -1221,17 +1223,10 @@ function CleveRoids.TestForAllActiveActions() local slots = actionsToSlots[actions] local stateChanged = CleveRoids.TestForActiveAction(actions) if stateChanged then - 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 + -- 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) end -- Clear for reuse (reset count and clear buffer reference) for j = 1, slots._count do diff --git a/Init.lua b/Init.lua index e716c27..72e250e 100644 --- a/Init.lua +++ b/Init.lua @@ -29,14 +29,17 @@ 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. -- --- 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 = +-- 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 = (type(C_Macro) == "table" and C_Macro.SetMacroDisplay ~= nil) and true or false -CleveRoids.ClassicAPIMacroDisplay = CleveRoids.useClassicAPIDisplay CleveRoids.ParsedMsg = {} CleveRoids.ExpandedGroups = {} diff --git a/README.md b/README.md index dbd54b6..686f836 100644 --- a/README.md +++ b/README.md @@ -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) | ✅ | Modern `C_*` API: dispel-type conditionals (`[magic]`, `[curse]`, …), `[moving]` speed | +| [ClassicAPI](https://github.com/brues-code/ClassicAPI/releases) (v1.15.0+) | ✅ | Modern `C_*` API: dispel-type conditionals (`[magic]`, `[curse]`, …), `[moving]` speed, unit-filtered events | ## Installation