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.

825 lines
29 KiB

local MAJ, REV, COMPAT, _, T = 1, 10, select(4,GetBuildInfo()), ...
if T.SkipLocalActionBook then return end
if T.TenEnv then T.TenEnv() end
local EV, AB, RW = T.Evie, T.ActionBook:compatible(2,34), T.ActionBook:compatible("Rewire", 1,27)
assert(EV and AB and RW and 1, "Incompatible library bundle")
local MODERN, CF_WRATH, CI_ERA = COMPAT >= 10e4, COMPAT < 10e4 and COMPAT >= 3e4, COMPAT < 2e4
local IM, L, XU = {}, T.ActionBook.L, T.exUI
local function assert(condition, text, level, ...)
return condition or error(tostring(text):format(...), 1 + (level or 1))((0)[0])
end
local GetModernTalentSpells do
local GMTS_next, GMTS_node
local function GMTS_entry(s, activeEID, entryID)
local entry = C_Traits.GetEntryInfo(s.cid, entryID)
local def = entry and entry.definitionID and C_Traits.GetDefinitionInfo(entry.definitionID)
local sid = def and def.spellID
if sid then
return sid, activeEID == entryID, def.overrideName
elseif entry.subTreeID then
local sn, nodes = #s, C_Traits.GetTreeNodes(entry.subTreeID)
if nodes and nodes[1] then
s[sn+1], s[sn+2], s[sn+3], s[sn+4] = nodes, 1, s.cid, GMTS_node
end
end
return GMTS_next(s)
end
function GMTS_node(s, confID, nodeID)
local node = C_Traits.GetNodeInfo(confID, nodeID)
local entryIDs = node and node.entryIDs
if entryIDs and entryIDs[1] then
local sn, activeEID = #s, node.activeEntry and node.activeEntry.entryID
s[sn+1], s[sn+2], s[sn+3], s[sn+4] = node.entryIDs, 1, activeEID, GMTS_entry
end
return GMTS_next(s)
end
function GMTS_next(s)
if not s then return end
local sn = s and #s
local a, i, c, fn = s[sn-3], s[sn-2], s[sn-1], s[sn]
if a and a[i+1] == nil then
s[sn-3], s[sn-2], s[sn-1], s[sn] = nil
elseif i then
s[sn-2] = i + 1
end
if fn then
return fn(s, c, a[i])
end
end
function GetModernTalentSpells()
local s
if MODERN then
local cid = C_ClassTalents.GetActiveConfigID()
if not cid then
local spec = GetSpecializationInfo(GetSpecialization())
local cc = C_ClassTalents.GetConfigIDsBySpecID(spec)
cid = cc and cc[1]
end
local conf = cid and C_Traits.GetConfigInfo(cid)
local tree = conf and conf.treeIDs and conf.treeIDs[1]
local nodes = tree and C_Traits.GetTreeNodes(tree)
s = nodes and {nodes, 1, cid, GMTS_node, cid=cid}
end
return GMTS_next, s
end
end
local commandType, addCommandType = {["#show"]=0, ["#showtooltip"]=0, ["#imp"]=-1} do
function addCommandType(slashToken, ct)
local idx, s = 1
while 1 do
s = _G["SLASH_" .. slashToken .. idx]
if not s then break end
commandType[s], idx = commandType[s] or ct, idx + 1
end
end
for n, ct in ("CAST:1 USE:1 CASTSEQUENCE:2 CASTRANDOM:3 USERANDOM:3"):gmatch("(%a+):(%d+)") do
addCommandType(n, ct+0)
end
if MODERN then
addCommandType("PING", 4)
end
end
local toMacroText, quantizeMacro, formatMacro, formatToken, setMountPreference do
local COMMA_LIST_COMMAND_TYPES, CAST_ESCAPE_COMMAND_TYPES = {[2]=1, [3]=1}, {[0]=1, [1]=1, [3]=1}
local genParser do
local doRewrite, replaceFunc, critFail, critLine
local function replaceAlternatives(ctype, args)
local ret, alt2, rfCtx
for alt, cpos in (args .. ","):gmatch("(.-),()") do
alt2, rfCtx = replaceFunc(ctype, alt, rfCtx, args, cpos)
if alt == alt2 or (alt2 and alt2:match("%S")) then
if doRewrite then
alt2 = alt2:match("^%s*(.-)%s*$")
ret = ret and ret .. ", " .. alt2 or alt2
else
ret = ret and ret .. "," .. alt2 or alt2
end
end
end
return ret
end
local function hasTautCondition(cond)
return (cond:match("%[%s*%]") or cond:match("%[%s*@[^,]*%]")) and true
end
local function procImpOptions(args)
if not doRewrite then
return
end
if args:match("^%s*critical%s*$") then
critLine = true
end
return ""
end
local function procLine(commandPrefix, nlc, command, args)
if critFail or (nlc ~= "" and nlc ~= "\n") then return end
local ctype = commandType[command:lower()] or command:match("^/!%S") and commandType["/cast"]
if ctype == -1 then
return procImpOptions(args)
elseif not ctype then
return
end
local isCritical, pos, len1, ret = critLine and ctype > 0, 1, #args+1
critLine = critLine and not isCritical
repeat
local cstart, cend, vend = pos
repeat
local ce, cc = args:match("()([%[;])", pos)
if cc == "[" then
pos = args:match("%]()", ce)
else
ce = ce or len1
cend, vend, pos = pos, ce-1, ce + 1
end
until cend or not pos
if not pos then return end
local cval = args:sub(cend, vend)
if ctype < 2 then
cval = replaceFunc(ctype, args:sub(cend, vend))
else
local val, reset = args:sub(cend, vend)
if ctype == 2 then
local r, r2, v = val:match("^(%s*(reset=%S+)%s*)(.*)")
reset, val = r and (doRewrite and r2 .. " " or r), v or val
end
val = replaceAlternatives(ctype, val)
cval = val and ((reset or "") .. val) or nil
end
if cval or ctype == 0 then
local cond = cstart < cend and args:sub(cstart, cend-1)
if doRewrite then
cond = cond and cond:match("^%s*(.-)%s*$")
cval = cval and cval:match("^%s*(.-)%s*$")
cond = cond and cval and (cond .. " ") or cond
end
ret = ret and (ret .. (doRewrite and "; " or ";")) or commandPrefix
ret = ret .. (cond or "") .. (cval or "")
if doRewrite and (not cond or hasTautCondition(cond)) then
break
end
end
until pos > len1
critFail = critFail or isCritical and (ret or "") == ""
return ret or ""
end
function genParser(pReplaceFunc)
return function(aDoRewrite, text)
doRewrite, replaceFunc, critFail, critLine = aDoRewrite, pReplaceFunc
text = text:gsub("((.?)([#/]%S+) ?)([^\n]*)", procLine)
return critFail and "" or text
end
end
end
local function parseVarPrefix(value, _ctype)
local varPrefixEnd = 1
repeat
local col, ep = value:match("^%s*%%[a-zA-Z\128-\255][a-zA-Z0-9_\128-\255]*(%S?)%s*()", varPrefixEnd)
if not ep or col ~= ":" and ep <= #value then
break
else
varPrefixEnd = ep
end
until ep > #value
return varPrefixEnd == 1 and "" or value:sub(1, varPrefixEnd-1)
end
local function restoreVarPrefix(varPrefix, emitText)
if varPrefix == "" then
return emitText
elseif emitText then
return varPrefix .. emitText
end
return (varPrefix:gsub("%:%s*$", ""))
end
local function replaceSpellID(ctype, sidlist, prefix, tk)
local noEscapes, sn, sr, ar = not CAST_ESCAPE_COMMAND_TYPES[ctype]
for id in sidlist:gmatch("%d+") do
id = id + 0
sn, sr = GetSpellInfo(id), GetSpellSubtext(id)
ar = sn and GetSpellSubtext(sn)
local isCastable, castFlag = RW:IsSpellCastable(id, noEscapes)
if not MODERN and not isCastable and tk ~= "spellr" then
local id2 = select(7,GetSpellInfo(sn))
if id2 then
id, isCastable, castFlag = id2, RW:IsSpellCastable(id2, noEscapes)
end
end
if isCastable then
if castFlag == "forced-id-cast" and (ctype == 1 or ctype == 3) then
sn = "spell:" .. id
elseif ctype == 3 and sn and sn:match(",") then
sn = "spell:" .. id
elseif sr and sr ~= "" and (MODERN or tk == "spellr") then
sn = sn .. "(" .. sr .. ")"
elseif tk == "spell" and not MODERN and ar ~= sr and ar then
sn = sn .. "(" .. ar .. ")"
end
return prefix .. sn
end
end
end
local replaceMountTag do -- +setMountPreference
local skip, gmSid, gmPref, fmSid, fmPref, drSid, drPref = {[44153]=1, [44151]=1, [61451]=1, [75596]=1, [61309]=1, [169952]=1, [171844]=1, [213339]=1,}
local function IsKnownSpell(sid)
local sn, sr = GetSpellInfo(sid or 0), GetSpellSubtext(sid or 0)
return GetSpellInfo(sn, sr) ~= nil and sid or (RW:GetCastEscapeAction(sn) and sid)
end
local function findMount(prefSID, mtype, ctype)
local wantDragonriding, escapeContext = mtype == 402, ctype == 2 and 0 or 1
local idm, myFactionId, nc, cs = C_MountJournal.GetMountIDs(), UnitFactionGroup("player") == "Horde" and 0 or 1, 0
local gmi, gmiex = C_MountJournal.GetMountInfoByID, C_MountJournal.GetMountInfoExtraByID
for i=1, #idm do
i = idm[i]
local _1, sid, _3, active, _5, _6, _7, factionLocked, factionId, hide, have, _12, isDragonriding = gmi(i)
if have and not hide
and (not factionLocked or factionId == myFactionId)
and RW:IsSpellCastable(sid, escapeContext)
then
local _, _, _, _, t = gmiex(i)
local isTypeMatch = t == mtype or (wantDragonriding and isDragonriding)
if sid == prefSID or (active and isTypeMatch and prefSID == nil) then
return sid
elseif isTypeMatch and not skip[sid] then
nc = nc + 1
if math.random(1,nc) == 1 then
cs = sid
end
end
end
end
return cs
end
function replaceMountTag(ctype, tag, prefix)
if tag == "ground" then
gmSid = gmSid and IsKnownSpell(gmSid) or findMount(gmPref or gmSid, 230, ctype)
return replaceSpellID(ctype, tostring(gmSid), prefix)
elseif tag == "air" then
fmSid = fmSid and IsKnownSpell(fmSid) or findMount(fmPref or fmSid, 248, ctype)
return replaceSpellID(ctype, tostring(fmSid), prefix)
elseif tag == "dragon" then
drSid = drSid and IsKnownSpell(drSid) or findMount(drPref or drSid, 402, ctype)
return replaceSpellID(ctype, tostring(drSid), prefix)
end
return nil
end
if not (MODERN or CF_WRATH) then
replaceMountTag = function () end
end
local function editPreference(orig, new)
return type(new) == "number" and new or new ~= false and orig or nil
end
function setMountPreference(groundSpellID, flyingSpellID, dragonSpellID)
gmPref = editPreference(gmPref, groundSpellID)
fmPref = editPreference(fmPref, flyingSpellID)
drPref = editPreference(drPref, dragonSpellID)
return gmPref, fmPref, drPref
end
end
local pingTextMap, pingTokenMap = {}, {
assist=PING_TYPE_ASSIST,
attack=PING_TYPE_ATTACK,
onmyway=PING_TYPE_ON_MY_WAY,
warning=PING_TYPE_WARNING,
}
for k,v in pairs(pingTokenMap) do
pingTextMap[v:lower()], pingTextMap[k] = k, k
end
toMacroText = genParser(function(ctype, value)
local varPrefix = parseVarPrefix(value, ctype)
local prefix, tkey, tval = value:match("^%s*(!?){{(%a+):([%a%d/]+)}}%s*$", #varPrefix+1)
if tkey == "spell" or tkey == "spellr" then
return restoreVarPrefix(varPrefix, replaceSpellID(ctype, tval, prefix, tkey))
elseif tkey == "mount" then
return restoreVarPrefix(varPrefix, replaceMountTag(ctype, tval, prefix))
elseif tkey == "ping" and ctype == 4 then
return restoreVarPrefix(varPrefix, pingTokenMap[tval] or value)
elseif value:match('^%s*!?|Hiptok|h|h%s*$') then
return '-'
end
return value
end)
local toImpText, prepareQuantizer do
local spells, specialTokens, OTHER_SPELL_IDS = {}, {}, {150544, 243819}
local abMountTokens = {["Ground Mount"]="{{mount:ground}}", ["Flying Mount"]="{{mount:air}}", ["Dragonriding Mount"]=MODERN and "{{mount:dragon}}" or nil}
toImpText = genParser(function(ctype, value, skipCount, args, cpos)
if type(skipCount) == "number" and skipCount > 0 then
return nil, skipCount-1
end
local commaList, noEscapes = COMMA_LIST_COMMAND_TYPES[ctype], not CAST_ESCAPE_COMMAND_TYPES[ctype]
local varPrefix = parseVarPrefix(value, ctype)
local cc, pre, name, tws = 0, value:match("^(%s*!?)(.-)(%s*)$", #varPrefix+1)
repeat
local lowname = name:lower()
local sid, peek, cnpos = spells[lowname]
if ctype == 4 then
name = pingTextMap[lowname]
if name then
return restoreVarPrefix(varPrefix, pre .. "{{ping:" .. name.. "}}" .. tws)
end
elseif sid and noEscapes and RW:IsCastEscape(lowname, true) then
-- Don't tokenize escapes in contexts they wont't work in
elseif sid then
if not MODERN then
local rname = name:gsub("%s*%([^)]+%)$", "")
local sid2 = rname ~= name and spells[rname:lower()]
if sid2 then
return restoreVarPrefix(varPrefix, (pre .. "{{spellr:" .. sid .. "}}" .. tws)), cc
end
end
return restoreVarPrefix(varPrefix, (pre .. "{{spell:" .. sid .. "}}" .. tws)), cc
elseif specialTokens[lowname] then
return restoreVarPrefix(varPrefix, pre .. specialTokens[lowname] .. tws), cc
elseif name:match("^{{.*}}$") then
return restoreVarPrefix(varPrefix, pre .. name .. tws), cc
end
if commaList and args then
peek, cnpos = args:match("^([^,]+),?()", cpos)
if peek then
cc, cpos, name, tws = cc + 1, cnpos, (name .. "," .. peek):match("^(.-)(%s*)$")
end
end
until not peek or cc > 5
return value
end)
local function addMountSpells()
local gmi, idm = C_MountJournal.GetMountInfoByID, C_MountJournal.GetMountIDs()
for i=1, #idm do
local _, sid = gmi(idm[i])
local sname = GetSpellInfo(sid)
if sname then
spells[sname:lower()] = sid
end
end
for k, tok in pairs(abMountTokens) do
specialTokens[k:lower()], specialTokens[L(k):lower()] = tok, tok
end
end
local function addModernTalents()
for sid, _active, overrideName in GetModernTalentSpells() do
local name = GetSpellInfo(sid)
if name then
spells[name:lower()] = sid
end
if overrideName and overrideName ~= name then
spells[overrideName:lower()] = sid
end
end
end
local function addSpell(n, id, allowGenericOverwrite)
local nl, sr, k = n:lower(), GetSpellSubtext(id)
spells[nl] = allowGenericOverwrite and id or spells[nl] or id
if sr and sr ~= "" then
k = nl .. "(" .. sr:lower() .. ")"; spells[k] = spells[k] or id
k = nl .. " (" .. sr:lower() .. ")"; spells[k] = spells[k] or id
end
end
local function addSpellBookTab(ofs, c, allowGenericOverwrite, isRuneBook)
for j=ofs+1,ofs+c do
local n, st, id = GetSpellBookItemName(j, "spell"), GetSpellBookItemInfo(j, "spell")
if type(n) ~= "string" or not id then
elseif st == "SPELL" or st == "FUTURESPELL" then
local ao = allowGenericOverwrite and st == "SPELL" and not isRuneBook
if isRuneBook then
local sn, id2 = GetSpellInfo(id), select(7, GetSpellInfo(n))
if sn ~= n then
addSpell(sn, id, ao)
end
if id2 and id2 ~= id then
addSpell(n, id2, ao)
end
end
addSpell(n, id, ao)
elseif st == "FLYOUT" then
for j=1,select(3,GetFlyoutInfo(id)) do
local sid, _, _, sname = GetFlyoutSlotInfo(id, j)
if sid and type(sname) == "string" then
addSpell(sname, sid)
end
end
end
end
end
function prepareQuantizer(skipCacheRefresh)
if skipCacheRefresh and next(spells) then return end
wipe(spells)
wipe(specialTokens)
for i=1,#OTHER_SPELL_IDS do
local sn = GetSpellInfo(OTHER_SPELL_IDS[i])
if sn then
spells[sn:lower()] = OTHER_SPELL_IDS[i]
end
end
if MODERN then
addMountSpells()
addModernTalents()
elseif CF_WRATH then
addMountSpells()
for i=1,GetNumCompanions("CRITTER") do
local _, _, sid = GetCompanionInfo("CRITTER", i)
local sn = GetSpellInfo(sid)
if sn then
addSpell(sn, sid)
end
end
end
for curSpec=0,1 do
for i=GetNumSpellTabs()+12,1,-1 do
local _, ico, ofs, c, _, sid = GetSpellTabInfo(i)
if ((curSpec == 0) == (sid == 0)) then
addSpellBookTab(ofs, c, not MODERN, CI_ERA and ico == 134419)
end
end
end
spells[""], spells["()"] = nil
end
end
function quantizeMacro(macro, skipCacheRefresh)
if type(macro) ~= "string" then
return macro
end
prepareQuantizer(skipCacheRefresh)
return toImpText(false, macro)
end
do -- formatMacro/formatToken
local formatTokenInner do
local names, tag, mountTokens = {}, 0, {
ground="|cff71d5ff|Hiltmount:ground|h" .. L"Ground Mount" .. "|h|r",
air ="|cff71d5ff|Hiltmount:air|h" .. L"Flying Mount" .. "|h|r",
dragon="|cff71d5ff|Hiltmount:dragon|h" .. L"Dragonriding Mount" .. "|h|r",
}
function formatTokenInner(token, targ)
if token == "spell" or token == "spellr" then
local forceRank, tname = token == "spellr"
tag = tag + 1
for id in targ:gmatch("%d+") do
id = id+0
local name, sr = GetSpellInfo(id), GetSpellSubtext(id)
if sr and sr ~= "" and (forceRank or MODERN) then
name = name .. "(" .. sr .. ")"
end
if name and names[name] ~= tag then
names[name], tname = tag, tname and (tname .. " / " .. name) or name
end
end
if tname then
return "|cff71d5ff|Hilt" .. token .. ":" .. targ .. "|h" .. tname .. "|h|r"
end
elseif token == "mount" then
return mountTokens[targ]
elseif token == "ping" then
local tname = pingTokenMap[targ]
if tname then
return "|cff71d5ff|Hilt" .. token .. ":" .. targ .. "|h" .. tname .. "|h|r"
end
end
return '{{' .. token .. ':' .. targ .. '}}'
end
end
local toUIText = genParser(function(ctype, value)
local varPrefix = parseVarPrefix(value, ctype)
local prefix, token, targ, suf = value:match("^(%s*!?){{(%a+):([%a%d/]+)}}(%s*)$", #varPrefix+1)
local v = token and formatTokenInner(token, targ)
return v and restoreVarPrefix(varPrefix, prefix .. v .. suf) or value
end)
local linkTag = 0
local function tagLinks(p)
linkTag = linkTag + 1
return p .. linkTag .. ":"
end
function formatToken(lastLink, tk, targ)
linkTag, tk = lastLink or 0, formatTokenInner(tk, targ)
tk = tk:gsub("%f[|]|Hilt", tagLinks)
return tk, linkTag
end
function formatMacro(macro)
if type(macro) == "string" then
linkTag, macro = 0, toUIText(false, macro)
macro = macro:gsub("%f[|]|Hilt", tagLinks)
end
return macro, linkTag
end
end
end
local encodeMacro, decodeMacro do
local skipCacheRefresh
local phash_ChatTypeInfoList, importLooseSlashCommands = setmetatable({}, {__index=hash_ChatTypeInfoList}) do
local imported = {}
-- Bootleg ChatFrame_ImportListToHash(SlashCmdList, hash_SlashCmdList); calling the FrameXML version directly
-- would defeat its iterator isolation, and SlashCmdList has late-added secure entries which would notice.
function importLooseSlashCommands()
for k in pairs(SlashCmdList) do
if not imported[k] then
local p, i, cmd = "SLASH_" .. k, 1
repeat
cmd, i = _G[p .. i], i + 1
if type(cmd) == "string" then
phash_ChatTypeInfoList[cmd:upper()] = k
end
until not cmd
imported[k] = true
end
end
end
end
local function encodeSlash(nl, command, lead)
if nl ~= "\n" and nl ~= "" then
return
elseif lead == "!" then
return nl .. "!" .. command
end
local cu = command:upper()
if not (skipCacheRefresh or next(SlashCmdList) == nil) then
skipCacheRefresh = true
importLooseSlashCommands()
end
local ctk = phash_ChatTypeInfoList[cu]
if type(ctk) == "string" and not ctk:match("!") then
return nl .. "!" .. ctk .. "!" .. command
elseif type(hash_EmoteTokenList[cu]) == "string" and not hash_EmoteTokenList[cu]:match("!") then
return nl .. "!" .. hash_EmoteTokenList[cu] .. "!" .. command
end
end
local function decodeSlash(nl, key, command)
if nl ~= "\n" and nl ~= "" then
return
elseif key == "" then
return nl .. "!" .. command
end
local cu = command:upper()
if not (skipCacheRefresh or next(SlashCmdList) == nil) then
skipCacheRefresh = true
importLooseSlashCommands()
end
if phash_ChatTypeInfoList[cu] == key or hash_EmoteTokenList[cu] == key then
elseif _G["SLASH_" .. key .. 1] then
return nl .. _G["SLASH_" .. key .. 1]
else
local i, v = 2, EMOTE1_TOKEN
while v do
if v == key then
return nl .. _G["EMOTE" .. (i-1) .. "_CMD1"]
end
i, v = i + 1, _G["EMOTE" .. i .. "_TOKEN"]
end
end
return nl .. command
end
function encodeMacro(m, skipImport)
skipCacheRefresh = skipImport
return (m:gsub("(.?)(([/!])%S*)", encodeSlash))
end
function decodeMacro(m, skipImport)
skipCacheRefresh = skipImport
return (m:gsub("(.?)!([^\n!]*)!(%S*)", decodeSlash))
end
end
do -- AB imptext action type
local function findAnySpellTokenIcon(imptext)
local argMod = 2^31
for sl in imptext:gmatch("{{spell:([%d/]+)}}") do
for sid in sl:gmatch("%d+") do
local _,_, sico = GetSpellInfo(sid % argMod)
if sico then
return sico
end
end
end
end
local function createImpMacro(macrotext)
if type(macrotext) ~= "string" then return end
local dt = toMacroText(true, macrotext)
return dt and dt:match("%S") and AB:GetActionSlot("macrotext", dt) or nil
end
local function describeImpMacro(imptext)
if type(imptext) ~= "string" then return end
if imptext == "" then return L"Custom Macro", L"New Macro", "Interface/Icons/INV_Misc_Note_03" end
local _, _, ico = RW:GetMacroAction(toMacroText(true, imptext))
local lp = ico and type(ico) == "string" and ico:gsub("\\", "/"):lower()
if ico == nil or lp == "interface/icons/temp" or lp == "interface/icons/inv_misc_questionmark" then
ico = findAnySpellTokenIcon(imptext)
end
return L"Custom Macro", "", ico or "interface/icons/inv_misc_questionmark"
end
AB:RegisterActionType("imptext", createImpMacro, describeImpMacro, 1)
end
do -- Editor UI
local function removeEditorLinks(text)
return (text:gsub("|c%x+|Hilt%d+:([%a:%d/]+)|h.-|h|r", "{{%1}}"))
end
local setImpText, getImpText, newImpToken do
local nextLinkID
function setImpText(box, imptext)
local uitext
uitext, nextLinkID = formatMacro(imptext)
return box:SetText(uitext)
end
function getImpText(box)
return quantizeMacro(removeEditorLinks(box:GetText()))
end
function newImpToken(_forBox, tok, targ)
local uitext
uitext, nextLinkID = formatToken(nextLinkID, tok, targ)
return uitext
end
end
local eb = XU:Create("TextArea", "ABE_MacroInput")
eb:Hide()
eb:SetStyle("tooltip")
eb:SetScript("OnEscapePressed", eb.ClearFocus)
eb:SetScript("OnEditFocusLost", function(self)
setImpText(self, getImpText(self))
local p = self:GetParent()
if p and type(p.OnActionChanged) == "function" then
p:OnActionChanged(self)
end
end)
eb:SetStickyFocus(true)
eb:SetHyperlinksEnabled(true)
eb:SetScript("OnHyperlinkClick", function(self, link, text, button)
local pos = string.find(self:GetText(), text, 1, 1)-1
self:HighlightText(pos, pos + #text)
if button == "RightButton" and link:match("^ilt%d+:") then
local replace = IsAltKeyDown() and text:match("|h(.-)|h") or removeEditorLinks(text)
self:Insert(replace)
self:HighlightText(pos, pos + #replace)
else
self:SetCursorPosition(pos + #text)
end
self:SetFocus()
end)
eb:SetScript("OnKeyDown", function(self, key)
if (key == "C" or key == "X") and (IsMacClient() and IsMetaKeyDown or IsControlKeyDown)() then
local stext = self:GetHighlightText()
if stext:match("[^|]|H.+|h.*|h") then
self:SetHighlightText(removeEditorLinks(stext), true)
self._rsText = key == "C" and stext or nil
end
end
end)
eb:SetScript("OnUpdate", function(self)
if self._rsText then
self:SetHighlightText(self._rsText, true)
self._rsText = nil
end
end)
eb:SetScript("OnTabPressed", function(self)
local p = self:GetParent()
if p and type(p.OnTabPressed) then
p:OnTabPressed()
end
end)
local function isImpTokenParsed(prefixText)
local suf = '|Hiptok|h|h'
return toMacroText(false, prefixText .. suf):sub(-#suf) ~= suf
end
local function stripUIEscapeCheck(preseq)
return (#preseq % 2) == 0 and preseq or nil
end
local function stripUIEscapes(link)
return (link:gsub("(|*)|H.-|h", stripUIEscapeCheck):gsub("(|*)|[hr]", stripUIEscapeCheck):gsub("(|*)|c%x%x%x%x%x%x%x%x", stripUIEscapeCheck))
end
hooksecurefunc("ChatEdit_InsertLink", function(link)
if not eb:HasFocus() then return end
local isItemLink = link:match("%f[|]|Hitem:")
local sid = not isItemLink and link:match("%f[|]|Hspell:(%d+)") or link:match("%f[|]|Htrade:[^:]+:(%d+)") or (CI_ERA and select(7,GetSpellInfo(link)))
local isCastableLink = sid and not IsPassiveSpell(sid+0)
local prefix, atext, skipPrefixSpace
if isItemLink or isCastableLink then
eb:Insert("") -- Inserting the link will clobber selection; do it now to i.a. converge cursor position
local cursor, text = eb:GetCursorPosition(), eb:GetText()
local isOnEmptyLineStart, lineCommand, lineStart do
local lep, sp, wep, ap = 0
while 1 do
sp, wep, ap, lep = text:match("()%S*()[^\n%S]*()[^\n]*()", lep+1)
if lep > cursor then
isOnEmptyLineStart = cursor < sp and ap == lep
lineCommand = cursor >= wep-1 and text:sub(sp, wep-1):lower()
lineStart = sp
break
end
end
end
local canTokenize, tokPrefix, tokSuffix, tokNoPreSpace do
if not isCastableLink then
elseif isOnEmptyLineStart then
canTokenize = true
elseif commandType[lineCommand] then
canTokenize = isImpTokenParsed(removeEditorLinks(text:sub(1, cursor)) .. ' ;')
if canTokenize then
local ct, nc = commandType[lineCommand], text:match('^[^%S\n]*(.?)', cursor+1)
tokSuffix = nc ~= '' and nc ~= '\n' and nc ~= ';' and (ct < 2 or nc ~= ',') and ((ct < 2 or nc == '[') and ';' or ',')
local excPrefix = text:sub(cursor, cursor) == '!'
local baseLineText = removeEditorLinks(text:sub(lineStart, cursor)) .. (excPrefix and '' or ' ')
tokPrefix = not isImpTokenParsed(baseLineText) and (ct >= 2 and isImpTokenParsed(baseLineText .. ',') and ',' or ';')
tokNoPreSpace = excPrefix and not tokPrefix
end
end
end
if isItemLink then
prefix = isOnEmptyLineStart and (C_Item.GetItemSpell(link) and SLASH_USE1 or SLASH_EQUIP1)
atext = C_Item.GetItemNameByID(link)
else
prefix = isOnEmptyLineStart and SLASH_CAST1
if canTokenize then
atext = newImpToken(eb, "spell", tostring(sid))
prefix = prefix or (atext and tokPrefix)
atext = atext and atext .. (tokSuffix or "")
skipPrefixSpace = tokNoPreSpace
end
end
end
prefix = skipPrefixSpace and (prefix or "") or (prefix and prefix .. " " or " ")
eb:Insert(prefix .. (atext or link:match("|h%[?(.-[^%]])%]?|h") or stripUIEscapes(link)))
end)
function eb:SetAction(owner, action)
local op = eb:GetParent()
if op and op ~= owner and type(op.OnEditorRelease) == "function" then
securecall(op.OnEditorRelease, op, self)
end
eb:SetParent(nil)
eb:ClearAllPoints()
eb:SetAllPoints(owner)
eb:SetParent(owner)
setImpText(eb, action[1] == "imptext" and action[2] or "")
eb:SetCursorPosition(0)
eb:Show()
end
function eb:GetAction(into)
into[1], into[2] = "imptext", getImpText(eb)
end
function eb:Release(owner)
if eb:IsOwned(owner) then
eb:SetParent(nil)
eb:ClearAllPoints()
eb:Hide()
end
end
function eb:IsOwned(owner)
return eb:GetParent() == owner
end
function eb:GetTabFocusWidget(_which)
return eb
end
AB:RegisterEditorPanel("imptext", eb)
end
function IM:EncodeCommands(macrotext, skipCacheRefresh)
assert(type(macrotext) == "string" and (skipCacheRefresh == nil or type(skipCacheRefresh) == "boolean"),
'Syntax: enctext = IM:EncodeCommands("macrotext"[, skipCacheRefresh])', 2)
return encodeMacro(macrotext, skipCacheRefresh)
end
function IM:DecodeCommands(enctext, skipCacheRefresh)
assert(type(enctext) == "string" and (skipCacheRefresh == nil or type(skipCacheRefresh) == "boolean"),
'Syntax: macrotext = IM:DecodeCommands("enctext"[, skipCacheRefresh])', 2)
return decodeMacro(enctext, skipCacheRefresh)
end
function IM:EncodeTokens(imptext, skipCacheRefresh)
assert(type(imptext) == "string" and (skipCacheRefresh == nil or type(skipCacheRefresh) == "boolean"),
'Syntax: imptext = IM:EncodeTokens("macrotext"[, skipCacheRefresh])', 2)
return quantizeMacro(imptext, skipCacheRefresh)
end
function IM:DecodeTokens(imptext)
assert(type(imptext) == "string", 'Syntax: macrotext = IM:DecodeTokens("imptext")', 2)
return toMacroText(true, imptext)
end
function IM:FormatTokens(imptext)
assert(type(imptext) == "string", 'Syntax: uitext = IM:FormatTokens("imptext")', 2)
return formatMacro(imptext)
end
function IM:AddTokenizableCommand(slashKey, behavesLikeCommand)
assert(type(slashKey) == 'string' and type(behavesLikeCommand) == 'string',
'Syntax: IM:AddTokenizableCommand("slashKey"[, "behavesLikeCommand"])', 2)
assert(type(_G['SLASH_' .. slashKey .. '1']) == 'string', 'Invalid slash command key %q', 2, slashKey)
assert((commandType[behavesLikeCommand] or -1) >= 0, 'Unrecognized behaves-like command %q', 2, behavesLikeCommand)
addCommandType(slashKey, commandType[behavesLikeCommand])
AB:NotifyObservers("imptext")
end
function IM:SetMountPreference(groundSpellID, flyingSpellID, dragonSpellID)
assert((type(groundSpellID) == "number" or not groundSpellID) and
(type(flyingSpellID) == "number" or not flyingSpellID) and
(type(dragonSpellID) == "number" or not dragonSpellID),
'Syntax: groundSpellID, flyingSpellID, dragonSpellID = IM:SetMountPreference(groundSpellID|false|nil, flyingSpellID|false|nil, dragonSpellID|false|nil)', 2)
return setMountPreference(groundSpellID, flyingSpellID, dragonSpellID)
end
-- HIDDEN, UNSUPPORTED METHODS: May vanish at any time.
local hum = {}
setmetatable(IM, {__index=hum})
hum.HUM = hum
hum.GetModernTalentSpells = GetModernTalentSpells
AB:RegisterModule("Imp", {
compatible=function(_, maj, rev)
if maj == MAJ and (rev == nil or rev <= REV) then
return IM
end
end
})