diff --git a/ClassicAPI.lua b/ClassicAPI.lua index 56dd8ff..6d37ef4 100644 --- a/ClassicAPI.lua +++ b/ClassicAPI.lua @@ -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 diff --git a/Conditionals.lua b/Conditionals.lua index 5b8adda..7db476d 100644 --- a/Conditionals.lua +++ b/Conditionals.lua @@ -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 diff --git a/Core.lua b/Core.lua index 5368fb1..155d7a0 100644 --- a/Core.lua +++ b/Core.lua @@ -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 diff --git a/README.md b/README.md index 506c3ac..88ca57b 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) | 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. diff --git a/docs/CLASSICAPI-TODO.md b/docs/CLASSICAPI-TODO.md index a5de1d3..974afbe 100644 --- a/docs/CLASSICAPI-TODO.md +++ b/docs/CLASSICAPI-TODO.md @@ -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