add gcd conditional and firstaction slashcommand and fix stopmacro

This commit is contained in:
Jrc13245
2026-01-15 16:16:49 -05:00
parent 649586c552
commit 612873260d
5 changed files with 341 additions and 6 deletions
+1
View File
@@ -20,6 +20,7 @@ do
local INTERCEPT = {
cast=true, castsequence=true, use=true,
startattack=true, stopattack=true, stopcasting=true, stopmacro=true,
skipmacro=true, firstaction=true, nofirstaction=true,
target=true, retarget=true, cancelaura=true, unbuff=true,
unqueue=true, unshift=true, equip=true, equipmh=true, equipoh=true,
-- runmacro intentionally NOT included - see note above
+87
View File
@@ -4449,6 +4449,93 @@ CleveRoids.Keywords = {
end, conditionals, "nocdgcd")
end,
-- GCD conditional - check if GCD is active/remaining
-- Usage: [gcd] - true if GCD is active
-- [gcd:<1] - true if GCD has less than 1 second remaining
-- [nogcd] - true if GCD is not active
gcd = function(conditionals)
return Multi(conditionals.gcd, function(args)
-- Get GCD remaining in seconds
local gcdRemaining = 0
-- Try Nampower API first (most accurate)
if CleveRoids.NampowerAPI and CleveRoids.NampowerAPI.GetGCDRemainingMs then
local gcdMs = CleveRoids.NampowerAPI.GetGCDRemainingMs()
if gcdMs and gcdMs > 0 then
gcdRemaining = gcdMs / 1000
end
else
-- Fallback: check the first spell in spellbook for GCD
-- GCD shows as cooldown <= 1.5s on any GCD-triggering spell
for i = 1, 200 do
local spellName = GetSpellName(i, BOOKTYPE_SPELL)
if not spellName then break end
local start, duration = GetSpellCooldown(i, BOOKTYPE_SPELL)
if start and duration and duration > 0 and duration <= 1.5 then
gcdRemaining = (start + duration) - GetTime()
if gcdRemaining < 0 then gcdRemaining = 0 end
break
end
end
end
-- No args or just [gcd] - check if GCD is active
if not args or args == "" or (type(args) == "table" and not args.operator) then
return gcdRemaining > 0
end
-- With operator: [gcd:<1] etc
if type(args) == "table" and args.operator and args.amount then
if CleveRoids.operators[args.operator] then
return CleveRoids.comparators[args.operator](gcdRemaining, args.amount)
end
end
return gcdRemaining > 0
end, conditionals, "gcd")
end,
nogcd = function(conditionals)
return NegatedMulti(conditionals.nogcd, function(args)
-- Get GCD remaining in seconds
local gcdRemaining = 0
-- Try Nampower API first (most accurate)
if CleveRoids.NampowerAPI and CleveRoids.NampowerAPI.GetGCDRemainingMs then
local gcdMs = CleveRoids.NampowerAPI.GetGCDRemainingMs()
if gcdMs and gcdMs > 0 then
gcdRemaining = gcdMs / 1000
end
else
-- Fallback: check the first spell in spellbook for GCD
for i = 1, 200 do
local spellName = GetSpellName(i, BOOKTYPE_SPELL)
if not spellName then break end
local start, duration = GetSpellCooldown(i, BOOKTYPE_SPELL)
if start and duration and duration > 0 and duration <= 1.5 then
gcdRemaining = (start + duration) - GetTime()
if gcdRemaining < 0 then gcdRemaining = 0 end
break
end
end
end
-- [nogcd] - true if GCD is NOT active
if not args or args == "" or (type(args) == "table" and not args.operator) then
return gcdRemaining <= 0
end
-- With operator: [nogcd:<1] means NOT (gcd < 1), i.e., gcd >= 1
if type(args) == "table" and args.operator and args.amount then
if CleveRoids.operators[args.operator] then
return not CleveRoids.comparators[args.operator](gcdRemaining, args.amount)
end
end
return gcdRemaining <= 0
end, conditionals, "nogcd")
end,
channeled = function(conditionals)
-- Use time-based prediction for accuracy
if CleveRoids.CurrentSpell.type == "channeled" and CleveRoids.channelStartTime and CleveRoids.channelDuration then
+24
View File
@@ -329,6 +329,30 @@ SlashCmdList.SKIPMACRO = function(msg)
CleveRoids.DoSkipMacro(msg)
end
-- Enable "first action only" mode - stop evaluation after first successful /cast or /use
-- Example:
-- /firstaction
-- /cast [myrawpower:>48] Shred
-- /cast [myrawpower:>40] Claw
-- Result: Only Shred casts if energy >= 48, Claw won't be queued
SLASH_FIRSTACTION1 = "/firstaction"
SlashCmdList.FIRSTACTION = function(msg)
CleveRoids.DoFirstAction(msg)
end
-- Re-enable multi-queue behavior after /firstaction
-- Use this to restore normal evaluation where multiple casts can queue
-- Example:
-- /firstaction
-- /cast [myrawpower:>48] Shred -- Priority section
-- /cast [myrawpower:>40] Claw
-- /nofirstaction
-- /cast Tiger's Fury -- Can queue alongside above
SLASH_NOFIRSTACTION1 = "/nofirstaction"
SlashCmdList.NOFIRSTACTION = function(msg)
CleveRoids.DoNoFirstAction(msg)
end
-- QuickHeal with conditionals support (requires QuickHeal addon)
-- Usage: /quickheal [conditionals] [target] [type]
-- Examples:
+190 -6
View File
@@ -1291,8 +1291,51 @@ function CleveRoids.ExecuteMacroBody(body,inline)
if CleveRoids.macroRefDebug then
CleveRoids.Print("|cff88ff88[MacroRef]|r Executing line " .. k .. ": " .. string.sub(v, 1, 60))
end
ChatFrameEditBox:SetText(v)
ChatEdit_SendText(ChatFrameEditBox)
-- IMPORTANT: Handle macro control commands directly to bypass Blizzard's built-in /stopmacro
-- Blizzard intercepts bare /stopmacro before it reaches our SlashCmdList handler
local trimmed = CleveRoids.Trim(v)
local cmdHandled = false
-- Check for /stopmacro (with or without conditionals)
local _, _, stopmacroArgs = string.find(trimmed, "^/stopmacro%s*(.*)")
if stopmacroArgs then
CleveRoids.DoStopMacro(stopmacroArgs)
cmdHandled = true
end
-- Check for /skipmacro (with or without conditionals)
if not cmdHandled then
local _, _, skipmacroArgs = string.find(trimmed, "^/skipmacro%s*(.*)")
if skipmacroArgs then
CleveRoids.DoSkipMacro(skipmacroArgs)
cmdHandled = true
end
end
-- Check for /firstaction (with or without conditionals)
if not cmdHandled then
local _, _, firstactionArgs = string.find(trimmed, "^/firstaction%s*(.*)")
if firstactionArgs then
CleveRoids.DoFirstAction(firstactionArgs)
cmdHandled = true
end
end
-- Check for /nofirstaction (with or without conditionals)
if not cmdHandled then
local _, _, nofirstactionArgs = string.find(trimmed, "^/nofirstaction%s*(.*)")
if nofirstactionArgs then
CleveRoids.DoNoFirstAction(nofirstactionArgs)
cmdHandled = true
end
end
-- For all other commands, use ChatEdit_SendText
if not cmdHandled then
ChatFrameEditBox:SetText(v)
ChatEdit_SendText(ChatFrameEditBox)
end
end
-- Clear skipMacroFlag after this macro completes (don't propagate to parent)
@@ -2093,9 +2136,9 @@ function CleveRoids.TestAction(cmd, args)
end
function CleveRoids.DoWithConditionals(msg, hook, fixEmptyTargetFunc, targetBeforeAction, action)
-- Check macro stop flags (skip non-STOPMACRO/SKIPMACRO commands when flag is set)
-- This enables /stopmacro and /skipmacro to work without SuperMacro for vanilla macros
if (CleveRoids.stopMacroFlag or CleveRoids.skipMacroFlag) and action ~= "STOPMACRO" and action ~= "SKIPMACRO" then
-- Check macro stop flags (skip non-control commands when flag is set)
-- This enables /stopmacro, /skipmacro, /firstaction, /nofirstaction to work without SuperMacro for vanilla macros
if (CleveRoids.stopMacroFlag or CleveRoids.skipMacroFlag) and action ~= "STOPMACRO" and action ~= "SKIPMACRO" and action ~= "FIRSTACTION" and action ~= "NOFIRSTACTION" then
return false
end
@@ -2119,13 +2162,19 @@ function CleveRoids.DoWithConditionals(msg, hook, fixEmptyTargetFunc, targetBefo
end
end
-- Handle STOPMACRO/SKIPMACRO without conditionals (bare command)
-- Handle STOPMACRO/SKIPMACRO/FIRSTACTION/NOFIRSTACTION without conditionals (bare command)
if action == "STOPMACRO" then
CleveRoids.stopMacroFlag = true
return true
elseif action == "SKIPMACRO" then
CleveRoids.skipMacroFlag = true
return true
elseif action == "FIRSTACTION" then
CleveRoids.stopOnCastFlag = true
return true
elseif action == "NOFIRSTACTION" then
CleveRoids.stopOnCastFlag = false
return true
end
if hook then
@@ -2251,6 +2300,14 @@ function CleveRoids.DoWithConditionals(msg, hook, fixEmptyTargetFunc, targetBefo
-- Set flag to skip remaining lines in current submacro only
CleveRoids.skipMacroFlag = true
return true
elseif action == "FIRSTACTION" then
-- Set flag to stop on first successful cast/use
CleveRoids.stopOnCastFlag = true
return true
elseif action == "NOFIRSTACTION" then
-- Clear flag to re-enable multi-queue behavior
CleveRoids.stopOnCastFlag = false
return true
end
local result = true
@@ -2323,6 +2380,10 @@ function CleveRoids.DoCast(msg)
for i = 1, table.getn(parts) do
local v = parts[i]
if CleveRoids.DoWithConditionals(v, CleveRoids.Hooks.CAST_SlashCmd, CleveRoids.FixEmptyTarget, not CleveRoids.hasSuperwow, CastSpellByName) then
-- If /firstaction was used, stop macro evaluation after first successful cast
if CleveRoids.stopOnCastFlag then
CleveRoids.stopMacroFlag = true
end
return true
end
end
@@ -2844,6 +2905,10 @@ function CleveRoids.DoUse(msg)
local parts = CleveRoids.splitStringIgnoringQuotes(msg)
for i = 1, table.getn(parts) do
if CleveRoids.DoWithConditionals(parts[i], action, CleveRoids.FixEmptyTarget, false, action) then
-- If /firstaction was used, stop macro evaluation after first successful use
if CleveRoids.stopOnCastFlag then
CleveRoids.stopMacroFlag = true
end
return true
end
end
@@ -3256,6 +3321,59 @@ function CleveRoids.DoSkipMacro(msg)
return false
end
-- Enables "stop on first successful cast" mode for the rest of the macro
-- When set, successful /cast or /use commands will set stopMacroFlag automatically
-- This allows priority-based macro evaluation: first matching cast wins
-- Example usage:
-- /firstaction
-- /cast [myrawpower:>48] Shred
-- /cast [myrawpower:>40] Claw
-- In this case, if Shred casts, Claw will NOT be queued
function CleveRoids.DoFirstAction(msg)
-- Check if there are conditionals
if msg and CleveRoids.Trim(msg) ~= "" then
-- Has conditionals - use DoWithConditionals to evaluate them
local parts = CleveRoids.splitStringIgnoringQuotes(CleveRoids.Trim(msg))
for i = 1, table.getn(parts) do
if CleveRoids.DoWithConditionals(msg, nil, nil, not CleveRoids.hasSuperwow, "FIRSTACTION") then
return true
end
end
return false
else
-- No conditionals - just set the flag
CleveRoids.stopOnCastFlag = true
return true
end
end
-- Disables "stop on first successful cast" mode, re-enabling multi-queue behavior
-- Use this to restore normal evaluation after /firstaction
-- Example usage:
-- /firstaction
-- /cast [myrawpower:>48] Shred -- Priority section
-- /cast [myrawpower:>40] Claw
-- /nofirstaction
-- /cast Tiger's Fury -- Can queue alongside above
-- /startattack
function CleveRoids.DoNoFirstAction(msg)
-- Check if there are conditionals
if msg and CleveRoids.Trim(msg) ~= "" then
-- Has conditionals - use DoWithConditionals to evaluate them
local parts = CleveRoids.splitStringIgnoringQuotes(CleveRoids.Trim(msg))
for i = 1, table.getn(parts) do
if CleveRoids.DoWithConditionals(msg, nil, nil, not CleveRoids.hasSuperwow, "NOFIRSTACTION") then
return true
end
end
return false
else
-- No conditionals - just clear the flag
CleveRoids.stopOnCastFlag = false
return true
end
end
function CleveRoids.DoCastSequence(sequence)
if not CleveRoids.hasSuperwow then
CleveRoids.Print("|cFFFF0000/castsequence|r requires |cFF00FFFFSuperWoW|r.")
@@ -3372,6 +3490,9 @@ function CleveRoids.OnUpdate(self)
if CleveRoids.skipMacroFlag then
CleveRoids.skipMacroFlag = false
end
if CleveRoids.stopOnCastFlag then
CleveRoids.stopOnCastFlag = false
end
-- PERFORMANCE: Single GetTime() call per frame
local time = GetTime()
@@ -4040,6 +4161,69 @@ function IsConsumableAction(slot)
return CleveRoids.Hooks.IsConsumableAction(slot)
end
-- ============================================================================
-- RunMacro Hook - Use our own macro execution system
-- ============================================================================
-- This allows /stopmacro, /skipmacro, /firstaction to work across parent/child
-- macro boundaries without requiring SuperMacro addon.
--
-- When SuperMacro is installed, its RunLine hook takes precedence for extended
-- macro features. This hook handles vanilla Blizzard macros.
-- ============================================================================
if not CleveRoids.RunMacroHooked then
CleveRoids.Hooks.RunMacro = RunMacro
function RunMacro(indexOrName)
-- Skip if SuperMacro is handling macro execution (it has its own hooks)
if CleveRoids.SM_RunLineHooked then
return CleveRoids.Hooks.RunMacro(indexOrName)
end
-- Resolve macro index
local macroIndex
if type(indexOrName) == "number" then
macroIndex = indexOrName
elseif type(indexOrName) == "string" then
-- Could be a name or a numeric string
local num = tonumber(indexOrName)
if num then
macroIndex = num
else
macroIndex = GetMacroIndexByName(indexOrName)
end
end
if not macroIndex or macroIndex == 0 then
-- Fallback to original if we can't resolve
return CleveRoids.Hooks.RunMacro(indexOrName)
end
-- Get macro body
local name, icon, body = GetMacroInfo(macroIndex)
if not body or body == "" then
return CleveRoids.Hooks.RunMacro(indexOrName)
end
-- Clear macro flags at the start of top-level macro execution
CleveRoids.stopMacroFlag = false
CleveRoids.skipMacroFlag = false
-- Note: stopOnCastFlag is intentionally NOT cleared here - it persists
-- within a frame and is cleared by OnUpdate
if CleveRoids.macroRefDebug then
CleveRoids.Print("|cff00ff00[RunMacro Hook]|r Executing macro '" .. (name or macroIndex) .. "' via CleveRoids")
end
-- Execute through our system
CleveRoids.ExecuteMacroBody(body)
-- Don't call original - we've handled it
return
end
CleveRoids.RunMacroHooked = true
end
-- Create a hidden tooltip frame to read buff names
if not AuraScanTooltip and not CleveRoids.hasSuperwow then
CreateFrame("GameTooltip", "AuraScanTooltip")
+39
View File
@@ -100,6 +100,7 @@ All conditionals support negation with `no` prefix (e.g., `[nocombat]`, `[nobuff
|-------------|---------|-------------|
| `cooldown` | `[cooldown:"Spell"<5]` | CD remaining (ignores GCD) |
| `cdgcd` | `[cdgcd:"Spell">0]` | CD remaining (includes GCD) |
| `gcd` | `[gcd]` `[gcd:<1]` | GCD is active / remaining time |
| `usable` | `[usable:"Spell"]` | Spell/item is usable |
| `reactive` | `[reactive:Overpower]` | Reactive ability available |
| `known` | `[known:"Spell">#2]` | Spell/talent known (with rank) |
@@ -322,6 +323,8 @@ These commands accept `[conditionals]` and use UnitXP 3D enemy scanning when app
| `/quickheal [cond]` | ✅ | — | Smart heal (requires QuickHeal) |
| `/stopmacro [cond]` | ✅ | — | Stop ALL macro execution (including parent macros) |
| `/skipmacro [cond]` | ✅ | — | Stop current submacro only, parent continues |
| `/firstaction [cond]` | ✅ | — | Stop on first successful `/cast` or `/use` (priority mode) |
| `/nofirstaction [cond]` | ✅ | — | Re-enable multi-queue after `/firstaction` |
| `/petattack [cond]` | ✅ | — | Pet attack with conditionals |
| `/petfollow [cond]` | ✅ | — | Pet follow with conditionals |
| `/petwait [cond]` | ✅ | — | Pet stay with conditionals |
@@ -336,6 +339,42 @@ These commands accept `[conditionals]` and use UnitXP 3D enemy scanning when app
| `/applymain [cond] Item` | ✅ | — | Apply poison/oil to main hand |
| `/applyoff [cond] Item` | ✅ | — | Apply poison/oil to off hand |
#### Priority-Based Macro Evaluation (`/firstaction` & `/nofirstaction`)
By default, all `/cast` lines in a macro are evaluated and may queue spells with Nampower. Use `/firstaction` to enable "first successful cast wins" behavior - once a `/cast` or `/use` succeeds, the macro stops evaluating subsequent lines. Use `/nofirstaction` to re-enable normal multi-queue behavior.
```lua
-- WITHOUT /firstaction: Both Shred AND Claw may queue if conditions are met
/cast [myrawpower:>48] Shred
/cast [myrawpower:>40] Claw
-- WITH /firstaction: Only Shred casts if energy >= 48, Claw is skipped
/firstaction
/cast [myrawpower:>48] Shred
/cast [myrawpower:>40] Claw
-- Mixed mode: priority section + always-queue section
/firstaction
/cast [myrawpower:>48] Shred -- Priority: only one of these
/cast [myrawpower:>40] Claw
/nofirstaction
/cast Tiger's Fury -- Always evaluates (can queue alongside)
/startattack
-- With conditionals
/firstaction [group] -- Priority mode only in groups
/cast [myrawpower:>48] Shred
/cast [myrawpower:>40] Claw
```
**Child Macro Behavior:** `/firstaction` and `/nofirstaction` in child macros (called via `{MacroName}`) affect subsequent lines in the parent macro. Think of the child's lines as being "inlined" at the call site.
**Comparison:**
- `/stopmacro [cond]` - Stop if condition is true (regardless of cast success)
- `/skipmacro [cond]` - Stop current submacro only
- `/firstaction [cond]` - Stop on first **successful** cast/use (priority mode)
- `/nofirstaction [cond]` - Re-enable multi-queue behavior after `/firstaction`
### Commands without Conditional Support
| Command | Description |