Make ClassicAPI a hard requirement; remove movement fallbacks

ClassicAPI is now required, not optional. The load-time requirement
check warns when it's missing (Core.lua), the same as Nampower/UnitXP,
and ClassicAPI-backed code calls the API directly with no fallbacks.

- ClassicAPI.lua: drop the HasUnitAuras/HasUnitSpeed/HasIsFalling
  capability gates and pcall hedging; wrappers call C_UnitAuras /
  GetUnitSpeed / IsFalling directly. Keep IsAvailable (used by the
  requirement check) and the version helpers.
- [moving]: ClassicAPI GetUnitSpeed is the only source. Removed the
  MonkeySpeed integration, the Nampower PlayerIsMoving branch, and the
  100 Hz position-tracking buffer in Core.lua's OnUpdate (with its
  POS_TRACK_INTERVAL constant and UnitPosition upvalue). Removed the
  now-dead speed==nil guards and WarnNoSpeedSource.
- README: ClassicAPI moved to Required; MonkeySpeed dropped from
  Supported Addons.
- docs/CLASSICAPI-TODO.md: record the mandatory/no-fallbacks policy.
This commit is contained in:
Brues
2026-06-20 23:47:23 -05:00
parent 59aad5b662
commit b28f4452b9
5 changed files with 79 additions and 204 deletions
+35 -26
View File
@@ -2,10 +2,10 @@
ClassicAPI.lua - ClassicAPI Integration Layer
ClassicAPI is a client mod (sibling to Nampower/SuperWoW) that backports the
modern C_* API into the 1.12.1 Lua environment. This module provides feature
detection and thin wrappers so every ClassicAPI-backed code path can fall
back gracefully when the DLL is absent, mirroring NampowerAPI's
HasMinimumVersion pattern.
modern C_* API into the 1.12.1 Lua environment. It is a HARD REQUIREMENT of
this addon, 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; 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
@@ -14,6 +14,7 @@
Currently used for:
- C_UnitAuras: dispel-type detection (Magic/Curse/Disease/Poison) on any unit,
powering the [magic]/[curse]/[disease]/[poison]/[dispellable] conditionals.
- GetUnitSpeed / IsFalling: the [moving] conditional.
]]
local _G = _G or getfenv(0)
@@ -51,20 +52,9 @@ end
-- C_UnitAuras
--------------------------------------------------------------------------------
-- Cached capability flag (C_UnitAuras is injected by the DLL before addons load).
local _hasUnitAuras = nil
function API.HasUnitAuras()
if _hasUnitAuras == nil then
_hasUnitAuras = (type(C_UnitAuras) == "table"
and type(C_UnitAuras.GetDebuffDataByIndex) == "function"
and type(C_UnitAuras.GetBuffDataByIndex) == "function") or false
end
return _hasUnitAuras
end
-- Module-level scan (passed to pcall with args to avoid per-call closure alloc).
-- indexFn is C_UnitAuras.GetBuffDataByIndex or GetDebuffDataByIndex.
-- Vanilla descriptors hold 32 helpful / 16 harmful slots; cap as a backstop.
-- Scan a unit's auras (indexFn = C_UnitAuras.GetBuffDataByIndex or
-- GetDebuffDataByIndex) for one matching the dispel type. Vanilla descriptors
-- hold 32 helpful / 16 harmful slots; cap as a backstop.
local function scanDispel(indexFn, unit, dispelType, wantAny)
local i = 1
while i <= 48 do
@@ -84,16 +74,35 @@ end
-- dispellable aura (any non-empty dispelName).
-- helpful: true -> scan buffs (offensive dispel / strip / purge)
-- false -> scan debuffs (defensive cleanse, default)
-- Returns false (never errors) when ClassicAPI/C_UnitAuras is unavailable or the
-- unit doesn't exist, so callers degrade gracefully.
function API.UnitHasDispelType(unit, dispelType, helpful)
if not unit then return false end
if not API.HasUnitAuras() then return false end
if not UnitExists(unit) then return false end
if not unit or not UnitExists(unit) then return false end
local indexFn = helpful and C_UnitAuras.GetBuffDataByIndex
or C_UnitAuras.GetDebuffDataByIndex
local wantAny = (dispelType == nil or dispelType == "any")
local ok, found = pcall(scanDispel, indexFn, unit, dispelType, wantAny)
return (ok and found) or false
return scanDispel(indexFn, unit, dispelType, wantAny)
end
--------------------------------------------------------------------------------
-- GetUnitSpeed
--------------------------------------------------------------------------------
-- Normal unmounted run speed in yards/second; treated as 100% on the speed
-- scale used by the [moving] conditional.
local BASE_RUN_SPEED = 7.0
-- Current speed of `unit` in yards/second (0 when stationary or the token
-- doesn't resolve).
function API.GetUnitCurrentSpeed(unit)
return GetUnitSpeed(unit or "player") or 0
end
-- Player's current speed as a percentage of normal run speed (100 = run).
function API.GetPlayerSpeedPercent()
return (API.GetUnitCurrentSpeed("player") / BASE_RUN_SPEED) * 100
end
-- True if the player is mid-jump or falling. GetUnitSpeed's currentSpeed is
-- horizontal-only, so this catches vertical movement it would report as 0.
function API.IsPlayerFalling()
return IsFalling()
end
+10 -98
View File
@@ -2978,92 +2978,23 @@ function CleveRoids.ValidateTTE(operator, amount)
end
-- ============================================================================
-- MONKEYSPEED ADDON INTEGRATION
-- MOVEMENT SPEED DETECTION
-- ============================================================================
-- Integrates with MonkeySpeed addon for accurate movement speed detection
-- MonkeySpeed uses SuperWoW's UnitPosition to calculate actual movement speed
--- Check if MonkeySpeed addon is available and loaded
--- @return boolean True if MonkeySpeed is available with speed data
function CleveRoids.HasMonkeySpeed()
return type(MonkeySpeed) == "table" and
MonkeySpeed.m_bLoaded == true and
MonkeySpeed.m_fSpeed ~= nil
end
--- Require MonkeySpeed for a feature, warn once if missing
--- @param feature string Feature name for warning message
--- @return boolean True if MonkeySpeed is available
function CleveRoids.RequireMonkeySpeed(feature)
if CleveRoids.HasMonkeySpeed() then
return true
end
if not CleveRoids._monkeySpeedErrorShown then
DEFAULT_CHAT_FRAME:AddMessage("|cffff0000[SuperCleveRoidMacros]|r The [moving] speed conditional requires the MonkeySpeed addon.", 1, 0.5, 0.5)
CleveRoids._monkeySpeedErrorShown = true
end
return false
end
-- Backed by ClassicAPI's GetUnitSpeed (a hard requirement of this addon).
--- Get current player movement speed as percentage (100 = normal run speed)
--- @return number|nil Speed percentage, or nil if MonkeySpeed not available
--- @return number|nil Speed percentage, or nil if ClassicAPI is unavailable
function CleveRoids.GetPlayerSpeed()
if not CleveRoids.HasMonkeySpeed() then
return nil
end
return MonkeySpeed.m_fSpeed or 0
return CleveRoids.ClassicAPI.GetPlayerSpeedPercent()
end
--- Check if player is currently moving (speed > 0)
--- Falls back to basic position checking if MonkeySpeed isn't available
--- Backed by ClassicAPI's GetUnitSpeed; currentSpeed is horizontal-only, so
--- jumping/falling is also treated as moving via IsFalling.
--- @return boolean True if player is moving
function CleveRoids.IsPlayerMoving()
-- If MonkeySpeed is available, use it for accurate speed-based detection
if CleveRoids.HasMonkeySpeed() then
return (MonkeySpeed.m_fSpeed or 0) > 0
end
-- Nampower 2.36+ provides direct movement flag query (catches jumping, falling, pitching)
if CleveRoids.NampowerAPI.features.hasPlayerIsMoving then
return PlayerIsMoving() == 1
end
-- Fallback: use continuously tracked position history from OnUpdate
-- Core.lua tracks position at ~100 Hz with 4-sample circular buffer (matches MonkeySpeed's 0.01s).
-- Movement is detected if ANY consecutive pair of samples shows movement.
-- This gives instant start-of-movement detection (first sample shows delta).
local history = CleveRoids._positionHistory
local count = CleveRoids._posHistoryCount or 0
local headIdx = CleveRoids._posHistoryIndex or 0
if history and count >= 2 then
-- Compare the most recent pair: headIdx (newest) vs. the slot before it.
-- Use (headIdx - 2 + 4) mod 4 to safely wrap backwards in the 1-based ring.
-- This avoids Lua 5.0 math.mod returning negative values for negative inputs.
local prevIdx = math.mod((headIdx - 2 + 4), 4) + 1
local curr = history[headIdx]
local prev = history[prevIdx]
if curr and prev then
local dx = curr.x - prev.x
local dy = curr.y - prev.y
return math.sqrt(dx * dx + dy * dy) > 0.001
end
return false
end
-- Legacy fallback for code that might not have history yet
local current = CleveRoids._currentPlayerPos
local previous = CleveRoids._previousPlayerPos
if current and previous then
local dx = current.x - previous.x
local dy = current.y - previous.y
local dist = math.sqrt(dx * dx + dy * dy)
return dist > 0.001
end
-- Not enough data yet (first 0.15s after login) - assume not moving
return false
local cur = CleveRoids.ClassicAPI.GetUnitCurrentSpeed("player") or 0
return cur > 0 or CleveRoids.ClassicAPI.IsPlayerFalling()
end
--- Validates a movement speed conditional.
@@ -3074,15 +3005,7 @@ end
function CleveRoids.ValidateMovingSpeed(operator, amount)
if not operator or not amount then return false end
-- Check if MonkeySpeed is available for speed comparisons
if not CleveRoids.RequireMonkeySpeed("moving speed comparison") then
return false
end
local speed = CleveRoids.GetPlayerSpeed()
if speed == nil then
return false
end
-- Compare speed against threshold
if CleveRoids.operators[operator] then
@@ -8998,12 +8921,11 @@ CleveRoids.Keywords = {
end,
-- =========================================================================
-- MOVEMENT SPEED CONDITIONALS (MonkeySpeed integration)
-- MOVEMENT SPEED CONDITIONALS (ClassicAPI GetUnitSpeed)
-- =========================================================================
-- [moving] - Player is moving (speed > 0)
-- [moving] - Player is moving (speed > 0, or jumping/falling)
-- [moving:>100] - Player speed is above 100% (faster than normal run)
-- [moving:<50] - Player speed is below 50% (slower than half speed)
-- Requires MonkeySpeed addon for speed comparisons (basic moving check uses fallback)
moving = function(conditionals)
-- Boolean form [moving] - just check if moving at all
if conditionals.moving == true then
@@ -9017,10 +8939,6 @@ CleveRoids.Keywords = {
-- Handle multi-comparison (e.g., >50&<150)
if args.comparisons and type(args.comparisons) == "table" then
local speed = CleveRoids.GetPlayerSpeed()
if speed == nil then
CleveRoids.RequireMonkeySpeed("moving speed comparison")
return false
end
-- ALL comparisons must pass (AND logic)
for _, comp in ipairs(args.comparisons) do
@@ -9052,10 +8970,6 @@ CleveRoids.Keywords = {
if args.comparisons and type(args.comparisons) == "table" then
local speed = CleveRoids.GetPlayerSpeed()
if speed == nil then
-- If MonkeySpeed not available, negated returns true (fail-safe)
return true
end
for _, comp in ipairs(args.comparisons) do
if not CleveRoids.operators[comp.operator] then
@@ -9233,7 +9147,6 @@ CleveRoids.Keywords = {
if type(args) ~= "table" then return false end
if args.comparisons and type(args.comparisons) == "table" then
local speed = CleveRoids.GetPlayerSpeed()
if speed == nil then return false end
for _, comp in ipairs(args.comparisons) do
if not CleveRoids.operators[comp.operator] then return false end
if not CleveRoids.comparators[comp.operator](speed, comp.amount) then return false end
@@ -9251,7 +9164,6 @@ CleveRoids.Keywords = {
if type(args) ~= "table" then return false end
if args.comparisons and type(args.comparisons) == "table" then
local speed = CleveRoids.GetPlayerSpeed()
if speed == nil then return true end
for _, comp in ipairs(args.comparisons) do
if not CleveRoids.operators[comp.operator] then return true end
if not CleveRoids.comparators[comp.operator](speed, comp.amount) then return true end
+8 -72
View File
@@ -179,8 +179,9 @@ requirementCheckFrame:SetScript("OnEvent", function()
local hasUnitXP = pcall(UnitXP, "nop", "nop")
local hasNampower30 = hasNampower and CleveRoids.NampowerAPI
and CleveRoids.NampowerAPI.HasMinimumVersion(3, 0, 0)
local hasClassicAPI = CleveRoids.ClassicAPI and CleveRoids.ClassicAPI.IsAvailable()
if not hasNampower30 or not hasUnitXP then
if not hasNampower30 or not hasUnitXP or not hasClassicAPI 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:")
@@ -196,6 +197,11 @@ requirementCheckFrame:SetScript("OnEvent", function()
CleveRoids.Print("|cFFFF9900WARNING:|r |cFF00FFFFKonaka's UnitXP_SP3|r is required:")
CleveRoids.Print("https://codeberg.org/konaka/UnitXP_SP3")
end
if not hasClassicAPI then
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.")
end
end
-- Requirements met - allow normal initialization
@@ -1654,20 +1660,7 @@ end
-- Returns the name of the focus target or nil
function CleveRoids.GetFocusName()
-- 1. Add specific compatibility for pfUI.
-- pfUI stores its focus unit information in a table.
if pfUI and pfUI.uf and pfUI.uf.focus and pfUI.uf.focus.unitname then
return pfUI.uf.focus.unitname
end
-- Fallback for other focus addons
if ClassicFocus_CurrentFocus then
return ClassicFocus_CurrentFocus
elseif CURR_FOCUS_TARGET then
return CURR_FOCUS_TARGET
end
return nil
return UnitName('focus')
end
-- Attempts to target the focus target.
@@ -4047,13 +4040,11 @@ local GetTime = GetTime
local UnitAffectingCombat = UnitAffectingCombat
local pairs = pairs
local next = next
local UnitPosition = UnitPosition
local IsAltKeyDown = IsAltKeyDown
local IsShiftKeyDown = IsShiftKeyDown
local IsControlKeyDown = IsControlKeyDown
-- PERFORMANCE: Module-level constants (avoid re-creation per frame)
local POS_TRACK_INTERVAL = 0.01 -- 10ms between position samples (100 Hz, matches MonkeySpeed)
local SEQUENCE_STALE_TIME = 300 -- 5 minutes
function CleveRoids.OnUpdate(self)
@@ -4118,61 +4109,6 @@ function CleveRoids.OnUpdate(self)
return
end
-- =========================================================================
-- CONTINUOUS POSITION TRACKING (for [moving] fallback without MonkeySpeed)
-- =========================================================================
-- Track player position at ~100 Hz with 4-sample circular buffer for snappy movement detection.
-- Matches MonkeySpeed's 0.01s detection interval for similar responsiveness.
-- PERFORMANCE: Pre-allocated circular buffer eliminates ~100 table allocs/sec and table.remove shifting.
local lastPosTime = CR._positionTrackTime or 0
if (time - lastPosTime) >= POS_TRACK_INTERVAL then
CR._positionTrackTime = time
if UnitPosition then
local x, y = UnitPosition("player")
if x and y then
-- Initialize circular buffer if needed (pre-allocate 4 entry tables)
local history = CR._positionHistory
if not history then
history = {
{ x = 0, y = 0, time = 0 },
{ x = 0, y = 0, time = 0 },
{ x = 0, y = 0, time = 0 },
{ x = 0, y = 0, time = 0 },
}
CR._positionHistory = history
CR._posHistoryIndex = 0
CR._posHistoryCount = 0
end
-- Advance circular index (1-based, wraps at 4)
local idx = CR._posHistoryIndex + 1
if idx > 4 then idx = 1 end
CR._posHistoryIndex = idx
-- Reuse existing entry (zero allocation)
local entry = history[idx]
entry.x = x
entry.y = y
entry.time = time
-- Track how many samples we have (caps at 4)
if CR._posHistoryCount < 4 then
CR._posHistoryCount = CR._posHistoryCount + 1
end
-- Maintain legacy vars for any code that uses them directly
if CR._posHistoryCount >= 2 then
local prevIdx = idx - 1
if prevIdx < 1 then prevIdx = 4 end
CR._previousPlayerPos = history[prevIdx]
CR._currentPlayerPos = entry
end
end
end
end
-- PERFORMANCE: Delayed WDB warmup after login (ensures GetItemInfo works after WDB clear)
if CR.wdbWarmupTime and time >= CR.wdbWarmupTime then
CR.wdbWarmupTime = nil
+2 -2
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) | Optional | Dispel-type conditionals (`[magic]`, `[curse]`, `[disease]`, `[poison]`, `[dispellable]`, `[magicbuff]`) |
| [ClassicAPI](https://github.com/brues-code/ClassicAPI/releases) | ✅ | Modern `C_*` API: dispel-type conditionals (`[magic]`, `[curse]`, …), `[moving]` speed |
## Installation
@@ -67,7 +67,7 @@ See the **[Wiki](https://github.com/brues-code/SuperCleveRoidMacros/wiki)** for
**Action Bars:** Blizzard, [pfUI](https://github.com/brues-code/pfUI), Bongos, Discord Action Bars
**Integrations:** [SP_SwingTimer](https://github.com/jrc13245/SP_SwingTimer), [TWThreat](https://github.com/MarcelineVQ/TWThreat), [TimeToKill](https://github.com/jrc13245/TimeToKill), [QuickHeal](https://github.com/jrc13245/QuickHeal), [Cursive](https://github.com/pepopo978/Cursive), [ClassicFocus](https://github.com/wtfcolt/Addons-for-Vanilla-1.12.1-CFM/tree/master/ClassicFocus), [SuperMacro](https://github.com/jrc13245/SuperMacro-turtle-SuperWoW), [MonkeySpeed](https://github.com/jrc13245/MonkeySpeed)
**Integrations:** [SP_SwingTimer](https://github.com/jrc13245/SP_SwingTimer), [TWThreat](https://github.com/MarcelineVQ/TWThreat), [TimeToKill](https://github.com/jrc13245/TimeToKill), [QuickHeal](https://github.com/jrc13245/QuickHeal), [Cursive](https://github.com/pepopo978/Cursive), [ClassicFocus](https://github.com/wtfcolt/Addons-for-Vanilla-1.12.1-CFM/tree/master/ClassicFocus), [SuperMacro](https://github.com/jrc13245/SuperMacro-turtle-SuperWoW)
> **Note:** For pfUI users, the [brues-code/pfUI fork](https://github.com/brues-code/pfUI) includes native SuperCleveRoidMacros integration for proper cooldown, icon, and tooltip display on conditional macros.
+24 -6
View File
@@ -1,11 +1,16 @@
# ClassicAPI Adoption Backlog
Opportunities to use [ClassicAPI](https://github.com/) (`C:\Git\ClassicAPI`, see
`docs/API.md`) in SuperCleveRoidMacros. ClassicAPI is a client mod (sibling to
nampower/SuperWoW) that backports the modern `C_*` API into the 1.12.1 client.
We already gate nampower behind version detection — every ClassicAPI path below
must follow the same pattern: **feature-detect, then fall back to the current
implementation** so users without ClassicAPI are unaffected.
Opportunities to use [ClassicAPI](https://github.com/brues-code/ClassicAPI)
(`C:\Git\ClassicAPI`, see `docs/API.md`) in SuperCleveRoidMacros. ClassicAPI is
a client mod (sibling to nampower/SuperWoW) that backports the modern `C_*` API
into the 1.12.1 client.
**Policy: ClassicAPI is a hard requirement of this fork — no fallbacks.** The
load-time requirement check warns when it's missing (`Core.lua`), the same as
nampower/UnitXP. ClassicAPI-backed code may assume the API is present (light
`pcall`/nil guards for runtime safety are fine, but do not maintain alternate
non-ClassicAPI implementations). Users who don't want ClassicAPI should run the
upstream addon instead.
API references are line numbers into `C:\Git\ClassicAPI\docs\API.md`.
@@ -60,6 +65,19 @@ API references are line numbers into `C:\Git\ClassicAPI\docs\API.md`.
- **Replaces today:** MonkeySpeed addon dependency (which needs SuperWoW
`UnitPosition`) for `[moving]` — `Conditionals.lua:2981`.
- **DONE:** `ClassicAPI.lua` exposes `HasUnitSpeed` / `GetUnitCurrentSpeed` /
`GetPlayerSpeedPercent` (100% = 7.0 yd/s) plus `IsPlayerFalling` (`IsFalling`).
`GetPlayerSpeed` and `IsPlayerMoving` are ClassicAPI-only now; the boolean
`[moving]` ORs in `IsPlayerFalling` so jumping/falling registers even though
`currentSpeed` is horizontal-only.
- **Fallbacks removed (ClassicAPI mandatory):** deleted the MonkeySpeed
integration (`HasMonkeySpeed`, `RequireMonkeySpeed`), the Nampower
`PlayerIsMoving` branch, and the 100 Hz position-tracking buffer in Core.lua's
OnUpdate (`_positionHistory`, `POS_TRACK_INTERVAL`, `UnitPosition` upvalue).
MonkeySpeed dropped from README "Supported Addons".
- **`IsSwimming` deliberately not used:** swim state is true while treading
water motionless, which would falsely report `[moving]`.
---
## Tier 2 — replace custom scanning / extra deps