-- 事件合并读取候选;只在脱战时更新变化的宏,保留仍在使用的槽位。 local slots, active, entries = {}, {}, {} local dirty, queued, notified, announce = true, false, false, false local checks, count = 0, 0 local schedule -- 只扫描一次预设;缺少占用信息时拒绝猜测,避免旧客户端覆盖只有正文的宏。 local firstSlot, capacity, available, reservedNames = 1, 0, {}, {} local compatible = true for 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 end end for 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 end end local function macroName(name) while reservedNames[name] do name = name .. "_" end return name end 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 0 end 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 = true end 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) end end 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 0 end 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) end frame:SetScript("OnEvent", function(_, event, unit) if event ~= "PLAYER_SPECIALIZATION_CHANGED" or unit == "player" then dirty, checks = true, 0 schedule() end end) schedule()