4316 lines
142 KiB
Lua
4316 lines
142 KiB
Lua
--[[ GenericTrigger.lua
|
|
This file contains the generic trigger system. That is every trigger except the aura triggers.
|
|
|
|
It registers the GenericTrigger table for the generic trigger types and "custom" and has the following API:
|
|
|
|
Add(data)
|
|
Adds a display, creating all internal data structures for all triggers.
|
|
|
|
Delete(id)
|
|
Deletes all triggers for display id.
|
|
|
|
Rename(oldid, newid)
|
|
Updates all trigger information from oldid to newid.
|
|
|
|
LoadDisplay(id)
|
|
Loads all triggers of display id.
|
|
|
|
UnloadAll
|
|
Unloads all triggers.
|
|
|
|
UnloadDisplays(id)
|
|
Unloads all triggers of the display ids.
|
|
|
|
Modernize(data)
|
|
Modernizes all generic triggers in data.
|
|
|
|
#####################################################
|
|
# Helper functions mainly for the WeakAuras Options #
|
|
#####################################################
|
|
|
|
GetOverlayInfo(data, triggernum)
|
|
Returns a table containing the names of all overlays
|
|
|
|
CanHaveTooltip(data, triggernum)
|
|
Returns the type of tooltip to show for the trigger.
|
|
|
|
GetNameAndIcon(data, triggernum)
|
|
Returns the name and icon to show in the options.
|
|
|
|
GetAdditionalProperties(data, triggernum)
|
|
Returns the a tooltip for the additional properties.
|
|
|
|
GetProgressSources(data, triggernum, outValues)
|
|
Fills outValues with the potential progress sources
|
|
|
|
GetTriggerConditions(data, triggernum)
|
|
Returns potential conditions that this trigger provides.
|
|
]]--
|
|
if not WeakAuras.IsLibsOK() then return end
|
|
local AddonName, Private = ...
|
|
|
|
-- Lua APIs
|
|
local tinsert, tconcat, wipe = table.insert, table.concat, wipe
|
|
local tonumber, tostring, pairs, type = tonumber, tostring, pairs, type
|
|
local error = error
|
|
|
|
local WeakAuras = WeakAuras;
|
|
local L = WeakAuras.L;
|
|
local GenericTrigger = {};
|
|
|
|
local event_prototypes = Private.event_prototypes;
|
|
|
|
local timer = WeakAuras.timer;
|
|
|
|
local events = {}
|
|
local loaded_events = {}
|
|
local loaded_unit_events = {};
|
|
local watched_trigger_events = Private.watched_trigger_events
|
|
local delayTimerEvents = {}
|
|
local loaded_auras = {}; -- id to bool map
|
|
|
|
-- Local functions
|
|
local LoadEvent, HandleEvent, HandleUnitEvent, TestForTriState, TestForToggle, TestForLongString, TestForMultiSelect
|
|
local ConstructTest, ConstructFunction
|
|
|
|
local nameplateExists = {}
|
|
|
|
---@param unit UnitToken
|
|
---@param smart? boolean
|
|
---@return boolean unitExists
|
|
function WeakAuras.UnitExistsFixed(unit, smart)
|
|
if #unit > 9 and unit:sub(1, 9) == "nameplate" then
|
|
return nameplateExists[unit]
|
|
end
|
|
if smart and IsInRaid() then
|
|
if unit:sub(1, 5) == "party" or unit == "player" or unit == "pet" then
|
|
return false
|
|
end
|
|
end
|
|
return UnitExists(unit) or UnitGUID(unit)
|
|
end
|
|
|
|
function WeakAuras.split(input)
|
|
input = input or "";
|
|
local ret = {};
|
|
local split, element = nil, nil;
|
|
split = input:find("[,%s]");
|
|
while(split) do
|
|
element, input = input:sub(1, split-1), input:sub(split+1);
|
|
if(element ~= "") then
|
|
tinsert(ret, element);
|
|
end
|
|
split = input:find("[,%s]");
|
|
end
|
|
if(input ~= "") then
|
|
tinsert(ret, input);
|
|
end
|
|
return ret;
|
|
end
|
|
|
|
local function findFirstOf(input, words, start, plain)
|
|
local startPos, endPos
|
|
for _, w in ipairs(words) do
|
|
local s, e = input:find(w, start, plain)
|
|
if s and (not startPos or startPos > s) then
|
|
startPos, endPos = s, e
|
|
end
|
|
end
|
|
return startPos, endPos
|
|
end
|
|
|
|
function Private.splitAtOr(input)
|
|
input = input or ""
|
|
local ret = {}
|
|
local splitStart, splitEnd, element = nil, nil, nil
|
|
local separators = { "|", " or "}
|
|
splitStart, splitEnd = findFirstOf(input, separators, 1, true);
|
|
while(splitStart) do
|
|
element, input = input:sub(1, splitStart -1 ), input:sub(splitEnd + 1)
|
|
if(element ~= "") then
|
|
tinsert(ret, element)
|
|
end
|
|
splitStart, splitEnd = findFirstOf(input, separators, 1, true);
|
|
end
|
|
if(input ~= "") then
|
|
tinsert(ret, input)
|
|
end
|
|
return ret;
|
|
end
|
|
|
|
function TestForTriState(trigger, arg)
|
|
local name = arg.name;
|
|
local test;
|
|
if(trigger["use_"..name] == false) then
|
|
test = "(not "..name..")";
|
|
elseif(trigger["use_"..name]) then
|
|
if(arg.test) then
|
|
test = "("..arg.test:format(trigger[name])..")";
|
|
else
|
|
test = name;
|
|
end
|
|
end
|
|
return test;
|
|
end
|
|
|
|
function TestForToggle(trigger, arg)
|
|
local name = arg.name;
|
|
local test;
|
|
if(trigger["use_"..name]) then
|
|
if(arg.test) then
|
|
test = "("..arg.test:format(trigger[name])..")";
|
|
else
|
|
test = name;
|
|
end
|
|
end
|
|
return test;
|
|
end
|
|
|
|
function TestForLongString(trigger, arg)
|
|
local name = arg.name;
|
|
local test;
|
|
local needle = trigger[name]
|
|
local caseInsensitive = arg.canBeCaseInsensitive and trigger[name .. "_caseInsensitive"]
|
|
if(trigger[name.."_operator"] == "==") then
|
|
if caseInsensitive then
|
|
test = ("(%s and (%s):lower() == (%s):lower())"):format(name, name, Private.QuotedString(needle))
|
|
else
|
|
test = ("(%s == %s)"):format(name, Private.QuotedString(needle))
|
|
end
|
|
elseif(trigger[name.."_operator"] == "find('%s')") then
|
|
if caseInsensitive then
|
|
test = ("(%s and %s:lower():find((%s):lower(), 1, true))"):format(name, name, Private.QuotedString(needle))
|
|
else
|
|
test = ("(%s and %s:find(%s, 1, true))"):format(name, name, Private.QuotedString(needle))
|
|
end
|
|
elseif(trigger[name.."_operator"] == "match('%s')") then
|
|
if caseInsensitive then
|
|
test = ("(%s and %s:lower():match((%s):lower()))"):format(name, name, Private.QuotedString(needle))
|
|
else
|
|
test = ("(%s and %s:match(%s))"):format(name, name, Private.QuotedString(needle))
|
|
end
|
|
end
|
|
return test;
|
|
end
|
|
|
|
function TestForMultiSelect(trigger, arg)
|
|
local name = arg.name;
|
|
local test;
|
|
if(trigger["use_"..name] == false) then -- multi selection
|
|
test = "(";
|
|
local any = false;
|
|
if trigger[name] and trigger[name].multi then
|
|
for value, _ in pairs(trigger[name].multi) do
|
|
if not arg.test then
|
|
test = test..name.."=="..(tonumber(value) or "[["..value.."]]").." or ";
|
|
else
|
|
test = test..arg.test:format(tonumber(value) or "[["..value.."]]").." or ";
|
|
end
|
|
any = true;
|
|
end
|
|
end
|
|
if(any) then
|
|
test = test:sub(1, -5);
|
|
else
|
|
test = "(false";
|
|
end
|
|
test = test..")";
|
|
elseif(trigger["use_"..name]) then -- single selection
|
|
local value = trigger[name] and trigger[name].single;
|
|
if (not value) then
|
|
test = "false";
|
|
return test;
|
|
end
|
|
if not arg.test then
|
|
test = trigger[name].single and "("..name.."=="..(tonumber(value) or "[["..value.."]]")..")";
|
|
else
|
|
test = trigger[name].single and "("..arg.test:format(tonumber(value) or "[["..value.."]]")..")";
|
|
end
|
|
end
|
|
return test;
|
|
end
|
|
|
|
local function singleTest(arg, trigger, name, value, operator, use_exact)
|
|
local number = tonumber(value)
|
|
if(arg.type == "tristate") then
|
|
return TestForTriState(trigger, arg);
|
|
elseif(arg.type == "multiselect") then
|
|
return TestForMultiSelect(trigger, arg);
|
|
elseif(arg.type == "toggle") then
|
|
return TestForToggle(trigger, arg);
|
|
elseif (arg.type == "spell" or arg.type == "item") then
|
|
if arg.test then
|
|
if arg.showExactOption then
|
|
return "("..arg.test:format(value, tostring(use_exact) or "false") ..")";
|
|
else
|
|
return "("..arg.test:format(value)..")";
|
|
end
|
|
else
|
|
return "(".. name .." and "..name.."==" ..(number or ("\""..(tostring(value) or "").."\""))..")";
|
|
end
|
|
elseif(arg.test) then
|
|
return "("..arg.test:format(tostring(value) or "")..")";
|
|
elseif(arg.type == "longstring" and operator) then
|
|
return TestForLongString(trigger, arg);
|
|
elseif (arg.type == "string" or arg.type == "select") then
|
|
return "(".. name .." and "..name.."==" ..(number or ("\""..(tostring(value) or "").."\""))..")";
|
|
elseif (arg.type == "number") then
|
|
return "(".. name .." and "..name..(operator or "==")..(number or 0) ..")";
|
|
else
|
|
-- Should be unused
|
|
return "(".. name .." and "..name..(operator or "==")..(number or ("\""..(tostring(value) or 0).."\""))..")";
|
|
end
|
|
end
|
|
|
|
function ConstructTest(trigger, arg, testGroups, preambleGroups)
|
|
local test
|
|
local preamble
|
|
local name = arg.name;
|
|
|
|
if arg.preamble then
|
|
if not arg.preambleGroup or not preambleGroups[arg.preambleGroup] then
|
|
preamble = arg.preamble:format(trigger[name] or "")
|
|
end
|
|
if arg.preambleGroup then
|
|
preambleGroups[arg.preambleGroup] = true
|
|
end
|
|
end
|
|
|
|
if arg.hidden
|
|
or arg.type == "tristate"
|
|
or arg.type == "toggle"
|
|
or (arg.type == "multiselect" and trigger["use_"..name] ~= nil)
|
|
or ((trigger["use_"..name] or arg.required) and trigger[name])
|
|
then
|
|
if arg.multiEntry then
|
|
if type(trigger[name]) == "table" and #trigger[name] > 0 then
|
|
test = ""
|
|
for i, value in ipairs(trigger[name]) do
|
|
local operator = name and type(trigger[name.."_operator"]) == "table" and trigger[name.."_operator"][i]
|
|
local use_exact = name and type(trigger["use_exact_" .. name]) == "table" and trigger["use_exact_" .. name][i]
|
|
|
|
if arg.multiEntry.operator == "preamble" then
|
|
preamble = preamble and (preamble .. "\n") or ""
|
|
preamble = preamble .. arg.multiEntry.preambleAdd:format(value)
|
|
else
|
|
local single = singleTest(arg, trigger, name, value, operator, use_exact)
|
|
if single then
|
|
if test ~= "" then
|
|
test = test .. arg.multiEntry.operator
|
|
end
|
|
test = test .. single
|
|
end
|
|
end
|
|
end
|
|
|
|
if arg.multiEntry.operator == "preamble" then
|
|
test = arg.test
|
|
end
|
|
|
|
if test == "" then
|
|
test = nil
|
|
else
|
|
test = "(" .. test .. ")"
|
|
end
|
|
end
|
|
else
|
|
local value = trigger[name]
|
|
local operator = name and trigger[name.."_operator"]
|
|
local use_exact = name and trigger["use_exact_" .. name]
|
|
test = singleTest(arg, trigger, name, value, operator, use_exact)
|
|
end
|
|
end
|
|
|
|
if not test or test == "(true)" or arg.testGroup and testGroups[arg.testGroup] then
|
|
return nil, preamble
|
|
end
|
|
|
|
return test, preamble
|
|
end
|
|
|
|
function ConstructFunction(prototype, trigger)
|
|
if (prototype.triggerFunction) then
|
|
return prototype.triggerFunction(trigger);
|
|
end
|
|
|
|
local input;
|
|
if (prototype.statesParameter) then
|
|
if prototype.countEvents then
|
|
input = {"state", "counter", "event"};
|
|
else
|
|
input = {"state", "event"};
|
|
end
|
|
else
|
|
if prototype.countEvents then
|
|
input = {"counter", "event"};
|
|
else
|
|
input = {"event"};
|
|
end
|
|
end
|
|
|
|
local required = {};
|
|
local tests = {};
|
|
local debug = {};
|
|
local store = {};
|
|
local init;
|
|
local preambles = "\n"
|
|
local orConjunctionGroups = {}
|
|
local preambleGroups = {}
|
|
local testGroups = {}
|
|
if(prototype.init) then
|
|
init = prototype.init(trigger);
|
|
else
|
|
init = "";
|
|
end
|
|
for index, arg in pairs(prototype.args) do
|
|
local enable = arg.type ~= "description";
|
|
if(type(arg.enable) == "function") then
|
|
enable = arg.enable(trigger);
|
|
elseif type(arg.enable) == "boolean" then
|
|
enable = arg.enable
|
|
end
|
|
if(enable) then
|
|
local name = arg.name;
|
|
if not(arg.name or arg.hidden) then
|
|
tinsert(input, "_");
|
|
else
|
|
if(arg.init == "arg") then
|
|
tinsert(input, name);
|
|
elseif(arg.init) then
|
|
init = init.."local "..name.." = "..arg.init.."\n";
|
|
end
|
|
if (arg.store) then
|
|
tinsert(store, name);
|
|
end
|
|
local test, preamble = ConstructTest(trigger, arg, testGroups, preambleGroups);
|
|
if (test) then
|
|
if(arg.required) then
|
|
tinsert(required, test);
|
|
else
|
|
if arg.orConjunctionGroup then
|
|
orConjunctionGroups[arg.orConjunctionGroup] = orConjunctionGroups[arg.orConjunctionGroup] or {}
|
|
tinsert(orConjunctionGroups[arg.orConjunctionGroup], test)
|
|
else
|
|
tinsert(tests, test);
|
|
end
|
|
end
|
|
if(arg.debug) then
|
|
tinsert(debug, arg.debug:format(trigger[name]));
|
|
end
|
|
end
|
|
if (preamble) then
|
|
preambles = preambles .. preamble .. "\n"
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
for _, orConjunctionGroup in pairs(orConjunctionGroups) do
|
|
tinsert(tests, "("..table.concat(orConjunctionGroup, " or ")..")")
|
|
end
|
|
|
|
local ret = {preambles .. "return function("..tconcat(input, ", ")..")\n"}
|
|
if init then
|
|
table.insert(ret, init)
|
|
end
|
|
if #debug > 0 then
|
|
table.insert(ret, tconcat(debug, "\n") or "")
|
|
end
|
|
|
|
table.insert(ret, "if("..((#required > 0) and tconcat(required, " and ").." and " or ""))
|
|
table.insert(ret, #tests > 0 and tconcat(tests, " and ") or "true")
|
|
table.insert(ret, ") then\n")
|
|
if(#debug > 0) then
|
|
table.insert(ret, "print('ret: true');\n")
|
|
end
|
|
|
|
if (prototype.statesParameter == "all") then
|
|
table.insert(ret, " state[cloneId] = state[cloneId] or {}\n")
|
|
table.insert(ret, " state = state[cloneId]\n")
|
|
table.insert(ret, " state.changed = true\n")
|
|
end
|
|
|
|
if prototype.countEvents then
|
|
table.insert(ret, " local count = counter:GetNext()\n")
|
|
if trigger.use_count and type(trigger.count) == "string" and trigger.count ~= "" then
|
|
table.insert(ret, " local match = counter:Match()")
|
|
table.insert(ret, " if not match then return false end\n")
|
|
end
|
|
table.insert(ret, " state.count = count\n")
|
|
table.insert(ret, " state.changed = true\n")
|
|
end
|
|
|
|
for _, v in ipairs(store) do
|
|
table.insert(ret, " if (state." .. v .. " ~= " .. v .. ") then\n")
|
|
table.insert(ret, " state." .. v .. " = " .. v .. "\n")
|
|
table.insert(ret, " state.changed = true\n")
|
|
table.insert(ret, " end\n")
|
|
end
|
|
table.insert(ret, "return true else return false end end")
|
|
|
|
return table.concat(ret);
|
|
end
|
|
|
|
function Private.EndEvent(state)
|
|
if state then
|
|
if (state.show ~= false and state.show ~= nil) then
|
|
state.show = false;
|
|
state.changed = true;
|
|
end
|
|
return state.changed;
|
|
else
|
|
return false
|
|
end
|
|
end
|
|
|
|
local function RunOverlayFuncs(event, state, id, errorHandler)
|
|
state.additionalProgress = state.additionalProgress or {};
|
|
local changed = false;
|
|
for i, overlayFunc in ipairs(event.overlayFuncs) do
|
|
state.additionalProgress[i] = state.additionalProgress[i] or {};
|
|
local additionalProgress = state.additionalProgress[i];
|
|
local ok, a, b, c = pcall(overlayFunc, event.trigger, state);
|
|
if (not ok) then
|
|
(errorHandler or Private.GetErrorHandlerId(id, L["Overlay %s"]:format(i)))(a)
|
|
additionalProgress.min = nil;
|
|
additionalProgress.max = nil;
|
|
additionalProgress.direction = nil;
|
|
additionalProgress.width = nil;
|
|
additionalProgress.offset = nil;
|
|
elseif (type(a) == "string") then
|
|
if (additionalProgress.direction ~= a) then
|
|
additionalProgress.direction = a;
|
|
changed = true;
|
|
end
|
|
if (additionalProgress.width ~= b) then
|
|
additionalProgress.width = b;
|
|
changed = true;
|
|
end
|
|
if (additionalProgress.offset ~= c) then
|
|
additionalProgress.offset = c;
|
|
changed = true;
|
|
end
|
|
additionalProgress.min = nil;
|
|
additionalProgress.max = nil;
|
|
else
|
|
if (additionalProgress.min ~= a) then
|
|
additionalProgress.min = a;
|
|
changed = true;
|
|
end
|
|
if (additionalProgress.max ~= b) then
|
|
additionalProgress.max = b;
|
|
changed = true;
|
|
end
|
|
if additionalProgress.direction then
|
|
changed = true
|
|
end
|
|
additionalProgress.direction = nil;
|
|
additionalProgress.width = nil;
|
|
additionalProgress.offset = nil;
|
|
end
|
|
|
|
end
|
|
state.changed = changed or state.changed;
|
|
end
|
|
|
|
local function callFunctionForActivateEvent(func, trigger, state, property, errorHandler)
|
|
if not func then
|
|
return
|
|
end
|
|
local ok, value = pcall(func, trigger)
|
|
if ok then
|
|
if state[property] ~= value then
|
|
state[property] = value
|
|
state.changed = true
|
|
end
|
|
else
|
|
if not ok then
|
|
errorHandler(value)
|
|
end
|
|
end
|
|
end
|
|
|
|
function Private.ActivateEvent(id, triggernum, data, state, errorHandler)
|
|
local changed = state.changed or false;
|
|
if (state.show ~= true) then
|
|
state.show = true;
|
|
changed = true;
|
|
end
|
|
if (data.duration) then
|
|
local expirationTime = GetTime() + data.duration;
|
|
if (state.expirationTime ~= expirationTime) then
|
|
state.expirationTime = expirationTime;
|
|
changed = true;
|
|
end
|
|
if (state.duration ~= data.duration) then
|
|
state.duration = data.duration;
|
|
changed = true;
|
|
end
|
|
if (state.progressType ~= "timed") then
|
|
state.progressType = "timed";
|
|
changed = true;
|
|
end
|
|
local autoHide = data.automaticAutoHide;
|
|
if (state.value or state.total or state.inverse or state.autoHide ~= autoHide) then
|
|
changed = true;
|
|
end
|
|
state.value = nil;
|
|
state.total = nil;
|
|
state.inverse = nil;
|
|
state.autoHide = autoHide;
|
|
elseif (data.durationFunc) then
|
|
local ok, arg1, arg2, arg3, inverse = pcall(data.durationFunc, data.trigger);
|
|
if not ok then
|
|
(errorHandler or Private.GetErrorHandlerId(id, L["Duration Function"]))(arg1)
|
|
arg1 = 0;
|
|
arg2 = 0;
|
|
else
|
|
arg1 = type(arg1) == "number" and arg1 or 0;
|
|
arg2 = type(arg2) == "number" and arg2 or 0;
|
|
end
|
|
|
|
|
|
if (state.inverse ~= inverse) then
|
|
state.inverse = inverse;
|
|
changed = true;
|
|
end
|
|
|
|
if (arg3) then
|
|
if (state.progressType ~= "static") then
|
|
state.progressType = "static";
|
|
changed = true;
|
|
end
|
|
if (state.duration) then
|
|
state.duration = nil;
|
|
changed = true;
|
|
end
|
|
if (state.expirationTime) then
|
|
state.expirationTime = nil;
|
|
changed = true;
|
|
end
|
|
|
|
local autoHide = nil;
|
|
if (state.autoHide ~= autoHide) then
|
|
changed = true;
|
|
state.autoHide = autoHide;
|
|
end
|
|
|
|
if (state.value ~= arg1) then
|
|
state.value = arg1;
|
|
changed = true;
|
|
end
|
|
if (state.total ~= arg2) then
|
|
state.total = arg2;
|
|
changed = true;
|
|
end
|
|
else
|
|
if (state.progressType ~= "timed") then
|
|
state.progressType = "timed";
|
|
changed = true;
|
|
end
|
|
if (state.duration ~= arg1) then
|
|
state.duration = arg1;
|
|
end
|
|
-- The Icon's SetCooldown requires that the **startTime** is positive, so ensure that
|
|
-- the expirationTime is bigger than the duration
|
|
if arg2 <= arg1 then
|
|
arg2 = arg1
|
|
end
|
|
if (state.expirationTime ~= arg2) then
|
|
state.expirationTime = arg2;
|
|
changed = true;
|
|
end
|
|
local autoHide = data.automaticAutoHide and arg1 > 0.01;
|
|
if (state.autoHide ~= autoHide) then
|
|
changed = true;
|
|
state.autoHide = autoHide;
|
|
end
|
|
if (state.value or state.total) then
|
|
changed = true;
|
|
end
|
|
state.value = nil;
|
|
state.total = nil;
|
|
end
|
|
end
|
|
|
|
callFunctionForActivateEvent(data.nameFunc, data.trigger, state, "name", errorHandler or Private.GetErrorHandlerId(id, L["Name Function"]))
|
|
callFunctionForActivateEvent(data.iconFunc, data.trigger, state, "icon", errorHandler or Private.GetErrorHandlerId(id, L["Icon Function"]))
|
|
callFunctionForActivateEvent(data.textureFunc, data.trigger, state, "texture", errorHandler or Private.GetErrorHandlerId(id, L["Texture Function"]))
|
|
callFunctionForActivateEvent(data.stacksFunc, data.trigger, state, "stacks", errorHandler or Private.GetErrorHandlerId(id, L["Stacks Function"]))
|
|
|
|
if (data.overlayFuncs) then
|
|
RunOverlayFuncs(data, state, id, errorHandler);
|
|
end
|
|
|
|
state.changed = state.changed or changed;
|
|
|
|
return state.changed;
|
|
end
|
|
|
|
local function ignoreErrorHandler()
|
|
|
|
end
|
|
|
|
local function RunTriggerFunc(allStates, data, id, triggernum, event, arg1, arg2, ...)
|
|
local optionsEvent = event == "OPTIONS";
|
|
local errorHandler = (optionsEvent and data.ignoreOptionsEventErrors) and ignoreErrorHandler or Private.GetErrorHandlerId(id, L["Trigger %s"]:format(triggernum))
|
|
local updateTriggerState = false;
|
|
|
|
local unitForUnitTrigger
|
|
local cloneIdForUnitTrigger
|
|
|
|
if(data.triggerFunc) then
|
|
local untriggerCheck = false;
|
|
if (data.statesParameter == "full") then
|
|
local ok, returnValue
|
|
if data.counter then
|
|
ok, returnValue = pcall(data.triggerFunc, allStates, data.counter, event, arg1, arg2, ...);
|
|
else
|
|
ok, returnValue = pcall(data.triggerFunc, allStates, event, arg1, arg2, ...);
|
|
end
|
|
if not ok then
|
|
errorHandler(returnValue)
|
|
elseif ok and returnValue then
|
|
updateTriggerState = true;
|
|
end
|
|
for key, state in pairs(allStates) do
|
|
if (type(state) ~= "table") then
|
|
errorHandler(string.format(L["All States table contains a non table at key: '%s'."], key))
|
|
wipe(allStates)
|
|
return
|
|
end
|
|
end
|
|
elseif (data.statesParameter == "all") then
|
|
local ok, returnValue
|
|
if data.counter then
|
|
ok, returnValue = pcall(data.triggerFunc, allStates, data.counter, event, arg1, arg2, ...);
|
|
else
|
|
ok, returnValue = pcall(data.triggerFunc, allStates, event, arg1, arg2, ...);
|
|
end
|
|
if not ok then
|
|
errorHandler(returnValue)
|
|
elseif (ok and returnValue) or optionsEvent then
|
|
for id, state in pairs(allStates) do
|
|
if (state.changed) then
|
|
if (Private.ActivateEvent(id, triggernum, data, state)) then
|
|
updateTriggerState = true;
|
|
end
|
|
end
|
|
end
|
|
else
|
|
untriggerCheck = true;
|
|
end
|
|
elseif (data.statesParameter == "unit") then
|
|
if arg1 then
|
|
if Private.multiUnitUnits[data.trigger.unit] then
|
|
unitForUnitTrigger = arg1
|
|
cloneIdForUnitTrigger = arg1
|
|
else
|
|
unitForUnitTrigger = data.trigger.unit
|
|
cloneIdForUnitTrigger = ""
|
|
end
|
|
allStates[cloneIdForUnitTrigger] = allStates[cloneIdForUnitTrigger] or {};
|
|
local state = allStates[cloneIdForUnitTrigger];
|
|
local ok, returnValue
|
|
if data.counter then
|
|
ok, returnValue = pcall(data.triggerFunc, state, data.counter, event, unitForUnitTrigger, arg1, arg2, ...);
|
|
else
|
|
ok, returnValue = pcall(data.triggerFunc, state, event, unitForUnitTrigger, arg1, arg2, ...);
|
|
end
|
|
if not ok then
|
|
errorHandler(returnValue)
|
|
elseif (ok and returnValue) or optionsEvent then
|
|
if(Private.ActivateEvent(id, triggernum, data, state)) then
|
|
updateTriggerState = true;
|
|
end
|
|
else
|
|
untriggerCheck = true;
|
|
end
|
|
end
|
|
elseif (data.statesParameter == "one") then
|
|
allStates[""] = allStates[""] or {};
|
|
local state = allStates[""];
|
|
local ok, returnValue
|
|
if data.counter then
|
|
ok, returnValue = pcall(data.triggerFunc, state, data.counter, event, arg1, arg2, ...);
|
|
else
|
|
ok, returnValue = pcall(data.triggerFunc, state, event, arg1, arg2, ...);
|
|
end
|
|
if not ok then
|
|
errorHandler(returnValue)
|
|
elseif (ok and returnValue) or optionsEvent then
|
|
if(Private.ActivateEvent(id, triggernum, data, state, (optionsEvent and data.ignoreOptionsEventErrors) and ignoreErrorHandler or nil)) then
|
|
updateTriggerState = true;
|
|
end
|
|
else
|
|
untriggerCheck = true;
|
|
end
|
|
else
|
|
local ok, returnValue
|
|
if data.counter then
|
|
ok, returnValue = pcall(data.triggerFunc, data.counter, event, arg1, arg2, ...);
|
|
else
|
|
ok, returnValue = pcall(data.triggerFunc, event, arg1, arg2, ...);
|
|
end
|
|
if not ok then
|
|
errorHandler(returnValue)
|
|
elseif (ok and returnValue) or optionsEvent then
|
|
allStates[""] = allStates[""] or {};
|
|
local state = allStates[""];
|
|
if(Private.ActivateEvent(id, triggernum, data, state, (optionsEvent and data.ignoreOptionsEventErrors) and ignoreErrorHandler or nil)) then
|
|
updateTriggerState = true;
|
|
end
|
|
else
|
|
untriggerCheck = true;
|
|
end
|
|
end
|
|
if (untriggerCheck and not optionsEvent) then
|
|
errorHandler = (optionsEvent and data.ignoreOptionsEventErrors) and ignoreErrorHandler or Private.GetErrorHandlerId(id, L["Untrigger %s"]:format(triggernum))
|
|
if (data.statesParameter == "all") then
|
|
if data.untriggerFunc then
|
|
local ok, returnValue = pcall(data.untriggerFunc, allStates, event, arg1, arg2, ...);
|
|
if not ok then
|
|
errorHandler(returnValue)
|
|
elseif (ok and returnValue) or optionsEvent then
|
|
for id, state in pairs(allStates) do
|
|
if (state.changed) then
|
|
if (Private.EndEvent(state)) then
|
|
updateTriggerState = true;
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
elseif data.statesParameter == "unit" then
|
|
if data.untriggerFunc then
|
|
if arg1 then
|
|
local state = allStates[cloneIdForUnitTrigger]
|
|
if state then
|
|
local ok, returnValue = pcall(data.untriggerFunc, state, event, unitForUnitTrigger, arg1, arg2, ...);
|
|
if not ok then
|
|
errorHandler(returnValue)
|
|
elseif ok and returnValue then
|
|
if (Private.EndEvent(state)) then
|
|
updateTriggerState = true;
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
if not updateTriggerState and not allStates[cloneIdForUnitTrigger].show then
|
|
-- We added this state automatically, but the trigger didn't end up using it,
|
|
-- so remove it again
|
|
allStates[cloneIdForUnitTrigger] = nil
|
|
end
|
|
elseif (data.statesParameter == "one") then
|
|
allStates[""] = allStates[""] or {};
|
|
local state = allStates[""];
|
|
if data.untriggerFunc then
|
|
local ok, returnValue = pcall(data.untriggerFunc, state, event, arg1, arg2, ...);
|
|
if not ok then
|
|
errorHandler(returnValue)
|
|
elseif (ok and returnValue) then
|
|
if (Private.EndEvent(state)) then
|
|
updateTriggerState = true;
|
|
end
|
|
end
|
|
end
|
|
else
|
|
if data.untriggerFunc then
|
|
local ok, returnValue = pcall(data.untriggerFunc, event, arg1, arg2, ...);
|
|
if not ok then
|
|
errorHandler(returnValue)
|
|
elseif (ok and returnValue) then
|
|
allStates[""] = allStates[""] or {};
|
|
local state = allStates[""];
|
|
if (Private.EndEvent(state)) then
|
|
updateTriggerState = true;
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
if updateTriggerState and watched_trigger_events[id] and watched_trigger_events[id][triggernum] then
|
|
-- if this trigger's updates are requested to be sent into one of the Aura's custom triggers
|
|
Private.AddToWatchedTriggerDelay(id, triggernum)
|
|
end
|
|
return updateTriggerState;
|
|
end
|
|
|
|
local function getGameEventFromComposedEvent(composedEvent)
|
|
local separatorPosition = composedEvent:find(":", 1, true)
|
|
return separatorPosition == nil and composedEvent or composedEvent:sub(1, separatorPosition - 1)
|
|
end
|
|
|
|
function Private.ScanEventsByID(event, id, ...)
|
|
if loaded_events[event] then
|
|
WeakAuras.ScanEvents(event, id, ...)
|
|
end
|
|
local eventWithID = event .. ":" .. id
|
|
if loaded_events[eventWithID] then
|
|
WeakAuras.ScanEvents(eventWithID, id, ...)
|
|
end
|
|
end
|
|
|
|
function WeakAuras.ScanEvents(event, arg1, arg2, ...)
|
|
local system = getGameEventFromComposedEvent(event)
|
|
Private.StartProfileSystem("generictrigger " .. system)
|
|
local event_list = loaded_events[event];
|
|
if (not event_list) then
|
|
Private.StopProfileSystem("generictrigger " .. system)
|
|
return
|
|
end
|
|
if(event == "COMBAT_LOG_EVENT_UNFILTERED") then
|
|
event_list = event_list[arg2];
|
|
if (not event_list) then
|
|
Private.StopProfileSystem("generictrigger " .. system)
|
|
return;
|
|
end
|
|
WeakAuras.ScanEventsInternal(event_list, event, arg1, arg2, ...);
|
|
|
|
elseif (event == "COMBAT_LOG_EVENT_UNFILTERED_CUSTOM") then
|
|
-- This reverts the COMBAT_LOG_EVENT_UNFILTERED_CUSTOM workaround so that custom triggers that check the event argument will work as expected
|
|
if(event == "COMBAT_LOG_EVENT_UNFILTERED_CUSTOM") then
|
|
event = "COMBAT_LOG_EVENT_UNFILTERED";
|
|
end
|
|
WeakAuras.ScanEventsInternal(event_list, event, arg1, arg2, ...);
|
|
else
|
|
WeakAuras.ScanEventsInternal(event_list, event, arg1, arg2, ...);
|
|
end
|
|
Private.StopProfileSystem("generictrigger " .. system)
|
|
end
|
|
|
|
function WeakAuras.ScanUnitEvents(event, unit, ...)
|
|
Private.StartProfileSystem("generictrigger " .. event .. " " .. unit)
|
|
local unit_list = loaded_unit_events[unit]
|
|
if unit_list then
|
|
local event_list = unit_list[event]
|
|
if event_list then
|
|
for id, triggers in pairs(event_list) do
|
|
Private.StartProfileAura(id);
|
|
Private.ActivateAuraEnvironment(id);
|
|
local updateTriggerState = false;
|
|
for triggernum, data in pairs(triggers) do
|
|
local delay = GenericTrigger.GetDelay(data)
|
|
if delay == 0 then
|
|
local allStates = WeakAuras.GetTriggerStateForTrigger(id, triggernum);
|
|
if (RunTriggerFunc(allStates, data, id, triggernum, event, unit, ...)) then
|
|
updateTriggerState = true;
|
|
end
|
|
else
|
|
Private.RunTriggerFuncWithDelay(delay, id, triggernum, data, event, unit, ...)
|
|
end
|
|
end
|
|
if (updateTriggerState) then
|
|
Private.UpdatedTriggerState(id);
|
|
end
|
|
Private.StopProfileAura(id);
|
|
Private.ActivateAuraEnvironment(nil);
|
|
end
|
|
end
|
|
end
|
|
Private.StopProfileSystem("generictrigger " .. event .. " " .. unit)
|
|
end
|
|
|
|
function WeakAuras.ScanEventsInternal(event_list, event, arg1, arg2, ... )
|
|
for id, triggers in pairs(event_list) do
|
|
Private.StartProfileAura(id);
|
|
Private.ActivateAuraEnvironment(id);
|
|
local updateTriggerState = false;
|
|
for triggernum, data in pairs(triggers) do
|
|
local allStates = WeakAuras.GetTriggerStateForTrigger(id, triggernum);
|
|
local delay = GenericTrigger.GetDelay(data)
|
|
if delay == 0 then
|
|
if (RunTriggerFunc(allStates, data, id, triggernum, event, arg1, arg2, ...)) then
|
|
updateTriggerState = true
|
|
end
|
|
else
|
|
Private.RunTriggerFuncWithDelay(delay, id, triggernum, data, event, arg1, arg2, ...)
|
|
end
|
|
end
|
|
if (updateTriggerState) then
|
|
Private.UpdatedTriggerState(id);
|
|
end
|
|
Private.StopProfileAura(id);
|
|
Private.ActivateAuraEnvironment(nil);
|
|
end
|
|
end
|
|
|
|
do
|
|
local function RunTriggerFuncForDelay(id, triggernum, data, event, ...)
|
|
Private.StartProfileAura(id)
|
|
Private.ActivateAuraEnvironment(id)
|
|
local allStates = WeakAuras.GetTriggerStateForTrigger(id, triggernum)
|
|
if (RunTriggerFunc(allStates, data, id, triggernum, event, ...)) then
|
|
Private.UpdatedTriggerState(id)
|
|
end
|
|
Private.StopProfileAura(id)
|
|
Private.ActivateAuraEnvironment(nil)
|
|
-- clear expired timers
|
|
for i, t in ipairs_reverse(delayTimerEvents[id][triggernum]) do
|
|
if t.ends <= GetTime() then
|
|
table.remove(delayTimerEvents[id][triggernum], i)
|
|
end
|
|
end
|
|
end
|
|
|
|
function Private.RunTriggerFuncWithDelay(delay, id, triggernum, data, event, ...)
|
|
delayTimerEvents[id] = delayTimerEvents[id] or {}
|
|
delayTimerEvents[id][triggernum] = delayTimerEvents[id][triggernum] or {}
|
|
local timerId = timer:ScheduleTimer(RunTriggerFuncForDelay, delay, id, triggernum, data, event, ...)
|
|
tinsert(delayTimerEvents[id][triggernum], timerId)
|
|
end
|
|
end
|
|
|
|
function Private.CancelDelayedTrigger(id)
|
|
if delayTimerEvents[id] then
|
|
for triggernum, timers in pairs(delayTimerEvents[id]) do
|
|
for _, timerId in ipairs(timers) do
|
|
timer:CancelTimer(timerId)
|
|
end
|
|
end
|
|
delayTimerEvents[id] = nil
|
|
end
|
|
end
|
|
|
|
function Private.CancelAllDelayedTriggers()
|
|
for id in pairs(delayTimerEvents) do
|
|
Private.CancelDelayedTrigger(id)
|
|
end
|
|
end
|
|
|
|
|
|
function Private.ScanEventsWatchedTrigger(id, watchedTriggernums)
|
|
Private.StartProfileAura(id);
|
|
Private.ActivateAuraEnvironment(id);
|
|
local updateTriggerState = false
|
|
|
|
for _, watchedTrigger in ipairs(watchedTriggernums) do
|
|
if watched_trigger_events[id] and watched_trigger_events[id][watchedTrigger] then
|
|
local updatedTriggerStates = WeakAuras.GetTriggerStateForTrigger(id, watchedTrigger)
|
|
for observerTrigger in pairs(watched_trigger_events[id][watchedTrigger]) do
|
|
local data = events and events[id] and events[id][observerTrigger]
|
|
local allstates = WeakAuras.GetTriggerStateForTrigger(id, observerTrigger)
|
|
if data and allstates and updatedTriggerStates then
|
|
if RunTriggerFunc(allstates, data, id, observerTrigger, "TRIGGER", watchedTrigger, updatedTriggerStates) then
|
|
updateTriggerState = true
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
if (updateTriggerState) then
|
|
Private.UpdatedTriggerState(id)
|
|
end
|
|
Private.StopProfileAura(id)
|
|
Private.ActivateAuraEnvironment(nil)
|
|
end
|
|
|
|
local function ProgressType(data, triggernum)
|
|
local trigger = data.triggers[triggernum].trigger
|
|
local prototype = GenericTrigger.GetPrototype(trigger)
|
|
if prototype then
|
|
if prototype.progressType then
|
|
local progressType = prototype.progressType
|
|
if type(progressType) == "function" then
|
|
progressType = progressType(trigger)
|
|
end
|
|
return progressType
|
|
elseif prototype.timedrequired then
|
|
return "timed"
|
|
end
|
|
elseif (trigger.type == "custom") then
|
|
if trigger.custom_type == "event" and trigger.custom_hide == "timed" and trigger.duration then
|
|
return "timed";
|
|
elseif (trigger.customDuration and trigger.customDuration ~= "") then
|
|
return "timed";
|
|
elseif (trigger.custom_type == "stateupdate") then
|
|
return false
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
|
|
local function AddFakeInformation(data, triggernum, state, eventData)
|
|
state.autoHide = false
|
|
if ProgressType(data, triggernum) == "timed" and state.expirationTime == nil then
|
|
state.progressType = "timed"
|
|
end
|
|
if state.progressType == "timed" then
|
|
if state.expirationTime and state.expirationTime ~= math.huge and state.expirationTime > GetTime() then
|
|
return
|
|
end
|
|
state.progressType = "timed"
|
|
state.expirationTime = GetTime() + 7
|
|
state.duration = 7
|
|
end
|
|
if eventData.prototype and eventData.prototype.GetNameAndIcon then
|
|
local name, icon = eventData.prototype.GetNameAndIcon(eventData.trigger)
|
|
if state.name == nil then
|
|
state.name = name
|
|
end
|
|
if state.icon == nil then
|
|
state.icon = icon
|
|
end
|
|
end
|
|
end
|
|
|
|
function GenericTrigger.CreateFakeStates(id, triggernum)
|
|
local data = WeakAuras.GetData(id)
|
|
local eventData = events[id][triggernum]
|
|
|
|
Private.ActivateAuraEnvironment(id);
|
|
local allStates = WeakAuras.GetTriggerStateForTrigger(id, triggernum);
|
|
|
|
local arg1
|
|
if eventData.statesParameter == "unit" then
|
|
local unit = eventData.trigger.unit
|
|
if Private.multiUnitUnits[unit] then
|
|
arg1 = next(Private.multiUnitUnits[unit])
|
|
else
|
|
arg1 = unit
|
|
end
|
|
end
|
|
RunTriggerFunc(allStates, eventData, id, triggernum, "OPTIONS", arg1)
|
|
|
|
local shown = 0
|
|
for id, state in pairs(allStates) do
|
|
if state.show then
|
|
shown = shown + 1
|
|
end
|
|
|
|
AddFakeInformation(data, triggernum, state, eventData)
|
|
end
|
|
|
|
if shown == 0 then
|
|
local state = {}
|
|
GenericTrigger.CreateFallbackState(data, triggernum, state)
|
|
allStates[""] = state
|
|
|
|
AddFakeInformation(data, triggernum, state, eventData)
|
|
end
|
|
|
|
Private.ActivateAuraEnvironment(nil);
|
|
end
|
|
|
|
function GenericTrigger.ScanWithFakeEvent(id, fake)
|
|
local updateTriggerState = false;
|
|
Private.ActivateAuraEnvironment(id);
|
|
for triggernum, event in pairs(events[id] or {}) do
|
|
local allStates = WeakAuras.GetTriggerStateForTrigger(id, triggernum);
|
|
if (event.force_events) then
|
|
if (type(event.force_events) == "string") then
|
|
updateTriggerState = RunTriggerFunc(allStates, events[id][triggernum], id, triggernum, event.force_events) or updateTriggerState;
|
|
elseif (type(event.force_events) == "table") then
|
|
for index, event_args in pairs(event.force_events) do
|
|
updateTriggerState = RunTriggerFunc(allStates, events[id][triggernum], id, triggernum, unpack(event_args)) or updateTriggerState;
|
|
end
|
|
elseif (type(event.force_events) == "boolean" and event.force_events) then
|
|
for i, eventName in pairs(event.events) do
|
|
if eventName == "COMBAT_LOG_EVENT_UNFILTERED_CUSTOM" then
|
|
eventName = "COMBAT_LOG_EVENT_UNFILTERED"
|
|
end
|
|
updateTriggerState = RunTriggerFunc(allStates, events[id][triggernum], id, triggernum, eventName) or updateTriggerState;
|
|
end
|
|
for unit, unitData in pairs(event.unit_events) do
|
|
for _, event in ipairs(unitData) do
|
|
updateTriggerState = RunTriggerFunc(allStates, events[id][triggernum], id, triggernum, event, unit) or updateTriggerState
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
if (updateTriggerState) then
|
|
Private.UpdatedTriggerState(id);
|
|
end
|
|
Private.ActivateAuraEnvironment(nil);
|
|
end
|
|
|
|
function HandleEvent(frame, event, arg1, arg2, ...)
|
|
Private.StartProfileSystem("generictrigger " .. event);
|
|
if event == "NAME_PLATE_UNIT_ADDED" then
|
|
nameplateExists[arg1] = true
|
|
elseif event == "NAME_PLATE_UNIT_REMOVED" then
|
|
nameplateExists[arg1] = false
|
|
end
|
|
|
|
if not(WeakAuras.IsPaused()) then
|
|
if(event == "COMBAT_LOG_EVENT_UNFILTERED") then
|
|
WeakAuras.ScanEvents(event, arg1, arg2, ...);
|
|
-- This triggers the scanning of "hacked" COMBAT_LOG_EVENT_UNFILTERED events that were renamed in order to circumvent
|
|
-- the "proper" COMBAT_LOG_EVENT_UNFILTERED checks
|
|
WeakAuras.ScanEvents("COMBAT_LOG_EVENT_UNFILTERED_CUSTOM", arg1, arg2, ...);
|
|
else
|
|
WeakAuras.ScanEvents(event, arg1, arg2, ...);
|
|
end
|
|
end
|
|
if (event == "PLAYER_ENTERING_WORLD") then
|
|
timer:ScheduleTimer(function()
|
|
HandleEvent(frame, "WA_DELAYED_PLAYER_ENTERING_WORLD");
|
|
Private.StartProfileSystem("generictrigger WA_DELAYED_PLAYER_ENTERING_WORLD");
|
|
Private.ScanForLoads(nil, "WA_DELAYED_PLAYER_ENTERING_WORLD")
|
|
Private.CheckCooldownReady();
|
|
Private.StopProfileSystem("generictrigger WA_DELAYED_PLAYER_ENTERING_WORLD");
|
|
Private.PreShowModels()
|
|
end,
|
|
0.8); -- Data not available
|
|
|
|
timer:ScheduleTimer(function()
|
|
Private.PreShowModels()
|
|
end,
|
|
4); -- Data not available
|
|
end
|
|
Private.StopProfileSystem("generictrigger " .. event);
|
|
end
|
|
|
|
function HandleUnitEvent(frame, event, unit, ...)
|
|
if frame.unit ~= unit then return end
|
|
Private.StartProfileSystem("generictrigger " .. event .. " " .. unit);
|
|
if not(WeakAuras.IsPaused()) then
|
|
if (UnitIsUnit(unit, frame.unit)) then
|
|
WeakAuras.ScanUnitEvents(event, frame.unit, ...);
|
|
end
|
|
end
|
|
Private.StopProfileSystem("generictrigger " .. event .. " " .. unit);
|
|
end
|
|
|
|
function GenericTrigger.UnloadAll()
|
|
wipe(loaded_auras);
|
|
wipe(loaded_events);
|
|
wipe(loaded_unit_events);
|
|
Private.CancelAllDelayedTriggers();
|
|
Private.UnregisterAllEveryFrameUpdate();
|
|
end
|
|
|
|
function GenericTrigger.UnloadDisplays(toUnload)
|
|
for id in pairs(toUnload) do
|
|
loaded_auras[id] = false;
|
|
for eventname, events in pairs(loaded_events) do
|
|
if(eventname == "COMBAT_LOG_EVENT_UNFILTERED") then
|
|
for subeventname, subevents in pairs(events) do
|
|
subevents[id] = nil;
|
|
end
|
|
else
|
|
events[id] = nil;
|
|
end
|
|
end
|
|
for unit, events in pairs(loaded_unit_events) do
|
|
for eventname, auras in pairs(events) do
|
|
auras[id] = nil;
|
|
end
|
|
end
|
|
|
|
Private.CancelDelayedTrigger(id);
|
|
Private.UnregisterEveryFrameUpdate(id);
|
|
end
|
|
end
|
|
|
|
local genericTriggerRegisteredEvents = {};
|
|
local genericTriggerRegisteredUnitEvents = {};
|
|
local frame = CreateFrame("Frame");
|
|
frame.unitFrames = {};
|
|
Private.frames["WeakAuras Generic Trigger Frame"] = frame;
|
|
frame:RegisterEvent("PLAYER_ENTERING_WORLD");
|
|
genericTriggerRegisteredEvents["PLAYER_ENTERING_WORLD"] = true;
|
|
if WeakAuras.isAwesomeEnabled() then
|
|
frame:RegisterEvent("NAME_PLATE_UNIT_ADDED")
|
|
frame:RegisterEvent("NAME_PLATE_UNIT_REMOVED")
|
|
genericTriggerRegisteredEvents["NAME_PLATE_UNIT_ADDED"] = true;
|
|
genericTriggerRegisteredEvents["NAME_PLATE_UNIT_REMOVED"] = true;
|
|
end
|
|
frame:SetScript("OnEvent", HandleEvent);
|
|
|
|
function GenericTrigger.Delete(id)
|
|
GenericTrigger.UnloadDisplays({[id] = true});
|
|
end
|
|
|
|
function GenericTrigger.Rename(oldid, newid)
|
|
events[newid] = events[oldid];
|
|
events[oldid] = nil;
|
|
|
|
for eventname, events in pairs(loaded_events) do
|
|
if(eventname == "COMBAT_LOG_EVENT_UNFILTERED") then
|
|
for subeventname, subevents in pairs(events) do
|
|
subevents[oldid] = subevents[newid];
|
|
subevents[oldid] = nil;
|
|
end
|
|
else
|
|
events[newid] = events[oldid];
|
|
events[oldid] = nil;
|
|
end
|
|
end
|
|
|
|
for unit, events in pairs(loaded_unit_events) do
|
|
for eventname, auras in pairs(events) do
|
|
auras[newid] = auras[oldid]
|
|
auras[oldid] = nil
|
|
end
|
|
end
|
|
|
|
watched_trigger_events[newid] = watched_trigger_events[oldid]
|
|
watched_trigger_events[oldid] = nil
|
|
|
|
Private.EveryFrameUpdateRename(oldid, newid)
|
|
end
|
|
|
|
local function MultiUnitLoop(Func, unit, includePets, ...)
|
|
unit = string.lower(unit)
|
|
if unit == "boss" then
|
|
for i = 1, 4 do
|
|
Func(unit..i, ...)
|
|
end
|
|
elseif unit == "arena" then
|
|
for i = 1, 5 do
|
|
Func(unit..i, ...)
|
|
end
|
|
elseif unit == "nameplate" then
|
|
for i = 1, 100 do
|
|
Func(unit..i, ...)
|
|
end
|
|
elseif unit == "group" then
|
|
if includePets ~= "PetsOnly" then
|
|
Func("player", ...)
|
|
end
|
|
if includePets ~= nil then
|
|
Func("pet", ...)
|
|
end
|
|
for i = 1, 4 do
|
|
if includePets ~= "PetsOnly" then
|
|
Func("party"..i, ...)
|
|
end
|
|
if includePets ~= nil then
|
|
Func("partypet"..i, ...)
|
|
end
|
|
end
|
|
for i = 1, 40 do
|
|
if includePets ~= "PetsOnly" then
|
|
Func("raid"..i, ...)
|
|
end
|
|
if includePets ~= nil then
|
|
Func("raidpet"..i, ...)
|
|
end
|
|
end
|
|
elseif unit == "party" then
|
|
if includePets ~= "PetsOnly" then
|
|
Func("player", ...)
|
|
end
|
|
if includePets ~= nil then
|
|
Func("pet", ...)
|
|
end
|
|
for i = 1, 4 do
|
|
if includePets ~= "PetsOnly" then
|
|
Func("party"..i, ...)
|
|
end
|
|
if includePets ~= nil then
|
|
Func("partypet"..i, ...)
|
|
end
|
|
end
|
|
elseif unit == "raid" then
|
|
for i = 1, 40 do
|
|
if includePets ~= "PetsOnly" then
|
|
Func("raid"..i, ...)
|
|
end
|
|
if includePets ~= nil then
|
|
Func("raidpet"..i, ...)
|
|
end
|
|
end
|
|
else
|
|
Func(unit, ...)
|
|
end
|
|
end
|
|
|
|
function LoadEvent(id, triggernum, data)
|
|
if data.events then
|
|
for index, event in pairs(data.events) do
|
|
loaded_events[event] = loaded_events[event] or {};
|
|
if(event == "COMBAT_LOG_EVENT_UNFILTERED" and data.subevents) then
|
|
for i, subevent in pairs(data.subevents) do
|
|
loaded_events[event][subevent] = loaded_events[event][subevent] or {};
|
|
loaded_events[event][subevent][id] = loaded_events[event][subevent][id] or {}
|
|
loaded_events[event][subevent][id][triggernum] = data;
|
|
end
|
|
else
|
|
loaded_events[event][id] = loaded_events[event][id] or {};
|
|
loaded_events[event][id][triggernum] = data;
|
|
end
|
|
end
|
|
end
|
|
if (data.internal_events) then
|
|
for index, event in pairs(data.internal_events) do
|
|
loaded_events[event] = loaded_events[event] or {};
|
|
loaded_events[event][id] = loaded_events[event][id] or {};
|
|
loaded_events[event][id][triggernum] = data;
|
|
end
|
|
end
|
|
-- this special internal_events function is run when aura load instead of when it is added
|
|
if data.loadInternalEventFunc then
|
|
local internal_events = data.loadInternalEventFunc(data.trigger)
|
|
for index, event in pairs(internal_events) do
|
|
loaded_events[event] = loaded_events[event] or {};
|
|
loaded_events[event][id] = loaded_events[event][id] or {};
|
|
loaded_events[event][id][triggernum] = data;
|
|
end
|
|
end
|
|
if data.unit_events then
|
|
local includePets = data.includePets
|
|
for unit, events in pairs(data.unit_events) do
|
|
unit = string.lower(unit)
|
|
for index, event in pairs(events) do
|
|
MultiUnitLoop(
|
|
function(u)
|
|
loaded_unit_events[u] = loaded_unit_events[u] or {};
|
|
loaded_unit_events[u][event] = loaded_unit_events[u][event] or {};
|
|
loaded_unit_events[u][event][id] = loaded_unit_events[u][event][id] or {}
|
|
loaded_unit_events[u][event][id][triggernum] = data;
|
|
end, unit, includePets
|
|
)
|
|
end
|
|
end
|
|
end
|
|
|
|
if (data.loadFunc) then
|
|
data.loadFunc(data.trigger);
|
|
end
|
|
end
|
|
|
|
local function trueFunction()
|
|
return true;
|
|
end
|
|
|
|
local eventsToRegister = {};
|
|
local unitEventsToRegister = {};
|
|
function GenericTrigger.LoadDisplays(toLoad, loadEvent, ...)
|
|
for id in pairs(toLoad) do
|
|
local register_for_frame_updates = false;
|
|
if(events[id]) then
|
|
loaded_auras[id] = true;
|
|
for triggernum, data in pairs(events[id]) do
|
|
if data.events then
|
|
for index, event in pairs(data.events) do
|
|
if (event == "COMBAT_LOG_EVENT_UNFILTERED_CUSTOM") then
|
|
if not genericTriggerRegisteredEvents["COMBAT_LOG_EVENT_UNFILTERED"] then
|
|
eventsToRegister["COMBAT_LOG_EVENT_UNFILTERED"] = true;
|
|
end
|
|
elseif (event == "FRAME_UPDATE") then
|
|
register_for_frame_updates = true;
|
|
else
|
|
if (genericTriggerRegisteredEvents[event]) then
|
|
-- Already registered event
|
|
else
|
|
eventsToRegister[event] = true;
|
|
end
|
|
end
|
|
end
|
|
end
|
|
if data.unit_events then
|
|
local includePets = data.includePets
|
|
for unit, events in pairs(data.unit_events) do
|
|
for index, event in pairs(events) do
|
|
MultiUnitLoop(
|
|
function (u)
|
|
if not (genericTriggerRegisteredUnitEvents[u] and genericTriggerRegisteredUnitEvents[u][event]) then
|
|
unitEventsToRegister[u] = unitEventsToRegister[u] or {}
|
|
unitEventsToRegister[u][event] = true
|
|
end
|
|
end, unit, includePets
|
|
)
|
|
end
|
|
end
|
|
end
|
|
|
|
if data.counter then
|
|
data.counter:Reset()
|
|
end
|
|
|
|
LoadEvent(id, triggernum, data);
|
|
end
|
|
end
|
|
|
|
if(register_for_frame_updates) then
|
|
Private.RegisterEveryFrameUpdate(id);
|
|
else
|
|
Private.UnregisterEveryFrameUpdate(id);
|
|
end
|
|
end
|
|
|
|
for event in pairs(eventsToRegister) do
|
|
pcall(frame.RegisterEvent, frame, event)
|
|
genericTriggerRegisteredEvents[event] = true;
|
|
end
|
|
|
|
for unit, events in pairs(unitEventsToRegister) do
|
|
for event in pairs(events) do
|
|
if not frame.unitFrames[unit] then
|
|
frame.unitFrames[unit] = CreateFrame("FRAME")
|
|
frame.unitFrames[unit].unit = unit
|
|
frame.unitFrames[unit]:SetScript("OnEvent", HandleUnitEvent);
|
|
end
|
|
pcall(frame.unitFrames[unit].RegisterEvent, frame.unitFrames[unit], event, unit)
|
|
genericTriggerRegisteredUnitEvents[unit] = genericTriggerRegisteredUnitEvents[unit] or {};
|
|
genericTriggerRegisteredUnitEvents[unit][event] = true;
|
|
end
|
|
end
|
|
|
|
for id in pairs(toLoad) do
|
|
GenericTrigger.ScanWithFakeEvent(id);
|
|
end
|
|
|
|
-- Replay events that lead to loading, if we weren't already registered for them
|
|
if (eventsToRegister[loadEvent]) then
|
|
WeakAuras.ScanEvents(loadEvent, ...);
|
|
end
|
|
local loadUnit = ...
|
|
if loadUnit and unitEventsToRegister[loadUnit] and unitEventsToRegister[loadUnit][loadEvent] then
|
|
WeakAuras.ScanUnitEvents(loadEvent, ...);
|
|
end
|
|
|
|
wipe(eventsToRegister);
|
|
wipe(unitEventsToRegister);
|
|
end
|
|
|
|
function GenericTrigger.FinishLoadUnload()
|
|
end
|
|
|
|
do
|
|
local function ParseCron(pattern)
|
|
local tests = {}
|
|
for test in pattern:gmatch("[^ ,]+") do
|
|
local startString, endString, intervalString = test:match("(%d*)-?(%d*)/?(%d*)")
|
|
local intervalNumber = tonumber(intervalString)
|
|
local startNumber = startString == "" and 0 or tonumber(startString) or 0
|
|
local endNumber = tonumber(endString)
|
|
if not endNumber then
|
|
endNumber = intervalNumber and math.huge or startNumber
|
|
end
|
|
intervalNumber = intervalNumber or 1
|
|
|
|
tinsert(tests, {
|
|
startNumber = startNumber,
|
|
endNumber = endNumber,
|
|
intervalNumber = intervalNumber,
|
|
Match = function(self, count)
|
|
return (count >= self.startNumber and count <= self.endNumber and (count - self.startNumber) % self.intervalNumber == 0)
|
|
end
|
|
})
|
|
end
|
|
return tests
|
|
end
|
|
|
|
function Private.ExecEnv.CreateTriggerCounter(pattern)
|
|
local counter = {
|
|
count = 0,
|
|
tests = {
|
|
|
|
},
|
|
fastMatches = {
|
|
},
|
|
Reset = function(self)
|
|
self.count = 0
|
|
end,
|
|
GetNext = function(self)
|
|
self.count = self.count + 1
|
|
return self.count
|
|
end,
|
|
SetCount = function(self, count)
|
|
self.count = count
|
|
end,
|
|
}
|
|
if pattern then
|
|
counter.tests = ParseCron(pattern)
|
|
counter.RunTests = function(self, count)
|
|
for _, test in ipairs(self.tests) do
|
|
if test:Match(count) then
|
|
return true
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
|
|
for i = 1, 20 do
|
|
counter.fastMatches[i] = counter.RunTests(counter, i)
|
|
end
|
|
|
|
counter.Match = function(self)
|
|
if self.count <= 20 then
|
|
return counter.fastMatches[self.count]
|
|
end
|
|
return self:RunTests(self.count)
|
|
end
|
|
|
|
else
|
|
counter.Match = function(self)
|
|
return true
|
|
end
|
|
end
|
|
|
|
return counter
|
|
end
|
|
end
|
|
|
|
--- Adds a display, creating all internal data structures for all triggers.
|
|
-- @param data
|
|
-- @param region
|
|
function GenericTrigger.Add(data, region)
|
|
local id = data.id;
|
|
events[id] = nil;
|
|
watched_trigger_events[id] = nil
|
|
|
|
local warnAboutCLEUEvents = false
|
|
|
|
for triggernum, triggerData in ipairs(data.triggers) do
|
|
local trigger, untrigger = triggerData.trigger, triggerData.untrigger
|
|
local triggerType;
|
|
if(trigger and type(trigger) == "table") then
|
|
triggerType = trigger.type;
|
|
if(Private.category_event_prototype[triggerType] or triggerType == "custom") then
|
|
local triggerFuncStr, triggerFunc, untriggerFunc, statesParameter;
|
|
local trigger_events = {};
|
|
local internal_events = {};
|
|
local trigger_unit_events = {};
|
|
local includePets
|
|
local trigger_subevents = {};
|
|
local force_events = false;
|
|
local durationFunc, overlayFuncs, nameFunc, iconFunc, textureFunc, stacksFunc, loadFunc, loadInternalEventFunc;
|
|
local tsuConditionVariables;
|
|
local prototype = nil
|
|
local automaticAutoHide
|
|
local duration
|
|
local counter
|
|
if(Private.category_event_prototype[triggerType]) then
|
|
if not(trigger.event) then
|
|
error("Improper arguments to WeakAuras.Add - trigger type is \"event\" but event is not defined");
|
|
elseif not(event_prototypes[trigger.event]) then
|
|
if(event_prototypes["Health"]) then
|
|
trigger.event = "Health";
|
|
else
|
|
error("Improper arguments to WeakAuras.Add - no event prototype can be found for event type \""..trigger.event.."\" and default prototype reset failed.");
|
|
end
|
|
else
|
|
if (trigger.event == "Combat Log") then
|
|
if (not trigger.subeventPrefix) then
|
|
trigger.subeventPrefix = ""
|
|
end
|
|
if (not trigger.subeventSuffix) then
|
|
trigger.subeventSuffix = "";
|
|
end
|
|
if not(Private.subevent_actual_prefix_types[trigger.subeventPrefix]) then
|
|
trigger.subeventSuffix = "";
|
|
end
|
|
end
|
|
|
|
prototype = event_prototypes[trigger.event]
|
|
triggerFuncStr = ConstructFunction(prototype, trigger);
|
|
|
|
statesParameter = prototype.statesParameter;
|
|
triggerFunc = Private.LoadFunction(triggerFuncStr);
|
|
|
|
durationFunc = prototype.durationFunc;
|
|
nameFunc = prototype.nameFunc;
|
|
iconFunc = prototype.iconFunc;
|
|
textureFunc = prototype.textureFunc;
|
|
stacksFunc = prototype.stacksFunc;
|
|
loadFunc = prototype.loadFunc;
|
|
loadInternalEventFunc = prototype.loadInternalEventFunc;
|
|
|
|
if (prototype.overlayFuncs) then
|
|
overlayFuncs = {};
|
|
local dest = 1;
|
|
for i, v in ipairs(prototype.overlayFuncs) do
|
|
local enable = true
|
|
if type(v.enable) == "function" then
|
|
enable = v.enable(trigger)
|
|
elseif type(v.enable) == "boolean" then
|
|
enable = v.enable
|
|
end
|
|
if enable then
|
|
overlayFuncs[dest] = v.func;
|
|
dest = dest + 1;
|
|
end
|
|
end
|
|
end
|
|
|
|
if (prototype.automaticrequired) then
|
|
untriggerFunc = trueFunction
|
|
elseif prototype.timedrequired then
|
|
automaticAutoHide = true
|
|
duration = tonumber(trigger.duration or "1")
|
|
else
|
|
WeakAuras.prettyPrint("Invalid Prototype found: " .. prototype.name)
|
|
end
|
|
|
|
if prototype.countEvents then
|
|
if trigger.use_count and type(trigger.count) == "string" and trigger.count ~= "" then
|
|
counter = Private.ExecEnv.CreateTriggerCounter(trigger.count)
|
|
else
|
|
counter = Private.ExecEnv.CreateTriggerCounter()
|
|
end
|
|
end
|
|
|
|
if(prototype) then
|
|
local trigger_all_events = prototype.events;
|
|
internal_events = prototype.internal_events;
|
|
force_events = prototype.force_events;
|
|
if prototype.subevents then
|
|
trigger_subevents = prototype.subevents
|
|
if trigger_subevents and type(trigger_subevents) == "function" then
|
|
trigger_subevents = trigger_subevents(trigger, untrigger)
|
|
end
|
|
end
|
|
|
|
if trigger.event == "Combat Log" and trigger.subeventPrefix and trigger.subeventSuffix then
|
|
tinsert(trigger_subevents, trigger.subeventPrefix .. trigger.subeventSuffix)
|
|
end
|
|
|
|
if (type(trigger_all_events) == "function") then
|
|
trigger_all_events = trigger_all_events(trigger, untrigger);
|
|
end
|
|
trigger_events = trigger_all_events.events
|
|
trigger_unit_events = trigger_all_events.unit_events
|
|
if (type(internal_events) == "function") then
|
|
internal_events = internal_events(trigger, untrigger);
|
|
end
|
|
if (type(force_events) == "function") then
|
|
force_events = force_events(trigger, untrigger)
|
|
end
|
|
|
|
|
|
if prototype.includePets then
|
|
includePets = trigger.use_includePets == true and trigger.includePets or nil
|
|
end
|
|
end
|
|
end
|
|
else -- CUSTOM
|
|
triggerFunc = WeakAuras.LoadFunction("return "..(trigger.custom or ""));
|
|
if (trigger.custom_type == "stateupdate") then
|
|
tsuConditionVariables = WeakAuras.LoadFunction("return function() return \n" .. (trigger.customVariables or "") .. "\n end");
|
|
if not tsuConditionVariables then
|
|
tsuConditionVariables = function() end
|
|
end
|
|
end
|
|
|
|
if(trigger.custom_type == "status" or trigger.custom_type == "event" and trigger.custom_hide == "custom") then
|
|
untriggerFunc = WeakAuras.LoadFunction("return "..(untrigger.custom or ""));
|
|
if (not untriggerFunc) then
|
|
untriggerFunc = trueFunction;
|
|
end
|
|
end
|
|
|
|
if(trigger.custom_type ~= "stateupdate" and trigger.customDuration and trigger.customDuration ~= "") then
|
|
durationFunc = WeakAuras.LoadFunction("return "..trigger.customDuration);
|
|
end
|
|
if(trigger.custom_type ~= "stateupdate") then
|
|
overlayFuncs = {};
|
|
for i = 1, 7 do
|
|
local property = "customOverlay" .. i;
|
|
if (trigger[property] and trigger[property] ~= "") then
|
|
overlayFuncs[i] = WeakAuras.LoadFunction("return ".. trigger[property]);
|
|
end
|
|
end
|
|
end
|
|
if(trigger.custom_type ~= "stateupdate" and trigger.customName and trigger.customName ~= "") then
|
|
nameFunc = WeakAuras.LoadFunction("return "..trigger.customName);
|
|
end
|
|
if(trigger.custom_type ~= "stateupdate" and trigger.customIcon and trigger.customIcon ~= "") then
|
|
iconFunc = WeakAuras.LoadFunction("return "..trigger.customIcon);
|
|
end
|
|
if(trigger.custom_type ~= "stateupdate" and trigger.customTexture and trigger.customTexture ~= "") then
|
|
textureFunc = WeakAuras.LoadFunction("return "..trigger.customTexture);
|
|
end
|
|
if(trigger.custom_type ~= "stateupdate" and trigger.customStacks and trigger.customStacks ~= "") then
|
|
stacksFunc = WeakAuras.LoadFunction("return "..trigger.customStacks);
|
|
end
|
|
|
|
if((trigger.custom_type == "status" or trigger.custom_type == "stateupdate") and trigger.check == "update") then
|
|
trigger_events = {"FRAME_UPDATE"};
|
|
else
|
|
local rawEvents = WeakAuras.split(trigger.events);
|
|
for index, event in pairs(rawEvents) do
|
|
-- custom events in the form of event:unit1:unit2:unitX are registered with RegisterUnitEvent
|
|
local trueEvent
|
|
local hasParam = false
|
|
local isCLEU = false
|
|
local isUnitEvent = false
|
|
local isTrigger = false
|
|
if event == "CLEU" or event == "COMBAT_LOG_EVENT_UNFILTERED" then
|
|
warnAboutCLEUEvents = true
|
|
end
|
|
for i in event:gmatch("[^:]+") do
|
|
if not trueEvent then
|
|
trueEvent = string.upper(i)
|
|
isCLEU = trueEvent == "CLEU" or trueEvent == "COMBAT_LOG_EVENT_UNFILTERED"
|
|
isTrigger = trueEvent == "TRIGGER"
|
|
elseif isCLEU then
|
|
local subevent = string.upper(i)
|
|
if Private.IsCLEUSubevent(subevent) then
|
|
tinsert(trigger_subevents, subevent)
|
|
hasParam = true
|
|
end
|
|
elseif Private.InternalEventByIDList[trueEvent] then
|
|
tinsert(trigger_events, trueEvent..":"..i)
|
|
elseif trueEvent:match("^UNIT_") then
|
|
isUnitEvent = true
|
|
|
|
if string.lower(strsub(i, #i - 3)) == "pets" then
|
|
i = strsub(i, 1, #i-4)
|
|
includePets = "PlayersAndPets"
|
|
elseif string.lower(strsub(i, #i - 7)) == "petsonly" then
|
|
includePets = "PetsOnly"
|
|
i = strsub(i, 1, #i - 8)
|
|
end
|
|
|
|
trigger_unit_events[i] = trigger_unit_events[i] or {}
|
|
tinsert(trigger_unit_events[i], trueEvent)
|
|
elseif isTrigger then
|
|
local requestedTriggernum = tonumber(i)
|
|
if requestedTriggernum then
|
|
if watched_trigger_events[id] and watched_trigger_events[id][triggernum] and watched_trigger_events[id][triggernum][requestedTriggernum] then
|
|
-- if the request is reciprocal (2 custom triggers request each other which would cause a stack overflow) then prevent the reciprocal one being added.
|
|
elseif requestedTriggernum and requestedTriggernum ~= triggernum then
|
|
watched_trigger_events[id] = watched_trigger_events[id] or {}
|
|
watched_trigger_events[id][requestedTriggernum] = watched_trigger_events[id][requestedTriggernum] or {}
|
|
watched_trigger_events[id][requestedTriggernum][triggernum] = true
|
|
end
|
|
end
|
|
end
|
|
end
|
|
if isCLEU then
|
|
if hasParam then
|
|
tinsert(trigger_events, "COMBAT_LOG_EVENT_UNFILTERED")
|
|
else
|
|
-- This is a dirty, lazy, dirty hack. "Proper" COMBAT_LOG_EVENT_UNFILTERED events are indexed by their sub-event types (e.g. SPELL_PERIODIC_DAMAGE),
|
|
-- but custom COMBAT_LOG_EVENT_UNFILTERED events are not guaranteed to have sub-event types. Thus, if the user specifies that they want to use
|
|
-- COMBAT_LOG_EVENT_UNFILTERED, this hack renames the event to COMBAT_LOG_EVENT_UNFILTERED_CUSTOM to circumvent the COMBAT_LOG_EVENT_UNFILTERED checks
|
|
-- that are already in place. Replacing all those checks would be a pain in the ass.
|
|
tinsert(trigger_events, "COMBAT_LOG_EVENT_UNFILTERED_CUSTOM")
|
|
end
|
|
elseif isUnitEvent then
|
|
-- not added to trigger_events
|
|
elseif isTrigger then
|
|
-- not added to trigger_events
|
|
else
|
|
tinsert(trigger_events, event)
|
|
end
|
|
end
|
|
end
|
|
if trigger.custom_type == "status" or trigger.custom_type == "stateupdate" then
|
|
force_events = data.information.forceEvents or "STATUS"
|
|
end
|
|
if (trigger.custom_type == "stateupdate") then
|
|
statesParameter = "full";
|
|
end
|
|
|
|
if(trigger.custom_type == "event" and trigger.custom_hide == "timed") then
|
|
automaticAutoHide = true;
|
|
if (not trigger.dynamicDuration) then
|
|
duration = tonumber(trigger.duration);
|
|
end
|
|
end
|
|
end
|
|
|
|
events[id] = events[id] or {};
|
|
events[id][triggernum] = {
|
|
trigger = trigger,
|
|
triggerFunc = triggerFunc,
|
|
untriggerFunc = untriggerFunc,
|
|
statesParameter = statesParameter,
|
|
event = trigger.event,
|
|
events = trigger_events,
|
|
internal_events = internal_events,
|
|
loadInternalEventFunc = loadInternalEventFunc,
|
|
force_events = force_events,
|
|
unit_events = trigger_unit_events,
|
|
includePets = includePets,
|
|
inverse = trigger.use_inverse,
|
|
subevents = trigger_subevents,
|
|
durationFunc = durationFunc,
|
|
overlayFuncs = overlayFuncs,
|
|
nameFunc = nameFunc,
|
|
iconFunc = iconFunc,
|
|
textureFunc = textureFunc,
|
|
stacksFunc = stacksFunc,
|
|
loadFunc = loadFunc,
|
|
duration = duration,
|
|
automaticAutoHide = automaticAutoHide,
|
|
tsuConditionVariables = tsuConditionVariables,
|
|
prototype = prototype,
|
|
ignoreOptionsEventErrors = data.information.ignoreOptionsEventErrors,
|
|
counter = counter,
|
|
};
|
|
end
|
|
end
|
|
end
|
|
|
|
if warnAboutCLEUEvents then
|
|
Private.AuraWarnings.UpdateWarning(data.uid, "spammy_event_warning", "warning",
|
|
L["COMBAT_LOG_EVENT_UNFILTERED with no filter can trigger frame drops in raid environment. Find more information:\nhttps://github.com/WeakAuras/WeakAuras2/wiki/Deprecated-CLEU"])
|
|
else
|
|
Private.AuraWarnings.UpdateWarning(data.uid, "spammy_event_warning")
|
|
end
|
|
end
|
|
|
|
do
|
|
local update_clients = {};
|
|
local update_clients_num = 0;
|
|
local update_frame = nil
|
|
Private.frames["Custom Trigger Every Frame Updater"] = update_frame;
|
|
local updating = false;
|
|
|
|
function Private.RegisterEveryFrameUpdate(id)
|
|
if not(update_clients[id]) then
|
|
update_clients[id] = true;
|
|
update_clients_num = update_clients_num + 1;
|
|
end
|
|
if not(update_frame) then
|
|
update_frame = CreateFrame("Frame");
|
|
end
|
|
if not(updating) then
|
|
update_frame:SetScript("OnUpdate", function(self, elapsed)
|
|
if not(WeakAuras.IsPaused()) then
|
|
WeakAuras.ScanEvents("FRAME_UPDATE", elapsed);
|
|
end
|
|
end);
|
|
updating = true;
|
|
end
|
|
end
|
|
|
|
function Private.EveryFrameUpdateRename(oldid, newid)
|
|
update_clients[newid] = update_clients[oldid];
|
|
update_clients[oldid] = nil;
|
|
end
|
|
|
|
function Private.UnregisterEveryFrameUpdate(id)
|
|
if(update_clients[id]) then
|
|
update_clients[id] = nil;
|
|
update_clients_num = update_clients_num - 1;
|
|
end
|
|
if(update_clients_num == 0 and update_frame and updating) then
|
|
update_frame:SetScript("OnUpdate", nil);
|
|
updating = false;
|
|
end
|
|
end
|
|
|
|
function Private.UnregisterAllEveryFrameUpdate()
|
|
if (not update_frame) then
|
|
return;
|
|
end
|
|
wipe(update_clients);
|
|
update_clients_num = 0;
|
|
update_frame:SetScript("OnUpdate", nil);
|
|
updating = false;
|
|
end
|
|
end
|
|
|
|
--#############################
|
|
--# Support code for triggers #
|
|
--#############################
|
|
|
|
-- Swing timer support code
|
|
do
|
|
local mh = GetInventorySlotInfo("MainHandSlot")
|
|
local oh = GetInventorySlotInfo("SecondaryHandSlot")
|
|
local ranged = GetInventorySlotInfo("RangedSlot")
|
|
|
|
local swingTimerFrame;
|
|
local lastSwingMain, lastSwingOff, lastSwingRange;
|
|
local swingDurationMain, swingDurationOff, swingDurationRange, mainSwingOffset;
|
|
local mainTimer, offTimer, rangeTimer;
|
|
local selfGUID;
|
|
local mainSpeed, offSpeed = UnitAttackSpeed("player")
|
|
local casting = false
|
|
|
|
function WeakAuras.GetSwingTimerInfo(hand)
|
|
if(hand == "main") then
|
|
local itemId = GetInventoryItemID("player", mh);
|
|
local name, _, _, _, _, _, _, _, _, icon = GetItemInfo(itemId or 0);
|
|
if(lastSwingMain) then
|
|
return swingDurationMain, lastSwingMain + swingDurationMain - mainSwingOffset, name, icon;
|
|
else
|
|
return 0, math.huge, name, icon;
|
|
end
|
|
elseif(hand == "off") then
|
|
local itemId = GetInventoryItemID("player", oh);
|
|
local name, _, _, _, _, _, _, _, _, icon = GetItemInfo(itemId or 0);
|
|
if(lastSwingOff) then
|
|
return swingDurationOff, lastSwingOff + swingDurationOff, name, icon;
|
|
else
|
|
return 0, math.huge, name, icon;
|
|
end
|
|
elseif(hand == "ranged") then
|
|
local itemId = GetInventoryItemID("player", ranged);
|
|
local name, _, _, _, _, _, _, _, _, icon = GetItemInfo(itemId or 0);
|
|
if (lastSwingRange) then
|
|
return swingDurationRange, lastSwingRange + swingDurationRange, name, icon;
|
|
else
|
|
return 0, math.huge, name, icon;
|
|
end
|
|
end
|
|
|
|
return 0, math.huge;
|
|
end
|
|
|
|
local function swingEnd(hand)
|
|
if(hand == "main") then
|
|
lastSwingMain, swingDurationMain, mainSwingOffset = nil, nil, nil;
|
|
elseif(hand == "off") then
|
|
lastSwingOff, swingDurationOff = nil, nil;
|
|
elseif(hand == "ranged") then
|
|
lastSwingRange, swingDurationRange = nil, nil;
|
|
end
|
|
WeakAuras.ScanEvents("SWING_TIMER_END");
|
|
end
|
|
|
|
local function swingTimerCLEUCheck(ts, event, sourceGUID, _, _, destGUID, _, _, ...)
|
|
Private.StartProfileSystem("generictrigger swing");
|
|
if(sourceGUID == selfGUID) then
|
|
if(event == "SWING_DAMAGE" or event == "SWING_MISSED") then
|
|
local isOffHand = select(event == "SWING_DAMAGE" and 10 or 2, ...);
|
|
|
|
local event;
|
|
local currentTime = GetTime();
|
|
mainSpeed, offSpeed = UnitAttackSpeed("player");
|
|
offSpeed = offSpeed or 0;
|
|
if not(lastSwingMain) then
|
|
lastSwingMain = currentTime;
|
|
swingDurationMain = mainSpeed;
|
|
mainSwingOffset = 0;
|
|
event = "SWING_TIMER_START";
|
|
timer:CancelTimer(mainTimer);
|
|
mainTimer = timer:ScheduleTimer(swingEnd, mainSpeed, "main");
|
|
elseif(OffhandHasWeapon() and not lastSwingOff) then
|
|
lastSwingOff = currentTime;
|
|
swingDurationOff = offSpeed;
|
|
event = "SWING_TIMER_START";
|
|
timer:CancelTimer(offTimer);
|
|
offTimer = timer:ScheduleTimer(swingEnd, offSpeed, "off");
|
|
else
|
|
-- A swing occurred while both weapons are supposed to be on cooldown
|
|
-- Simply refresh the timer of the weapon swing which would have ended sooner
|
|
local mainRem, offRem = (lastSwingMain or math.huge) + mainSpeed - currentTime, (lastSwingOff or math.huge) + offSpeed - currentTime;
|
|
if(mainRem < offRem or not OffhandHasWeapon()) then
|
|
timer:CancelTimer(mainTimer, true);
|
|
lastSwingMain = currentTime;
|
|
swingDurationMain = mainSpeed;
|
|
event = "SWING_TIMER_CHANGE";
|
|
mainTimer = timer:ScheduleTimer(swingEnd, mainSpeed, "main");
|
|
else
|
|
timer:CancelTimer(mainTimer, true);
|
|
lastSwingOff = currentTime;
|
|
swingDurationOff = offSpeed;
|
|
event = "SWING_TIMER_CHANGE";
|
|
offTimer = timer:ScheduleTimer(swingEnd, offSpeed, "off");
|
|
end
|
|
end
|
|
WeakAuras.ScanEvents(event);
|
|
end
|
|
elseif (destGUID == selfGUID and (select(1, ...) == "PARRY" or select(4, ...) == "PARRY")) then
|
|
if (lastSwingMain) then
|
|
local timeLeft = lastSwingMain + swingDurationMain - GetTime();
|
|
if (timeLeft > 0.6 * swingDurationMain) then
|
|
timer:CancelTimer(mainTimer);
|
|
mainTimer = timer:ScheduleTimer(swingEnd, timeLeft - 0.4 * swingDurationMain, "main");
|
|
mainSwingOffset = 0.4 * swingDurationMain
|
|
WeakAuras.ScanEvents("SWING_TIMER_CHANGE");
|
|
elseif (timeLeft > 0.2 * swingDurationMain) then
|
|
timer:CancelTimer(mainTimer);
|
|
mainTimer = timer:ScheduleTimer(swingEnd, timeLeft - 0.2 * swingDurationMain, "main");
|
|
mainSwingOffset = 0.2 * swingDurationMain
|
|
WeakAuras.ScanEvents("SWING_TIMER_CHANGE");
|
|
end
|
|
end
|
|
end
|
|
Private.StopProfileSystem("generictrigger swing");
|
|
end
|
|
|
|
local function swingTimerCheck(event, unit, spell)
|
|
if unit ~= "player" then return end
|
|
Private.StartProfileSystem("generictrigger swing");
|
|
if event == "UNIT_ATTACK_SPEED" then
|
|
local mainSpeedNew, offSpeedNew = UnitAttackSpeed("player")
|
|
offSpeedNew = offSpeedNew or 0
|
|
if lastSwingMain then
|
|
if mainSpeedNew ~= mainSpeed then
|
|
timer:CancelTimer(mainTimer)
|
|
local multiplier = mainSpeedNew / mainSpeed
|
|
local timeLeft = (lastSwingMain + swingDurationMain - GetTime()) * multiplier
|
|
swingDurationMain = mainSpeedNew
|
|
mainTimer = timer:ScheduleTimer(swingEnd, timeLeft, "main")
|
|
WeakAuras.ScanEvents("SWING_TIMER_CHANGE")
|
|
end
|
|
end
|
|
if lastSwingOff then
|
|
if offSpeedNew ~= offSpeed then
|
|
timer:CancelTimer(offTimer)
|
|
local multiplier = offSpeedNew / mainSpeed
|
|
local timeLeft = (lastSwingOff + swingDurationOff - GetTime()) * multiplier
|
|
swingDurationOff = offSpeedNew
|
|
offTimer = timer:ScheduleTimer(swingEnd, timeLeft, "off")
|
|
WeakAuras.ScanEvents("SWING_TIMER_CHANGE")
|
|
end
|
|
end
|
|
mainSpeed, offSpeed = mainSpeedNew, offSpeedNew
|
|
elseif casting and (event == "UNIT_SPELLCAST_INTERRUPTED" or event == "UNIT_SPELLCAST_FAILED") then
|
|
casting = false
|
|
elseif event == "UNIT_SPELLCAST_SUCCEEDED" then
|
|
if Private.reset_swing_spells[spell] or casting then
|
|
if casting then
|
|
casting = false
|
|
end
|
|
local event;
|
|
mainSpeed, offSpeed = UnitAttackSpeed("player");
|
|
lastSwingMain = GetTime();
|
|
swingDurationMain = mainSpeed;
|
|
mainSwingOffset = 0;
|
|
if (lastSwingMain) then
|
|
timer:CancelTimer(mainTimer);
|
|
event = "SWING_TIMER_CHANGE";
|
|
else
|
|
event = "SWING_TIMER_START";
|
|
end
|
|
mainTimer = timer:ScheduleTimer(swingEnd, mainSpeed, "main");
|
|
WeakAuras.ScanEvents(event);
|
|
elseif Private.reset_ranged_swing_spells[spell] then
|
|
local event;
|
|
local currentTime = GetTime();
|
|
local speed = UnitRangedDamage("player");
|
|
if(lastSwingRange) then
|
|
timer:CancelTimer(rangeTimer, true)
|
|
event = "SWING_TIMER_CHANGE";
|
|
else
|
|
event = "SWING_TIMER_START";
|
|
end
|
|
lastSwingRange = currentTime;
|
|
swingDurationRange = speed;
|
|
rangeTimer = timer:ScheduleTimer(swingEnd, speed, "ranged");
|
|
WeakAuras.ScanEvents(event);
|
|
end
|
|
elseif event == "UNIT_SPELLCAST_START" then
|
|
-- pause swing timer
|
|
casting = true
|
|
lastSwingMain, swingDurationMain, mainSwingOffset = nil, nil, nil
|
|
lastSwingOff, swingDurationOff = nil, nil
|
|
WeakAuras.ScanEvents("SWING_TIMER_END")
|
|
end
|
|
Private.StopProfileSystem("generictrigger swing");
|
|
end
|
|
|
|
function WeakAuras.InitSwingTimer()
|
|
if not(swingTimerFrame) then
|
|
swingTimerFrame = CreateFrame("Frame");
|
|
swingTimerFrame:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED");
|
|
swingTimerFrame:RegisterEvent("PLAYER_ENTER_COMBAT");
|
|
swingTimerFrame:RegisterEvent("UNIT_ATTACK_SPEED");
|
|
swingTimerFrame:RegisterEvent("UNIT_SPELLCAST_SUCCEEDED");
|
|
swingTimerFrame:RegisterEvent("UNIT_SPELLCAST_START")
|
|
swingTimerFrame:RegisterEvent("UNIT_SPELLCAST_INTERRUPTED")
|
|
swingTimerFrame:RegisterEvent("UNIT_SPELLCAST_FAILED")
|
|
swingTimerFrame:SetScript("OnEvent",
|
|
function(_, event, ...)
|
|
if event == "COMBAT_LOG_EVENT_UNFILTERED" then
|
|
swingTimerCLEUCheck(...)
|
|
else
|
|
swingTimerCheck(event, ...)
|
|
end
|
|
end);
|
|
selfGUID = UnitGUID("player");
|
|
end
|
|
end
|
|
end
|
|
|
|
-- CD/Rune/GCD support code
|
|
do
|
|
local cdReadyFrame;
|
|
|
|
local spells = {};
|
|
local spellKnown = {};
|
|
|
|
local spellCounts = {}
|
|
|
|
local items = {};
|
|
local itemCdDurs = {};
|
|
local itemCdExps = {};
|
|
local itemCdHandles = {};
|
|
local itemCdEnabled = {};
|
|
|
|
local itemSlots = {};
|
|
local itemSlotsCdDurs = {};
|
|
local itemSlotsCdExps = {};
|
|
local itemSlotsCdHandles = {};
|
|
local itemSlotsEnable = {};
|
|
|
|
local runes = {};
|
|
local runeCdDurs = {};
|
|
local runeCdExps = {};
|
|
local runeCdHandles = {};
|
|
|
|
local gcdStart;
|
|
local gcdDuration;
|
|
local gcdSpellName;
|
|
local gcdSpellIcon;
|
|
local gcdEndCheck;
|
|
|
|
local function GetRuneDuration()
|
|
local runeDuration = -100;
|
|
for id, _ in pairs(runes) do
|
|
local startTime, duration = GetRuneCooldown(id);
|
|
duration = duration or 0;
|
|
runeDuration = duration > 0 and duration or runeDuration
|
|
end
|
|
return runeDuration
|
|
end
|
|
|
|
local function CheckGCD()
|
|
local event;
|
|
local startTime, duration = GetSpellCooldown(61304);
|
|
if(duration and duration > 0) then
|
|
if not(gcdStart) then
|
|
event = "GCD_START";
|
|
elseif(gcdStart ~= startTime or gcdDuration ~= duration) then
|
|
event = "GCD_CHANGE";
|
|
end
|
|
gcdStart, gcdDuration = startTime, duration;
|
|
local endCheck = startTime + duration + 0.1;
|
|
if(gcdEndCheck ~= endCheck) then
|
|
gcdEndCheck = endCheck;
|
|
timer:ScheduleTimer(CheckGCD, duration + 0.1);
|
|
end
|
|
else
|
|
if(gcdStart) then
|
|
event = "GCD_END"
|
|
end
|
|
gcdStart, gcdDuration = nil, nil;
|
|
gcdSpellName, gcdSpellIcon = nil, nil;
|
|
gcdEndCheck = 0;
|
|
end
|
|
if(event and not WeakAuras.IsPaused()) then
|
|
WeakAuras.ScanEvents(event);
|
|
end
|
|
end
|
|
|
|
local RecheckHandles = {
|
|
expirationTime = {},
|
|
handles = {},
|
|
Recheck = function(self, id)
|
|
self.handles[id] = nil
|
|
self.expirationTime[id] = nil
|
|
CheckGCD();
|
|
Private.CheckSpellCooldown(id, GetRuneDuration())
|
|
end,
|
|
Schedule = function(self, expirationTime, id)
|
|
if (not self.expirationTime[id] or expirationTime < self.expirationTime[id]) and expirationTime > 0 then
|
|
if self.handles[id] then
|
|
timer:CancelTimer(self.handles[id])
|
|
self.handles[id] = nil
|
|
self.expirationTime[id] = nil
|
|
end
|
|
|
|
local duration = expirationTime - GetTime()
|
|
if duration > 0 then
|
|
self.handles[id] = timer:ScheduleTimer(self.Recheck, duration, self, id)
|
|
self.expirationTime[id] = expirationTime
|
|
end
|
|
end
|
|
end
|
|
}
|
|
|
|
local function FetchSpellCooldown(self, id)
|
|
if self.duration[id] and self.expirationTime[id] then
|
|
return self.expirationTime[id] - self.duration[id], self.duration[id], self.readyTime[id]
|
|
end
|
|
return 0, 0, nil
|
|
end
|
|
|
|
local function HandleSpell(self, id, startTime, duration)
|
|
local changed = false
|
|
local nowReady = false
|
|
local time = GetTime()
|
|
if self.expirationTime[id] and self.expirationTime[id] <= time and self.expirationTime[id] ~= 0 then
|
|
self.duration[id] = 0
|
|
self.expirationTime[id] = 0
|
|
changed = true
|
|
nowReady = true
|
|
end
|
|
local endTime = startTime + duration;
|
|
if endTime <= time then
|
|
startTime = 0
|
|
duration = 0
|
|
endTime = 0
|
|
end
|
|
|
|
if duration > 0 then
|
|
if startTime == gcdStart and duration == gcdDuration then
|
|
-- GCD cooldown, this could mean that the spell reset!
|
|
if self.expirationTime[id] and self.expirationTime[id] > endTime and self.expirationTime[id] ~= 0 then
|
|
self.duration[id] = 0
|
|
self.expirationTime[id] = 0
|
|
if not self.readyTime[id] then
|
|
self.readyTime[id] = time
|
|
end
|
|
changed = true
|
|
nowReady = true
|
|
end
|
|
RecheckHandles:Schedule(endTime, id)
|
|
return changed, nowReady
|
|
end
|
|
end
|
|
|
|
if self.duration[id] ~= duration then
|
|
self.duration[id] = duration
|
|
changed = true
|
|
end
|
|
|
|
if self.expirationTime[id] ~= endTime then
|
|
self.expirationTime[id] = endTime
|
|
changed = true
|
|
nowReady = endTime == 0
|
|
end
|
|
|
|
if duration == 0 then
|
|
if not self.readyTime[id] then
|
|
self.readyTime[id] = time
|
|
end
|
|
else
|
|
self.readyTime[id] = nil
|
|
end
|
|
|
|
RecheckHandles:Schedule(endTime, id)
|
|
return changed, nowReady
|
|
end
|
|
|
|
local function CreateSpellCDHandler()
|
|
local cd = {
|
|
duration = {},
|
|
expirationTime = {},
|
|
readyTime = {},
|
|
handles = {}, -- Share handles, and use lowest time to schedule
|
|
HandleSpell = HandleSpell,
|
|
FetchSpellCooldown = FetchSpellCooldown
|
|
}
|
|
return cd
|
|
end
|
|
|
|
local spellCds = CreateSpellCDHandler();
|
|
local spellCdsRune = CreateSpellCDHandler();
|
|
|
|
local spellDetails = {}
|
|
local mark_ACTIONBAR_UPDATE_COOLDOWN, mark_PLAYER_ENTERING_WORLD
|
|
|
|
function Private.InitCooldownReady()
|
|
cdReadyFrame = CreateFrame("Frame");
|
|
cdReadyFrame.inWorld = 0
|
|
Private.frames["Cooldown Trigger Handler"] = cdReadyFrame
|
|
cdReadyFrame:RegisterEvent("RUNE_POWER_UPDATE");
|
|
cdReadyFrame:RegisterEvent("RUNE_TYPE_UPDATE");
|
|
cdReadyFrame:RegisterEvent("PLAYER_TALENT_UPDATE");
|
|
cdReadyFrame:RegisterEvent("CHARACTER_POINTS_CHANGED");
|
|
cdReadyFrame:RegisterEvent("SPELL_UPDATE_COOLDOWN");
|
|
cdReadyFrame:RegisterEvent("UNIT_SPELLCAST_SENT");
|
|
cdReadyFrame:RegisterEvent("BAG_UPDATE_COOLDOWN");
|
|
cdReadyFrame:RegisterEvent("UNIT_INVENTORY_CHANGED")
|
|
cdReadyFrame:RegisterEvent("PLAYER_EQUIPMENT_CHANGED");
|
|
cdReadyFrame:RegisterEvent("ACTIONBAR_UPDATE_COOLDOWN");
|
|
cdReadyFrame:RegisterEvent("SPELLS_CHANGED");
|
|
cdReadyFrame:RegisterEvent("PLAYER_ENTERING_WORLD");
|
|
cdReadyFrame:RegisterEvent("PLAYER_LEAVING_WORLD");
|
|
cdReadyFrame.HandleEvent = function(self, event, ...)
|
|
if (event == "PLAYER_ENTERING_WORLD") then
|
|
cdReadyFrame.inWorld = GetTime()
|
|
end
|
|
if (event == "PLAYER_LEAVING_WORLD") then
|
|
cdReadyFrame.inWorld = nil
|
|
end
|
|
if not cdReadyFrame.inWorld then
|
|
return
|
|
end
|
|
|
|
if GetTime() - cdReadyFrame.inWorld < 2 then
|
|
mark_PLAYER_ENTERING_WORLD = true
|
|
cdReadyFrame:Show()
|
|
return
|
|
end
|
|
if (event == "ACTIONBAR_UPDATE_COOLDOWN") then
|
|
mark_ACTIONBAR_UPDATE_COOLDOWN = true
|
|
cdReadyFrame:Show()
|
|
return
|
|
end
|
|
|
|
Private.StartProfileSystem("generictrigger cd tracking");
|
|
if type(event) == "number" then-- Called from OnUpdate!
|
|
if mark_PLAYER_ENTERING_WORLD then
|
|
Private.CheckSpellKnown()
|
|
Private.CheckCooldownReady()
|
|
Private.CheckItemSlotCooldowns()
|
|
mark_PLAYER_ENTERING_WORLD = nil
|
|
mark_ACTIONBAR_UPDATE_COOLDOWN = nil
|
|
elseif mark_ACTIONBAR_UPDATE_COOLDOWN then
|
|
Private.CheckCooldownReady()
|
|
mark_ACTIONBAR_UPDATE_COOLDOWN = nil
|
|
end
|
|
elseif(event == "SPELL_UPDATE_COOLDOWN" or event == "RUNE_POWER_UPDATE"
|
|
or event == "PLAYER_TALENT_UPDATE"
|
|
or event == "CHARACTER_POINTS_CHANGED" or event == "RUNE_TYPE_UPDATE") then
|
|
if event == "SPELL_UPDATE_COOLDOWN" then
|
|
mark_ACTIONBAR_UPDATE_COOLDOWN = nil
|
|
end
|
|
Private.CheckCooldownReady();
|
|
elseif(event == "SPELLS_CHANGED") then
|
|
Private.CheckSpellKnown()
|
|
Private.CheckCooldownReady()
|
|
elseif(event == "UNIT_SPELLCAST_SENT") then
|
|
local unit, name, _ = ...;
|
|
if(unit == "player") then
|
|
if(gcdSpellName ~= name) then
|
|
local icon = GetSpellTexture(name);
|
|
gcdSpellName = name;
|
|
gcdSpellIcon = icon;
|
|
if not WeakAuras.IsPaused() then
|
|
WeakAuras.ScanEvents("GCD_UPDATE")
|
|
end
|
|
end
|
|
end
|
|
elseif(event == "UNIT_INVENTORY_CHANGED" and ... and ... == "player" or event == "BAG_UPDATE_COOLDOWN" or event == "PLAYER_EQUIPMENT_CHANGED") then
|
|
Private.CheckItemSlotCooldowns();
|
|
end
|
|
Private.StopProfileSystem("generictrigger cd tracking");
|
|
if mark_PLAYER_ENTERING_WORLD == nil and mark_ACTIONBAR_UPDATE_COOLDOWN == nil then
|
|
cdReadyFrame:Hide()
|
|
else
|
|
cdReadyFrame:Show()
|
|
end
|
|
end
|
|
cdReadyFrame:Hide()
|
|
cdReadyFrame:SetScript("OnEvent", cdReadyFrame.HandleEvent)
|
|
cdReadyFrame:SetScript("OnUpdate", cdReadyFrame.HandleEvent)
|
|
end
|
|
|
|
function WeakAuras.GetRuneCooldown(id)
|
|
if(runes[id] and runeCdExps[id] and runeCdDurs[id]) then
|
|
return runeCdExps[id] - runeCdDurs[id], runeCdDurs[id];
|
|
else
|
|
return 0, 0;
|
|
end
|
|
end
|
|
|
|
function WeakAuras.GetSpellCooldown(id, ignoreRuneCD, showgcd)
|
|
local startTime, duration, gcdCooldown, readyTime
|
|
if (ignoreRuneCD) then
|
|
startTime, duration, readyTime = spellCdsRune:FetchSpellCooldown(id)
|
|
else
|
|
startTime, duration, readyTime = spellCds:FetchSpellCooldown(id)
|
|
end
|
|
|
|
if (showgcd) then
|
|
if ((gcdStart or 0) + (gcdDuration or 0) > startTime + duration) then
|
|
if startTime == 0 then
|
|
gcdCooldown = true
|
|
end
|
|
startTime = gcdStart;
|
|
duration = gcdDuration;
|
|
end
|
|
end
|
|
|
|
return startTime, duration, gcdCooldown, readyTime
|
|
end
|
|
|
|
function WeakAuras.GetSpellCharges(id)
|
|
return spellCounts[id];
|
|
end
|
|
|
|
function WeakAuras.GetItemCooldown(id, showgcd)
|
|
local startTime, duration, enabled, gcdCooldown;
|
|
if(items[id] and itemCdExps[id] and itemCdDurs[id]) then
|
|
startTime, duration, enabled = itemCdExps[id] - itemCdDurs[id], itemCdDurs[id], itemCdEnabled[id];
|
|
else
|
|
startTime, duration, enabled = 0, 0, itemCdEnabled[id] or 1;
|
|
end
|
|
if (showgcd) then
|
|
if ((gcdStart or 0) + (gcdDuration or 0) > startTime + duration) then
|
|
if startTime == 0 then
|
|
gcdCooldown = true
|
|
end
|
|
startTime = gcdStart;
|
|
duration = gcdDuration;
|
|
end
|
|
end
|
|
return startTime, duration, enabled, gcdCooldown;
|
|
end
|
|
|
|
function WeakAuras.GetGCDInfo()
|
|
if(gcdStart) then
|
|
return gcdDuration, gcdStart + gcdDuration, gcdSpellName or "Invalid", gcdSpellIcon or "Interface\\Icons\\INV_Misc_QuestionMark";
|
|
else
|
|
return 0, math.huge, gcdSpellName or "Invalid", gcdSpellIcon or "Interface\\Icons\\INV_Misc_QuestionMark";
|
|
end
|
|
end
|
|
|
|
function WeakAuras.gcdDuration()
|
|
return gcdDuration or 0;
|
|
end
|
|
|
|
function WeakAuras.GcdSpellName()
|
|
return gcdSpellName;
|
|
end
|
|
|
|
function WeakAuras.GetItemSlotCooldown(id, showgcd)
|
|
local startTime, duration, enabled, gcdCooldown;
|
|
if(itemSlots[id] and itemSlotsCdExps[id] and itemSlotsCdDurs[id]) then
|
|
startTime, duration, enabled = itemSlotsCdExps[id] - itemSlotsCdDurs[id], itemSlotsCdDurs[id], itemSlotsEnable[id];
|
|
else
|
|
startTime, duration, enabled = 0, 0, itemSlotsEnable[id];
|
|
end
|
|
|
|
if (showgcd) then
|
|
if ((gcdStart or 0) + (gcdDuration or 0) > startTime + duration) then
|
|
if startTime == 0 then
|
|
gcdCooldown = true
|
|
end
|
|
startTime = gcdStart;
|
|
duration = gcdDuration;
|
|
end
|
|
end
|
|
return startTime, duration, enabled, gcdCooldown;
|
|
end
|
|
|
|
local function RuneCooldownFinished(id)
|
|
runeCdHandles[id] = nil;
|
|
runeCdDurs[id] = nil;
|
|
runeCdExps[id] = nil;
|
|
WeakAuras.ScanEvents("RUNE_COOLDOWN_READY", id);
|
|
end
|
|
|
|
local function ItemCooldownFinished(id)
|
|
itemCdHandles[id] = nil;
|
|
itemCdDurs[id] = nil;
|
|
itemCdExps[id] = nil;
|
|
itemCdEnabled[id] = 1;
|
|
Private.ScanEventsByID("ITEM_COOLDOWN_READY", id);
|
|
end
|
|
|
|
local function ItemSlotCooldownFinished(id)
|
|
itemSlotsCdHandles[id] = nil;
|
|
itemSlotsCdDurs[id] = nil;
|
|
itemSlotsCdExps[id] = nil;
|
|
Private.ScanEventsByID("ITEM_SLOT_COOLDOWN_READY", id);
|
|
end
|
|
|
|
function Private.CheckRuneCooldown()
|
|
for id, _ in pairs(runes) do
|
|
local startTime, duration = GetRuneCooldown(id);
|
|
startTime = startTime or 0;
|
|
duration = duration or 0;
|
|
local time = GetTime();
|
|
|
|
if(not startTime or startTime == 0) then
|
|
startTime = 0
|
|
duration = 0
|
|
end
|
|
|
|
if(duration > 0 and duration ~= WeakAuras.gcdDuration()) then
|
|
-- On non-GCD cooldown
|
|
local endTime = startTime + duration;
|
|
|
|
if not(runeCdExps[id]) then
|
|
-- New cooldown
|
|
runeCdDurs[id] = duration;
|
|
runeCdExps[id] = endTime;
|
|
runeCdHandles[id] = timer:ScheduleTimer(RuneCooldownFinished, endTime - time, id);
|
|
WeakAuras.ScanEvents("RUNE_COOLDOWN_STARTED", id);
|
|
elseif(runeCdExps[id] ~= endTime) then
|
|
-- Cooldown is now different
|
|
if(runeCdHandles[id]) then
|
|
timer:CancelTimer(runeCdHandles[id]);
|
|
end
|
|
runeCdDurs[id] = duration;
|
|
runeCdExps[id] = endTime;
|
|
runeCdHandles[id] = timer:ScheduleTimer(RuneCooldownFinished, endTime - time, id);
|
|
WeakAuras.ScanEvents("RUNE_COOLDOWN_CHANGED", id);
|
|
end
|
|
elseif(startTime > 0 and duration > 0) then
|
|
-- GCD, do nothing
|
|
else
|
|
if(runeCdExps[id]) then
|
|
-- Somehow CheckCooldownReady caught the rune cooldown before the timer callback
|
|
-- This shouldn't happen, but if it does, no problem
|
|
if(runeCdHandles[id]) then
|
|
timer:CancelTimer(runeCdHandles[id]);
|
|
end
|
|
RuneCooldownFinished(id);
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function WeakAuras.GetSpellCooldownUnified(id, runeDuration)
|
|
local startTimeCooldown, durationCooldown, enabled = GetSpellCooldown(id)
|
|
|
|
startTimeCooldown = startTimeCooldown or 0;
|
|
durationCooldown = durationCooldown or 0;
|
|
|
|
-- WORKAROUND Sometimes the API returns very high bogus numbers causing client freeezes, discard them here. WowAce issue #1008
|
|
if (durationCooldown > 604800) then
|
|
durationCooldown = 0;
|
|
startTimeCooldown = 0;
|
|
end
|
|
|
|
if (startTimeCooldown > GetTime() + 2^31 / 1000) then
|
|
-- WORKAROUND WoW wraps around negative values with 2^32/1000
|
|
-- So if we find a cooldown in the far future, then undo the wrapping
|
|
startTimeCooldown = startTimeCooldown - 2^32 / 1000
|
|
end
|
|
|
|
local cooldownBecauseRune = false;
|
|
if (enabled == 0) then
|
|
startTimeCooldown, durationCooldown = 0, 0
|
|
end
|
|
|
|
local onNonGCDCD = durationCooldown and startTimeCooldown and durationCooldown > 0 and (durationCooldown ~= gcdDuration or startTimeCooldown ~= gcdStart);
|
|
if (onNonGCDCD) then
|
|
cooldownBecauseRune = runeDuration and durationCooldown and abs(durationCooldown - runeDuration) < 0.001;
|
|
end
|
|
|
|
return startTimeCooldown, durationCooldown, cooldownBecauseRune, GetSpellCount(id);
|
|
end
|
|
|
|
function Private.CheckSpellKnown()
|
|
for id, _ in pairs(spells) do
|
|
local known = WeakAuras.IsSpellKnownIncludingPet(id);
|
|
local changed = false
|
|
if (known ~= spellKnown[id]) then
|
|
spellKnown[id] = known
|
|
changed = true
|
|
end
|
|
|
|
local name, _, icon = GetSpellInfo(id)
|
|
if spellDetails[id].name ~= name then
|
|
spellDetails[id].name = name
|
|
changed = true
|
|
end
|
|
if spellDetails[id].icon ~= icon then
|
|
spellDetails[id].icon = icon
|
|
changed = true
|
|
end
|
|
|
|
if changed and not WeakAuras.IsPaused() then
|
|
Private.ScanEventsByID("SPELL_COOLDOWN_CHANGED", id)
|
|
end
|
|
end
|
|
end
|
|
|
|
function Private.CheckSpellCooldown(id, runeDuration)
|
|
local startTimeCooldown, durationCooldown, cooldownBecauseRune, spellCount = WeakAuras.GetSpellCooldownUnified(id, runeDuration);
|
|
|
|
local time = GetTime();
|
|
local remaining = startTimeCooldown + durationCooldown - time;
|
|
|
|
local chargesChanged = spellCounts[id] ~= spellCount;
|
|
local chargesDifference = (spellCount or 0) - (spellCount or 0)
|
|
spellCounts[id] = spellCount
|
|
|
|
local changed = false
|
|
|
|
local cdChanged, nowReady = spellCds:HandleSpell(id, startTimeCooldown, durationCooldown)
|
|
changed = cdChanged or changed
|
|
if not cooldownBecauseRune then
|
|
changed = spellCdsRune:HandleSpell(id, startTimeCooldown, durationCooldown) or changed
|
|
end
|
|
|
|
if not WeakAuras.IsPaused() then
|
|
if nowReady then
|
|
Private.ScanEventsByID("SPELL_COOLDOWN_READY", id)
|
|
end
|
|
|
|
if changed or chargesChanged then
|
|
Private.ScanEventsByID("SPELL_COOLDOWN_CHANGED", id)
|
|
end
|
|
|
|
if (chargesDifference ~= 0 ) then
|
|
Private.ScanEventsByID("SPELL_CHARGES_CHANGED", id, chargesDifference, spellCount or 0);
|
|
end
|
|
end
|
|
end
|
|
|
|
function Private.CheckSpellCooldowns(runeDuration)
|
|
for id, _ in pairs(spells) do
|
|
Private.CheckSpellCooldown(id, runeDuration)
|
|
end
|
|
end
|
|
|
|
function Private.CheckItemCooldowns()
|
|
for id, _ in pairs(items) do
|
|
local startTime, duration, enabled = GetItemCooldown(id);
|
|
if (duration == 0) then
|
|
enabled = 1;
|
|
end
|
|
if (enabled == 0) then
|
|
startTime, duration = 0, 0
|
|
end
|
|
|
|
local itemCdEnabledChanged = (itemCdEnabled[id] ~= enabled);
|
|
itemCdEnabled[id] = enabled;
|
|
startTime = startTime or 0;
|
|
duration = duration or 0;
|
|
local time = GetTime();
|
|
|
|
-- We check against 1.5 and gcdDuration, as apparently the durations might not match exactly.
|
|
-- But there shouldn't be any trinket with a actual cd of less than 1.5 anyway
|
|
if(duration and duration > 0 and duration > 1.5 and duration ~= WeakAuras.gcdDuration()) then
|
|
-- On non-GCD cooldown
|
|
local endTime = startTime + duration;
|
|
|
|
if not(itemCdExps[id]) then
|
|
-- New cooldown
|
|
itemCdDurs[id] = duration;
|
|
itemCdExps[id] = endTime;
|
|
itemCdHandles[id] = timer:ScheduleTimer(ItemCooldownFinished, endTime - time, id);
|
|
if not WeakAuras.IsPaused() then
|
|
Private.ScanEventsByID("ITEM_COOLDOWN_STARTED", id)
|
|
end
|
|
itemCdEnabledChanged = false;
|
|
elseif(itemCdExps[id] ~= endTime) then
|
|
-- Cooldown is now different
|
|
if(itemCdHandles[id]) then
|
|
timer:CancelTimer(itemCdHandles[id]);
|
|
end
|
|
itemCdDurs[id] = duration;
|
|
itemCdExps[id] = endTime;
|
|
itemCdHandles[id] = timer:ScheduleTimer(ItemCooldownFinished, endTime - time, id);
|
|
if not WeakAuras.IsPaused() then
|
|
Private.ScanEventsByID("ITEM_COOLDOWN_CHANGED", id)
|
|
end
|
|
itemCdEnabledChanged = false;
|
|
end
|
|
elseif(duration > 0) then
|
|
-- GCD, do nothing
|
|
else
|
|
if(itemCdExps[id]) then
|
|
-- Somehow CheckCooldownReady caught the item cooldown before the timer callback
|
|
-- This shouldn't happen, but if it does, no problem
|
|
if(itemCdHandles[id]) then
|
|
timer:CancelTimer(itemCdHandles[id]);
|
|
end
|
|
ItemCooldownFinished(id);
|
|
itemCdEnabledChanged = false;
|
|
end
|
|
end
|
|
if (itemCdEnabledChanged and not WeakAuras.IsPaused()) then
|
|
Private.ScanEventsByID("ITEM_COOLDOWN_CHANGED", id);
|
|
end
|
|
end
|
|
end
|
|
|
|
function Private.CheckItemSlotCooldowns()
|
|
for id, itemId in pairs(itemSlots) do
|
|
local startTime, duration, enable = GetInventoryItemCooldown("player", id);
|
|
itemSlotsEnable[id] = enable;
|
|
startTime = startTime or 0;
|
|
duration = duration or 0;
|
|
local time = GetTime();
|
|
|
|
-- We check against 1.5 and gcdDuration, as apparently the durations might not match exactly.
|
|
-- But there shouldn't be any trinket with a actual cd of less than 1.5 anyway
|
|
if(duration > 0 and duration > 1.5 and duration ~= WeakAuras.gcdDuration()) then
|
|
-- On non-GCD cooldown
|
|
local endTime = startTime + duration;
|
|
|
|
if not(itemSlotsCdExps[id]) then
|
|
-- New cooldown
|
|
itemSlotsCdDurs[id] = duration;
|
|
itemSlotsCdExps[id] = endTime;
|
|
itemSlotsCdHandles[id] = timer:ScheduleTimer(ItemSlotCooldownFinished, endTime - time, id);
|
|
if not WeakAuras.IsPaused() then
|
|
Private.ScanEventsByID("ITEM_SLOT_COOLDOWN_STARTED", id)
|
|
end
|
|
elseif(itemSlotsCdExps[id] ~= endTime) then
|
|
-- Cooldown is now different
|
|
if(itemSlotsCdHandles[id]) then
|
|
timer:CancelTimer(itemSlotsCdHandles[id]);
|
|
end
|
|
itemSlotsCdDurs[id] = duration;
|
|
itemSlotsCdExps[id] = endTime;
|
|
itemSlotsCdHandles[id] = timer:ScheduleTimer(ItemSlotCooldownFinished, endTime - time, id);
|
|
if not WeakAuras.IsPaused() then
|
|
Private.ScanEventsByID("ITEM_SLOT_COOLDOWN_CHANGED", id)
|
|
end
|
|
end
|
|
elseif(duration > 0) then
|
|
-- GCD, do nothing
|
|
else
|
|
if(itemSlotsCdExps[id]) then
|
|
-- Somehow CheckCooldownReady caught the item cooldown before the timer callback
|
|
-- This shouldn't happen, but if it does, no problem
|
|
if(itemSlotsCdHandles[id]) then
|
|
timer:CancelTimer(itemSlotsCdHandles[id]);
|
|
end
|
|
ItemSlotCooldownFinished(id);
|
|
end
|
|
end
|
|
|
|
local newItemId = GetInventoryItemID("player", id);
|
|
if (itemId ~= newItemId) then
|
|
if not WeakAuras.IsPaused() then
|
|
Private.ScanEventsByID("ITEM_SLOT_COOLDOWN_ITEM_CHANGED", id)
|
|
end
|
|
itemSlots[id] = newItemId or 0;
|
|
end
|
|
end
|
|
end
|
|
|
|
function Private.CheckCooldownReady()
|
|
CheckGCD();
|
|
Private.CheckRuneCooldown();
|
|
Private.CheckSpellCooldowns();
|
|
Private.CheckItemCooldowns();
|
|
Private.CheckItemSlotCooldowns();
|
|
end
|
|
|
|
function WeakAuras.WatchGCD()
|
|
if not(cdReadyFrame) then
|
|
Private.InitCooldownReady();
|
|
end
|
|
end
|
|
|
|
function WeakAuras.WatchRuneCooldown(id)
|
|
if not(cdReadyFrame) then
|
|
Private.InitCooldownReady();
|
|
end
|
|
|
|
if not id or id == 0 then return end
|
|
|
|
if not(runes[id]) then
|
|
runes[id] = true;
|
|
local startTime, duration = GetRuneCooldown(id);
|
|
|
|
if(not startTime or startTime == 0) then
|
|
startTime = 0
|
|
duration = 0
|
|
end
|
|
|
|
if(duration > 0 and duration ~= WeakAuras.gcdDuration()) then
|
|
local time = GetTime();
|
|
local endTime = startTime + duration;
|
|
runeCdDurs[id] = duration;
|
|
runeCdExps[id] = endTime;
|
|
if not(runeCdHandles[id]) then
|
|
runeCdHandles[id] = timer:ScheduleTimer(RuneCooldownFinished, endTime - time, id);
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function WeakAuras.WatchSpellCooldown(id, ignoreRunes)
|
|
if not(cdReadyFrame) then
|
|
Private.InitCooldownReady();
|
|
end
|
|
|
|
if not id or id == 0 then return end
|
|
|
|
if ignoreRunes then
|
|
for i = 1, 6 do
|
|
WeakAuras.WatchRuneCooldown(i);
|
|
end
|
|
end
|
|
|
|
if (spells[id]) then
|
|
return;
|
|
end
|
|
spells[id] = true;
|
|
local name, _, icon = GetSpellInfo(id)
|
|
spellDetails[id] = {
|
|
name = name,
|
|
icon = icon
|
|
}
|
|
spellKnown[id] = WeakAuras.IsSpellKnownIncludingPet(id);
|
|
|
|
local startTimeCooldown, durationCooldown, cooldownBecauseRune, spellCount = WeakAuras.GetSpellCooldownUnified(id, GetRuneDuration());
|
|
spellCounts[id] = spellCount
|
|
spellCds:HandleSpell(id, startTimeCooldown, durationCooldown)
|
|
if not cooldownBecauseRune then
|
|
spellCdsRune:HandleSpell(id, startTimeCooldown, durationCooldown)
|
|
end
|
|
end
|
|
|
|
function WeakAuras.WatchItemCooldown(id)
|
|
if not(cdReadyFrame) then
|
|
Private.InitCooldownReady();
|
|
end
|
|
|
|
if not id or id == 0 then return end
|
|
|
|
if not(items[id]) then
|
|
items[id] = true;
|
|
local startTime, duration, enabled = GetItemCooldown(id);
|
|
if (duration == 0) then
|
|
enabled = 1;
|
|
end
|
|
if (enabled == 0) then
|
|
startTime, duration = 0, 0
|
|
end
|
|
itemCdEnabled[id] = enabled;
|
|
if(duration > 0 and duration > 1.5 and duration ~= WeakAuras.gcdDuration()) then
|
|
local time = GetTime();
|
|
local endTime = startTime + duration;
|
|
itemCdDurs[id] = duration;
|
|
itemCdExps[id] = endTime;
|
|
if not(itemCdHandles[id]) then
|
|
itemCdHandles[id] = timer:ScheduleTimer(ItemCooldownFinished, endTime - time, id);
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function WeakAuras.WatchItemSlotCooldown(id)
|
|
if not(cdReadyFrame) then
|
|
Private.InitCooldownReady();
|
|
end
|
|
|
|
if not id or id == 0 then return end
|
|
|
|
if not(itemSlots[id]) then
|
|
itemSlots[id] = GetInventoryItemID("player", id);
|
|
local startTime, duration, enable = GetInventoryItemCooldown("player", id);
|
|
itemSlotsEnable[id] = enable;
|
|
if(duration > 0 and duration > 1.5 and duration ~= WeakAuras.gcdDuration()) then
|
|
local time = GetTime();
|
|
local endTime = startTime + duration;
|
|
itemSlotsCdDurs[id] = duration;
|
|
itemSlotsCdExps[id] = endTime;
|
|
if not(itemSlotsCdHandles[id]) then
|
|
itemSlotsCdHandles[id] = timer:ScheduleTimer(ItemSlotCooldownFinished, endTime - time, id);
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local watchUnitChange
|
|
|
|
-- Nameplates only distinguish between friends and everyone else
|
|
---@param unit UnitToken
|
|
---@return string? reaction
|
|
function WeakAuras.GetPlayerReaction(unit)
|
|
local r = UnitReaction("player", unit)
|
|
if r then
|
|
return r < 5 and "hostile" or "friendly"
|
|
end
|
|
end
|
|
|
|
function WeakAuras.WatchUnitChange(unit)
|
|
unit = string.lower(unit)
|
|
if not watchUnitChange then
|
|
watchUnitChange = CreateFrame("Frame");
|
|
watchUnitChange.trackedUnits = {}
|
|
watchUnitChange.unitIdToGUID = {}
|
|
watchUnitChange.GUIDToUnitIds = {}
|
|
watchUnitChange.unitRoles = {}
|
|
watchUnitChange.unitRaidRole = {}
|
|
watchUnitChange.inRaid = IsInRaid()
|
|
watchUnitChange.nameplateFaction = {}
|
|
watchUnitChange.raidmark = {}
|
|
watchUnitChange.unitIsUnit = {}
|
|
|
|
Private.frames["Unit Change Frame"] = watchUnitChange;
|
|
watchUnitChange:RegisterEvent("PLAYER_TARGET_CHANGED")
|
|
watchUnitChange:RegisterEvent("PLAYER_FOCUS_CHANGED");
|
|
watchUnitChange:RegisterEvent("ARENA_OPPONENT_UPDATE")
|
|
watchUnitChange:RegisterEvent("PLAYER_ROLES_ASSIGNED");
|
|
watchUnitChange:RegisterEvent("UNIT_TARGET");
|
|
watchUnitChange:RegisterEvent("INSTANCE_ENCOUNTER_ENGAGE_UNIT");
|
|
watchUnitChange:RegisterEvent("PARTY_MEMBERS_CHANGED");
|
|
watchUnitChange:RegisterEvent("RAID_ROSTER_UPDATE");
|
|
if WeakAuras.isAwesomeEnabled() then
|
|
watchUnitChange:RegisterEvent("NAME_PLATE_UNIT_ADDED")
|
|
watchUnitChange:RegisterEvent("NAME_PLATE_UNIT_REMOVED")
|
|
end
|
|
watchUnitChange:RegisterEvent("UNIT_FACTION")
|
|
watchUnitChange:RegisterEvent("PLAYER_ENTERING_WORLD")
|
|
watchUnitChange:RegisterEvent("UNIT_PET")
|
|
watchUnitChange:RegisterEvent("RAID_TARGET_UPDATE")
|
|
|
|
local function unitUpdate(unitA, eventsToSend)
|
|
local oldGUID = watchUnitChange.unitIdToGUID[unitA]
|
|
local newGUID = WeakAuras.UnitExistsFixed(unitA) and UnitGUID(unitA)
|
|
if oldGUID ~= newGUID then
|
|
eventsToSend["UNIT_CHANGED_" .. unitA] = unitA
|
|
if watchUnitChange.GUIDToUnitIds[oldGUID] then
|
|
for unitB in pairs(watchUnitChange.GUIDToUnitIds[oldGUID]) do
|
|
if unitA ~= unitB then
|
|
eventsToSend["UNIT_IS_UNIT_CHANGED_" .. unitA .. "_" .. unitB] = unitA
|
|
eventsToSend["UNIT_IS_UNIT_CHANGED_" .. unitB .. "_" .. unitA] = unitB
|
|
end
|
|
end
|
|
end
|
|
if watchUnitChange.GUIDToUnitIds[newGUID] then
|
|
for unitB in pairs(watchUnitChange.GUIDToUnitIds[newGUID]) do
|
|
if unitA ~= unitB then
|
|
eventsToSend["UNIT_IS_UNIT_CHANGED_" .. unitA .. "_" .. unitB] = unitA
|
|
eventsToSend["UNIT_IS_UNIT_CHANGED_" .. unitB .. "_" .. unitA] = unitB
|
|
end
|
|
end
|
|
end
|
|
end
|
|
-- update data
|
|
if oldGUID and watchUnitChange.GUIDToUnitIds[oldGUID] then
|
|
watchUnitChange.GUIDToUnitIds[oldGUID][unitA] = nil
|
|
if next(watchUnitChange.GUIDToUnitIds[oldGUID]) == nil then
|
|
watchUnitChange.GUIDToUnitIds[oldGUID] = nil
|
|
end
|
|
end
|
|
if newGUID then
|
|
watchUnitChange.GUIDToUnitIds[newGUID] = watchUnitChange.GUIDToUnitIds[newGUID] or {}
|
|
watchUnitChange.GUIDToUnitIds[newGUID][unitA] = true
|
|
end
|
|
watchUnitChange.unitIdToGUID[unitA] = newGUID
|
|
end
|
|
|
|
local function markerUpdate(unit, eventsToSend)
|
|
local oldMarker = watchUnitChange.raidmark[unit]
|
|
local newMarker = GetRaidTargetIndex(unit) or 0
|
|
if newMarker ~= oldMarker then
|
|
eventsToSend["UNIT_CHANGED_" .. unit] = unit
|
|
watchUnitChange.raidmark[unit] = newMarker
|
|
end
|
|
end
|
|
|
|
local function markerInit(unit)
|
|
watchUnitChange.raidmark[unit] = GetRaidTargetIndex(unit) or 0
|
|
end
|
|
|
|
local function markerClear(unit)
|
|
watchUnitChange.raidmark[unit] = nil
|
|
end
|
|
|
|
local function reactionUpdate(unit, eventsToSend)
|
|
local oldReaction = watchUnitChange.nameplateFaction[unit]
|
|
local newReaction = WeakAuras.GetPlayerReaction(unit)
|
|
if oldReaction ~= newReaction then
|
|
eventsToSend["UNIT_CHANGED_" .. unit] = unit
|
|
watchUnitChange.nameplateFaction[unit] = newReaction
|
|
end
|
|
end
|
|
|
|
local function reactionInit(unit)
|
|
watchUnitChange.nameplateFaction[unit] = WeakAuras.GetPlayerReaction(unit)
|
|
end
|
|
|
|
local function reactionClear(unit)
|
|
watchUnitChange.nameplateFaction[unit] = nil
|
|
end
|
|
|
|
local function roleUpdate(unit, eventsToSend)
|
|
local oldRaidRole = watchUnitChange.unitRaidRole[unit]
|
|
local newRaidRole = WeakAuras.UnitRaidRole(unit)
|
|
if oldRaidRole ~= newRaidRole then
|
|
eventsToSend["UNIT_ROLE_CHANGED_" .. unit] = unit
|
|
watchUnitChange.unitRaidRole[unit] = newRaidRole
|
|
end
|
|
end
|
|
|
|
local function handleUnit(unit, eventsToSend, ...)
|
|
if watchUnitChange.trackedUnits[unit] then
|
|
local fn
|
|
for i = 1, select("#", ...) do
|
|
fn = select(i, ...)
|
|
fn(unit, eventsToSend)
|
|
end
|
|
end
|
|
end
|
|
|
|
local handleEvent = {
|
|
PLAYER_ENTERING_WORLD = function(_, eventsToSend)
|
|
for unit in pairs(watchUnitChange.unitIdToGUID) do
|
|
handleUnit(unit, eventsToSend, unitUpdate, markerUpdate, reactionUpdate)
|
|
end
|
|
end,
|
|
NAME_PLATE_UNIT_ADDED = function(unit, eventsToSend)
|
|
handleUnit(unit, eventsToSend, unitUpdate, markerInit, reactionInit)
|
|
end,
|
|
NAME_PLATE_UNIT_REMOVED = function(unit, eventsToSend)
|
|
handleUnit(unit, eventsToSend, unitUpdate, markerClear, reactionClear)
|
|
end,
|
|
INSTANCE_ENCOUNTER_ENGAGE_UNIT = function(_, eventsToSend)
|
|
for i = 1, 5 do
|
|
handleUnit("boss" .. i, eventsToSend, unitUpdate, markerInit, reactionInit)
|
|
handleUnit("boss" .. i .. "target", eventsToSend, unitUpdate, markerInit, reactionInit)
|
|
end
|
|
end,
|
|
ARENA_OPPONENT_UPDATE = function(unit, eventsToSend)
|
|
handleUnit(unit, eventsToSend, unitUpdate, markerInit, reactionInit)
|
|
handleUnit(unit .. "target", eventsToSend, unitUpdate, markerInit, reactionInit)
|
|
end,
|
|
PLAYER_TARGET_CHANGED = function(_, eventsToSend)
|
|
handleUnit("target", eventsToSend, unitUpdate, markerInit, reactionInit)
|
|
handleUnit("targettarget", eventsToSend, unitUpdate, markerInit, reactionInit)
|
|
end,
|
|
PLAYER_FOCUS_CHANGED = function(_, eventsToSend)
|
|
handleUnit("focus", eventsToSend, unitUpdate, markerInit, reactionInit)
|
|
handleUnit("focustarget", eventsToSend, unitUpdate, markerInit, reactionInit)
|
|
end,
|
|
RAID_TARGET_UPDATE = function(_, eventsToSend)
|
|
for unit in pairs(watchUnitChange.raidmark) do
|
|
handleUnit(unit, eventsToSend, markerUpdate)
|
|
end
|
|
end,
|
|
UNIT_FACTION = function(unit, eventsToSend)
|
|
handleUnit(unit, eventsToSend, reactionUpdate)
|
|
end,
|
|
UNIT_PET = function(unit, eventsToSend)
|
|
local pet = WeakAuras.unitToPetUnit[unit]
|
|
if pet and watchUnitChange.trackedUnits[pet] then
|
|
eventsToSend["UNIT_CHANGED_" .. pet] = pet
|
|
end
|
|
end,
|
|
PLAYER_ROLES_ASSIGNED = function(_, eventsToSend)
|
|
for unit in pairs(Private.multiUnitUnits.group) do
|
|
handleUnit(unit, eventsToSend, roleUpdate)
|
|
end
|
|
end,
|
|
UNIT_TARGET = function(unit, eventsToSend)
|
|
handleUnit(unit .. "target", eventsToSend, unitUpdate, markerInit, reactionInit)
|
|
end,
|
|
GROUP_ROSTER_UPDATE = function(_, eventsToSend)
|
|
for unit in pairs(Private.multiUnitUnits.group) do
|
|
handleUnit(unit, eventsToSend, unitUpdate, markerInit, reactionInit)
|
|
end
|
|
local inRaid = IsInRaid()
|
|
local inRaidChanged = inRaid ~= watchUnitChange.inRaid
|
|
if inRaidChanged then
|
|
for unit in pairs(Private.multiUnitUnits.group) do
|
|
if watchUnitChange.trackedUnits[unit] and watchUnitChange.unitIdToGUID[unit] then
|
|
eventsToSend["UNIT_CHANGED_" .. unit] = unit
|
|
end
|
|
end
|
|
watchUnitChange.inRaid = inRaid
|
|
end
|
|
end
|
|
}
|
|
handleEvent["RAID_ROSTER_UPDATE"] = handleEvent["GROUP_ROSTER_UPDATE"]
|
|
|
|
watchUnitChange:SetScript("OnEvent", function(self, event, unit)
|
|
Private.StartProfileSystem("generictrigger unit change");
|
|
local eventsToSend = {}
|
|
handleEvent[event](unit, eventsToSend)
|
|
-- send events
|
|
for event, unit in pairs(eventsToSend) do
|
|
WeakAuras.ScanEvents(event, unit)
|
|
end
|
|
|
|
Private.StopProfileSystem("generictrigger unit change");
|
|
end)
|
|
end
|
|
if watchUnitChange.trackedUnits[unit] then
|
|
return
|
|
end
|
|
local guid = UnitGUID(unit)
|
|
watchUnitChange.trackedUnits[unit] = true
|
|
watchUnitChange.unitIdToGUID[unit] = guid
|
|
if guid then
|
|
watchUnitChange.GUIDToUnitIds[guid] = watchUnitChange.GUIDToUnitIds[guid] or {}
|
|
watchUnitChange.GUIDToUnitIds[guid][unit] = true
|
|
end
|
|
watchUnitChange.raidmark = watchUnitChange.raidmark or {}
|
|
watchUnitChange.raidmark[unit] = GetRaidTargetIndex(unit) or 0
|
|
watchUnitChange.inRaid = IsInRaid()
|
|
end
|
|
|
|
local equipmentItemIDs, equipmentSetItemIDs = {}, {}
|
|
function WeakAuras.GetEquipmentSetInfo(itemSetName, partial)
|
|
local bestMatchNumItems = 0;
|
|
local bestMatchNumEquipped = 0;
|
|
local bestMatchName = nil;
|
|
local bestMatchIcon = nil;
|
|
|
|
for slot = 1, 19 do
|
|
equipmentItemIDs[slot] = GetInventoryItemID("player", slot) or 0
|
|
end
|
|
|
|
for id = 1, GetNumEquipmentSets() do
|
|
local numItems, numEquipped = 0, 0
|
|
local name, icon = GetEquipmentSetInfo(id);
|
|
if (itemSetName == nil or (name and itemSetName == name)) then
|
|
if (name ~= nil) then
|
|
equipmentSetItemIDs = GetEquipmentSetItemIDs(name, equipmentSetItemIDs)
|
|
for slot, item in ipairs(equipmentSetItemIDs) do
|
|
if item > 0 then
|
|
numItems = numItems + 1
|
|
if equipmentItemIDs[slot] == item then
|
|
numEquipped = numEquipped + 1
|
|
end
|
|
end
|
|
end
|
|
local match = (not partial and numItems == numEquipped)
|
|
or (partial and (numEquipped or 0) > bestMatchNumEquipped);
|
|
if (match) then
|
|
bestMatchNumEquipped = numEquipped;
|
|
bestMatchNumItems = numItems;
|
|
bestMatchName = name;
|
|
bestMatchIcon = icon;
|
|
end
|
|
end
|
|
end
|
|
end
|
|
return bestMatchName, bestMatchIcon, bestMatchNumEquipped, bestMatchNumItems;
|
|
end
|
|
|
|
function Private.ExecEnv.CheckTotemName(totemName, triggerTotemName, triggerTotemPattern, triggerTotemOperator)
|
|
if not totemName or totemName == "" then
|
|
return false
|
|
end
|
|
|
|
if triggerTotemName and #triggerTotemName > 0 and triggerTotemName ~= totemName then
|
|
return false
|
|
end
|
|
|
|
if triggerTotemPattern and #triggerTotemPattern > 0 then
|
|
if triggerTotemOperator == "==" then
|
|
if totemName ~= triggerTotemPattern then
|
|
return false
|
|
end
|
|
elseif triggerTotemOperator == "find('%s')" then
|
|
if not totemName:find(triggerTotemPattern, 1, true) then
|
|
return false
|
|
end
|
|
elseif triggerTotemOperator == "match('%s')" then
|
|
if not totemName:match(triggerTotemPattern) then
|
|
return false
|
|
end
|
|
end
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
-- Weapon Enchants
|
|
do
|
|
local mh = GetInventorySlotInfo("MainHandSlot")
|
|
local oh = GetInventorySlotInfo("SecondaryHandSlot")
|
|
local rw = GetInventorySlotInfo("RangedSlot")
|
|
|
|
local mh_name, mh_shortenedName, mh_exp, mh_dur, mh_charges;
|
|
local mh_icon = GetInventoryItemTexture("player", mh);
|
|
|
|
local oh_name, oh_shortenedName, oh_exp, oh_dur, oh_charges;
|
|
local oh_icon = GetInventoryItemTexture("player", oh);
|
|
|
|
local rw_name, rw_shortenedName, rw_exp, rw_dur, rw_charges;
|
|
local rw_icon = GetInventoryItemTexture("player", rw) or "Interface\\Icons\\INV_Misc_QuestionMark"
|
|
|
|
local tenchFrame = nil
|
|
Private.frames["Temporary Enchant Handler"] = tenchFrame;
|
|
local tenchTip;
|
|
|
|
function WeakAuras.TenchInit()
|
|
if not(tenchFrame) then
|
|
tenchFrame = CreateFrame("Frame");
|
|
tenchFrame:RegisterEvent("UNIT_INVENTORY_CHANGED");
|
|
tenchFrame:RegisterEvent("PLAYER_ENTERING_WORLD");
|
|
|
|
tenchTip = WeakAuras.GetHiddenTooltip();
|
|
|
|
local function getTenchName(id)
|
|
tenchTip:SetInventoryItem("player", id);
|
|
local lines = { tenchTip:GetRegions() };
|
|
for i,v in ipairs(lines) do
|
|
if(v:GetObjectType() == "FontString") then
|
|
local text = v:GetText();
|
|
if(text) then
|
|
local _, _, name, shortenedName = text:find("^((.-) ?+?[VI%d]*) %(%d+ .+%)$");
|
|
if(name) then
|
|
return name, shortenedName;
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
return "Unknown", "Unknown";
|
|
end
|
|
|
|
local function tenchUpdate()
|
|
Private.StartProfileSystem("generictrigger temporary enchant");
|
|
local _, mh_rem, oh_rem, rw_rem, re_charges
|
|
_, mh_rem, mh_charges, _, oh_rem, oh_charges, _, rw_rem, rw_charges = GetWeaponEnchantInfo();
|
|
local time = GetTime();
|
|
local mh_exp_new = mh_rem and (time + (mh_rem / 1000));
|
|
local oh_exp_new = oh_rem and (time + (oh_rem / 1000));
|
|
local rw_exp_new = rw_rem and (time + (rw_rem / 1000));
|
|
if(math.abs((mh_exp or 0) - (mh_exp_new or 0)) > 1) then
|
|
mh_exp = mh_exp_new;
|
|
mh_dur = mh_rem and mh_rem / 1000;
|
|
if mh_exp then
|
|
mh_name, mh_shortenedName = getTenchName(mh)
|
|
else
|
|
mh_name, mh_shortenedName = "None", "None"
|
|
end
|
|
mh_icon = GetInventoryItemTexture("player", mh)
|
|
end
|
|
if(math.abs((oh_exp or 0) - (oh_exp_new or 0)) > 1) then
|
|
oh_exp = oh_exp_new;
|
|
oh_dur = oh_rem and oh_rem / 1000;
|
|
if oh_exp then
|
|
oh_name, oh_shortenedName = getTenchName(oh)
|
|
else
|
|
oh_name, oh_shortenedName = "None", "None"
|
|
end
|
|
oh_icon = GetInventoryItemTexture("player", oh)
|
|
end
|
|
if(math.abs((rw_exp or 0) - (rw_exp_new or 0)) > 1) then
|
|
rw_exp = rw_exp_new;
|
|
rw_dur = rw_rem and rw_rem / 1000;
|
|
if rw_exp then
|
|
rw_name, rw_shortenedName = getTenchName(rw)
|
|
else
|
|
rw_name, rw_shortenedName = "None", "None"
|
|
end
|
|
rw_icon = GetInventoryItemTexture("player", rw)
|
|
end
|
|
WeakAuras.ScanEvents("TENCH_UPDATE");
|
|
Private.StopProfileSystem("generictrigger temporary enchant");
|
|
end
|
|
|
|
tenchFrame:SetScript("OnEvent", function(_, unit, ...)
|
|
if unit and unit ~= "player" then return end
|
|
Private.StartProfileSystem("generictrigger temporary enchant");
|
|
timer:ScheduleTimer(tenchUpdate, 0.1);
|
|
Private.StopProfileSystem("generictrigger temporary enchant");
|
|
end);
|
|
|
|
tenchUpdate();
|
|
end
|
|
end
|
|
|
|
function WeakAuras.GetMHTenchInfo()
|
|
return mh_exp, mh_dur, mh_name, mh_shortenedName, mh_icon, mh_charges;
|
|
end
|
|
|
|
function WeakAuras.GetOHTenchInfo()
|
|
return oh_exp, oh_dur, oh_name, oh_shortenedName, oh_icon, oh_charges;
|
|
end
|
|
|
|
function WeakAuras.GetRangeTenchInfo()
|
|
return rw_exp, rw_dur, rw_name, rw_shortenedName, rw_icon, rw_charges;
|
|
end
|
|
end
|
|
|
|
-- Pets
|
|
do
|
|
local petFrame = nil
|
|
Private.frames["Pet Use Handler"] = petFrame;
|
|
function WeakAuras.WatchForPetDeath()
|
|
if not(petFrame) then
|
|
petFrame = CreateFrame("Frame");
|
|
petFrame:RegisterEvent("UNIT_PET")
|
|
petFrame:SetScript("OnEvent", function(_, event, unit)
|
|
if unit ~= "player" then return end
|
|
Private.StartProfileSystem("generictrigger pet update")
|
|
WeakAuras.ScanEvents("PET_UPDATE", "pet")
|
|
Private.StopProfileSystem("generictrigger pet update")
|
|
end)
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Cast Latency
|
|
do
|
|
local castLatencyFrame
|
|
function WeakAuras.WatchForCastLatency()
|
|
if not castLatencyFrame then
|
|
castLatencyFrame = CreateFrame("Frame")
|
|
Private.frames["Cast Latency Handler"] = castLatencyFrame
|
|
castLatencyFrame:RegisterEvent("CURRENT_SPELL_CAST_CHANGED")
|
|
castLatencyFrame:RegisterEvent("UNIT_SPELLCAST_START")
|
|
castLatencyFrame:RegisterEvent("UNIT_SPELLCAST_CHANNEL_START")
|
|
castLatencyFrame:RegisterEvent("UNIT_SPELLCAST_STOP")
|
|
castLatencyFrame:RegisterEvent("UNIT_SPELLCAST_CHANNEL_STOP")
|
|
castLatencyFrame:RegisterEvent("UNIT_SPELLCAST_INTERRUPTED")
|
|
castLatencyFrame:RegisterEvent("UNIT_SPELLCAST_SUCCEEDED")
|
|
|
|
castLatencyFrame:SetScript("OnEvent", function(self, event, unit, ...)
|
|
if unit and unit ~= "player" then return end
|
|
|
|
if event == "CURRENT_SPELL_CAST_CHANGED" then
|
|
castLatencyFrame.sendTime = GetTime()
|
|
return
|
|
end
|
|
if event == "UNIT_SPELLCAST_SUCCEEDED" or event == "UNIT_SPELLCAST_STOP" or event == "UNIT_SPELLCAST_CHANNEL_STOP" or event == "UNIT_SPELLCAST_INTERRUPTED" then
|
|
castLatencyFrame.sendTime = nil
|
|
return
|
|
end
|
|
if castLatencyFrame.sendTime then
|
|
castLatencyFrame.timeDiff = (GetTime() - castLatencyFrame.sendTime)
|
|
else
|
|
castLatencyFrame.timeDiff = nil
|
|
end
|
|
end)
|
|
end
|
|
end
|
|
|
|
function WeakAuras.GetCastLatency()
|
|
return castLatencyFrame and castLatencyFrame.timeDiff or 0
|
|
end
|
|
|
|
end
|
|
|
|
-- Nameplate Target
|
|
do
|
|
local nameplateTargetFrame = nil
|
|
local nameplateTargets = {}
|
|
|
|
local function nameplateTargetOnEvent(self, event, unit)
|
|
if event == "NAME_PLATE_UNIT_ADDED" then
|
|
nameplateTargets[unit] = UnitGUID(unit.."-target") or true
|
|
elseif event == "NAME_PLATE_UNIT_REMOVED" then
|
|
nameplateTargets[unit] = nil
|
|
end
|
|
end
|
|
|
|
local tick_throttle = 0.2
|
|
local throttle_update = tick_throttle
|
|
local function nameplateTargetOnUpdate(self, delta)
|
|
throttle_update = throttle_update - delta
|
|
if throttle_update < 0 then
|
|
for unit, targetGUID in pairs(nameplateTargets) do
|
|
local newTargetGUID = UnitGUID(unit.."-target")
|
|
if (newTargetGUID == nil and targetGUID ~= true)
|
|
or (newTargetGUID ~= nil and targetGUID ~= newTargetGUID)
|
|
then
|
|
nameplateTargets[unit] = newTargetGUID or true
|
|
WeakAuras.ScanEvents("WA_UNIT_TARGET_NAME_PLATE", unit)
|
|
end
|
|
end
|
|
throttle_update = tick_throttle
|
|
end
|
|
end
|
|
|
|
Private.frames["Nameplate Target Handler"] = nameplateTargetFrame
|
|
function WeakAuras.WatchForNameplateTargetChange()
|
|
if not nameplateTargetFrame then
|
|
nameplateTargetFrame = CreateFrame("Frame")
|
|
nameplateTargetFrame:SetScript("OnUpdate", nameplateTargetOnUpdate)
|
|
nameplateTargetFrame:RegisterEvent("NAME_PLATE_UNIT_ADDED")
|
|
nameplateTargetFrame:RegisterEvent("NAME_PLATE_UNIT_REMOVED")
|
|
nameplateTargetFrame:SetScript("OnEvent", nameplateTargetOnEvent)
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Mounted Frame
|
|
do
|
|
local mountedFrame
|
|
local elapsed = 0;
|
|
local delay = 0.5;
|
|
local isMounted = IsMounted();
|
|
|
|
local function checkForMounted(self, elaps)
|
|
Private.StartProfileSystem("generictrigger mounted");
|
|
elapsed = elapsed + elaps
|
|
if(isMounted ~= IsMounted()) then
|
|
isMounted = IsMounted();
|
|
WeakAuras.ScanEvents("MOUNTED_UPDATE");
|
|
mountedFrame:SetScript("OnUpdate", nil);
|
|
end
|
|
if(elapsed > delay) then
|
|
mountedFrame:SetScript("OnUpdate", nil);
|
|
end
|
|
Private.StopProfileSystem("generictrigger mounted");
|
|
end
|
|
|
|
function WeakAuras.WatchForMounts()
|
|
if not(mountedFrame) then
|
|
mountedFrame = CreateFrame("Frame");
|
|
Private.frames["Mount Use Handler"] = mountedFrame;
|
|
mountedFrame:RegisterEvent("COMPANION_UPDATE");
|
|
mountedFrame:SetScript("OnEvent", function()
|
|
elapsed = 0;
|
|
mountedFrame:SetScript("OnUpdate", checkForMounted);
|
|
end)
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Player Moving
|
|
do
|
|
local playerMovingFrame = nil
|
|
|
|
local function PlayerMoveSpeedUpdate()
|
|
Private.StartProfileSystem("generictrigger player moving");
|
|
local speed = GetUnitSpeed("player")
|
|
if speed ~= playerMovingFrame.speed then
|
|
playerMovingFrame.speed = speed
|
|
WeakAuras.ScanEvents("PLAYER_MOVE_SPEED_UPDATE")
|
|
end
|
|
Private.StopProfileSystem("generictrigger player moving");
|
|
end
|
|
|
|
function WeakAuras.WatchPlayerMoveSpeed()
|
|
if not (playerMovingFrame) then
|
|
playerMovingFrame = CreateFrame("Frame");
|
|
Private.frames["Player Moving Frame"] = playerMovingFrame;
|
|
end
|
|
playerMovingFrame.speed = GetUnitSpeed("player")
|
|
playerMovingFrame:SetScript("OnUpdate", PlayerMoveSpeedUpdate)
|
|
end
|
|
end
|
|
|
|
-- Nameplates
|
|
do
|
|
local watchNameplates
|
|
|
|
local select = select
|
|
local gsub = string.gsub
|
|
|
|
local WorldFrame = WorldFrame
|
|
local WorldGetChildren = WorldFrame.GetChildren
|
|
local WorldGetNumChildren = WorldFrame.GetNumChildren
|
|
|
|
local lastUpdate = 0
|
|
local lastChildern, numChildren = 0, 0
|
|
local nameplateList = {}
|
|
local visibleNameplates = {}
|
|
|
|
local OVERLAY = [=[Interface\TargetingFrame\UI-TargetingFrame-Flash]=]
|
|
local FSPAT = "%s*"..(gsub(gsub(FOREIGN_SERVER_LABEL, "^%s", ""), "[%*()]", "%%%1")).."$"
|
|
|
|
local function nameplateShow(self)
|
|
Private.StartProfileSystem("nameplatetrigger")
|
|
local name = gsub(self.nameText:GetText() or "", FSPAT, "")
|
|
visibleNameplates[self] = name
|
|
WeakAuras.ScanEvents("NP_SHOW", self, name)
|
|
Private.StopProfileSystem("nameplatetrigger")
|
|
end
|
|
|
|
local function nameplateHide(self)
|
|
Private.StartProfileSystem("nameplatetrigger")
|
|
visibleNameplates[self] = nil
|
|
WeakAuras.ScanEvents("NP_HIDE", self, gsub(self.nameText:GetText() or "", FSPAT, ""))
|
|
Private.StopProfileSystem("nameplatetrigger")
|
|
end
|
|
|
|
local function findNewPlate(...)
|
|
for i = lastChildern + 1, numChildren do
|
|
local frame = select(i, ...)
|
|
local region, _, _, _, _, _, nameText = frame:GetRegions()
|
|
if (frame.UnitFrame or (region and region:GetObjectType() == "Texture" and region:GetTexture() == OVERLAY)) and not nameplateList[frame] then
|
|
frame.nameText = nameText
|
|
frame:HookScript("OnShow", nameplateShow)
|
|
frame:HookScript("OnHide", nameplateHide)
|
|
nameplateShow(frame)
|
|
nameplateList[frame] = true
|
|
end
|
|
end
|
|
end
|
|
|
|
local function nameplatesUpdate(_, elaps)
|
|
lastUpdate = lastUpdate + elaps
|
|
if lastUpdate < 1 then return end
|
|
numChildren = WorldGetNumChildren(WorldFrame)
|
|
if lastChildern ~= numChildren then
|
|
Private.StartProfileSystem("nameplatetrigger")
|
|
findNewPlate(WorldGetChildren(WorldFrame))
|
|
Private.StopProfileSystem("nameplatetrigger")
|
|
lastChildern = numChildren
|
|
end
|
|
lastUpdate = 0
|
|
end
|
|
|
|
local resultNameplates = {}
|
|
function WeakAuras.GetUnitNameplate(name, results)
|
|
if not name or name == "" then return end
|
|
results = results or resultNameplates
|
|
wipe(results)
|
|
for frame, nameplateName in pairs(visibleNameplates) do
|
|
if name == nameplateName then
|
|
results[#results + 1] = frame
|
|
end
|
|
end
|
|
return results[1], results
|
|
end
|
|
|
|
function WeakAuras.WatchNamePlates()
|
|
if not(watchNameplates) then
|
|
watchNameplates = CreateFrame("Frame")
|
|
Private.frames["Watch NamePlates Frames"] = watchNameplates
|
|
end
|
|
watchNameplates:SetScript("OnUpdate", nameplatesUpdate)
|
|
end
|
|
end
|
|
|
|
-- Item Count
|
|
local itemCountWatchFrame;
|
|
function WeakAuras.RegisterItemCountWatch()
|
|
if not(itemCountWatchFrame) then
|
|
itemCountWatchFrame = CreateFrame("Frame");
|
|
itemCountWatchFrame:RegisterEvent("UNIT_SPELLCAST_SUCCEEDED");
|
|
itemCountWatchFrame:SetScript("OnEvent", function(_, _, unit)
|
|
if unit ~= "player" then return end
|
|
Private.StartProfileSystem("generictrigger item count");
|
|
timer:ScheduleTimer(WeakAuras.ScanEvents, 0.2, "ITEM_COUNT_UPDATE");
|
|
timer:ScheduleTimer(WeakAuras.ScanEvents, 0.5, "ITEM_COUNT_UPDATE");
|
|
Private.StopProfileSystem("generictrigger item count");
|
|
end);
|
|
end
|
|
end
|
|
|
|
-- Queued Action
|
|
do
|
|
local GetActionInfo, GetMacroSpell, GetSpellLink = GetActionInfo, GetMacroSpell, GetSpellLink
|
|
|
|
local queuedActionFrame = nil
|
|
local buttonIDList = {}
|
|
local spellIDList = {}
|
|
|
|
local function GetActionSpellID(slot)
|
|
local actionType, id, _, spellId = GetActionInfo(slot)
|
|
if actionType == "spell" then
|
|
return spellId
|
|
elseif actionType == "macro" then
|
|
local name, rank = GetMacroSpell(id)
|
|
if name then
|
|
local spellLink = GetSpellLink(name, rank or "")
|
|
if spellLink then
|
|
return tonumber(spellLink:match("spell:(%d+)"))
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function WeakAuras.WatchQueuedAction()
|
|
if not(queuedActionFrame) then
|
|
queuedActionFrame = CreateFrame("Frame");
|
|
Private.frames["Queued Action Frame"] = queuedActionFrame
|
|
for slotID = 1, 120 do
|
|
local spellID = GetActionSpellID(slotID)
|
|
if spellID then
|
|
buttonIDList[slotID] = spellID
|
|
spellIDList[spellID] = slotID
|
|
end
|
|
end
|
|
end
|
|
queuedActionFrame:RegisterEvent("ACTIONBAR_SLOT_CHANGED")
|
|
queuedActionFrame:SetScript("OnEvent", function(_, _, slotID)
|
|
Private.StartProfileSystem("generictrigger queued action");
|
|
local spellID = GetActionSpellID(slotID)
|
|
if spellID then
|
|
buttonIDList[slotID] = spellID
|
|
spellIDList[spellID] = slotID
|
|
elseif buttonIDList[slotID] then
|
|
spellIDList[buttonIDList[slotID]] = nil
|
|
buttonIDList[slotID] = nil
|
|
end
|
|
Private.StopProfileSystem("generictrigger queued action");
|
|
end)
|
|
end
|
|
|
|
function WeakAuras.FindSpellActionButtons(spellID)
|
|
return spellIDList[spellID]
|
|
end
|
|
end
|
|
|
|
do
|
|
local scheduled_scans = {};
|
|
|
|
local function doScan(fireTime, event)
|
|
scheduled_scans[event][fireTime] = nil;
|
|
WeakAuras.ScanEvents(event);
|
|
end
|
|
function Private.ExecEnv.ScheduleScan(fireTime, event)
|
|
event = event or "COOLDOWN_REMAINING_CHECK"
|
|
scheduled_scans[event] = scheduled_scans[event] or {}
|
|
if not(scheduled_scans[event][fireTime]) then
|
|
scheduled_scans[event][fireTime] = timer:ScheduleTimer(doScan, fireTime - GetTime() + 0.1, fireTime, event);
|
|
end
|
|
end
|
|
end
|
|
|
|
do
|
|
local scheduled_scans = {};
|
|
|
|
local function doCastScan(firetime, unit)
|
|
scheduled_scans[unit][firetime] = nil;
|
|
WeakAuras.ScanEvents("CAST_REMAINING_CHECK_" .. string.lower(unit), unit);
|
|
end
|
|
|
|
function Private.ExecEnv.ScheduleCastCheck(fireTime, unit)
|
|
scheduled_scans[unit] = scheduled_scans[unit] or {}
|
|
if not(scheduled_scans[unit][fireTime]) then
|
|
scheduled_scans[unit][fireTime] = timer:ScheduleTimer(doCastScan, fireTime - GetTime() + 0.1, fireTime, unit);
|
|
end
|
|
end
|
|
end
|
|
|
|
local uniqueId = 0;
|
|
function WeakAuras.GetUniqueCloneId()
|
|
uniqueId = (uniqueId + 1) % 1000000;
|
|
return uniqueId;
|
|
end
|
|
|
|
function GenericTrigger.GetPrototype(trigger)
|
|
if trigger.type and trigger.event then
|
|
if Private.category_event_prototype[trigger.type] then
|
|
return Private.event_prototypes[trigger.event]
|
|
end
|
|
end
|
|
end
|
|
|
|
function GenericTrigger.GetDelay(data)
|
|
if data.event then
|
|
local prototype = GenericTrigger.GetPrototype(data.trigger)
|
|
if prototype and prototype.delayEvents then
|
|
local trigger = data.trigger
|
|
if trigger.use_delay and type(trigger.delay) == "number" and trigger.delay > 0 then
|
|
return trigger.delay
|
|
end
|
|
end
|
|
end
|
|
return 0
|
|
end
|
|
|
|
function GenericTrigger.GetTsuConditionVariables(id, triggernum)
|
|
local ok, variables = xpcall(events[id][triggernum].tsuConditionVariables, Private.GetErrorHandlerId(id, L["Custom Variables"]));
|
|
if ok then
|
|
return variables
|
|
end
|
|
end
|
|
|
|
--- Returns a table containing the names of all overlays
|
|
function GenericTrigger.GetOverlayInfo(data, triggernum)
|
|
local result;
|
|
|
|
local trigger = data.triggers[triggernum].trigger
|
|
|
|
local prototype = GenericTrigger.GetPrototype(trigger)
|
|
if (prototype and prototype.overlayFuncs) then
|
|
result = {};
|
|
local dest = 1;
|
|
for i, v in ipairs(prototype.overlayFuncs) do
|
|
local enable = true
|
|
if type(v.enable) == "function" then
|
|
enable = v.enable(trigger)
|
|
elseif type(v.enable) == "boolean" then
|
|
enable = v.enable
|
|
end
|
|
if enable then
|
|
result[dest] = v.name;
|
|
dest = dest + 1;
|
|
end
|
|
end
|
|
end
|
|
|
|
if (trigger.type == "custom") then
|
|
if (trigger.custom_type == "stateupdate") then
|
|
local count = 0;
|
|
local variables = GenericTrigger.GetTsuConditionVariables(data.id, triggernum)
|
|
if (type(variables) == "table") then
|
|
if (type(variables.additionalProgress) == "table") then
|
|
count = #variables.additionalProgress;
|
|
elseif (type(variables.additionalProgress) == "number") then
|
|
count = variables.additionalProgress;
|
|
end
|
|
else
|
|
local allStates = {};
|
|
Private.ActivateAuraEnvironment(data.id);
|
|
RunTriggerFunc(allStates, events[data.id][triggernum], data.id, triggernum, "OPTIONS");
|
|
Private.ActivateAuraEnvironment(nil);
|
|
local count = 0;
|
|
for id, state in pairs(allStates) do
|
|
if (type(state.additionalProgress) == "table") then
|
|
count = max(count, #state.additionalProgress);
|
|
end
|
|
end
|
|
end
|
|
|
|
count = min(count, 7);
|
|
for i = 1, count do
|
|
result = result or {};
|
|
result[i] = string.format(L["Overlay %s"], i);
|
|
end
|
|
else
|
|
for i = 1, 7 do
|
|
local property = "customOverlay" .. i;
|
|
if (trigger[property] and trigger[property] ~= "") then
|
|
result = result or {};
|
|
result[i] = string.format(L["Overlay %s"], i);
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
return result;
|
|
end
|
|
|
|
function GenericTrigger.GetNameAndIcon(data, triggernum)
|
|
local trigger = data.triggers[triggernum].trigger
|
|
local icon, name
|
|
local prototype = GenericTrigger.GetPrototype(trigger)
|
|
if prototype then
|
|
if prototype.GetNameAndIcon then
|
|
return prototype.GetNameAndIcon(trigger)
|
|
else
|
|
if prototype.iconFunc then
|
|
icon = prototype.iconFunc(trigger)
|
|
end
|
|
if prototype.nameFunc then
|
|
name = prototype.nameFunc(trigger)
|
|
end
|
|
end
|
|
end
|
|
|
|
return name, icon
|
|
end
|
|
|
|
---Returns the type of tooltip to show for the trigger.
|
|
-- @param data
|
|
-- @param triggernum
|
|
-- @return string
|
|
function GenericTrigger.CanHaveTooltip(data, triggernum)
|
|
local trigger = data.triggers[triggernum].trigger
|
|
local prototype = GenericTrigger.GetPrototype(trigger)
|
|
if prototype then
|
|
if prototype.hasSpellID then
|
|
return "spell";
|
|
elseif prototype.hasItemID then
|
|
return "item";
|
|
end
|
|
end
|
|
|
|
if (trigger.type == "custom") then
|
|
if (trigger.custom_type == "stateupdate") then
|
|
return true;
|
|
end
|
|
end
|
|
|
|
return false;
|
|
end
|
|
|
|
function GenericTrigger.SetToolTip(trigger, state)
|
|
if (trigger.type == "custom" and trigger.custom_type == "stateupdate") then
|
|
if (state.tooltip) then
|
|
local lines = { strsplit("\n", state.tooltip) };
|
|
GameTooltip:ClearLines();
|
|
for i, line in ipairs(lines) do
|
|
GameTooltip:AddLine(line, nil, nil, nil, state.tooltipWrap);
|
|
end
|
|
return true
|
|
elseif (state.spellId) then
|
|
GameTooltip:SetSpellByID(state.spellId);
|
|
return true
|
|
elseif (state.link) then
|
|
GameTooltip:SetHyperlink(state.link);
|
|
return true
|
|
elseif (state.itemId) then
|
|
GameTooltip:SetHyperlink("item:"..state.itemId..":0:0:0:0:0:0:0");
|
|
return true
|
|
elseif (state.unit and state.unitBuffIndex) then
|
|
GameTooltip:SetUnitBuff(state.unit, state.unitBuffIndex, state.unitBuffFilter);
|
|
return true
|
|
elseif (state.unit and state.unitDebuffIndex) then
|
|
GameTooltip:SetUnitDebuff(state.unit, state.unitDebuffIndex, state.unitDebuffFilter);
|
|
return true
|
|
elseif (state.unit and state.unitAuraIndex) then
|
|
GameTooltip:SetUnitAura(state.unit, state.unitAuraIndex, state.unitAuraFilter)
|
|
return true
|
|
end
|
|
end
|
|
|
|
local prototype = GenericTrigger.GetPrototype(trigger)
|
|
if prototype then
|
|
if prototype.hasSpellID then
|
|
GameTooltip:SetSpellByID(trigger.spellName);
|
|
return true
|
|
elseif prototype.hasItemID then
|
|
GameTooltip:SetHyperlink("item:"..trigger.itemName..":0:0:0:0:0:0:0")
|
|
return true
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
|
|
function GenericTrigger.GetAdditionalProperties(data, triggernum)
|
|
local trigger = data.triggers[triggernum].trigger
|
|
local props = {}
|
|
local prototype = GenericTrigger.GetPrototype(trigger)
|
|
if prototype then
|
|
for _, v in pairs(prototype.args) do
|
|
local enable = true
|
|
if(type(v.enable) == "function") then
|
|
enable = v.enable(trigger)
|
|
elseif type(v.enable) == "boolean" then
|
|
enable = v.enable
|
|
end
|
|
|
|
if (enable and v.store and v.name and v.display and v.conditionType ~= "bool") then
|
|
props[v.name] = v.display
|
|
end
|
|
end
|
|
if prototype.countEvents then
|
|
props.count = L["Count"]
|
|
end
|
|
else
|
|
if (trigger.custom_type == "stateupdate") then
|
|
local variables = GenericTrigger.GetTsuConditionVariables(data.id, triggernum)
|
|
if (type(variables) == "table") then
|
|
for var, varData in pairs(variables) do
|
|
if (type(varData) == "table") then
|
|
props[var] = varData.display or var
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
return props;
|
|
end
|
|
|
|
function GenericTrigger.GetProgressSources(data, triggernum, values)
|
|
local variables = GenericTrigger.GetTriggerConditions(data, triggernum)
|
|
if (type(variables) == "table") then
|
|
for var, varData in pairs(variables) do
|
|
if (type(varData) == "table") then
|
|
if (varData.type == "number" or varData.type == "timer" or varData.type == "elapsedTimer")
|
|
and not varData.noProgressSource
|
|
then
|
|
|
|
tinsert(values, {
|
|
trigger = triggernum,
|
|
property = var,
|
|
type = varData.type,
|
|
display = varData.display,
|
|
total = varData.total,
|
|
inverse = varData.inverse,
|
|
paused = varData.paused,
|
|
remaining = varData.remaining
|
|
})
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local commonConditions = {
|
|
expirationTime = {
|
|
display = L["Remaining Duration"],
|
|
type = "timer",
|
|
total = "duration",
|
|
inverse = "inverse",
|
|
paused = "paused",
|
|
remaining = "remaining",
|
|
},
|
|
duration = {
|
|
display = L["Total Duration"],
|
|
type = "number",
|
|
},
|
|
paused = {
|
|
display = L["Is Paused"],
|
|
type = "bool",
|
|
test = function(state, needle)
|
|
return (state.paused and 1 or 0) == needle
|
|
end,
|
|
},
|
|
value = {
|
|
display = L["Progress Value"],
|
|
type = "number",
|
|
total = "total",
|
|
},
|
|
total = {
|
|
display = L["Progress Total"],
|
|
type = "number",
|
|
},
|
|
stacks = {
|
|
display = L["Stacks"],
|
|
type = "number",
|
|
},
|
|
name = {
|
|
display = L["Name"],
|
|
type = "string",
|
|
}
|
|
}
|
|
|
|
function Private.ExpandCustomVariables(variables)
|
|
-- Make the life of tsu authors easier, by automatically filling in the details for
|
|
-- expirationTime, duration, value, total, stacks, if those exists but aren't a table value
|
|
-- By allowing a short-hand notation of just variable = type
|
|
-- In addition to the long form of variable = { type = xyz, display = "desc"}
|
|
for k, v in pairs(commonConditions) do
|
|
if (variables[k] and type(variables[k]) ~= "table") then
|
|
variables[k] = v;
|
|
end
|
|
end
|
|
|
|
for k, v in pairs(variables) do
|
|
if (type(v) == "string") then
|
|
variables[k] = {
|
|
display = k,
|
|
type = v,
|
|
};
|
|
end
|
|
end
|
|
end
|
|
|
|
function Private.GetTsuConditionVariablesExpanded(id, triggernum)
|
|
if events[id][triggernum] and events[id][triggernum].tsuConditionVariables then
|
|
Private.ActivateAuraEnvironment(id, nil, nil, nil, true)
|
|
local result = GenericTrigger.GetTsuConditionVariables(id, triggernum)
|
|
Private.ActivateAuraEnvironment(nil)
|
|
if type(result) ~= "table" then
|
|
return nil
|
|
end
|
|
Private.ExpandCustomVariables(result)
|
|
-- Clean up, remove non table entries and check for a valid display name
|
|
for k, v in pairs(result) do
|
|
if type(v) ~= "table" then
|
|
result[k] = nil
|
|
elseif (v.display == nil or type(v.display) ~= "string") then
|
|
if type(k) == "string" then
|
|
v.display = k
|
|
else
|
|
result[k] = nil
|
|
end
|
|
end
|
|
end
|
|
return result
|
|
end
|
|
end
|
|
|
|
function GenericTrigger.GetTriggerConditions(data, triggernum)
|
|
local trigger = data.triggers[triggernum].trigger
|
|
|
|
local prototype = GenericTrigger.GetPrototype(trigger)
|
|
if prototype then
|
|
local result = {};
|
|
|
|
local progressType = ProgressType(data, triggernum);
|
|
if progressType == "timed" then
|
|
result.expirationTime = commonConditions.expirationTime;
|
|
result.duration = commonConditions.duration;
|
|
result.paused = commonConditions.paused
|
|
end
|
|
|
|
if progressType == "static" then
|
|
result.value = commonConditions.value;
|
|
result.total = commonConditions.total;
|
|
end
|
|
|
|
if prototype.stacksFunc then
|
|
result.stacks = commonConditions.stacks;
|
|
end
|
|
|
|
if prototype.nameFunc then
|
|
result.name = commonConditions.name;
|
|
end
|
|
|
|
for _, v in pairs(prototype.args) do
|
|
if (v.conditionType and v.name and v.display) then
|
|
local enable = true;
|
|
if (v.enable ~= nil) then
|
|
if type(v.enable) == "function" then
|
|
enable = v.enable(trigger);
|
|
elseif type(v.enable) == "boolean" then
|
|
enable = v.enable
|
|
end
|
|
end
|
|
|
|
if (enable) then
|
|
result[v.name] = {
|
|
display = v.display,
|
|
type = v.conditionType
|
|
}
|
|
if (result[v.name].type == "select" or result[v.name].type == "unit") then
|
|
if (v.conditionValues) then
|
|
result[v.name].values = Private[v.conditionValues] or WeakAuras[v.conditionValues];
|
|
else
|
|
if type(v.values) == "function" then
|
|
result[v.name].values = v.values()
|
|
else
|
|
result[v.name].values = Private[v.values] or WeakAuras[v.values];
|
|
end
|
|
end
|
|
end
|
|
if (v.conditionPreamble) then
|
|
result[v.name].preamble = v.conditionPreamble;
|
|
end
|
|
if (v.conditionTest) then
|
|
result[v.name].test = v.conditionTest;
|
|
end
|
|
if (v.conditionEvents) then
|
|
result[v.name].events = v.conditionEvents;
|
|
end
|
|
if (v.operator_types) then
|
|
result[v.name].operator_types = v.operator_types;
|
|
end
|
|
-- for ProgressSource
|
|
if v.noProgressSource then
|
|
result[v.name].noProgressSource = true
|
|
end
|
|
if v.progressTotal then
|
|
result[v.name].total = v.progressTotal
|
|
end
|
|
if v.progressInverse then
|
|
result[v.name].inverse = v.progressInverse
|
|
end
|
|
if v.progressPaused then
|
|
result[v.name].paused = v.progressPaused
|
|
end
|
|
if v.progressRemaining then
|
|
result[v.name].remaining = v.progressRemaining
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
if prototype.countEvents then
|
|
result.count = {
|
|
display = L["Count"],
|
|
type = "number"
|
|
}
|
|
end
|
|
|
|
return result;
|
|
elseif(trigger.type == "custom") then
|
|
if (trigger.custom_type == "status" or trigger.custom_type == "event") then
|
|
local result = {};
|
|
|
|
local canHaveDurationFunc = trigger.custom_type == "status" or (trigger.custom_type == "event" and (trigger.custom_hide ~= "timed" or trigger.dynamicDuration));
|
|
|
|
if (canHaveDurationFunc and trigger.customDuration and trigger.customDuration ~= "") then
|
|
result.expirationTime = commonConditions.expirationTime;
|
|
result.duration = commonConditions.duration;
|
|
result.value = commonConditions.value;
|
|
result.total = commonConditions.total;
|
|
end
|
|
|
|
if (trigger.custom_type == "event" and trigger.custom_hide ~= "custom" and trigger.dynamicDuration ~= true) then
|
|
-- This is the static duration of a event/timed trigger
|
|
result.expirationTime = commonConditions.expirationTime;
|
|
result.duration = commonConditions.duration;
|
|
end
|
|
|
|
if (trigger.customStacks and trigger.customStacks ~= "") then
|
|
result.stacks = commonConditions.stacks;
|
|
end
|
|
|
|
if (trigger.customName and trigger.customName ~= "") then
|
|
result.name = commonConditions.name;
|
|
end
|
|
|
|
return result;
|
|
elseif (trigger.custom_type == "stateupdate") then
|
|
return Private.GetTsuConditionVariablesExpanded(data.id, triggernum)
|
|
end
|
|
end
|
|
|
|
return nil;
|
|
end
|
|
|
|
function GenericTrigger.CreateFallbackState(data, triggernum, state)
|
|
state.show = true;
|
|
state.changed = true;
|
|
local event = events[data.id][triggernum];
|
|
|
|
Private.ActivateAuraEnvironment(data.id, "", state);
|
|
local trigger = data.triggers[triggernum].trigger
|
|
|
|
if event.GetNameAndIcon then
|
|
local ok, name, icon = pcall(event.GetNameAndIcon, trigger);
|
|
state.name = ok and name or nil;
|
|
state.icon = ok and icon or nil;
|
|
if not ok then
|
|
Private.GetErrorHandlerUid(data.uid, L["GetNameAndIcon Function (fallback state)"])
|
|
end
|
|
else
|
|
if (event.nameFunc) then
|
|
local ok, name = pcall(event.nameFunc, trigger);
|
|
state.name = ok and name or nil;
|
|
if not ok then
|
|
Private.GetErrorHandlerUid(data.uid, L["Name Function (fallback state)"])
|
|
end
|
|
end
|
|
if (event.iconFunc) then
|
|
local ok, icon = pcall(event.iconFunc, trigger);
|
|
state.icon = ok and icon or nil;
|
|
if not ok then
|
|
Private.GetErrorHandlerUid(data.uid, L["Icon Function (fallback state)"])
|
|
end
|
|
end
|
|
end
|
|
|
|
if (event.textureFunc ) then
|
|
local ok, texture = pcall(event.textureFunc, trigger);
|
|
if not ok then
|
|
Private.GetErrorHandlerUid(data.uid, L["Texture Function (fallback state)"])
|
|
state.texture = nil
|
|
else
|
|
state.texture = texture or nil
|
|
end
|
|
end
|
|
|
|
if (event.stacksFunc) then
|
|
local ok, stacks = pcall(event.stacksFunc, trigger);
|
|
if not ok then
|
|
Private.GetErrorHandlerUid(data.uid, L["Stacks Function (fallback state)"])
|
|
state.stacks = nil
|
|
else
|
|
state.stacks = stacks or nil
|
|
end
|
|
end
|
|
|
|
if (event.durationFunc) then
|
|
local ok, arg1, arg2, arg3, inverse = pcall(event.durationFunc, trigger);
|
|
if not ok then
|
|
Private.GetErrorHandlerUid(data.uid, L["Duration Function (fallback state)"])
|
|
state.progressType = "timed";
|
|
state.duration = 0;
|
|
state.expirationTime = math.huge;
|
|
state.value = nil;
|
|
state.total = nil;
|
|
Private.ActivateAuraEnvironment(nil)
|
|
return;
|
|
end
|
|
arg1 = type(arg1) == "number" and arg1 or 0;
|
|
arg2 = type(arg2) == "number" and arg2 or 0;
|
|
|
|
if(type(arg3) == "string") then
|
|
state.durationFunc = event.durationFunc;
|
|
elseif (type(arg3) == "function") then
|
|
state.durationFunc = arg3;
|
|
else
|
|
state.durationFunc = nil;
|
|
end
|
|
|
|
if (arg3) then
|
|
state.progressType = "static";
|
|
state.duration = nil;
|
|
state.expirationTime = nil;
|
|
state.value = arg1;
|
|
state.total = arg2;
|
|
state.inverse = inverse;
|
|
else
|
|
state.progressType = "timed";
|
|
state.duration = arg1;
|
|
state.expirationTime = arg2;
|
|
state.autoHide = nil;
|
|
state.value = nil;
|
|
state.total = nil;
|
|
state.inverse = inverse;
|
|
end
|
|
else
|
|
state.progressType = "timed";
|
|
state.duration = 0;
|
|
state.expirationTime = math.huge;
|
|
state.value = nil;
|
|
state.total = nil;
|
|
end
|
|
if (event.overlayFuncs) then
|
|
RunOverlayFuncs(event, state, data.id);
|
|
end
|
|
Private.ActivateAuraEnvironment(nil);
|
|
end
|
|
|
|
function GenericTrigger.GetName(triggerType)
|
|
return Private.event_categories[triggerType].name
|
|
end
|
|
|
|
function GenericTrigger.GetTriggerDescription(data, triggernum, namestable)
|
|
local trigger = data.triggers[triggernum].trigger
|
|
local prototype = GenericTrigger.GetPrototype(trigger)
|
|
if prototype then
|
|
tinsert(namestable, {L["Trigger:"], (prototype.name or L["Undefined"])});
|
|
if(trigger.event == "Combat Log" and trigger.subeventPrefix and trigger.subeventSuffix) then
|
|
tinsert(namestable, {L["Message type:"], (Private.subevent_prefix_types[trigger.subeventPrefix] or L["Undefined"]).." "..(Private.subevent_suffix_types[trigger.subeventSuffix] or L["Undefined"])});
|
|
end
|
|
else
|
|
tinsert(namestable, {L["Trigger:"], L["Custom"]});
|
|
end
|
|
end
|
|
|
|
do
|
|
-- Based on Code by DejaCharacterStats. Ugly code to figure out the GCD
|
|
local GetCombatRatingBonus = GetCombatRatingBonus
|
|
local CR_HASTE_MELEE = CR_HASTE_MELEE
|
|
local CR_HASTE_RANGED = CR_HASTE_RANGED
|
|
local CR_HASTE_SPELL = CR_HASTE_SPELL
|
|
|
|
local class = select(2, UnitClass("player"))
|
|
if class == "DRUID" then
|
|
function WeakAuras.CalculatedGcdDuration()
|
|
return GetShapeshiftForm() == 3 and 1 or max(0.75, 1.5 * 100 / (100 + GetCombatRatingBonus(CR_HASTE_SPELL)))
|
|
end
|
|
elseif class == "ROGUE" then
|
|
function WeakAuras.CalculatedGcdDuration()
|
|
return 1
|
|
end
|
|
else
|
|
local GetHaste
|
|
if class == "HUNTER" then
|
|
function GetHaste()
|
|
return GetCombatRatingBonus(CR_HASTE_RANGED)
|
|
end
|
|
elseif class == "DEATHKNIGHT" or class == "PALADIN" or class == "WARRIOR" then
|
|
function GetHaste()
|
|
return GetCombatRatingBonus(CR_HASTE_MELEE)
|
|
end
|
|
else
|
|
function GetHaste()
|
|
return GetCombatRatingBonus(CR_HASTE_SPELL)
|
|
end
|
|
end
|
|
function WeakAuras.CalculatedGcdDuration()
|
|
return max(0.75, 1.5 * 100 / (100 + GetHaste()))
|
|
end
|
|
end
|
|
end
|
|
|
|
WeakAuras.CheckForItemEquipped = function(itemName, specificSlot)
|
|
if not specificSlot then
|
|
return IsEquippedItem(itemName)
|
|
end
|
|
local equippedItemID = GetInventoryItemID("player", specificSlot)
|
|
return itemName and equippedItemID and (
|
|
(type(itemName) == "number" and itemName == equippedItemID)
|
|
or itemName == GetItemInfo(equippedItemID)
|
|
)
|
|
end
|
|
|
|
WeakAuras.GetCritChance = function()
|
|
-- Based on what the wow paper doll does
|
|
local spellCrit = 0
|
|
for i = 2, MAX_SPELL_SCHOOLS or 7 do -- WORKAROUND: MAX_SPELL_SCHOOLS is nil on classic_era
|
|
spellCrit = max(spellCrit, GetSpellCritChance(i))
|
|
end
|
|
return max(spellCrit, GetRangedCritChance(), GetCritChance())
|
|
end
|
|
|
|
WeakAuras.GetHitChance = function()
|
|
local melee = (GetCombatRatingBonus(CR_HIT_MELEE) or 0)
|
|
local ranged = (GetCombatRatingBonus(CR_HIT_RANGED) or 0)
|
|
local spell = (GetCombatRatingBonus(CR_HIT_SPELL) or 0)
|
|
return max(melee, ranged, spell)
|
|
end
|
|
|
|
WeakAuras.GetResilienceDamageReduction = function()
|
|
local ratings = {
|
|
{value = GetCombatRating(CR_CRIT_TAKEN_MELEE), type = CR_CRIT_TAKEN_MELEE},
|
|
{value = GetCombatRating(CR_CRIT_TAKEN_RANGED), type = CR_CRIT_TAKEN_RANGED},
|
|
{value = GetCombatRating(CR_CRIT_TAKEN_SPELL), type = CR_CRIT_TAKEN_SPELL},
|
|
}
|
|
local lowest = ratings[1]
|
|
for _, rating in ipairs(ratings) do
|
|
if rating.value < lowest.value then lowest = rating end
|
|
end
|
|
return GetCombatRatingBonus(lowest.type) * 2
|
|
end
|
|
|
|
|
|
local types = {}
|
|
tinsert(types, "custom")
|
|
for type in pairs(Private.category_event_prototype) do
|
|
tinsert(types, type)
|
|
end
|
|
|
|
-- The Options/GenericTrigger.lua needs this table, since at the time
|
|
-- of registering the types the options code doesn't yet have access
|
|
-- to the Private table.
|
|
|
|
-- So for now make it simply a member of WeakAuras
|
|
WeakAuras.genericTriggerTypes = types
|
|
|
|
WeakAuras.RegisterTriggerSystem(types, GenericTrigger);
|