动态宏与完整实例
无需预先写死宏名称与正文:在游戏端初始化代码里指定槽位并调用 SuperDPS.SetMacro,桌面端逻辑再用 Macro 按序号或名称调用。两个环境通过经过校验的注册表连接。
最小实例:按游戏语言生成技能宏
Section titled “最小实例:按游戏语言生成技能宏”在编辑器保留第 1 槽的有效热键,名称与正文可以为空。将以下代码粘贴到「初始化代码」;1766 是示例技能 ID,请替换成当前角色要使用的技能。保存、生成插件并在游戏中 /reload。
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)end在桌面端逻辑里选择一种调用方式即可:下面按名称调用,换成 Macro(1) 则按序号调用。不要为了演示两种写法而连续调用两次。
local submitted = Macro("interrupt")接口与覆盖规则
Section titled “接口与覆盖规则”| 接口 | 规则 |
|---|---|
SuperDPS.SetMacro(slot, name, body) |
仅游戏端。首次登记或更新返回 false, "pending";相同内容实际已生效返回 true, "ready"。非法参数报错,不改原注册表。 |
Macro(slotOrName) |
仅桌面逻辑。按整数序号或精确名称匹配,返回布尔值;提交热键不等于游戏成功施法。 |
1..105 |
允许动态设置的槽位;106–150、reload、SelectTarget 名称保留。热键无效、显式为空或冲突的槽位不能注册。 |
name / body |
名称为 1–96 字节有效 UTF-8,不可纯空白或含控制字符;不同序号不能重名。正文非空且不含 NUL,多行用 Lua 的 \n 或 table.concat。 |
SetMacro(1, ...) |
相同序号覆盖名称与正文,热键不变,旧名称失效。修改只属于当前游戏会话,不回写编辑器或 .wa1;重载后重新初始化。 |
脱战后重建内容
Section titled “脱战后重建内容”将初始化逻辑收在函数中,通过事件触发更新。接口会合并同一槽位的最新请求;战斗中只排队,不改安全按钮。以下展示多行正文与脱战更新,槽位 2 的热键也需有效。
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()更新先标记不可调用,至少等待 250ms 并脱战后写入按钮,再读回正文与实际热键。注册表完整同步之前,Macro/Cast 暂停调用。热键被改变时会撤销就绪状态并排队重绑;重绑失败可再次提交相同请求重试。宏名越长、数量越多,同步越久,尽量使用简短稳定的名称。
完整实例:官方辅助作战精简模块
Section titled “完整实例:官方辅助作战精简模块”初始化从官方轮转候选获取技能,以基础技能 ID 去重、分配稳定槽位,并用本地化技能名生成宏。一个 AssistMacro 属性回传当前推荐对应的槽位;一条逻辑完成调用。没有骑乘、聊天、死亡、距离等额外暂停条件;无推荐、未知技能或无效数据返回 0,不发键。
下载完整 .wa1 示例 · 下载初始化 Lua。导入后绑定 official 逻辑;宏表保留空名称与正文,热键使用模板。实例依赖正式服 C_AssistedCombat,API 不存在时不执行。
属性名填写 AssistMacro,自定义属性代码如下:
return SuperDPS.AssistMacro()逻辑代码:
local slot = Prop("AssistMacro")if slot > 0 then Macro(slot) end展开完整初始化代码
-- 事件合并读取候选;只在脱战时更新变化的宏,保留仍在使用的槽位。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()预设宏保持不变,辅助宏从最后一个预设宏之后追加,跳过无效或冲突热键。候选变化只在脱战后差异更新并复用辅助槽位;剩余容量不足时提示。新版示例依赖生成器 occupied 标记,需要客户端 20260913.120630 或更新版本。此实例已通过导入导出、云端往返、Lua 5.1 与跨端调用回归,尚未完成游戏实机验收;不能把代码测试通过当成实战施法成功率。