From 9f8a5553bcab1dd1665b48404d31aed345f78421 Mon Sep 17 00:00:00 2001 From: liiora <206033431+liiora@users.noreply.github.com> Date: Thu, 10 Sep 2026 21:39:16 +0200 Subject: [PATCH] feat: add /stopchanneling command (#5) * feat: add /stopchannel command * review: give /stopchanneling a real handler and warn when nampower can't honor it --------- Co-authored-by: Brues <5278969+brues-code@users.noreply.github.com> --- Console.lua | 24 +++++++++++++++++++++ Core.lua | 49 +++++++++++++++++++++++++++++++++++++++++++ MacroErrorChecker.lua | 1 + 3 files changed, 74 insertions(+) diff --git a/Console.lua b/Console.lua index 47c6777..133a23f 100644 --- a/Console.lua +++ b/Console.lua @@ -130,6 +130,12 @@ local StopAttack = function(msg) CleveRoids.DeferStopAttack() end +local StopChanneling = function(msg) + -- No Blizzard equivalent: 1.12 has SpellStopCasting (immediate) and nothing + -- that waits for the next channel tick, so this is nampower-only. + CleveRoids.StopChanneling() +end + -- Register slash commands and assign original handlers. -- These will be hooked immediately after. SLASH_STARTATTACK1 = "/startattack" @@ -141,6 +147,9 @@ SlashCmdList.STOPATTACK = StopAttack SLASH_STOPCASTING1 = "/stopcasting" SlashCmdList.STOPCASTING = SpellStopCasting +SLASH_STOPCHANNELING1 = "/stopchanneling" +SlashCmdList.STOPCHANNELING = StopChanneling + SLASH_CLEARTARGET1 = "/cleartarget" SlashCmdList.CLEARTARGET = ClearTarget @@ -202,6 +211,21 @@ SlashCmdList.STOPCASTING = function(msg) end end +-- /stopchanneling hook +CleveRoids.Hooks.STOPCHANNELING_SlashCmd = SlashCmdList.STOPCHANNELING +SlashCmdList.STOPCHANNELING = function(msg) + if CleveRoids.stopMacroFlag then return end + msg = msg or "" + if string.find(msg, "%[") then + -- If conditionals are present, let the function handle it. + -- It will only stop the channel if the conditions are met. + CleveRoids.DoConditionalStopChanneling(msg) + else + -- If no conditionals, run the original command. + CleveRoids.Hooks.STOPCHANNELING_SlashCmd(msg) + end +end + -- /unqueue hook CleveRoids.Hooks.UNQUEUE_SlashCmd = SlashCmdList.UNQUEUE SlashCmdList.UNQUEUE = function(msg) diff --git a/Core.lua b/Core.lua index 95710ed..4930659 100644 --- a/Core.lua +++ b/Core.lua @@ -3005,6 +3005,37 @@ function CleveRoids.DoConditionalStartAttack(msg) return false end +-- Stop channeling on the next tick (nampower v2.18+), behind /stopchanneling. +-- The underlying API no-ops in two cases and a command that does nothing +-- without saying why reads as broken, so each gets a one-shot warning -- the +-- same treatment the [rooted] conditional gives a too-old nampower. +local _stopChannelingWarned = {} +local function warnStopChanneling(key, reason) + if _stopChannelingWarned[key] then return end + _stopChannelingWarned[key] = true + DEFAULT_CHAT_FRAME:AddMessage("|cffff0000[SuperCleveRoidMacros]|r /stopchanneling " + .. reason .. ".", 1, 0.5, 0.5) +end + +function CleveRoids.StopChanneling() + local API = CleveRoids.NampowerAPI + + -- Nothing was sent: nampower is absent or predates ChannelStopCastingNextTick. + if not API.StopChannelNextTick() then + warnStopChanneling("version", "requires Nampower v2.18.0 or newer") + return false + end + + -- The call went through, but nampower only acts on it while channel + -- queueing is on, so the channel would run to completion regardless. + if not API.IsQueueingEnabled("channeling") then + warnStopChanneling("setting", "requires Nampower's NP_QueueChannelingSpells setting to be enabled") + return false + end + + return true +end + -- PERFORMANCE: Module-level actions to avoid closure allocation per call local function _stopAttackAction() CleveRoids.DeferStopAttack() @@ -3014,6 +3045,10 @@ local function _stopCastingAction() SpellStopCasting() end +local function _stopChannelingAction() + CleveRoids.StopChanneling() +end + -- Attempts to conditionally stop an attack. Returns false if no conditionals are found. function CleveRoids.DoConditionalStopAttack(msg) if not string.find(msg, "%[") then return false end @@ -3042,6 +3077,20 @@ function CleveRoids.DoConditionalStopCasting(msg) return false end +-- Attempts to conditionally interrupt channeling. Returns false if no conditionals are found. +function CleveRoids.DoConditionalStopChanneling(msg) + if not string.find(msg, "%[") then return false end + + -- PERFORMANCE: Use numeric iteration to avoid pairs() iterator allocation + local parts = CleveRoids.splitStringIgnoringQuotes(msg) + for i = 1, table.getn(parts) do + if CleveRoids.DoWithConditionals(parts[i], nil, CleveRoids.FixEmptyTarget, false, _stopChannelingAction) then + return true + end + end + return false +end + local function _clearTargetAction() ClearTarget() end diff --git a/MacroErrorChecker.lua b/MacroErrorChecker.lua index 350a75c..c91c953 100644 --- a/MacroErrorChecker.lua +++ b/MacroErrorChecker.lua @@ -506,6 +506,7 @@ local COMMANDS_NO_ACTION_NEEDED = { ["/startattack"] = true, ["/stopattack"] = true, ["/stopcasting"] = true, + ["/stopchanneling"] = true, ["/unqueue"] = true, ["/retarget"] = true, ["/stopmacro"] = true,