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.
471 lines
14 KiB
471 lines
14 KiB
if not WeakAuras.IsCorrectVersion() then return end
|
|
local AddonName, Private = ...
|
|
|
|
local WeakAuras = WeakAuras
|
|
local L = WeakAuras.L
|
|
|
|
local LCD
|
|
if WeakAuras.IsClassic() then
|
|
LCD = LibStub("LibClassicDurations")
|
|
LCD:RegisterFrame("WeakAuras")
|
|
end
|
|
|
|
local UnitAura = UnitAura
|
|
-- Unit Aura functions that return info about the first Aura matching the spellName or spellID given on the unit.
|
|
local WA_GetUnitAura = function(unit, spell, filter)
|
|
if filter and not filter:upper():find("FUL") then
|
|
filter = filter.."|HELPFUL"
|
|
end
|
|
for i = 1, 255 do
|
|
local name, _, _, _, _, _, _, _, _, spellId = UnitAura(unit, i, filter)
|
|
if not name then return end
|
|
if spell == spellId or spell == name then
|
|
return UnitAura(unit, i, filter)
|
|
end
|
|
end
|
|
end
|
|
|
|
if WeakAuras.IsClassic() then
|
|
local WA_GetUnitAuraBase = WA_GetUnitAura
|
|
WA_GetUnitAura = function(unit, spell, filter)
|
|
local name, icon, count, debuffType, duration, expirationTime, source, isStealable, nameplateShowPersonal, spellId, canApplyAura, isBossDebuff, castByPlayer, nameplateShowAll, timeMod = WA_GetUnitAuraBase(unit, spell, filter)
|
|
if spellId then
|
|
local durationNew, expirationTimeNew = LCD:GetAuraDurationByUnit(unit, spellId, source, name)
|
|
if duration == 0 and durationNew then
|
|
duration = durationNew
|
|
expirationTime = expirationTimeNew
|
|
end
|
|
end
|
|
return name, icon, count, debuffType, duration, expirationTime, source, isStealable, nameplateShowPersonal, spellId, canApplyAura, isBossDebuff, castByPlayer, nameplateShowAll, timeMod
|
|
end
|
|
end
|
|
|
|
local WA_GetUnitBuff = function(unit, spell, filter)
|
|
filter = filter and filter.."|HELPFUL" or "HELPFUL"
|
|
return WA_GetUnitAura(unit, spell, filter)
|
|
end
|
|
|
|
local WA_GetUnitDebuff = function(unit, spell, filter)
|
|
filter = filter and filter.."|HARMFUL" or "HARMFUL"
|
|
return WA_GetUnitAura(unit, spell, filter)
|
|
end
|
|
|
|
-- Function to assist iterating group members whether in a party or raid.
|
|
local WA_IterateGroupMembers = function(reversed, forceParty)
|
|
local unit = (not forceParty and IsInRaid()) and 'raid' or 'party'
|
|
local numGroupMembers = unit == 'party' and GetNumSubgroupMembers() or GetNumGroupMembers()
|
|
local i = reversed and numGroupMembers or (unit == 'party' and 0 or 1)
|
|
return function()
|
|
local ret
|
|
if i == 0 and unit == 'party' then
|
|
ret = 'player'
|
|
elseif i <= numGroupMembers and i > 0 then
|
|
ret = unit .. i
|
|
end
|
|
i = i + (reversed and -1 or 1)
|
|
return ret
|
|
end
|
|
end
|
|
|
|
-- Wrapping a unit's name in its class colour is very common in custom Auras
|
|
local WA_ClassColorName = function(unit)
|
|
if unit and UnitExists(unit) then
|
|
local name = UnitName(unit)
|
|
local _, class = UnitClass(unit)
|
|
if not class then
|
|
return name
|
|
else
|
|
local classData = (CUSTOM_CLASS_COLORS or RAID_CLASS_COLORS)[class]
|
|
local coloredName = ("|c%s%s|r"):format(classData.colorStr, name)
|
|
return coloredName
|
|
end
|
|
else
|
|
return "" -- ¯\_(ツ)_/¯
|
|
end
|
|
end
|
|
|
|
WeakAuras.WA_ClassColorName = WA_ClassColorName
|
|
|
|
-- UTF-8 Sub is pretty commonly needed
|
|
local WA_Utf8Sub = function(input, size)
|
|
local output = ""
|
|
if type(input) ~= "string" then
|
|
return output
|
|
end
|
|
local i = 1
|
|
while (size > 0) do
|
|
local byte = input:byte(i)
|
|
if not byte then
|
|
return output
|
|
end
|
|
if byte < 128 then
|
|
-- ASCII byte
|
|
output = output .. input:sub(i, i)
|
|
size = size - 1
|
|
elseif byte < 192 then
|
|
-- Continuation bytes
|
|
output = output .. input:sub(i, i)
|
|
elseif byte < 244 then
|
|
-- Start bytes
|
|
output = output .. input:sub(i, i)
|
|
size = size - 1
|
|
end
|
|
i = i + 1
|
|
end
|
|
|
|
-- Add any bytes that are part of the sequence
|
|
while (true) do
|
|
local byte = input:byte(i)
|
|
if byte and byte >= 128 and byte < 192 then
|
|
output = output .. input:sub(i, i)
|
|
else
|
|
break
|
|
end
|
|
i = i + 1
|
|
end
|
|
|
|
return output
|
|
end
|
|
|
|
WeakAuras.WA_Utf8Sub = WA_Utf8Sub
|
|
|
|
local LCG = LibStub("LibCustomGlow-1.0")
|
|
WeakAuras.ShowOverlayGlow = LCG.ButtonGlow_Start
|
|
WeakAuras.HideOverlayGlow = LCG.ButtonGlow_Stop
|
|
|
|
local LGF = LibStub("LibGetFrame-1.0")
|
|
WeakAuras.GetUnitFrame = LGF.GetUnitFrame
|
|
WeakAuras.GetUnitNameplate = function(unit)
|
|
if Private.multiUnitUnits.nameplate[unit] then
|
|
return LGF.GetUnitNameplate(unit)
|
|
end
|
|
end
|
|
|
|
local blockedFunctions = {
|
|
-- Lua functions that may allow breaking out of the environment
|
|
getfenv = true,
|
|
setfenv = true,
|
|
loadstring = true,
|
|
pcall = true,
|
|
xpcall = true,
|
|
-- blocked WoW API
|
|
SendMail = true,
|
|
SetTradeMoney = true,
|
|
AddTradeMoney = true,
|
|
PickupTradeMoney = true,
|
|
PickupPlayerMoney = true,
|
|
TradeFrame = true,
|
|
MailFrame = true,
|
|
EnumerateFrames = true,
|
|
RunScript = true,
|
|
AcceptTrade = true,
|
|
SetSendMailMoney = true,
|
|
EditMacro = true,
|
|
DevTools_DumpCommand = true,
|
|
hash_SlashCmdList = true,
|
|
CreateMacro = true,
|
|
SetBindingMacro = true,
|
|
GuildDisband = true,
|
|
GuildUninvite = true,
|
|
securecall = true,
|
|
DeleteCursorItem = true,
|
|
}
|
|
|
|
local blockedTables = {
|
|
SlashCmdList = true,
|
|
SendMailMailButton = true,
|
|
SendMailMoneyGold = true,
|
|
MailFrameTab2 = true,
|
|
}
|
|
|
|
local aura_environments = {}
|
|
-- nil == Not initiliazed
|
|
-- 1 == config initialized
|
|
-- 2 == fully initialized
|
|
local environment_initialized = {}
|
|
|
|
function Private.IsEnvironmentInitialized(id)
|
|
return environment_initialized[id] == 2
|
|
end
|
|
|
|
function Private.DeleteAuraEnvironment(id)
|
|
aura_environments[id] = nil
|
|
environment_initialized[id] = nil
|
|
end
|
|
|
|
function Private.RenameAuraEnvironment(oldid, newid)
|
|
aura_environments[oldid], aura_environments[newid] = nil, aura_environments[oldid]
|
|
environment_initialized[oldid], environment_initialized[newid] = nil, environment_initialized[oldid]
|
|
end
|
|
|
|
local current_uid = nil
|
|
local current_aura_env = nil
|
|
-- Stack of of aura environments/uids, allows use of recursive aura activations through calls to WeakAuras.ScanEvents().
|
|
local aura_env_stack = {}
|
|
|
|
function Private.ClearAuraEnvironment(id)
|
|
environment_initialized[id] = nil;
|
|
end
|
|
|
|
function Private.ActivateAuraEnvironmentForRegion(region, onlyConfig)
|
|
Private.ActivateAuraEnvironment(region.id, region.cloneId, region.state, region.states, onlyConfig)
|
|
end
|
|
|
|
function Private.ActivateAuraEnvironment(id, cloneId, state, states, onlyConfig)
|
|
local data = WeakAuras.GetData(id)
|
|
local region = WeakAuras.GetRegion(id, cloneId)
|
|
if not data then
|
|
-- Pop the last aura_env from the stack, and update current_aura_env appropriately.
|
|
tremove(aura_env_stack)
|
|
if aura_env_stack[#aura_env_stack] then
|
|
current_aura_env, current_uid = unpack(aura_env_stack[#aura_env_stack])
|
|
else
|
|
current_aura_env = nil
|
|
current_uid = nil
|
|
end
|
|
else
|
|
-- Existing config is initialized to a high enough value
|
|
if environment_initialized[id] == 2 or (onlyConfig and environment_initialized[id] == 1) then
|
|
-- Point the current environment to the correct table
|
|
current_uid = data.uid
|
|
current_aura_env = aura_environments[id]
|
|
current_aura_env.id = id
|
|
current_aura_env.cloneId = cloneId
|
|
current_aura_env.state = state
|
|
current_aura_env.states = states
|
|
current_aura_env.region = region
|
|
-- Push the new environment onto the stack
|
|
tinsert(aura_env_stack, {current_aura_env, data.uid})
|
|
elseif onlyConfig then
|
|
environment_initialized[id] = 1
|
|
aura_environments[id] = {}
|
|
current_uid = data.uid
|
|
current_aura_env = aura_environments[id]
|
|
current_aura_env.id = id
|
|
current_aura_env.cloneId = cloneId
|
|
current_aura_env.state = state
|
|
current_aura_env.states = states
|
|
current_aura_env.region = region
|
|
tinsert(aura_env_stack, {current_aura_env, data.uid})
|
|
|
|
if not data.controlledChildren then
|
|
current_aura_env.config = CopyTable(data.config)
|
|
end
|
|
else
|
|
-- Either this aura environment has not yet been initialized, or it was reset via an edit in WeakaurasOptions
|
|
environment_initialized[id] = 2
|
|
aura_environments[id] = aura_environments[id] or {}
|
|
current_uid = data.uid
|
|
current_aura_env = aura_environments[id]
|
|
current_aura_env.id = id
|
|
current_aura_env.cloneId = cloneId
|
|
current_aura_env.state = state
|
|
current_aura_env.states = states
|
|
current_aura_env.region = region
|
|
-- push new environment onto the stack
|
|
tinsert(aura_env_stack, {current_aura_env, data.uid})
|
|
|
|
if data.controlledChildren then
|
|
current_aura_env.child_envs = {}
|
|
for dataIndex, childID in ipairs(data.controlledChildren) do
|
|
local childData = WeakAuras.GetData(childID)
|
|
if childData then
|
|
if not environment_initialized[childID] then
|
|
Private.ActivateAuraEnvironment(childID)
|
|
Private.ActivateAuraEnvironment()
|
|
end
|
|
current_aura_env.child_envs[dataIndex] = aura_environments[childID]
|
|
end
|
|
end
|
|
else
|
|
if environment_initialized[id] == 1 then
|
|
-- Already done
|
|
else
|
|
current_aura_env.config = CopyTable(data.config)
|
|
end
|
|
end
|
|
-- Finally, run the init function if supplied
|
|
local actions = data.actions.init
|
|
if(actions and actions.do_custom and actions.custom) then
|
|
local func = Private.customActionsFunctions[id]["init"]
|
|
if func then
|
|
xpcall(func, geterrorhandler())
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local function blocked(key)
|
|
Private.AuraWarnings.UpdateWarning(current_uid, "SandboxForbidden", "error",
|
|
string.format(L["Forbidden function or table: %s"], key))
|
|
end
|
|
|
|
local function MakeReadOnly(input, options)
|
|
return setmetatable({},
|
|
{
|
|
__index = function(t, k)
|
|
if options.blockedFunctions[k] then
|
|
options.blocked(k)
|
|
return function() end
|
|
elseif options.blockedTables[k] then
|
|
options.blocked(k)
|
|
return {}
|
|
elseif options.override[k] then
|
|
return options.override[k]
|
|
else
|
|
return input[k]
|
|
end
|
|
end,
|
|
__newindex = options.setBlocked,
|
|
__metatable = false
|
|
})
|
|
end
|
|
|
|
local FakeWeakAurasMixin = {
|
|
blockedFunctions = {
|
|
-- Other addons might use these, so before moving them to the Private space, we need
|
|
-- to discuss these. But Auras have no purpose for calling these
|
|
Add = true,
|
|
AddMany = true,
|
|
AddManyFromAddons = true,
|
|
Delete = true,
|
|
HideOptions = true,
|
|
Rename = true,
|
|
NewAura = true,
|
|
OptionsFrame = true,
|
|
RegisterAddon = true,
|
|
RegisterDisplay = true,
|
|
RegisterRegionOptions = true,
|
|
RegisterSubRegionOptions = true,
|
|
RegisterSubRegionType = true,
|
|
RegisterRegionType = true,
|
|
RegisterTriggerSystem = true,
|
|
RegisterTriggerSystemOptions = true,
|
|
ShowOptions = true,
|
|
-- Note these shouldn't exist in the WeakAuras namespace, but moving them takes a bit of effort,
|
|
-- so for now just block them and clean them up later
|
|
CollisionResolved = true,
|
|
ClearAndUpdateOptions = true,
|
|
CloseCodeReview = true,
|
|
CloseImportExport = true,
|
|
CreateTemplateView = true,
|
|
FillOptions = true,
|
|
FindUnusedId = true,
|
|
GetMoverSizerId = true,
|
|
GetDisplayButton = true,
|
|
Import = true,
|
|
NewDisplayButton = true,
|
|
OpenCodeReview = true,
|
|
PickDisplay = true,
|
|
SetMoverSizer = true,
|
|
SetImporting = true,
|
|
ToggleOptions = true,
|
|
UpdateDisplayButton = true,
|
|
UpdateGroupOrders = true,
|
|
UpdateThumbnail = true,
|
|
validate = true,
|
|
getDefaultGlow = true,
|
|
},
|
|
blockedTables = {
|
|
AuraWarnings = true,
|
|
ModelPaths = true,
|
|
regionPrototype = true,
|
|
-- Note these shouldn't exist in the WeakAuras namespace, but moving them takes a bit of effort,
|
|
-- so for now just block them and clean them up later
|
|
data_stub = true,
|
|
displayButtons = true,
|
|
regionTypes = true,
|
|
regionOptions = true,
|
|
spellCache = true,
|
|
triggerTemplates = true,
|
|
frames = true,
|
|
loadFrame = true,
|
|
unitLoadFrame = true,
|
|
importDisplayButtons = true,
|
|
loaded = true
|
|
|
|
},
|
|
override = {
|
|
me = GetUnitName("player", true),
|
|
myGUID = UnitGUID("player")
|
|
},
|
|
blocked = blocked,
|
|
setBlocked = function()
|
|
Private.AuraWarnings.UpdateWarning(current_uid, "FakeWeakAurasSet", "error",
|
|
L["Writing to the WeakAuras table is not allowed."], true)
|
|
end
|
|
}
|
|
|
|
local FakeWeakAuras = MakeReadOnly(WeakAuras, FakeWeakAurasMixin)
|
|
|
|
local overridden = {
|
|
WA_GetUnitAura = WA_GetUnitAura,
|
|
WA_GetUnitBuff = WA_GetUnitBuff,
|
|
WA_GetUnitDebuff = WA_GetUnitDebuff,
|
|
WA_IterateGroupMembers = WA_IterateGroupMembers,
|
|
WA_ClassColorName = WA_ClassColorName,
|
|
WA_Utf8Sub = WA_Utf8Sub,
|
|
ActionButton_ShowOverlayGlow = WeakAuras.ShowOverlayGlow,
|
|
ActionButton_HideOverlayGlow = WeakAuras.HideOverlayGlow,
|
|
WeakAuras = FakeWeakAuras
|
|
}
|
|
|
|
local env_getglobal
|
|
local exec_env = setmetatable({},
|
|
{
|
|
__index = function(t, k)
|
|
if k == "_G" then
|
|
return t
|
|
elseif k == "getglobal" then
|
|
return env_getglobal
|
|
elseif k == "aura_env" then
|
|
return current_aura_env
|
|
elseif blockedFunctions[k] then
|
|
blocked(k)
|
|
return function() end
|
|
elseif blockedTables[k] then
|
|
blocked(k)
|
|
return {}
|
|
elseif overridden[k] then
|
|
return overridden[k]
|
|
else
|
|
return _G[k]
|
|
end
|
|
end,
|
|
__newindex = function(table, key, value)
|
|
if _G[key] then
|
|
Private.AuraWarnings.UpdateWarning(current_uid, "OverridingGlobal", "warning",
|
|
string.format(L["The aura has overwritten the global '%s', this might affect other auras."], key))
|
|
end
|
|
rawset(table, key, value)
|
|
end,
|
|
__metatable = false
|
|
})
|
|
|
|
function env_getglobal(k)
|
|
return exec_env[k]
|
|
end
|
|
|
|
local function_cache = {}
|
|
function WeakAuras.LoadFunction(string, id, inTrigger)
|
|
if function_cache[string] then
|
|
return function_cache[string]
|
|
else
|
|
local loadedFunction, errorString = loadstring(string, "Error in: " .. (id or "Unknown") .. (inTrigger and ("':'".. inTrigger) or ""))
|
|
if errorString then
|
|
print(errorString)
|
|
else
|
|
setfenv(loadedFunction, exec_env)
|
|
local success, func = pcall(assert(loadedFunction))
|
|
if success then
|
|
function_cache[string] = func
|
|
return func
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function Private.GetSanitizedGlobal(key)
|
|
return exec_env[key]
|
|
end
|
|
|