You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
904 lines
31 KiB
904 lines
31 KiB
local MAJ, REV, _, T = 1, 25, ...
|
|
if T.SkipLocalActionBook then return end
|
|
local RW, AB, KR = {}, nil, assert(T.Kindred:compatible(1,18), "A compatible version of Kindred is required.")
|
|
local MODERN = select(4,GetBuildInfo()) >= 8e4
|
|
|
|
local function assert(condition, err, ...)
|
|
return (not condition) and error(tostring(err):format(...), 3) or condition
|
|
end
|
|
local safequote do
|
|
local r = {u="\\117", ["{"]="\\123", ["}"]="\\125"}
|
|
function safequote(s)
|
|
return (("%q"):format(s):gsub("[{}u]", r))
|
|
end
|
|
end
|
|
local forall do
|
|
local function r(n, f, a, ...)
|
|
if n > 0 then
|
|
return f(a), r(n-1, f, ...)
|
|
end
|
|
end
|
|
function forall(f, ...)
|
|
return r(select("#", ...), f, ...)
|
|
end
|
|
end
|
|
|
|
local Spell_UncastableIDs do
|
|
local classLockedMounts = {
|
|
[48778]="DEATHKNIGHT", [54729]="DEATHKNIGHT", [229387]="DEATHKNIGHT",
|
|
[229417]="DEMONHUNTER", [200175]="DEMONHUNTER",
|
|
[229386]="HUNTER", [229438]="HUNTER", [229439]="HUNTER",
|
|
[229376]="MAGE",
|
|
[229385]="MONK",
|
|
[13819]="PALADIN", [23214]="PALADIN", [34767]="PALADIN", [34769]="PALADIN", [66906]="PALADIN", [69820]="PALADIN", [69826]="PALADIN", [221883]="PALADIN", [205656]="PALADIN", [221885]="PALADIN", [221886]="PALADIN", [231587]="PALADIN", [231588]="PALADIN", [231589]="PALADIN", [231435]="PALADIN",
|
|
[229377]="PRIEST",
|
|
[231434]="ROGUE", [231523]="ROGUE", [231524]="ROGUE", [231525]="ROGUE",
|
|
[231442]="SHAMAN",
|
|
[5784]="WARLOCK", [23161]="WARLOCK", [232412]="WARLOCK", [238452]="WARLOCK", [238454]="WARLOCK",
|
|
[229388]="WARRIOR",
|
|
}
|
|
local _, class = UnitClass("player")
|
|
for k,v in pairs(classLockedMounts) do
|
|
if v == class then
|
|
classLockedMounts[k] = nil
|
|
end
|
|
end
|
|
Spell_UncastableIDs = classLockedMounts
|
|
end
|
|
local Spell_CheckKnown = {[33891]=IsSpellKnown, [102543]=IsSpellKnown, [102558]=IsSpellKnown} do
|
|
Spell_CheckKnown[102560] = function()
|
|
return IsSpellKnown(194223) and select(7, GetSpellInfo(GetSpellInfo(194223))) == 102560 or false
|
|
end
|
|
end
|
|
local Spell_ForcedID = {[126819]=1, [28272]=1, [28271]=1, [161372]=1, [51514]=1, [210873]=1, [211004]=1, [211010]=1, [211015]=1, [783]=1}
|
|
|
|
local namedMacros, namedMacroText, namedMacroTextOwnerPriority, coreNamedMacroText = {}, {}, {}
|
|
local core, coreEnv = CreateFrame("Frame", nil, nil, "SecureHandlerBaseTemplate") do
|
|
local bni = 1
|
|
for i=1,9 do
|
|
local bn repeat
|
|
bn, bni = "RW!" .. bni, bni + 1
|
|
until GetClickFrame(bn) == nil
|
|
core:WrapScript(T.TenSABT(CreateFrame("Button", bn, core, "SecureActionButtonTemplate")), "OnClick",
|
|
[=[-- Rewire:OnClick_Pre
|
|
if ns == 0 then return false end
|
|
idle[self], numIdle, numActive, ns = nil, numIdle - 1, numActive + 1, ns - 1
|
|
self:SetAttribute("macrotext", owner:RunAttribute("RunMacro", nil))
|
|
return nil, 1
|
|
]=], [=[-- Rewire:OnClick_Post
|
|
idle[self], numIdle, numActive = 1, numIdle + 1, numActive - 1
|
|
if ns == 0 and #execQueue > 0 then
|
|
owner:CallMethod("throw", "Rewire executor pool exhausted; spilling queue (n=" .. #execQueue .. ").")
|
|
wipe(execQueue)
|
|
overfull, mutedAbove, modLock = false, -1, mutedAbove >= 0 and owner:CallMethod("setMute", false), nil
|
|
end
|
|
]=])
|
|
end
|
|
core:SetFrameRef("Kindred", KR:seclib())
|
|
core:Execute([=[-- Rewire_Init
|
|
KR, AB = self:GetFrameRef("Kindred"), nil
|
|
execQueue, mutedAbove, QUEUE_LIMIT, overfull = newtable(), -1, 20000, false
|
|
idle, cache, numIdle, numActive, ns, modLock = newtable(), newtable(), 0, 0, 0
|
|
macros, namedMacroText, commandInfo, commandHandler, commandAlias = newtable(), newtable(), newtable(), newtable(), newtable()
|
|
MACRO_TOKEN, NIL, metaCommands, transferTokens = newtable(nil, nil, nil, "MACRO_TOKEN"), newtable(), newtable(), newtable()
|
|
metaCommands.mute, metaCommands.unmute, metaCommands.mutenext, metaCommands.parse, metaCommands.nounshift = 1, 1, 1, 1, 1
|
|
castEscapes, castAliases = newtable(), newtable()
|
|
for _, k in pairs(self:GetChildList(newtable())) do
|
|
idle[k], numIdle = 1, numIdle + 1
|
|
k:SetAttribute("type", "macro")
|
|
end
|
|
RW_ReleaseTransferToken = [==[-- RW_ReleaseTransferToken
|
|
local m = transferTokens[#transferTokens]
|
|
if m[3] ~= NIL then
|
|
modLock, m[3] = m[3], NIL
|
|
end
|
|
]==]
|
|
]=])
|
|
if MODERN then
|
|
core:Execute("TEN = true")
|
|
end
|
|
coreEnv = GetManagedEnvironment(core)
|
|
coreNamedMacroText = coreEnv.namedMacroText
|
|
end
|
|
core:SetAttribute("RunSlashCmd", [=[-- Rewire:Internal_RunSlashCmd
|
|
local slash, v, target, exArg = ...
|
|
if not v then
|
|
elseif slash == "/cast" or slash == "/use" then
|
|
local vl = v:lower()
|
|
local ac, av = 0, castAliases[vl]
|
|
while av and ac < 10 do
|
|
ac, v, vl = ac + 1, av, av:lower()
|
|
end
|
|
local oid = v and castEscapes[vl]
|
|
local sid = v and not oid and v:match("^%s*spell:(%d+)%s*$")
|
|
if oid then
|
|
return AB:RunAttribute("UseAction", oid, target)
|
|
elseif sid then
|
|
return AB:RunAttribute("CastSpellByID", tonumber(sid), target)
|
|
elseif v then
|
|
if exArg == "opt-into-cr-fallback" and (tonumber(v) or v:match("^%s*[Ii][Tt][Ee][Mm]:%d")) then
|
|
slash = "/castrandom"
|
|
end
|
|
return (target and (slash .. " [@" .. target .. "] ") or (slash .. " ")) .. v
|
|
end
|
|
elseif slash == "/stopmacro" then
|
|
local i, r, m = #execQueue
|
|
repeat
|
|
r, i, execQueue[i] = execQueue[i], i - 1
|
|
m = r and r[4]
|
|
if m == "TRANSFER_TOKEN" then
|
|
transferTokens[#transferTokens+1] = r
|
|
self:Run(RW_ReleaseTransferToken)
|
|
elseif m == "UNSHIFT_RESTORE" then
|
|
self:CallMethod("manageUnshift", true)
|
|
end
|
|
until r == MACRO_TOKEN or r == nil
|
|
elseif slash == "#mutenext" or slash == "#mute" then
|
|
local breakOnCommand = slash == "#mutenext"
|
|
for i=#execQueue,1,-1 do
|
|
local m = execQueue[i]
|
|
if m == MACRO_TOKEN or (breakOnCommand and m[4] == nil) then
|
|
if i > 1 then i = i - 1 end
|
|
if mutedAbove < 0 or i < mutedAbove then
|
|
mutedAbove = i, mutedAbove < 0 and self:CallMethod("setMute", true)
|
|
end
|
|
return
|
|
end
|
|
end
|
|
elseif slash == "#unmute" and mutedAbove > -1 then
|
|
for i=#execQueue,mutedAbove+1,-1 do
|
|
if m == MACRO_TOKEN then
|
|
return
|
|
end
|
|
end
|
|
mutedAbove = -1, self:CallMethod("setMute", false)
|
|
elseif slash == "#nounshift" then
|
|
local breakOnCommand = v:lower() == "next"
|
|
for i=#execQueue,1,-1 do
|
|
local m = execQueue[i]
|
|
if m == MACRO_TOKEN or (breakOnCommand and m[4] == nil) or i == 1 then
|
|
table.insert(execQueue, i, newtable(nil, nil, nil, "UNSHIFT_RESTORE"))
|
|
break
|
|
end
|
|
end
|
|
self:CallMethod("manageUnshift", false)
|
|
elseif slash == "#parse" then
|
|
local m = execQueue[#execQueue]
|
|
if m and m[2] and m[3] then
|
|
execQueue[#execQueue] = nil
|
|
local parsed = KR:RunAttribute("EvaluateCmdOptions", m[3], modLock, nil)
|
|
if parsed then
|
|
return m[2] .. " " .. parsed
|
|
end
|
|
end
|
|
elseif slash == "/runmacro" then
|
|
if macros[v] then
|
|
return macros[v]:RunAttribute("RunNamedMacro", v)
|
|
elseif namedMacroText[v] then
|
|
return self:RunAttribute("RunMacro", namedMacroText[v])
|
|
end
|
|
print(('|cffffff00Macro %q is unknown.'):format(v))
|
|
elseif slash then
|
|
return (target and (slash .. " [@" .. target .. "] ") or (slash .. " ")) .. v
|
|
end
|
|
]=])
|
|
core:SetAttribute("RunMacro", [=[-- Rewire:RunMacro
|
|
local m, macrotext, _transferButtonState, applyModLock = cache[...], ...
|
|
if macrotext and not m then
|
|
m = newtable()
|
|
for line in macrotext:gmatch("%S[^\n\r]*") do
|
|
local slash, args = line:match("^(%S+)%s*(.-)%s*$")
|
|
slash = slash:lower()
|
|
local meta, meta4 = slash:match("^#((.?.?.?.?).*)")
|
|
if meta == nil or metaCommands[meta] then
|
|
m[#m+1] = newtable(line, slash, args, meta)
|
|
end
|
|
end
|
|
cache[macrotext] = m
|
|
end
|
|
|
|
if m and #m > 0 and not overfull then
|
|
if #execQueue > QUEUE_LIMIT then
|
|
overfull = true, owner:CallMethod("throw", "Rewire execution queue overfull; ignoring subsequent commands.")
|
|
else
|
|
local ni = #execQueue+1
|
|
local nbs = nil
|
|
local nml = (applyModLock == true and KR:GetAttribute("cndLock") or type(applyModLock) == "string" and applyModLock) or nil
|
|
nml = nml and nml ~= modLock and nml
|
|
if nml then
|
|
local nt = #transferTokens
|
|
local tt = nt > 0 and transferTokens[nt] or newtable(nil, nil, NIL, "TRANSFER_TOKEN")
|
|
execQueue[ni], ni, transferTokens[nt] = tt, ni + 1
|
|
modLock, tt[3] = nml, modLock
|
|
end
|
|
execQueue[ni], ni = MACRO_TOKEN, ni + 1
|
|
for i=#m, 1, -1 do
|
|
execQueue[ni], ni = m[i], ni + 1
|
|
end
|
|
end
|
|
end
|
|
|
|
if ns < #execQueue and numIdle > 0 then
|
|
local m = "\n/click " .. next(idle):GetName()
|
|
local n = math.min(math.floor(1000/#m), math.ceil(#execQueue*1.25 + numActive*1.3^numActive))
|
|
ns = ns + n
|
|
return m:rep(n)
|
|
else
|
|
local i, nextLine, m, t, k, v, ct = #execQueue
|
|
repeat
|
|
m, i, execQueue[i] = execQueue[i], i-1
|
|
until i < 1 or m ~= MACRO_TOKEN
|
|
if mutedAbove > 0 and mutedAbove > i then
|
|
mutedAbove = -1, self:CallMethod("setMute", false)
|
|
end
|
|
if not m then
|
|
overfull = false
|
|
return ""
|
|
end
|
|
k, v = commandAlias[m[2]] or m[2], m[3]
|
|
ct = commandInfo[k] or 0
|
|
local meta = m[4]
|
|
if ct % 2 > 0 and m[3] ~= "" then
|
|
local skipChunks = nil
|
|
v, t = KR:RunAttribute("EvaluateCmdOptions", m[3], modLock, skipChunks)
|
|
if v and ct % 32 >= 16 then
|
|
v = KR:RunAttribute("ResolveUnitAlias", v)
|
|
end
|
|
if v then
|
|
nextLine = m[2] .. (t and " [@" .. t .. "] " or " ") .. v
|
|
else
|
|
nextLine = m[2] .. " [form:42]"
|
|
end
|
|
elseif meta == "TRANSFER_TOKEN" then
|
|
transferTokens[#transferTokens+1], nextLine = m
|
|
self:Run(RW_ReleaseTransferToken)
|
|
elseif meta == "UNSHIFT_RESTORE" then
|
|
self:CallMethod("manageUnshift", true)
|
|
else
|
|
nextLine = m[1]
|
|
end
|
|
if commandHandler[k] then
|
|
nextLine = commandHandler[k]:RunAttribute("RunSlashCmd", m[2], v, t)
|
|
end
|
|
return (nextLine or "") ~= "" and nextLine or #execQueue > 0 and self:RunAttribute("RunMacro", nil) or ""
|
|
end
|
|
]=])
|
|
core:SetAttribute("SetNamedMacroHandler", [=[-- Rewire:SetNamedMacroHandler
|
|
local handlerFrame, name, skipNotifyAB = self:GetFrameRef("SetNamedMacroHandler-handlerFrame"), ...
|
|
local om, nm = macros[name], handlerFrame or (namedMacroText[name] and false)
|
|
if type(name) == "string" and om ~= nm then
|
|
macros[name] = nm
|
|
self:CallMethod("clearNamedMacroHinter", name, skipNotifyAB or (om == nil) == (nm == nil))
|
|
end
|
|
self:SetAttribute("frameref-SetNamedMacroHandler-handlerFrame", nil)
|
|
]=])
|
|
function core:throw(err)
|
|
error(err)
|
|
end
|
|
function core:clearNamedMacroHinter(name, skipNotifyAB)
|
|
namedMacros[name] = nil
|
|
if skipNotifyAB ~= true then
|
|
AB:NotifyObservers("macro")
|
|
end
|
|
end
|
|
do -- core:setMute
|
|
local f, oSFX, oES, oUEM = CreateFrame("Frame")
|
|
local muteArmed, muteReqState, muteCount = false, false, 0
|
|
function core:setMute(mute)
|
|
local arm, disarm = mute and not muteArmed, muteCount == 0 and not mute
|
|
if arm then
|
|
oSFX, oES = GetCVar("Sound_EnableSFX"), GetCVar("Sound_EnableErrorSpeech")
|
|
oUEM = UIErrorsFrame:UnregisterEvent("UI_ERROR_MESSAGE")
|
|
elseif oUEM and disarm then
|
|
UIErrorsFrame:RegisterEvent("UI_ERROR_MESSAGE")
|
|
end
|
|
if arm or disarm then
|
|
SetCVar("Sound_EnableSFX", mute and 0 or oSFX)
|
|
SetCVar("Sound_EnableErrorSpeech", mute and 0 or oES)
|
|
elseif muteArmed and not mute then
|
|
SetCVar("Sound_EnableSFX", oSFX)
|
|
SetCVar("Sound_EnableErrorSpeech", oES)
|
|
end
|
|
muteReqState, muteArmed = mute, arm or muteArmed and not disarm
|
|
f:SetShown(muteArmed)
|
|
end
|
|
if MODERN then
|
|
f:SetScript("OnEvent", function(_, e, u)
|
|
if e == "UI_ERROR_MESSAGE" then
|
|
if muteCount == 1 then
|
|
muteCount = 0
|
|
core:setMute(false)
|
|
elseif muteCount > 0 then
|
|
muteCount = muteCount - 1
|
|
end
|
|
elseif e == "UNIT_SPELLCAST_FAILED" and muteReqState and u == "player" then
|
|
muteCount = muteCount + 1
|
|
end
|
|
end)
|
|
f:RegisterEvent("UI_ERROR_MESSAGE")
|
|
f:RegisterUnitEvent("UNIT_SPELLCAST_FAILED", "player")
|
|
end
|
|
f:SetScript("OnUpdate", function()
|
|
local ost = muteReqState
|
|
muteCount = 0
|
|
core:setMute(false)
|
|
if ost then
|
|
error("Muted state persisted after macro execution")
|
|
end
|
|
end)
|
|
f:Hide()
|
|
end
|
|
do -- core:manageUnshift
|
|
local isModified, origValue, modDepth = false, nil, 0
|
|
local cleanupArmed = false
|
|
local function cleanup()
|
|
cleanupArmed = false
|
|
if isModified then
|
|
SetCVar("autoUnshift", origValue)
|
|
isModified, modDepth = false, 0
|
|
securecall(error, "RW unshift cleanup panic")
|
|
end
|
|
end
|
|
function core:manageUnshift(isRestore)
|
|
if isRestore then
|
|
if modDepth > 0 then
|
|
modDepth = modDepth - 1
|
|
if modDepth == 0 then
|
|
SetCVar("autoUnshift", origValue)
|
|
isModified = false
|
|
end
|
|
end
|
|
else
|
|
if not isModified then
|
|
origValue = GetCVar("autoUnshift")
|
|
SetCVar("autoUnshift", 0)
|
|
end
|
|
isModified, modDepth = true, modDepth + 1
|
|
if not cleanupArmed then
|
|
C_Timer.After(0, cleanup)
|
|
cleanupArmed = true
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local function setCommandType(slash, ctype, handler)
|
|
if handler ~= nil then core:SetFrameRef('hand', handler) end
|
|
core:Execute(("commandInfo[%s], commandHandler[%1$s] = %d, %s"):format(safequote(slash), ctype, handler and "self:GetFrameRef('hand')" or "nil"))
|
|
end
|
|
local function getAliases(p, i)
|
|
local v = _G[p .. i]
|
|
if v then
|
|
return v, getAliases(p, i+1)
|
|
end
|
|
end
|
|
|
|
local setCommandHinter, getMacroHint, getCommandHint, getCommandHintRaw, getSpeculationID, metaFilters, metaFilterTypes do
|
|
local hintFunc, pri, cache, ht, ht2, cDepth = {}, {}, {}, {}, {}, 0
|
|
local DEPTH_LIMIT, UNKNOWN_PRIORITY, nInf = 20, -2^52, -math.huge
|
|
local speculationID, nextSpeculationID, SPECULATION_ID_WRAP = nil, 221125, 2^53
|
|
local store do
|
|
local function write(t, n, i, a,b,c,d, ...)
|
|
if n > 0 then
|
|
t[i], t[i+1], t[i+2], t[i+3] = a,b,c,d
|
|
return write(t, n-4, i+4, ...)
|
|
end
|
|
end
|
|
function store(ok, ...)
|
|
if ok then
|
|
local n = select("#", ...)
|
|
write(ht2, n+1, 0, n, ...)
|
|
end
|
|
return ok
|
|
end
|
|
end
|
|
metaFilters, metaFilterTypes = {}, {} do
|
|
local function fillToSize(sz, stopFillAt)
|
|
if ht[0] < sz then
|
|
for i=ht[0]+1,stopFillAt do
|
|
ht[i] = nil
|
|
end
|
|
ht[0] = sz
|
|
end
|
|
return true
|
|
end
|
|
function metaFilterTypes:replaceIcon(...)
|
|
local doReplace, icon = self(...)
|
|
if doReplace then
|
|
ht[3] = icon
|
|
return fillToSize(3, 2)
|
|
end
|
|
end
|
|
function metaFilterTypes:replaceTooltip(...)
|
|
local doReplace, tipFunc, tipArg = self(...)
|
|
if doReplace then
|
|
ht[8], ht[9] = tipFunc, tipArg
|
|
return fillToSize(9, 7)
|
|
end
|
|
end
|
|
function metaFilterTypes:replaceCooldown(...)
|
|
local doReplace, cdLeft, cdLength = self(...)
|
|
if doReplace then
|
|
ht[6], ht[7] = cdLeft, cdLength
|
|
return fillToSize(7, 5)
|
|
end
|
|
end
|
|
function metaFilterTypes:replaceCount(...)
|
|
local doReplace, count = self(...)
|
|
if doReplace then
|
|
ht[5] = count
|
|
return fillToSize(5, 4)
|
|
end
|
|
end
|
|
function metaFilterTypes:replaceLabel(...)
|
|
local doReplace, stext = self(...)
|
|
if doReplace then
|
|
ht[11] = stext
|
|
return fillToSize(11, 10)
|
|
end
|
|
end
|
|
function metaFilterTypes:macroFallback(...)
|
|
local doReplace, usable, icon, label = self(...)
|
|
if doReplace then
|
|
local d, hl = false, ht[0]
|
|
if label and (hl < 11 or ht[11] == nil) then
|
|
ht[11], d = label, d or fillToSize(11, 10)
|
|
end
|
|
if label and (hl < 4 or ht[4] == nil) then
|
|
ht[4], d = label, d or fillToSize(4, 3)
|
|
end
|
|
if icon and (doReplace == 2 or hl < 3 or not ht[3]) then
|
|
ht[3], d = icon, d or fillToSize(3, 2)
|
|
end
|
|
if usable == true and (ht[1] == nil or hl < 1) then
|
|
ht[1], ht[2], d = true, 0, d or fillToSize(2,0)
|
|
end
|
|
return d
|
|
end
|
|
end
|
|
function metaFilterTypes:replaceHint(...)
|
|
if store(self(...)) then
|
|
ht, ht2 = ht2, ht
|
|
return true
|
|
end
|
|
end
|
|
end
|
|
function getCommandHintRaw(hslash, ...)
|
|
local hf = hintFunc[hslash]
|
|
if not hf then return false end
|
|
return hf(...)
|
|
end
|
|
local function clearDepth(...)
|
|
cDepth, speculationID = 0, nil
|
|
return ...
|
|
end
|
|
local function prepCall(...)
|
|
if cDepth ~= 0 then error("invalid state") end
|
|
cDepth, speculationID, nextSpeculationID = 1, nextSpeculationID, nextSpeculationID ~= SPECULATION_ID_WRAP and nextSpeculationID + 1 or -nextSpeculationID
|
|
return clearDepth(securecall(...))
|
|
end
|
|
function getCommandHint(priLimit, slash, args, modState, otarget, msg, priBias)
|
|
slash = coreEnv.commandAlias[slash] or slash
|
|
local hf, pri, args2, target = hintFunc[slash], pri[slash]
|
|
if hf and pri > (priLimit or nInf) - (priBias or 0) then
|
|
if cDepth == 0 then
|
|
return prepCall(getCommandHint, priLimit, slash, args, modState, otarget, msg, priBias)
|
|
elseif cDepth > DEPTH_LIMIT then
|
|
return false
|
|
elseif otarget ~= nil then
|
|
args, args2, target = nil, args, otarget
|
|
elseif (coreEnv.commandInfo[slash] or 0) % 2 > 0 then
|
|
if args == "" then
|
|
args2, args = ""
|
|
else
|
|
args, args2, target = nil, KR:EvaluateCmdOptions(args, modState, nil, speculationID)
|
|
end
|
|
end
|
|
cDepth = cDepth + 1
|
|
local res = store(securecall(hf, slash, args, args2, target, modState, priLimit, msg, speculationID))
|
|
cDepth = cDepth - 1
|
|
if res == "stop" then
|
|
return res, pri
|
|
elseif priLimit then
|
|
return res, (res ~= true and res or pri) + (priBias or 0)
|
|
elseif res then
|
|
return res, unpack(ht2, 1, ht2[0])
|
|
end
|
|
elseif not pri then
|
|
return false
|
|
end
|
|
end
|
|
function getMacroHint(macrotext, modState, minPriority)
|
|
if not macrotext then return end
|
|
if cDepth == 0 then
|
|
return prepCall(getMacroHint, macrotext, modState, minPriority)
|
|
end
|
|
local m, lowPri = cache[macrotext], minPriority or nInf
|
|
if not m then
|
|
m = {}
|
|
for line in macrotext:gmatch("%S[^\n\r]*") do
|
|
local slash, args = line:match("^(%S+)%s*(.-)%s*$")
|
|
slash = slash:lower()
|
|
local meta, meta4 = slash:match("^#((.?.?.?.?).*)")
|
|
if meta4 == "show" and args ~= "" then
|
|
m[-1], m[0] = "/use", args
|
|
elseif meta == nil or meta == "skip" or meta == "important" then
|
|
m[#m+1], m[#m+2] = slash, args
|
|
else
|
|
if m.metaKeys == nil then
|
|
m.metaKeys, m.metaArgs = {}, {}
|
|
end
|
|
local idx = #m.metaKeys+1
|
|
m.metaKeys[idx], m.metaArgs[idx] = meta, args
|
|
end
|
|
end
|
|
cache[macrotext] = m
|
|
end
|
|
local bestPri, bias, haveUnknown = lowPri, m[-1] and 1000 or 0
|
|
for i=m[-1] and -1 or 1, #m, 2 do
|
|
local cmd, args = m[i], m[i+1]
|
|
if cmd == "#skip" or cmd == "#important" then
|
|
local v = args == "" or KR:EvaluateCmdOptions(args, modState)
|
|
if v ~= nil then
|
|
v = tonumber(v)
|
|
bias = cmd == "#skip" and (v and -v or nInf) or (v or 1000)
|
|
end
|
|
else
|
|
local res, pri = getCommandHint(bestPri, cmd, args, modState, nil, nil, bias)
|
|
if res == "stop" then
|
|
break
|
|
elseif res and pri > bestPri then
|
|
bestPri, ht, ht2 = pri, ht2, ht
|
|
elseif res == false and i > 0 then
|
|
haveUnknown = true
|
|
end
|
|
bias = 0
|
|
end
|
|
end
|
|
local mk, mv = m.metaKeys
|
|
if (bestPri <= lowPri) and (haveUnknown or mk) then
|
|
store(true, nil, 0, nil, "", 0, 0, 0)
|
|
ht, ht2 = ht2, ht
|
|
end
|
|
for i=1,mk and #mk or 0 do
|
|
local k = mk[i]
|
|
local fi = metaFilters[k]
|
|
if fi then
|
|
mv = mv or m.metaArgs
|
|
local filterRun, parseConditional, filterFunc = fi[1], fi[2], fi[3]
|
|
local v, vt = mv[i], nil
|
|
if parseConditional then
|
|
v, vt = KR:EvaluateCmdOptions(v, modState)
|
|
end
|
|
if v and securecall(filterRun, filterFunc, k, v, vt) then
|
|
haveUnknown = true
|
|
end
|
|
end
|
|
end
|
|
|
|
if bestPri > lowPri or haveUnknown then
|
|
if minPriority then
|
|
if haveUnknown and bestPri == nInf then
|
|
bestPri = UNKNOWN_PRIORITY - cDepth
|
|
end
|
|
return bestPri > lowPri and bestPri or false, unpack(ht, 1, ht[0])
|
|
else
|
|
return unpack(ht, 1, ht[0])
|
|
end
|
|
end
|
|
end
|
|
function setCommandHinter(slash, priority, hint)
|
|
hintFunc[slash], pri[slash] = hint, hint and priority
|
|
end
|
|
function getSpeculationID()
|
|
return speculationID
|
|
end
|
|
end
|
|
|
|
local function init()
|
|
for k, v in pairs(_G) do
|
|
local k = type(k) == "string" and k:match("^SLASH_(.*)1$")
|
|
if k and IsSecureCmd(v) then
|
|
RW:ImportSlashCmd(k, true, false)
|
|
end
|
|
end
|
|
for k in ("DISMOUNT LEAVEVEHICLE SET_TITLE USE_TALENT_SPEC TARGET_MARKER"):gmatch("%S+") do
|
|
RW:ImportSlashCmd(k, true, false)
|
|
end
|
|
for k in ("STARTATTACK TARGET TARGET_EXACT ASSIST FOCUS MAINTANKON MAINTANKOFF MAINASSISTON MAINASSISTOFF PET_ATTACK"):gmatch("%S+") do
|
|
local cmd = _G["SLASH_" .. k .. "1"]
|
|
if cmd and IsSecureCmd(cmd) then
|
|
setCommandType(cmd, 1+2+16)
|
|
end
|
|
end
|
|
for m in ("#mute #unmute #mutenext #parse #nounshift"):gmatch("%S+") do
|
|
RW:RegisterCommand(m, true, false, core)
|
|
end
|
|
RW:RegisterCommand("/stopmacro", true, false, core)
|
|
RW:AddCommandAliases("/stopmacro", getAliases("SLASH_STOPMACRO", 1))
|
|
RW:SetCommandHint("/stopmacro", math.huge, function(_, _, clause)
|
|
return clause and "stop" or nil
|
|
end)
|
|
RW:SetCommandHint(SLASH_CLICK1, math.huge, function(...)
|
|
local _, _, clause = ...
|
|
local name = clause and clause:match("%S+")
|
|
return getCommandHintRaw(name and ("/click " .. name), ...)
|
|
end)
|
|
setCommandType("/use", 1+2, core)
|
|
setCommandType("/cast", 1+2, core)
|
|
RW:AddCommandAliases("/cast", getAliases("SLASH_CAST", 1))
|
|
RW:AddCommandAliases("/use", getAliases("SLASH_USE", 1))
|
|
setCommandType(SLASH_USERANDOM1, 1+2+4)
|
|
RW:AddCommandAliases(SLASH_USERANDOM1, getAliases("SLASH_CASTRANDOM", 1))
|
|
setCommandType(SLASH_CASTSEQUENCE1, 1+2+4+8)
|
|
RW:RegisterCommand("/runmacro", true, false, core)
|
|
RW:SetCommandHint("/runmacro", math.huge, function(_slash, _, n, _t, ...)
|
|
local f = namedMacros[n]
|
|
if f then
|
|
return f(n, nil, ...)
|
|
end
|
|
local mt = coreNamedMacroText[n]
|
|
if mt then
|
|
local cndState, minPriority = ...
|
|
return getMacroHint(mt, cndState, minPriority)
|
|
end
|
|
end)
|
|
|
|
local iconReplCache = setmetatable({}, {__index=function(t,k)
|
|
if k then
|
|
local v = tonumber(k)
|
|
if not v then
|
|
if k:match("[/\\]") or tonumber(k) then
|
|
v = k
|
|
elseif k ~= "" then
|
|
v = "Interface\\Icons\\" .. k
|
|
end
|
|
end
|
|
t[k] = v ~= 0 and v
|
|
return v
|
|
end
|
|
end})
|
|
RW:SetMetaHintFilter("icon", "replaceIcon", true, function(_meta, value, _target)
|
|
return true, iconReplCache[value]
|
|
end)
|
|
RW:SetMetaHintFilter("count", "replaceCount", true, function(_meta, value, _target)
|
|
local c = value == "none" and 0 or (value and GetItemCount(value))
|
|
return not not c, c
|
|
end)
|
|
RW:SetMetaHintFilter("label", "replaceLabel", true, function(_meta, value, _target)
|
|
return not not value, value or ""
|
|
end)
|
|
|
|
AB = assert(T.ActionBook:compatible(2, 22), "A compatible version of ActionBook is required.")
|
|
core:SetFrameRef("ActionBook", AB:seclib())
|
|
core:Execute([[AB = self:GetFrameRef('ActionBook')]])
|
|
end
|
|
local caEscapeCache, caAliasCache, cuHints = {}, {}, {} do
|
|
setmetatable(caEscapeCache, {__index=function(t, k)
|
|
if k then
|
|
local v = coreEnv.castEscapes[k:lower()] or false
|
|
t[k] = v
|
|
return v
|
|
end
|
|
end})
|
|
setmetatable(caAliasCache, {__index=function(t, k)
|
|
if k then
|
|
local at = coreEnv.castAliases
|
|
local v = at[k:lower()]
|
|
repeat
|
|
local vl = v and v:lower()
|
|
v = at[vl] or v
|
|
until not at[vl]
|
|
t[k] = v
|
|
return v
|
|
end
|
|
end})
|
|
local function mixHint(slash, _, clause, target, ...)
|
|
clause = caAliasCache[clause] or clause
|
|
local ca = caEscapeCache[clause]
|
|
if ca then
|
|
return true, AB:GetSlotInfo(ca)
|
|
else
|
|
local hint = cuHints[slash]
|
|
if hint then
|
|
return hint(slash, _, clause, target, ...)
|
|
end
|
|
end
|
|
end
|
|
setCommandHinter("/use", 100, mixHint)
|
|
setCommandHinter("/cast", 100, mixHint)
|
|
end
|
|
|
|
function RW:compatible(cmaj, crev)
|
|
local acceptable = (cmaj == MAJ and crev <= REV)
|
|
if acceptable and init then
|
|
init()
|
|
init = nil
|
|
end
|
|
return acceptable and RW or nil, MAJ, REV
|
|
end
|
|
function RW:seclib()
|
|
return core
|
|
end
|
|
function RW:RegisterCommand(slash, isConditional, allowVars, handlerFrame)
|
|
assert(type(slash) == "string" and (handlerFrame == nil or type(handlerFrame) == "table" and type(handlerFrame.GetAttribute) == "function"),
|
|
'Syntax: Rewire:RegisterCommand("/slash", parseConditional, allowVars[, handlerFrame])')
|
|
assert(handlerFrame == nil or handlerFrame:GetAttribute("RunSlashCmd"), 'Handler frame must have "RunSlashCmd" attribute set.')
|
|
assert(not InCombatLockdown(), 'Combat lockdown in effect')
|
|
local ct = (isConditional and 1 or 0) + (allowVars and 2 or 0)
|
|
setCommandType(slash, ct, handlerFrame)
|
|
end
|
|
function RW:AddCommandAliases(primary, ...)
|
|
assert(type(primary) == "string", 'Syntax: Rewire:AddCommandAliases("/slash", ["/alias1", "/alias2", ...])')
|
|
assert(not InCombatLockdown(), 'Combat lockdown in effect')
|
|
local n, s = select("#", ...), "-- Rewire_AddCommandAliases\nlocal a, p = commandAlias, %s\n"
|
|
s = s .. ("a[%s], "):rep(n-1) .. "a[%s] = " .. ("p, "):rep(n-1) .. "p\n"
|
|
core:Execute(s:format(forall(safequote, primary, ...)))
|
|
end
|
|
function RW:GetCommandInfo(slash)
|
|
assert(type(slash) == "string", 'Syntax: isConditional, allowVars, isCommaListArg, isSequenceListArg, resolveUnitTargets = Rewire:GetCommandInfo("/slash")')
|
|
local ct = coreEnv.commandInfo[slash]
|
|
if ct then
|
|
return ct % 2 >= 1, ct % 4 >= 2, ct % 8 >= 4, ct % 16 >= 8, ct % 32 >= 16
|
|
end
|
|
end
|
|
function RW:ImportSlashCmd(key, isConditional, allowVars, priority, hint)
|
|
assert(type(key) == "string" and (hint == nil or type(hint) == "function" and type(priority) == "number"), 'Syntax: Rewire:ImportSlashCmd("KEY", parseConditional, allowVars[, hintPriority, hintFunc])')
|
|
assert(not InCombatLockdown(), 'Combat lockdown in effect')
|
|
local primary = _G["SLASH_" .. key .. "1"]
|
|
RW:RegisterCommand(primary, isConditional, allowVars)
|
|
if _G["SLASH_" .. key .. "2"] then
|
|
RW:AddCommandAliases(getAliases("SLASH_" .. key, 1))
|
|
end
|
|
if primary and hint then
|
|
self:SetCommandHint(primary, priority, hint)
|
|
end
|
|
end
|
|
function RW:SetCommandHint(slash, priority, hint)
|
|
assert(type(slash) == "string" and (hint == nil or type(hint) == "function" and type(priority) == "number"), 'Syntax: Rewire:SetCommandHint("/slash", priority, hintFunc)')
|
|
if slash ~= "/use" and slash ~= "/cast" then
|
|
setCommandHinter(slash, priority, hint)
|
|
else
|
|
cuHints[slash] = hint
|
|
end
|
|
end
|
|
function RW:SetClickHint(buttonName, priority, hint)
|
|
assert(type(buttonName) == "string" and (hint == nil or type(hint) == "function" and type(priority) == "number"), 'Syntax: Rewire:SetClickHint("buttonName", priority, hintFunc)')
|
|
setCommandHinter("/click " .. buttonName, priority, hint)
|
|
end
|
|
function RW:SetMetaHintFilter(meta, filterType, isConditional, hint)
|
|
assert(type(meta) == "string" and type(isConditional) == "boolean" and type(hint) == "function", 'Syntax: Rewire:SetMetaHintFilter("meta", "filterType", isConditional, hintFunc)')
|
|
local filterRun = assert(metaFilterTypes[filterType], 'Unsupported meta hint filter type')
|
|
metaFilters[meta:lower()] = {filterRun, isConditional, hint}
|
|
end
|
|
function RW:SetNamedMacroHandler(name, handlerFrame, hintFunc, skipNotifyAB)
|
|
assert(type(name) == "string" and type(handlerFrame) == "table" and type(handlerFrame.GetAttribute) == "function" and (hintFunc == nil or type(hintFunc) == "function"),
|
|
'Syntax: Rewire:SetNamedMacroHandler(name, handlerFrame, hintFunc?, skipNotifyAB?)')
|
|
assert(handlerFrame:GetAttribute("RunNamedMacro"), 'Handler frame must have "RunNamedMacro" attribute set.')
|
|
if handlerFrame ~= GetFrameHandleFrame(coreEnv.macros[name]) then
|
|
assert(not InCombatLockdown(), 'Combat lockdown in effect')
|
|
core:SetFrameRef("SetNamedMacroHandler-handlerFrame", handlerFrame)
|
|
core:Execute(('self:RunAttribute("SetNamedMacroHandler", %s, %s)'):format(safequote(name), skipNotifyAB == true and 'true' or 'nil'))
|
|
end
|
|
namedMacros[name] = hintFunc
|
|
end
|
|
function RW:ClearNamedMacroHandler(name, handlerFrame, skipNotifyAB)
|
|
assert(type(handlerFrame) == "table" and type(name) == "string", 'Syntax: Rewire:ClearNamedMacroHandler("name", handlerFrame)')
|
|
if GetFrameHandleFrame(coreEnv.macros[name]) == handlerFrame then
|
|
assert(not InCombatLockdown(), 'Combat lockdown in effect')
|
|
core:Execute(('macros[%s] = %s'):format(safequote(name), coreNamedMacroText[name] and 'false' or 'nil'))
|
|
core:clearNamedMacroHinter(name, skipNotifyAB)
|
|
end
|
|
end
|
|
function RW:GetNamedMacros()
|
|
return rtable.pairs(coreEnv.macros)
|
|
end
|
|
function RW:IsNamedMacroKnown(name)
|
|
return coreEnv.macros[name] ~= nil
|
|
end
|
|
function RW:RegisterNamedMacroTextOwner(owner, priority)
|
|
assert(owner ~= nil and type(priority) == "number", 'Syntax: ownerToken = Rewire:RegisterNamedMacroTextOwner(owner, priority)')
|
|
assert(namedMacroTextOwnerPriority[owner] == nil, 'Duplicate registration')
|
|
namedMacroTextOwnerPriority[owner] = priority
|
|
return owner
|
|
end
|
|
function RW:SetNamedMacroText(name, text, ownerToken, skipNotifyAB)
|
|
assert(type(name) == "string" and (not text or type(text) == "string"),
|
|
'Syntax: Rewire:SetNamedMacroText("name", "text", ownerToken, priority?, skipNotifyAB?)')
|
|
assert(not InCombatLockdown(), 'Combat lockdown in effect')
|
|
assert(namedMacroTextOwnerPriority[ownerToken], 'ownerToken is not registered')
|
|
local a = namedMacroText[name]
|
|
local et = a and a[ownerToken]
|
|
if not (text or et) or et == text then
|
|
return
|
|
else
|
|
a = a or {}
|
|
namedMacroText[name], a[ownerToken] = a, text or nil
|
|
end
|
|
local nv, k, p = nil, next(a)
|
|
if k == nil then
|
|
namedMacroText[name] = nil
|
|
else
|
|
nv, p = p, namedMacroTextOwnerPriority[k]
|
|
for co, text in next, a, k do
|
|
local cp = namedMacroTextOwnerPriority[co]
|
|
if cp > p then
|
|
nv, p = text, cp
|
|
end
|
|
end
|
|
end
|
|
if nv ~= coreNamedMacroText[name] then
|
|
core:Execute(('local n, t = %s, %s; namedMacroText[n], macros[n] = t, macros[n] or (t and false)'):format(safequote(name), nv and safequote(nv) or 'nil'))
|
|
if skipNotifyAB then
|
|
return true
|
|
elseif AB then
|
|
AB:NotifyObservers("macro")
|
|
end
|
|
end
|
|
end
|
|
function RW:GetMacroAction(macrotext, cndState, minPriority)
|
|
return getMacroHint(macrotext, cndState, minPriority)
|
|
end
|
|
function RW:GetNamedMacroAction(name, cndState, minPriority)
|
|
local hf = namedMacros[name]
|
|
if hf then
|
|
return hf(name, nil, cndState, minPriority)
|
|
end
|
|
local mt = coreNamedMacroText[name]
|
|
if mt then
|
|
return getMacroHint(mt, cndState, minPriority)
|
|
end
|
|
end
|
|
function RW:GetCommandAction(slash, args, target, modState, msg)
|
|
return getCommandHint(nil, slash, args, modState, target, msg)
|
|
end
|
|
function RW:SetCastEscapeAction(castArg, action)
|
|
assert(type(castArg) == "string" and (type(action) == "number" and action % 1 == 0 or action == nil), 'Syntax: Rewire:SetCastEscapeAction("castAction", abActionID or nil)')
|
|
assert(not InCombatLockdown(), 'Combat lockdown in effect')
|
|
core:Execute(([[castEscapes[%q] = %s]]):format(castArg:lower(), action or "nil"))
|
|
wipe(caEscapeCache)
|
|
end
|
|
function RW:GetCastEscapeAction(castArg)
|
|
return coreEnv.castEscapes[castArg and castArg:lower()]
|
|
end
|
|
function RW:SetCastAlias(castArg, aliasTo)
|
|
assert(type(castArg) == "string" and (type(aliasTo) == "string" or aliasTo == nil), 'Syntax: Rewire:SetCastAlias("castAction", "aliasTo" or nil)')
|
|
assert(not InCombatLockdown(), 'Combat lockdown in effect')
|
|
if aliasTo == castArg or aliasTo and strcmputf8i(aliasTo, castArg) == 0 then
|
|
aliasTo = nil
|
|
elseif aliasTo then
|
|
local cl, al, at = castArg:lower(), aliasTo:lower(), coreEnv.castAliases
|
|
while al ~= cl and al and at[al] do
|
|
al = at[al]:lower()
|
|
end
|
|
assert(al ~= cl, 'Aliasing %q creates an alias cycle.', aliasTo)
|
|
end
|
|
core:Execute(([[castAliases[%q] = %s]]):format(castArg:lower(), aliasTo and safequote(aliasTo) or "nil"))
|
|
wipe(caAliasCache)
|
|
end
|
|
function RW:GetCastAlias(castArg)
|
|
return coreEnv.castEscapes[castArg and castArg:lower()]
|
|
end
|
|
function RW:IsSpellCastable(id, disallowRewireEscapes, laxRank)
|
|
local cks = Spell_CheckKnown[id]
|
|
if cks and not cks(id) then
|
|
return false, "known-check"
|
|
elseif Spell_UncastableIDs[id] then
|
|
return false, "uncastable-class-lock"
|
|
elseif MODERN and Spell_ForcedID[id] then
|
|
return not not FindSpellBookSlotBySpellID(id), "forced-id-cast"
|
|
end
|
|
local name, rank = GetSpellInfo(id), GetSpellSubtext(id)
|
|
if disallowRewireEscapes ~= true and coreEnv.castEscapes[name and name:lower()] then
|
|
return true, "rewire-escape"
|
|
elseif disallowRewireEscapes ~= true and coreEnv.castAliases[name and name:lower()] then
|
|
return true, "rewire-alias"
|
|
elseif laxRank == "lax-rank" then
|
|
rank = nil
|
|
end
|
|
local castable = not not (name and GetSpellInfo(name, rank))
|
|
return castable, castable and "double-gsi"
|
|
end
|
|
RW.GetSpeculationID = getSpeculationID
|
|
|
|
T.Rewire = {compatible=RW.compatible}
|