Dynamic Macros
You no longer need to pre-fill macro names and bodies. Use SuperDPS.SetMacro in game-side initialization code, then call Macro by slot or name in desktop logic. A validated registry connects the two environments.
Minimal example: generate a localized spell macro
Section titled “Minimal example: generate a localized spell macro”Keep a valid hotkey for slot 1 in the editor; its name and body may be empty. Paste this into Initialization Code. Spell ID 1766 is an example; replace it with a spell appropriate to your character. Save, generate the addon and /reload in game.
local spellID = 1766local name = C_Spell and C_Spell.GetSpellName and SuperDPS.SN(C_Spell.GetSpellName(spellID), "macro.name")if type(name) == "string" and name ~= "" then local body = "/cast " .. name SuperDPS.SetMacro(1, "interrupt", body)endUse one call in desktop logic: the following uses the name; replace it with Macro(1) to use the slot. Do not call both forms consecutively just to demonstrate them.
local submitted = Macro("interrupt")API and replacement rules
Section titled “API and replacement rules”| Interface | Rule |
|---|---|
SuperDPS.SetMacro(slot, name, body) |
Game-side only. A new or updated request returns false, "pending"; identical, applied content returns true, "ready". Invalid arguments raise an error without changing the registry. |
Macro(slotOrName) |
Desktop logic only. Resolves an integer slot or exact name and returns a boolean. Submitting a key does not confirm a successful cast. |
1..105 |
Dynamic slots. Slots 106–150 and reload/SelectTarget names are reserved. Invalid, explicitly empty or conflicting hotkeys cannot be registered. |
name / body |
Names are 1–96 bytes of valid UTF-8, not whitespace-only and without control characters. Names must be unique across slots. Bodies are nonempty and contain no NUL; use Lua \n or table.concat for multiple lines. |
SetMacro(1, ...) |
Replaces the name and body at the same slot while preserving its hotkey; the old name stops resolving. Changes last for the game session and do not rewrite the editor or .wa1. Reload runs initialization again. |
Rebuild content after combat
Section titled “Rebuild content after combat”Keep initialization in a function and refresh it through events. The API keeps the latest request for each slot; during combat it queues requests without modifying secure buttons. This example shows a multiline body and an out-of-combat refresh; slot 2 also needs a valid hotkey.
local function refresh() if InCombatLockdown() then return end local body = table.concat({"/say ready", "/startattack"}, "\n") SuperDPS.SetMacro(2, "start", body)endlocal frame = CreateFrame("Frame")frame:RegisterEvent("PLAYER_REGEN_ENABLED")frame:SetScript("OnEvent", refresh)refresh()Updates mark the slot unavailable, wait at least 250ms and for combat to end, then write the button and read back its body and effective binding. Macro/Cast calls pause until the registry has fully synchronized. Binding changes invalidate readiness and queue a rebind; submit the same request again to retry a failed write. Longer names and larger registries take longer to synchronize, so prefer short, stable names.
Complete example: minimal Assisted Combat module
Section titled “Complete example: minimal Assisted Combat module”Initialization reads the official rotation candidates, deduplicates base spell IDs, allocates stable slots and generates macros with localized spell names. One AssistMacro property returns the slot of the current recommendation; one logic script calls it. There are no extra mounted, chat, death or range pause conditions. Missing recommendations, unknown spells or invalid data return 0 and do not dispatch a key.
Download the complete .wa1 example · Download initialization Lua. Import and bind the official logic. Macro names and bodies remain empty in the editor, with template hotkeys. The example requires retail C_AssistedCombat and does nothing when the API is absent.
Name the custom property AssistMacro and use:
return SuperDPS.AssistMacro()Logic code:
local slot = Prop("AssistMacro")if slot > 0 then Macro(slot) endShow complete initialization code
-- 事件合并读取候选;只在脱战时更新变化的宏,保留仍在使用的槽位。local slots, active, entries = {}, {}, {}local dirty, queued, notified, announce = true, false, false, falselocal checks, count = 0, 0local schedule-- 只扫描一次预设;缺少占用信息时拒绝猜测,避免旧客户端覆盖只有正文的宏。local firstSlot, capacity, available, reservedNames = 1, 0, {}, {}local compatible = truefor slot, row in pairs(SuperDPS.DynamicSlots) do if row.name and row.name ~= "" then reservedNames[row.name] = true end if slot >= 1 and slot <= 105 then if type(row.occupied) ~= "boolean" then compatible = false end if row.occupied then firstSlot = math.max(firstSlot, slot + 1) end endendfor slot = firstSlot, 105 do local row = SuperDPS.DynamicSlots[slot] if row and row.occupied == false and row.key and row.key ~= "" then available[slot], capacity = true, capacity + 1 endendlocal function macroName(name) while reservedNames[name] do name = name .. "_" end return nameend
local function baseSpell(id) id = SuperDPS.SN(id, "assist.spell") if type(id) ~= "number" or id <= 0 then return 0 end local base = SuperDPS.SN(C_Spell.GetBaseSpell(id), "assist.base") return type(base) == "number" and base > 0 and base or 0end
local function request(slot, name, body) local entry = entries[slot] if entry and entry.name == name and entry.body == body then return end entries[slot] = {name = name, body = body, sentName = entry and entry.sentName} announce = trueend
local function refresh() if InCombatLockdown() then return end if not compatible then error("请更新客户端并重新生成插件:缺少预设宏占用信息") end if dirty then if not (C_AssistedCombat and C_AssistedCombat.GetRotationSpells and C_Spell and C_Spell.GetBaseSpell and C_Spell.GetSpellName) then active = {} return end local wanted, order, used, nextSlots = {}, {}, {}, {} for _, id in ipairs(C_AssistedCombat.GetRotationSpells()) do local base = baseSpell(id) local name = base > 0 and SuperDPS.SN(C_Spell.GetSpellName(base), "assist.name") if type(name) == "string" and name ~= "" and not wanted[base] then wanted[base] = "/cast " .. name order[#order + 1] = base end end if #order > capacity then active = {} error("辅助宏槽位不足:需要" .. #order .. "个,预设宏之后可用" .. capacity .. "个") end -- 先保留仍在使用的槽位,再回收移出的技能;旧宏改为无动作正文。 for base, slot in pairs(slots) do if wanted[base] then nextSlots[base], used[slot] = slot, true else request(slot, macroName("assist_empty_" .. slot), "/stopmacro") end end local free = firstSlot for _, base in ipairs(order) do local slot = nextSlots[base] if not slot then while not available[free] or used[free] do free = free + 1 end slot = free nextSlots[base], used[slot] = slot, true end request(slot, macroName("a" .. base), wanted[base]) end slots, count, active = nextSlots, #order, {} dirty, checks = false, 0 end -- 先释放已提交的旧名称,避免连续切换列表时跨槽位名称冲突。 for slot, entry in pairs(entries) do if entry.sentName and entry.sentName ~= entry.name then if InCombatLockdown() then return end local emptyName = macroName("assist_empty_" .. slot) SuperDPS.SetMacro(slot, emptyName, "/stopmacro") entry.sentName = emptyName end end local ready = true for slot, entry in pairs(entries) do if not entry.ready then if InCombatLockdown() then return end entry.ready = SuperDPS.SetMacro(slot, entry.name, entry.body) entry.sentName = entry.name ready = entry.ready and ready end end -- 仅公布已写入的宏;客户端仍通过完整快照和新鲜度校验后执行。 active = {} for base, slot in pairs(slots) do if entries[slot].ready then active[base] = slot end end if ready then if announce and (notified or count > 0) then local label = notified and "辅助宏更新完毕" or "辅助宏初始化完毕" SuperDPS.Print(label .. "(" .. count .. " 个),请等待客户端同步") notified = true end announce = false elseif checks < 20 then checks = checks + 1 schedule(0.3) endend
schedule = function(delay) if queued or InCombatLockdown() then return end queued = true C_Timer.After(delay or 0.1, function() queued = false local ok, err = pcall(refresh) if not ok then active, dirty = {}, true SuperDPS.Print("辅助宏更新失败:" .. tostring(err)) end end)end
SuperDPS.AssistMacro = function() if not (C_AssistedCombat and C_AssistedCombat.GetNextCastSpell and C_Spell and C_Spell.GetBaseSpell) then return 0 end return active[baseSpell(C_AssistedCombat.GetNextCastSpell())] or 0end
local frame = CreateFrame("Frame")for _, event in ipairs({"PLAYER_LOGIN", "PLAYER_ENTERING_WORLD", "SPELLS_CHANGED", "PLAYER_SPECIALIZATION_CHANGED", "PLAYER_REGEN_ENABLED"}) do frame:RegisterEvent(event)endframe:SetScript("OnEvent", function(_, event, unit) if event ~= "PLAYER_SPECIALIZATION_CHANGED" or unit == "player" then dirty, checks = true, 0 schedule() endend)schedule()Preset macros are preserved. Assisted macros append after the last preset slot, skipping invalid or conflicting hotkeys. Only changed assisted macros are updated out of combat; their slots can be reused. Insufficient remaining capacity is reported. This example requires the generator occupied flag, available in client 20260913.120630 or later. The example passed import/export, cloud round-trip, Lua 5.1 and cross-runtime dispatch tests; in-game acceptance is still pending. Code tests are not a measurement of live casting success.