mirror of
https://github.com/brues-code/SuperCleveRoidMacros.git
synced 2026-09-16 03:38:00 +00:00
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.
This commit is contained in:
@@ -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
|
||||
|
||||
+7
-150
@@ -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 = {}
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user