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 \n local 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 }