From c710ccf832320c31a6d8f297eab023ce56bf37ad Mon Sep 17 00:00:00 2001 From: Brues <5278969+brues-code@users.noreply.github.com> Date: Sun, 21 Jun 2026 00:30:30 -0500 Subject: [PATCH] Identify action-bar macros by slot/index; drop name restrictions Resolve a slot's macro by its Blizzard macro index via ClassicAPI's GetActionInfo instead of GetActionText -> name -> GetMacro, so action-bar macros no longer depend on the macro name. - Core.lua: GetAction uses GetActionInfo(slot) -> macro index -> GetMacroByIndex; falls back to the name path for SuperMacro (no index). ParseMacro split into a shared BuildMacro + ParseMacroByIndex (caches by index) + ParseMacro(name). Added GetMacroByIndex. The macro cache is now keyed by index for action-bar macros, so blank/duplicate names no longer collide. GetMacroIndexByName remains only for genuine name references ({MacroName}, /runmacro, /macrocheck). - MacroErrorUI.lua: remove the now-obsolete macro-name warnings (duplicate, blank, spell-conflict, item-conflict) from both the live editor validation and the on-close report; delete the unused name-set helpers. - README: update the known-issue to note only name-referenced macros need unique names. --- Core.lua | 92 ++++++++++++++------- Extensions/MacroErrorUI.lua | 157 ++---------------------------------- README.md | 2 +- 3 files changed, 73 insertions(+), 178 deletions(-) diff --git a/Core.lua b/Core.lua index 6bf0101..1c26ed4 100644 --- a/Core.lua +++ b/Core.lua @@ -1265,19 +1265,31 @@ function CleveRoids.GetAction(slot) local actions = CleveRoids.Actions[slot] if actions then return actions end - local text = GetActionText(slot) + -- Identify the macro by its index via ClassicAPI's GetActionInfo so the + -- slot is mapped without going through the macro name (no unique-name + -- requirement for action-bar macros). + local macro + local kind, macroID = CleveRoids.ClassicAPI.GetActionInfo(slot) + if kind == "macro" and macroID then + macro = CleveRoids.GetMacroByIndex(macroID) + end - if text then - local macro = CleveRoids.GetMacro(text) - if macro then - actions = macro.actions - - CleveRoids.TestForActiveAction(actions) - CleveRoids.Actions[slot] = actions - CleveRoids.SendEventForAction(slot, "ACTIONBAR_SLOT_CHANGED", slot) - return actions + -- Fallback for SuperMacro (and anything GetActionInfo doesn't tag as a + -- macro): resolve by name. + if not macro then + local text = GetActionText(slot) + if text then + macro = CleveRoids.GetMacro(text) end end + + if macro then + actions = macro.actions + CleveRoids.TestForActiveAction(actions) + CleveRoids.Actions[slot] = actions + CleveRoids.SendEventForAction(slot, "ACTIONBAR_SLOT_CHANGED", slot) + return actions + end end function CleveRoids.GetActiveAction(slot) @@ -1831,22 +1843,11 @@ function CleveRoids.ParseSequence(text) return sequence end -function CleveRoids.ParseMacro(name) - if not name then return end - - local macroID = GetMacroIndexByName(name) - - local _, texture, body - if macroID and macroID ~= 0 then - _, texture, body = GetMacroInfo(macroID) - end - - if (not body) and GetSuperMacroInfo then - _, texture, body = GetSuperMacroInfo(name) - end - - if not texture or not body then return end - +-- Shared: build, parse, and cache a macro under `cacheKey`. For action-bar +-- macros the key is the Blizzard macro index (unique), so duplicate or blank +-- macro names no longer collide; SuperMacro macros (no index) are keyed by name. +-- macroID is the Blizzard index if known, else nil. +local function BuildMacro(cacheKey, macroID, name, texture, body) local macro = { id = macroID, name = name, @@ -1918,10 +1919,40 @@ function CleveRoids.ParseMacro(name) -- Store whether #showtooltip had an explicit argument (for icon fallback logic) macro.actions.explicitTooltip = showTooltipHasArg - CleveRoids.Macros[name] = macro + CleveRoids.Macros[cacheKey] = macro return macro end +-- Parse a macro by its Blizzard macro index (1-36), caching by index. This is +-- the action-bar path: a slot's macro is identified by index via ClassicAPI's +-- GetActionInfo, so duplicate/blank macro names no longer collide. +function CleveRoids.ParseMacroByIndex(macroID) + if not macroID or macroID == 0 then return end + local name, texture, body = GetMacroInfo(macroID) + if not texture or not body then return end + return BuildMacro(macroID, macroID, name, texture, body) +end + +-- Parse a macro by name (explicit {MacroName} / runmacro references, and +-- SuperMacro macros that have no Blizzard index). Resolves to the index path +-- when possible so the cache stays index-keyed. +function CleveRoids.ParseMacro(name) + if not name then return end + + local macroID = GetMacroIndexByName(name) + if macroID and macroID ~= 0 then + return CleveRoids.ParseMacroByIndex(macroID) + end + + -- SuperMacro has no Blizzard macro index; cache by name. + if GetSuperMacroInfo then + local _, texture, body = GetSuperMacroInfo(name) + if texture and body then + return BuildMacro(name, nil, name, texture, body) + end + end +end + function CleveRoids.ParseMsg(msg) if not msg then return end local conditionals = {} @@ -2242,6 +2273,13 @@ function CleveRoids.GetMacro(name) return CleveRoids.Macros[name] or CleveRoids.ParseMacro(name) end +-- Get a parsed macro by its Blizzard macro index (1-36). Used by the action-bar +-- path so macros are identified by slot/index rather than name. +function CleveRoids.GetMacroByIndex(macroID) + if not macroID then return end + return CleveRoids.Macros[macroID] or CleveRoids.ParseMacroByIndex(macroID) +end + function CleveRoids.GetSequence(args) return CleveRoids.Sequences[args] or CleveRoids.ParseSequence(args) end diff --git a/Extensions/MacroErrorUI.lua b/Extensions/MacroErrorUI.lua index ea5560c..e030a08 100644 --- a/Extensions/MacroErrorUI.lua +++ b/Extensions/MacroErrorUI.lua @@ -1072,100 +1072,12 @@ end -- Validation & Debounce -- ============================================================================ --- Build a set of player spell names (lowercase) for name conflict checks -local function GetPlayerSpellNames() - local spellNames = {} - -- Primary source: CleveRoids.Spells indexed table - if CleveRoids.Spells then - for bookType, spells in CleveRoids.Spells do - if type(spells) == "table" then - for spellName, _ in pairs(spells) do - if type(spellName) == "string" then - spellNames[string.lower(spellName)] = true - end - end - end - end - end - -- Fallback: iterate spellbook directly - if not next(spellNames) then - local i = 1 - while true do - local name, rank = GetSpellName(i, "spell") - if not name then break end - spellNames[string.lower(name)] = true - i = i + 1 - end - end - return spellNames -end - --- Build a set of player item names (lowercase) for name conflict checks -local function GetPlayerItemNames() - local itemNames = {} - if CleveRoids.Items then - for key, value in pairs(CleveRoids.Items) do - if type(key) == "string" and type(value) == "table" and value.name then - itemNames[string.lower(value.name)] = true - end - end - end - return itemNames -end - --- Validate the current macro's name and return any name errors +-- Macro names are no longer restricted: action-bar macros are identified by +-- slot/index (ClassicAPI GetActionInfo), so blank, duplicate, and spell/item-name +-- macros are all valid. (Name-based references like {MacroName}/runmacro still +-- need a unique name, but that's not enforced here.) local function ValidateMacroName() - local nameErrors = {} - if not MacroFrame or not MacroFrame.selectedMacro then return nameErrors end - - local selectedSlot = MacroFrame.selectedMacro - local nameOk, name = pcall(GetMacroInfo, selectedSlot) - if not nameOk or not name then return nameErrors end - - -- Blank/whitespace name - local trimmedName = CleveRoids.Trim and CleveRoids.Trim(name) or name - if trimmedName == "" then - table.insert(nameErrors, { - type = "NAME_ERROR", - message = "Macro name is blank or only spaces" - }) - end - - -- Spell conflict - local lowerName = string.lower(name) - local spellNames = GetPlayerSpellNames() - if spellNames[lowerName] then - table.insert(nameErrors, { - type = "NAME_ERROR", - message = "Name '" .. name .. "' conflicts with a spell/ability" - }) - end - - -- Item conflict - local itemNames = GetPlayerItemNames() - if itemNames[lowerName] then - table.insert(nameErrors, { - type = "NAME_ERROR", - message = "Name '" .. name .. "' conflicts with an item" - }) - end - - -- Duplicate name check - local dupeCount = 0 - for i = 1, 36 do - local ok, otherName = pcall(GetMacroInfo, i) - if ok and otherName and string.lower(otherName) == lowerName then - dupeCount = dupeCount + 1 - end - end - if dupeCount > 1 then - table.insert(nameErrors, { - type = "NAME_ERROR", - message = "Duplicate name '" .. name .. "' (used " .. dupeCount .. " times)" - }) - end - - return nameErrors + return {} end local function RunValidation() @@ -1249,12 +1161,9 @@ end local function ReportAllMacroErrors() local accountEntries = {} -- { {name=, slot=, errors=}, ... } local characterEntries = {} - local nameCount = {} -- lowercase name -> count (for duplicate detection) - local nameSlots = {} -- lowercase name -> { slot1, slot2, ... } - local spellNames = GetPlayerSpellNames() - local itemNames = GetPlayerItemNames() - -- First pass: collect all macro names and body errors + -- Collect body (syntax) errors per macro. Macro names are no longer + -- restricted (slot/index-based identification), so no name checks here. for i = 1, 36 do local nameOk, name = pcall(GetMacroInfo, i) if nameOk and name and name ~= "" then @@ -1271,38 +1180,6 @@ local function ReportAllMacroErrors() end end - -- Check blank/whitespace name - local trimmedName = CleveRoids.Trim and CleveRoids.Trim(name) or name - if trimmedName == "" then - table.insert(errors, { - type = "NAME_ERROR", - message = "Macro name is blank or only spaces" - }) - end - - -- Check if name matches a player spell - local lowerNameCheck = string.lower(name) - if spellNames[lowerNameCheck] then - table.insert(errors, { - type = "NAME_ERROR", - message = "Name '" .. name .. "' conflicts with a known spell/ability" - }) - end - - -- Check if name matches a player item - if itemNames[lowerNameCheck] then - table.insert(errors, { - type = "NAME_ERROR", - message = "Name '" .. name .. "' conflicts with an inventory item" - }) - end - - -- Track name for duplicate detection - local lowerName = string.lower(name) - nameCount[lowerName] = (nameCount[lowerName] or 0) + 1 - if not nameSlots[lowerName] then nameSlots[lowerName] = {} end - table.insert(nameSlots[lowerName], i) - local entry = { name = name, slot = i, errors = errors } if i <= 18 then table.insert(accountEntries, entry) @@ -1312,26 +1189,6 @@ local function ReportAllMacroErrors() end end - -- Second pass: inject duplicate name errors - for lowerName, count in pairs(nameCount) do - if count > 1 then - local slots = nameSlots[lowerName] - for _, slot in ipairs(slots) do - -- Find the entry for this slot and add the error - local list = slot <= 18 and accountEntries or characterEntries - for _, entry in ipairs(list) do - if entry.slot == slot then - table.insert(entry.errors, 1, { - type = "NAME_ERROR", - message = "Duplicate name '" .. entry.name .. "' (used " .. count .. " times)" - }) - break - end - end - end - end - end - -- Output: filter to only entries with errors local accountErrors = {} local characterErrors = {} diff --git a/README.md b/README.md index 88ca57b..ca67c78 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ See the **[Wiki](https://github.com/brues-code/SuperCleveRoidMacros/wiki)** for ## Known Issues -- Unique macro names required (no blanks, duplicates, or spell names) +- Action-bar macros are identified by slot, so blank or duplicate macro names are fine. Only macros referenced *by name* (`{MacroName}` nesting, `/runmacro "Name"`) still need a unique name to disambiguate. - Reactive abilities must be on action bars for detection - Debuff time-left conditionals only work on own debuffs unless pfUI libdebuff or Cursive has data - Macro line length: 261 characters max (MacroLengthWarn extension prevents crashes)