From 2da28c2653da8a1841763b2e305031ada15e1ce7 Mon Sep 17 00:00:00 2001 From: Jrc13245 Date: Thu, 28 Aug 2025 16:19:51 -0400 Subject: [PATCH] increase supermacro support --- Compatibility/SuperMacro.lua | 186 +++++++++----- Core.lua | 455 +++++++++++++++++++++-------------- 2 files changed, 412 insertions(+), 229 deletions(-) diff --git a/Compatibility/SuperMacro.lua b/Compatibility/SuperMacro.lua index 2cd1c01..e3734ef 100644 --- a/Compatibility/SuperMacro.lua +++ b/Compatibility/SuperMacro.lua @@ -1,75 +1,153 @@ --- CleveRoidMacros/Compatibility/SuperMacro.lua --- Make SuperMacro’s RunLine play nicely with CleveRoidMacros (incl. /castsequence) +--[[ + SuperMacro compatibility shim for CleveRoidMacros (1.12.1-safe) + - Installs only when BOTH SuperMacro and SuperCleveRoidMacros/CleveRoidMacros are loaded + - Hooks SuperMacro's RunLine order-agnostically (ADDON_LOADED + PLAYER_LOGIN) + - Intercepts CRM-owned slashcommands (including {…}, […], !, ?, ~) + - Forwards to CRM's SlashCmdList handlers; preserves /castsequence + - Leaves SuperMacro unmodified; safe fallback to original RunLine +]] -local _G = _G or getfenv(0) -local CRM = _G.CleveRoids or {} -_G.CleveRoids = CRM +do + local _G = _G or getfenv(0) + local CRM = _G.CleveRoids or {} + _G.CleveRoids = CRM + CRM.Hooks = CRM.Hooks or {} -local function install_supermacro_hook() - if CRM.SM_RunLineHooked then return end - if type(_G.RunLine) ~= "function" then return end - - -- Commands we only intercept when they use bracket/extended syntax - local bracketHooks = { - cast = CRM.DoCast, - target = CRM.DoTarget, - use = CRM.DoUse, + -- Commands owned by CRM (keep in sync with Console.lua registrations) + local INTERCEPT = { + cast=true, castsequence=true, use=true, + startattack=true, stopattack=true, stopcasting=true, + target=true, retarget=true, cancelaura=true, unbuff=true, + unqueue=true, unshift=true, equip=true, equipmh=true, equipoh=true, + runmacro=true, + -- pet + petattack=true, petfollow=true, petpassive=true, + petaggressive=true, petdefensive=true, petwait=true, } - CRM.Hooks = CRM.Hooks or {} - CRM.Hooks.RunLine = CRM.Hooks.RunLine or _G.RunLine + -- alias map (if CRM only registers /cancelaura, but /unbuff appears) + local ALIASES = { unbuff = "cancelaura" } - _G.RunLine = function(...) - for i = 1, arg.n do - local text = arg[i] + local function normalize_cmd(cmd) + if not cmd or cmd == "" then return nil end + local _, _, s = string.find(cmd, "^(.*)$") + s = s and string.lower(s) or nil + if s and ALIASES[s] then s = ALIASES[s] end + return s + end + + local function has_handler(cmd) + local list = _G.SlashCmdList + return cmd and list and type(list[string.upper(cmd)]) == "function" + end + + local function parse_slash(text) + if type(text) ~= "string" then return nil, nil end + text = (string.gsub(text, "^[%s]+", "")) + if not string.find(text, "^/") then return nil, nil end + local _, _, cmd, rest = string.find(text, "^/(%S+)%s*(.*)$") + return cmd, rest + end + + local function install_supermacro_hook() + if CRM.SM_RunLineHooked then return end + if type(_G.RunLine) ~= "function" then return end + + local orig_RunLine = _G.RunLine + CRM.Hooks.RunLine = CRM.Hooks.RunLine or orig_RunLine + + _G.RunLine = function(...) + -- SuperMacro calls RunLine(line) one line at a time; handle first arg. + local text = arg and arg[1] + + -- respect /stopmacro guard if CRM.stopmacro then CRM.stopmacro = false return true end - local handled = false if type(text) == "string" then - -- 1) Always handle /castsequence (with or without conditionals) - local b1, e1, rest = string.find(text, "^%s*/castsequence%s*(.*)") - if b1 then - local msg = CRM.Trim(rest or "") - local seq = CRM.GetSequence(msg) - if seq then pcall(CRM.DoCastSequence, seq) end - handled = true + -- 1) special-case /castsequence → call CRM directly and RETURN TRUE + local b, e, rest = string.find(text, "^%s*/castsequence%s*(.*)") + if b then + if type(CleveRoids) == "table" and type(CleveRoids.DoCastSequence) == "function" then + pcall(CleveRoids.DoCastSequence, rest or "") + return true + end + -- fallback: slash handler if defined + local fn = _G.SlashCmdList and _G.SlashCmdList["CASTSEQUENCE"] + if type(fn) == "function" then + pcall(fn, rest or "") + return true + end + -- nothing to do; pass through to SM else - -- 2) Bracketed/advanced forms for /cast, /target, /use - for cmd, action in bracketHooks do - local b2, e2 = string.find(text, "^%s*/" .. cmd .. "%s+[!%[]") - if b2 then - local msg = string.sub(text, e2) - msg = string.gsub(msg, "^%s+", "") - pcall(action, msg) - handled = true - break + -- 2) generic /cmd forwarding (lets CRM handlers parse []) + local _, _, raw, msg = string.find(text, "^/(%S+)%s*(.*)$") + if raw then + local cmd = string.lower(raw) + -- alias map: /unbuff → /cancelaura + if cmd == "unbuff" then cmd = "cancelaura" end + if INTERCEPT[cmd] and has_handler(cmd) then + pcall(_G.SlashCmdList[string.upper(cmd)], msg or "") + return true -- IMPORTANT: tell SM we handled it + end + end + + -- 3) fast-path for extended/bracket tokens (/cast {…} [/…] ! ? ~) + local hooks = { cast = CRM.DoCast, target = CRM.DoTarget, use = CRM.DoUse } + for k, fn in pairs(hooks) do + if type(fn) == "function" then + local b2, e2 = string.find(text, "^%s*/" .. k .. "%s+[!%[%{%?~]") + if b2 then + local msg2 = string.gsub(string.sub(text, e2 + 1), "^%s+", "") + pcall(fn, msg2) + return true -- IMPORTANT + end end end end end - if not handled then - CRM.Hooks.RunLine(text) -- fall back to SuperMacro’s original logic - end + -- Not handled by CRM: let SuperMacro do its normal processing + return orig_RunLine(text) + end + + CRM.SM_RunLineHooked = true + end + + -- ===== Order-agnostic loader: install only when BOTH addons are present ===== + local SM_LOADED, CRM_LOADED = false, false + local f = CreateFrame("Frame") + f:RegisterEvent("ADDON_LOADED") + f:RegisterEvent("PLAYER_LOGIN") + + local function note_loaded(name) + if name == "SuperMacro" then + SM_LOADED = true + elseif name == "SuperCleveRoidMacros" or name == "CleveRoidMacros" then + CRM_LOADED = true end end - CRM.SM_RunLineHooked = true + local function both_loaded() + if SM_LOADED and CRM_LOADED then return true end + -- heuristic fallback in case this file loads late: + local sm_ok = type(_G.RunLine) == "function" + local crm_ok = _G.CleveRoids and _G.SlashCmdList and type(_G.SlashCmdList.CAST) == "function" + return sm_ok and crm_ok + end + + local function try_install() + if both_loaded() then install_supermacro_hook() end + end + + f:SetScript("OnEvent", function(_, evt, addon) + if evt == "ADDON_LOADED" then + if type(addon) == "string" then note_loaded(addon) end + try_install() + elseif evt == "PLAYER_LOGIN" then + try_install() + end + end) end - --- Install after either addon is ready (order-agnostic) -local f = CreateFrame("Frame") -f:RegisterEvent("ADDON_LOADED") -f:RegisterEvent("PLAYER_LOGIN") -f:SetScript("OnEvent", function(_, event, addon) - if event == "ADDON_LOADED" then - if addon == "SuperMacro" or addon == "CleveRoidMacros" then - install_supermacro_hook() - end - else - install_supermacro_hook() - end -end) diff --git a/Core.lua b/Core.lua index fdd4a25..5597444 100644 --- a/Core.lua +++ b/Core.lua @@ -447,17 +447,41 @@ function CleveRoids.GetMacroBody(name) return macro and macro.body end --- Attempts to execute a macro by the given name --- name: The name of the macro --- returns: Whether the macro was executed or not +-- Attempts to execute a macro by the given name (Blizzard or Super tab) +-- Returns: true if something was executed, false otherwise function CleveRoids.ExecuteMacroByName(name) - local body = CleveRoids.GetMacroBody(name) - if not body then - return false + if not name or name == "" then return false end + + local body + + -- 1) Blizzard macro runner by name/ID + local id = GetMacroIndexByName(name) + if id and id ~= 0 and type(RunMacro) == "function" then + if pcall(RunMacro, name) then return true end + if pcall(RunMacro, id) then return true end + local _n, _tex, b2 = GetMacroInfo(id) + if b2 and b2 ~= "" then body = body or b2 end end - CleveRoids.ExecuteMacroBody(body) - return true + -- 2) SuperMacro runner by name + if type(GetSuperMacroInfo) == "function" and type(RunSuperMacro) == "function" then + local _n2, _t2, b3 = GetSuperMacroInfo(name) + if b3 and b3 ~= "" then + if pcall(RunSuperMacro, name) then return true end + body = body or b3 + end + end + + -- 3) CRM cache fallback + if not body and type(CleveRoids.GetMacro) == "function" then + local m = CleveRoids.GetMacro(name) + if m and m.body and m.body ~= "" then + body = m.body + end + end + + if not body or body == "" then return false end + return CleveRoids.ExecuteMacroBody(body) end function CleveRoids.SetHelp(conditionals) @@ -607,44 +631,54 @@ function CleveRoids.SplitCommandAndArgs(text) end function CleveRoids.ParseSequence(text) + if not text or text == "" then return end + + -- normalize commas local args = string.gsub(text, "(%s*,%s*)", ",") - local _, c, cond = string.find(args, "(%[.*%])") - local _, r, reset, resetVal = string.find(args, "(%s*%]*%s*reset=([%w/]+)%s+)") - actionSeq = CleveRoids.Trim((r and string.sub(args, r+1)) or (c and string.sub(args, c+1)) or args) - args = (cond or "") .. actionSeq + -- optional [conditionals] block + local _, condEnd, condBlock = string.find(args, "(%[.*%])") - if not actionSeq then - return + -- accept reset= anywhere; no trailing space required; strip it out once + local _, _, resetVal = string.find(args, "[Rr][Ee][Ss][Ee][Tt]=([%w/]+)") + if resetVal then + args = string.gsub(args, "%s*[Rr][Ee][Ss][Ee][Tt]=[%w/]+%s*", " ", 1) end + -- actions are what's left after any ] (if present) + local actionSeq = CleveRoids.Trim((condEnd and string.sub(args, condEnd + 1)) or args) + args = (condBlock or "") .. actionSeq + if actionSeq == "" then return end + local sequence = { - index = 1, - reset = {}, - status = 0, - list = {}, + index = 1, + reset = {}, + status = 0, lastUpdate = 0, - cond = cond, - args = args, - cmd = "/castsequence" + args = args, + list = {}, } + + -- fill reset rules: seconds or flags (target/combat/alt/ctrl/shift) if resetVal then - for _, rule in ipairs(CleveRoids.Split(resetVal, "/")) do + for _, rule in pairs(CleveRoids.Split(resetVal, "/")) do + rule = string.lower(CleveRoids.Trim(rule)) local secs = tonumber(rule) if secs and secs > 0 then sequence.reset.secs = secs else - sequence.reset[string.lower(rule)] = true + sequence.reset[rule] = true end end end + -- build steps for _, a in ipairs(CleveRoids.Split(actionSeq, ",")) do local sa = CleveRoids.CreateActionInfo(CleveRoids.GetParsedMsg(a)) table.insert(sequence.list, sa) end - CleveRoids.Sequences[text] = sequence + CleveRoids.Sequences[text] = sequence return sequence end @@ -652,22 +686,25 @@ function CleveRoids.ParseMacro(name) if not name then return end local macroID = GetMacroIndexByName(name) - if not macroID then return end - local _, texture, body = GetMacroInfo(macroID) + -- Try Blizzard macro first (macroID may be nil/0 if not found on 1.12) + local _, texture, body + if macroID and macroID ~= 0 then + _, texture, body = GetMacroInfo(macroID) + end - if not body and GetSuperMacroInfo then + -- Fallback: SuperMacro “Super” tab + if (not body) and GetSuperMacroInfo then _, texture, body = GetSuperMacroInfo(name) end if not texture or not body then return end - local macro = { - id = macroID, - name = name, + id = macroID, + name = name, texture = texture, - body = body, + body = body, actions = {}, } macro.actions.list = {} @@ -689,21 +726,20 @@ function CleveRoids.ParseMacro(name) -- #showtooltip and item/spell/macro specified, only use this tooltip if st and tt ~= "" then - for _, arg in ipairs(CleveRoids.splitStringIgnoringQuotes(tt)) do - macro.actions.tooltip = CleveRoids.CreateActionInfo(arg) - local action = CleveRoids.CreateActionInfo(CleveRoids.GetParsedMsg(arg)) - action.cmd = "/cast" - action.args = arg - action.isReactive = CleveRoids.reactiveSpells[action.action] - table.insert(macro.actions.list, action) - end + for _, arg in ipairs(CleveRoids.splitStringIgnoringQuotes(tt)) do + macro.actions.tooltip = CleveRoids.CreateActionInfo(arg) + local action = CleveRoids.CreateActionInfo(CleveRoids.GetParsedMsg(arg)) + action.cmd = "/cast" + action.args = arg + action.isReactive = CleveRoids.reactiveSpells[action.action] + table.insert(macro.actions.list, action) + end break end else -- make sure we have a testable action if line ~= "" and args ~= "" and CleveRoids.dynamicCmds[cmd] then for _, arg in ipairs(CleveRoids.splitStringIgnoringQuotes(args)) do - local action = CleveRoids.CreateActionInfo(CleveRoids.GetParsedMsg(arg)) if cmd == "/castsequence" then @@ -729,16 +765,38 @@ function CleveRoids.ParseMsg(msg) if not msg then return end local conditionals = {} - msg, conditionals.ignoretooltip = string.gsub(CleveRoids.Trim(msg), "^%?", "") + -- reset side flag for this parse + CleveRoids._ignoretooltip = 0 + + -- strip optional leading '?' and remember how many we stripped + local ignorecount + msg, ignorecount = string.gsub(CleveRoids.Trim(msg), "^%?", "") + conditionals.ignoretooltip = ignorecount + CleveRoids._ignoretooltip = ignorecount + + -- capture a single [...] conditional block if present local _, cbEnd, conditionBlock = string.find(msg, "%[(.+)%]") - local _, _, noSpam, cancelAura, action = string.find(string.sub(msg, (cbEnd or 0) + 1), "^%s*(!?)(~?)([^!~]+.*)") + + -- split off flags/action after the condition block (or from start if none) + local _, _, noSpam, cancelAura, action = string.find( + string.sub(msg, (cbEnd or 0) + 1), + "^%s*(!?)(~?)([^!~]+.*)" + ) action = CleveRoids.Trim(action or "") - -- Store the action along with the conditionals incase it's needed + -- store the raw action for callers and strip trailing "(Rank X)" for comparisons conditionals.action = action - -- FLEXIBLY remove rank text like "(Rank 9)" to get the base spell name action = string.gsub(action, "%s*%(.-%)%s*$", "") + -- IMPORTANT: if there's NO conditional block, return nil conditionals so + -- DoWithConditionals will hit the {macroName} execution branch. + if not conditionBlock then + return conditionals.action, nil + end + + -- With a condition block present, build out the conditionals table + + -- optional spam/cancel flags (apply only when we actually have a [] block) if noSpam and noSpam ~= "" then local spamCond = CleveRoids.GetSpammableConditional(action) if spamCond then @@ -749,18 +807,14 @@ function CleveRoids.ParseMsg(msg) conditionals.cancelaura = action end - if not conditionBlock then - return conditionals.action, conditionals - end - - -- Set the action's target to @unitid if found + -- Set the action's target to @unitid if found (e.g., @mouseover) local _, _, target = string.find(conditionBlock, "(@[^%s,]+)") if target then conditionBlock = CleveRoids.Trim(string.gsub(conditionBlock, target, "")) conditionals.target = string.sub(target, 2) end - if conditionBlock and action then + if conditionBlock and conditionals.action then -- Split the conditional block by comma or space for _, conditionGroups in CleveRoids.splitStringIgnoringQuotes(conditionBlock, {",", " "}) do if conditionGroups ~= "" then @@ -768,25 +822,25 @@ function CleveRoids.ParseMsg(msg) local conditionGroup = CleveRoids.splitStringIgnoringQuotes(conditionGroups, ":") local condition, args = conditionGroup[1], conditionGroup[2] - -- No args, the action is the implicit argument + -- No args → the action is the implicit argument if not args or args == "" then if not conditionals[condition] then - conditionals[condition] = action + conditionals[condition] = conditionals.action else if type(conditionals[condition]) ~= "table" then - conditionals[condition] = { conditionals[condition] } + conditionals[condition] = { conditionals[condition] } end - table.insert(conditionals[condition], action) + table.insert(conditionals[condition], conditionals.action) end else - -- Has args. Ensure the key's value is a table and add the new arguments. + -- Has args. Ensure the key's value is a table and add new arguments. if not conditionals[condition] then conditionals[condition] = {} elseif type(conditionals[condition]) ~= "table" then conditionals[condition] = { conditionals[condition] } end - -- Split the args by / for multiple values + -- Split args by '/' for multiple values for _, arg_item in CleveRoids.splitString(args, "/") do local processed_arg = CleveRoids.Trim(arg_item) @@ -794,24 +848,24 @@ function CleveRoids.ParseMsg(msg) processed_arg = string.gsub(processed_arg, "_", " ") processed_arg = CleveRoids.Trim(processed_arg) + -- normalize "name#N" → "name=#N" and "#N" → "=#N" local arg_for_find = processed_arg arg_for_find = string.gsub(arg_for_find, "^#(%d+)$", "=#%1") arg_for_find = string.gsub(arg_for_find, "([^>~=<]+)#(%d+)", "%1=#%2") - -- FIXED: This regex now accepts decimal numbers + -- accept decimals too; capture name/op/amount local _, _, name, operator, amount = string.find(arg_for_find, "([^>~=<]*)([>~=<]+)(#?%d*%.?%d+)") if not operator or not amount then table.insert(conditionals[condition], processed_arg) else local name_to_use = (name and name ~= "") and name or conditionals.action - local final_amount_str, num_replacements = string.gsub(amount, "#", "") local should_check_stacks = (num_replacements == 1) table.insert(conditionals[condition], { - name = CleveRoids.Trim(name_to_use), - operator = operator, - amount = tonumber(final_amount_str), + name = CleveRoids.Trim(name_to_use), + operator = operator, + amount = tonumber(final_amount_str), checkStacks = should_check_stacks }) end @@ -823,20 +877,32 @@ function CleveRoids.ParseMsg(msg) end end + -- Get previously parsed or parse, store and return function CleveRoids.GetParsedMsg(msg) if not msg then return end - if CleveRoids.ParsedMsg[msg] then - return CleveRoids.ParsedMsg[msg].action, CleveRoids.ParsedMsg[msg].conditionals + -- ALWAYS refresh the side-flag for '?' even when we hit the cache + local _, ignorecount = string.gsub(CleveRoids.Trim(msg), "^%?", "") + CleveRoids._ignoretooltip = ignorecount + + local cached = CleveRoids.ParsedMsg[msg] + if cached then + -- keep a per-msg copy too (helps future readers/tools) + cached.ignoretooltip = cached.ignoretooltip or ignorecount + return cached.action, cached.conditionals end - CleveRoids.ParsedMsg[msg] = {} - CleveRoids.ParsedMsg[msg].action, CleveRoids.ParsedMsg[msg].conditionals = CleveRoids.ParseMsg(msg) - - return CleveRoids.ParsedMsg[msg].action, CleveRoids.ParsedMsg[msg].conditionals + local action, conditionals = CleveRoids.ParseMsg(msg) + CleveRoids.ParsedMsg[msg] = { + action = action, + conditionals = conditionals, + ignoretooltip = ignorecount, + } + return action, conditionals end + function CleveRoids.GetMacro(name) return CleveRoids.Macros[name] or CleveRoids.ParseMacro(name) end @@ -864,47 +930,48 @@ end function CleveRoids.TestAction(cmd, args) local msg, conditionals = CleveRoids.GetParsedMsg(args) - if string.find(msg, "#showtooltip") or conditionals.ignoretooltip == 1 then + -- Nil-safe guards + local hasShowTooltip = (type(msg) == "string") and string.find(msg, "#showtooltip") + local ignoreTooltip = ((type(conditionals) == "table") and (conditionals.ignoretooltip == 1)) + or (CleveRoids._ignoretooltip == 1) + + -- If the line is explicitly ignored for tooltip (via '?'), + -- or it is a '#showtooltip' token, do not contribute icon/tooltip. + if hasShowTooltip or ignoreTooltip then + CleveRoids._ignoretooltip = 0 -- clear for next parse return end + -- No [] block → return a testable token so the UI can pick a texture if not conditionals then - if not msg then + if not msg or msg == "" then return else - -- action is a {macro} or item/spell return CleveRoids.GetMacroNameFromAction(msg) or msg end end local origTarget = conditionals.target if cmd == "" or not CleveRoids.dynamicCmds[cmd] then - -- untestables return end - + if conditionals.target == "focus" then local focusUnitId = nil - -- First, try to get the specific UnitID from pfUI's data for reliability. - if pfUI and pfUI.uf and pfUI.uf.focus and pfUI.uf.focus.label and pfUI.uf.focus.id and UnitExists(pfUI.uf.focus.label .. pfUI.uf.focus.id) then + if pfUI and pfUI.uf and pfUI.uf.focus and pfUI.uf.focus.label and pfUI.uf.focus.id + and UnitExists(pfUI.uf.focus.label .. pfUI.uf.focus.id) then focusUnitId = pfUI.uf.focus.label .. pfUI.uf.focus.id end - if focusUnitId then - -- If we found a specific unit (e.g., "party1", "raid2"), use it for the test. conditionals.target = focusUnitId else - -- If we only have a name, check if that name exists. If not, the condition fails. if not CleveRoids.GetFocusName() then - return -- No focus exists, so this action is not valid. + return end - -- Fallback for testing: We can't safely target by name here, so we default to "target". - -- This part of the original logic remains as a fallback, but the pfUI check above will handle most cases. conditionals.target = "target" end end - if conditionals.target == "mouseover" then if not CleveRoids.IsValidTarget("mouseover", conditionals.help) then return false @@ -912,19 +979,16 @@ function CleveRoids.TestAction(cmd, args) end CleveRoids.FixEmptyTarget(conditionals) - -- CleveRoids.SetHelp(conditionals) for k, v in pairs(conditionals) do if not CleveRoids.ignoreKeywords[k] then if not CleveRoids.Keywords[k] or not CleveRoids.Keywords[k](conditionals) then - -- failed test conditionals.target = origTarget return end end end - -- tests passed conditionals.target = origTarget return CleveRoids.GetMacroNameFromAction(msg) or msg end @@ -1087,7 +1151,7 @@ end function CleveRoids.DoTarget(msg) local action, conditionals = CleveRoids.GetParsedMsg(msg) - if action ~= "" or not next(conditionals) then + if action ~= "" or type(conditionals) ~= "table" or not next(conditionals) then CleveRoids.Hooks.TARGET_SlashCmd(msg) return true end @@ -1450,55 +1514,67 @@ end return handled end +-- Keep THIS version function CleveRoids.DoCastSequence(sequence) - if not CleveRoids.hasSuperwow then - CleveRoids.Print("|cFFFF0000/castsequence|r requires |cFF00FFFFSuperWoW|r.") - return + if not CleveRoids.hasSuperwow then + CleveRoids.Print("|cFFFF0000/castsequence|r requires |cFF00FFFFSuperWoW|r.") + return + end + if type(sequence) == "string" then + sequence = CleveRoids.GetSequence(sequence) + if not sequence then return end + end + + if CleveRoids.currentSequence and not CleveRoids.CheckSpellCast("player") then + CleveRoids.currentSequence = nil + elseif CleveRoids.currentSequence then + return + end + + if sequence.index > 1 and sequence.reset then + for k,_ in pairs(sequence.reset) do + if CleveRoids.kmods[k] and CleveRoids.kmods[k]() then + CleveRoids.ResetSequence(sequence) + break + end end + end - if CleveRoids.currentSequence and not CleveRoids.CheckSpellCast("player") then - CleveRoids.currentSequence = nil - elseif CleveRoids.currentSequence then - return + local active = CleveRoids.GetCurrentSequenceAction(sequence) + if not (active and active.action) then return end + + sequence.status = 0 + sequence.lastUpdate = GetTime() + sequence.expires = 0 + + local prevSeq = CleveRoids.currentSequence + CleveRoids.currentSequence = sequence + + local actionText = (sequence.cond or "") .. active.action + local resolvedText, conds = CleveRoids.GetParsedMsg(actionText) + + local function cast_by_name(msg) + msg = msg or "" + if not string.find(msg, "%(%s*.-%s*%)%s*$") then + local sp = CleveRoids.GetSpell(msg) + local r = (sp and sp.rank) or (sp and sp.highest and sp.highest.rank) + if r and r ~= "" then msg = msg .. "(" .. r .. ")" end end + CastSpellByName(msg) + return true + end - if sequence.index > 1 then - if sequence.reset then - for k, _ in sequence.reset do - if CleveRoids.kmods[k] and CleveRoids.kmods[k]() then - CleveRoids.ResetSequence(sequence) - end - end - end - end + local attempted = false + if not conds then + attempted = cast_by_name(resolvedText or active.action) + else + local final = CleveRoids.DoWithConditionals(actionText, nil, CleveRoids.FixEmptyTarget, false, CastSpellByName) + if final then attempted = cast_by_name(final) end + end - local active = CleveRoids.GetCurrentSequenceAction(sequence) - if active and active.action then - sequence.status = 0 - sequence.lastUpdate = GetTime() - sequence.expires = 0 - - -- Tentatively claim the active sequence, but only keep it if a cast actually fires. - local prevSeq = CleveRoids.currentSequence - CleveRoids.currentSequence = sequence - - local action = (sequence.cond or "") .. active.action - - -- Ensure we still cast if there are no [] conditionals - local result = CleveRoids.DoWithConditionals( - action, - function(msg) CastSpellByName(msg) end, -- fallback cast when no [] - CleveRoids.FixEmptyTarget, -- optional: preserves your targeting behavior - not CleveRoids.hasSuperwow, - CastSpellByName - ) - - -- If the conditionals failed and no cast was started, don't steal currentSequence - if not result then - CleveRoids.currentSequence = prevSeq - end - return result - end + if not attempted then + CleveRoids.currentSequence = prevSeq + end end CleveRoids.DoConditionalCancelAura = function(msg) @@ -1883,11 +1959,68 @@ CleveRoids.Frame:RegisterEvent("STOP_AUTOREPEAT_SPELL") CleveRoids.Frame:RegisterEvent("SPELLCAST_CHANNEL_START") CleveRoids.Frame:RegisterEvent("SPELLCAST_CHANNEL_STOP") +-- Order-agnostic SuperMacro hook installer +local function CRM_SM_InstallHook() + if CleveRoids.SM_RunLineHooked then return end + if not SuperMacroFrame or type(RunLine) ~= "function" then return end + + local orig_RunLine = RunLine + + -- Fast-path targets for extended/bracket syntax + local tokenHooks = { + cast = CleveRoids.DoCast, + target = CleveRoids.DoTarget, + use = CleveRoids.DoUse, + } + + RunLine = function(...) + local text = (arg and arg[1]) or nil + + if CleveRoids.stopmacro then + CleveRoids.stopmacro = false + return true + end + + if type(text) == "string" then + -- 1) SPECIAL-CASE: /castsequence (no token required) + local b, e, rest = string.find(text, "^%s*/castsequence%s*(.*)") + if b then + if type(CleveRoids.DoCastSequence) == "function" then + pcall(CleveRoids.DoCastSequence, rest or "") + return true + end + local fn = _G.SlashCmdList and _G.SlashCmdList["CASTSEQUENCE"] + if type(fn) == "function" then + pcall(fn, rest or "") + return true + end + -- fall through to SM if no handler + else + -- 2) FAST-PATH: /cast|/use|/target followed by extended token + for k, fn in pairs(tokenHooks) do + if type(fn) == "function" and string.find(text, "^%s*/"..k.."%s+[!%[%{%?~]") then + -- IMPORTANT: keep the opening token — grab the entire remainder + local _, _, remainder = string.find(text, "^%s*/"..k.."%s+(.*)$") + remainder = remainder and string.gsub(remainder, "^%s+", "") or "" + pcall(fn, remainder) + return true + end + end + end + end + + return orig_RunLine(text) + end + + CleveRoids.SM_RunLineHooked = true +end + function CleveRoids.Frame:PLAYER_LOGIN() _, CleveRoids.playerClass = UnitClass("player") _, CleveRoids.playerGuid = UnitExists("player") CleveRoids.IndexSpells() CleveRoids.initializationTimer = GetTime() + 1.5 + CRM_SM_InstallHook() if not CleveRoids.hasSuperwow or not IsSpellInRange then if not CleveRoids.hasSuperwow then CleveRoids.Print("|cFFFF0000CleveRoidMacros|r requires |cFF00FFFFbalakethelock's SuperWoW|r:") @@ -1905,47 +2038,13 @@ function CleveRoids.Frame:PLAYER_LOGIN() end function CleveRoids.Frame:ADDON_LOADED(addon) - if addon ~= "CleveRoidMacros" then - return + -- keep your existing init for CRM: + if addon == "CleveRoidMacros" then + CleveRoids.InitializeExtensions() end - - CleveRoids.InitializeExtensions() - - if SuperMacroFrame then - local hooks = { - cast = { action = CleveRoids.DoCast }, - target = { action = CleveRoids.DoTarget }, - use = { action = CleveRoids.DoUse }, - castsequence = { action = CleveRoids.DoCastSequence } - } - - -- Hook SuperMacro's RunLine to stay compatible - CleveRoids.Hooks.RunLine = RunLine - CleveRoids.RunLine = function(...) - for i = 1, arg.n do - if CleveRoids.stopmacro then - CleveRoids.stopmacro = false - return true - end - local intercepted = false - local text = arg[i] - - for k,v in pairs(hooks) do - local begin, _end = string.find(text, "^/"..k.."%s+[!%[]") - if begin then - local msg = string.sub(text, _end) - v.action(msg) - intercepted = true - break - end - end - - if not intercepted then - CleveRoids.Hooks.RunLine(text) - end - end - end - RunLine = CleveRoids.RunLine + -- (re)attempt hook when either addon arrives + if addon == "SuperMacro" or addon == "CleveRoidMacros" then + CRM_SM_InstallHook() end end @@ -1965,21 +2064,26 @@ function CleveRoids.Frame:UNIT_CASTEVENT(caster,target,action,spell_id,cast_time CleveRoids.spell_tracking[caster] = nil end - -- handle cast sequence + -- handle cast sequence (SuperWoW) if CleveRoids.currentSequence and caster == CleveRoids.playerGuid then local active = CleveRoids.GetCurrentSequenceAction(CleveRoids.currentSequence) local name, rank = SpellInfo(spell_id) - local isSeqSpell = (active.action == name or active.action == (name.."("..rank..")")) + local nameRank = (rank and rank ~= "") and (name .. "(" .. rank .. ")") or nil + local isSeqSpell = active and active.action and ( + active.action == name or + (nameRank and active.action == nameRank) + ) + if isSeqSpell then local status = CleveRoids.currentSequence.status if status == 0 and (action == "START" or action == "CHANNEL") and cast_time > 0 then - CleveRoids.currentSequence.status = 1 - CleveRoids.currentSequence.expires = GetTime() + cast_time - 2000 + -- cast_time is ms; GetTime() is seconds + CleveRoids.currentSequence.status = 1 + CleveRoids.currentSequence.expires = GetTime() + (cast_time / 1000) - 2 elseif (status == 0 and action == "CAST" and cast_time == 0) - or (status == 1 and action == "CAST" and CleveRoids.currentSequence.expires) - then - CleveRoids.currentSequence.status = 2 + or (status == 1 and action == "CAST" and CleveRoids.currentSequence.expires) then + CleveRoids.currentSequence.status = 2 CleveRoids.currentSequence.lastUpdate = GetTime() CleveRoids.AdvanceSequence(CleveRoids.currentSequence) CleveRoids.currentSequence = nil @@ -1988,6 +2092,7 @@ function CleveRoids.Frame:UNIT_CASTEVENT(caster,target,action,spell_id,cast_time end end end + if CleveRoidMacros.realtime == 0 then CleveRoids.QueueActionUpdate() end