mirror of
https://github.com/brues-code/SuperCleveRoidMacros.git
synced 2026-09-16 03:38:00 +00:00
add gcd conditional and firstaction slashcommand and fix stopmacro
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
@@ -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:
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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 |
|
||||
|
||||
Reference in New Issue
Block a user