cfc26d8c99
The login message was triggered incorrectly due to the "Beta" tag in the version string. This has been fixed. Almost completely removed due to impracticality with the 3.3.5a API. When RAID_ROSTER_UPDATE or PARTY_MEMBERS_CHANGED fires, data is unavailable for ~1.5 seconds. If these events fire again within that time, the timer needs to be restarted, leading to excessive code complexity and requiring handling for every edge case. Instead, we now simply check if the unit has changed when the library fires its callback.
4480 lines
146 KiB
Lua
4480 lines
146 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 tostring, pairs, type = 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, 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)" 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 = {}
|
||
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, 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
|
||
if data.trigger.unit == "group" and IsInRaid() and Private.multiUnitUnits.party[arg1] then
|
||
return
|
||
end
|
||
|
||
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
|
||
|
||
local scannerFrame = CreateFrame("Frame")
|
||
scannerFrame.queue = {}
|
||
scannerFrame:Hide()
|
||
scannerFrame:SetScript("OnUpdate", function(self)
|
||
local todo = self.queue
|
||
self.queue = {}
|
||
for _, event in ipairs(todo) do
|
||
event.func(unpack(event.args))
|
||
end
|
||
-- there's a chance that a joker dispatched an event in in trigger code,
|
||
-- so the queue might already be populated
|
||
-- in that case, we'll process next frame by declining to hide
|
||
if #self.queue == 0 then
|
||
self:Hide()
|
||
end
|
||
end)
|
||
|
||
function scannerFrame:Queue(func, ...)
|
||
tinsert(self.queue, {func = func, args = {...}})
|
||
self:Show()
|
||
end
|
||
|
||
function Private.ScanEventsByID(event, id, ...)
|
||
if loaded_events[event] then
|
||
Private.ScanEvents(event, id, ...)
|
||
end
|
||
local eventWithID = event .. ":" .. id
|
||
if loaded_events[eventWithID] then
|
||
Private.ScanEvents(eventWithID, id, ...)
|
||
end
|
||
end
|
||
|
||
function WeakAuras.ScanEventsByID(event, id, ...)
|
||
scannerFrame:Queue(Private.ScanEventsByID, event, id, ...)
|
||
end
|
||
|
||
function Private.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
|
||
Private.ScanEventsInternal(event_list, event, arg1, arg2, ...);
|
||
else
|
||
Private.ScanEventsInternal(event_list, event, arg1, arg2, ...);
|
||
end
|
||
Private.StopProfileSystem("generictrigger " .. system)
|
||
end
|
||
|
||
function WeakAuras.ScanEvents(event, arg1, arg2, ...)
|
||
if type(event) ~= "string" then
|
||
return
|
||
end
|
||
scannerFrame:Queue(Private.ScanEvents, event, arg1, arg2, ...)
|
||
end
|
||
|
||
function Private.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.ScanUnitEvents(event, unit, ...)
|
||
scannerFrame:Queue(Private.ScanUnitEvents, event, unit, ...)
|
||
end
|
||
|
||
function Private.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 delay = GenericTrigger.GetDelay(data)
|
||
if delay == 0 then
|
||
local allStates = WeakAuras.GetTriggerStateForTrigger(id, triggernum);
|
||
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
|
||
|
||
function WeakAuras.ScanEventsInternal(event_list, event, arg1, arg2, ... )
|
||
scannerFrame:Queue(Private.ScanEventsInternal, event_list, event, arg1, arg2, ...)
|
||
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)
|
||
if #watchedTriggernums == 0 then return end
|
||
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
|
||
local expirationTime = state.expirationTime
|
||
if expirationTime and type(expirationTime) == "number" and expirationTime ~= math.huge and 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
|
||
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
|
||
Private.ScanEvents(event, arg1, arg2, ...);
|
||
else
|
||
Private.ScanEvents(event, arg1, arg2, ...);
|
||
end
|
||
end
|
||
if (event == "PLAYER_ENTERING_WORLD") then
|
||
timer:ScheduleTimer(function()
|
||
HandleEvent(frame, "WA_DELAYED_PLAYER_ENTERING_WORLD");
|
||
Private.ScanForLoads(nil, "WA_DELAYED_PLAYER_ENTERING_WORLD")
|
||
Private.StartProfileSystem("generictrigger 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
|
||
Private.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 == "FRAME_UPDATE") then
|
||
register_for_frame_updates = true;
|
||
elseif not genericTriggerRegisteredEvents[event] then
|
||
eventsToRegister[event] = true;
|
||
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
|
||
Private.ScanEvents(loadEvent, ...);
|
||
end
|
||
local loadUnit = ...
|
||
if loadUnit and unitEventsToRegister[loadUnit] and unitEventsToRegister[loadUnit][loadEvent] then
|
||
Private.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 isTrigger = false
|
||
local isUnitEvent = 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")
|
||
-- We don't register CLEU events without parameters anymore
|
||
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", "error",
|
||
L["|cFFFF0000Support for unfiltered COMBAT_LOG_EVENT_UNFILTERED is deprecated|r\nCOMBAT_LOG_EVENT_UNFILTERED without a filter are disabled as it’s very performance costly.\nFind more information:\nhttps://github.com/WeakAuras/WeakAuras2/wiki/Custom-Triggers#events"])
|
||
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
|
||
Private.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
|
||
local skipNextAttack, skipNextAttackCount
|
||
local isAttacking
|
||
|
||
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 swingTriggerUpdate()
|
||
Private.ScanEvents("SWING_TIMER_UPDATE")
|
||
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
|
||
swingTriggerUpdate()
|
||
end
|
||
|
||
local function swingStart(hand)
|
||
mainSpeed, offSpeed = UnitAttackSpeed("player")
|
||
offSpeed = offSpeed or 0
|
||
local currentTime = GetTime()
|
||
if hand == "main" then
|
||
lastSwingMain = currentTime
|
||
swingDurationMain = mainSpeed
|
||
mainSwingOffset = 0
|
||
if mainTimer then
|
||
timer:CancelTimer(mainTimer)
|
||
end
|
||
if mainSpeed and mainSpeed > 0 then
|
||
mainTimer = timer:ScheduleTimer(swingEnd, mainSpeed, hand)
|
||
else
|
||
swingEnd(hand)
|
||
end
|
||
elseif hand == "off" then
|
||
lastSwingOff = currentTime
|
||
swingDurationOff = offSpeed
|
||
if offTimer then
|
||
timer:CancelTimer(offTimer)
|
||
end
|
||
if offSpeed and offSpeed > 0 then
|
||
offTimer = timer:ScheduleTimer(swingEnd, offSpeed, hand)
|
||
else
|
||
swingEnd(hand)
|
||
end
|
||
elseif hand == "ranged" then
|
||
local rangeSpeed = UnitRangedDamage("player")
|
||
lastSwingRange = currentTime
|
||
swingDurationRange = rangeSpeed
|
||
if rangeTimer then
|
||
timer:CancelTimer(rangeTimer)
|
||
end
|
||
if rangeSpeed and rangeSpeed > 0 then
|
||
rangeTimer = timer:ScheduleTimer(swingEnd, rangeSpeed, hand)
|
||
else
|
||
swingEnd(hand)
|
||
end
|
||
end
|
||
end
|
||
|
||
local function swingTimerCLEUCheck(ts, event, sourceGUID, _, _, destGUID, _, _, ...)
|
||
Private.StartProfileSystem("generictrigger swing");
|
||
if(sourceGUID == selfGUID) then
|
||
if event == "SPELL_EXTRA_ATTACKS" then
|
||
skipNextAttack = ts
|
||
skipNextAttackCount = select(4, ...)
|
||
elseif(event == "SWING_DAMAGE" or event == "SWING_MISSED") then
|
||
if tonumber(skipNextAttack) and (ts - skipNextAttack) < 0.04 and tonumber(skipNextAttackCount) then
|
||
if skipNextAttackCount > 0 then
|
||
skipNextAttackCount = skipNextAttackCount - 1
|
||
return
|
||
end
|
||
end
|
||
local isOffHand = select(event == "SWING_DAMAGE" and 10 or 2, ...);
|
||
if not isOffHand then
|
||
swingStart("main")
|
||
elseif(isOffHand) then
|
||
swingStart("off")
|
||
end
|
||
swingTriggerUpdate()
|
||
end
|
||
elseif (destGUID == selfGUID and (... == "PARRY" or select(4, ...) == "PARRY")) then
|
||
if (lastSwingMain) then
|
||
local timeLeft = lastSwingMain + swingDurationMain - GetTime() - (mainSwingOffset or 0);
|
||
if (timeLeft > 0.2 * swingDurationMain) then
|
||
local offset = 0.4 * swingDurationMain
|
||
if (timeLeft - offset < 0.2 * swingDurationMain) then
|
||
offset = timeLeft - 0.2 * swingDurationMain
|
||
end
|
||
timer:CancelTimer(mainTimer);
|
||
mainTimer = timer:ScheduleTimer(swingEnd, timeLeft - offset, "main");
|
||
mainSwingOffset = (mainSwingOffset or 0) + offset
|
||
swingTriggerUpdate()
|
||
end
|
||
end
|
||
end
|
||
Private.StopProfileSystem("generictrigger swing");
|
||
end
|
||
|
||
local function swingTimerCheck(event, unit, spell)
|
||
if event ~= "PLAYER_EQUIPMENT_CHANGED" and unit and unit ~= "player" then return end
|
||
Private.StartProfileSystem("generictrigger swing");
|
||
local now = GetTime()
|
||
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 - now) * multiplier
|
||
swingDurationMain = mainSpeedNew
|
||
mainSwingOffset = (lastSwingMain + swingDurationMain) - (now + timeLeft)
|
||
mainTimer = timer:ScheduleTimer(swingEnd, timeLeft, "main")
|
||
end
|
||
end
|
||
if lastSwingOff then
|
||
if offSpeedNew ~= offSpeed then
|
||
timer:CancelTimer(offTimer)
|
||
local multiplier = offSpeedNew / mainSpeed
|
||
local timeLeft = (lastSwingOff + swingDurationOff - now) * multiplier
|
||
swingDurationOff = offSpeedNew
|
||
offTimer = timer:ScheduleTimer(swingEnd, timeLeft, "off")
|
||
end
|
||
end
|
||
mainSpeed, offSpeed = mainSpeedNew, offSpeedNew
|
||
swingTriggerUpdate()
|
||
elseif casting and (event == "UNIT_SPELLCAST_INTERRUPTED" or event == "UNIT_SPELLCAST_FAILED") then
|
||
casting = false
|
||
elseif event == "PLAYER_EQUIPMENT_CHANGED" and isAttacking then
|
||
swingStart("main")
|
||
swingStart("off")
|
||
swingStart("ranged")
|
||
swingTriggerUpdate()
|
||
elseif event == "UNIT_SPELLCAST_SUCCEEDED" then
|
||
if Private.reset_swing_spells[spell] or casting then
|
||
if casting then
|
||
casting = false
|
||
end
|
||
-- check next frame
|
||
swingTimerFrame:SetScript("OnUpdate", function(self)
|
||
if isAttacking then
|
||
swingStart("main")
|
||
swingTriggerUpdate()
|
||
end
|
||
self:SetScript("OnUpdate", nil)
|
||
end)
|
||
end
|
||
if Private.reset_ranged_swing_spells[spell] then
|
||
swingStart("ranged")
|
||
swingTriggerUpdate()
|
||
end
|
||
elseif event == "UNIT_SPELLCAST_START" then
|
||
if not Private.noreset_swing_spells[spell] then
|
||
-- pause swing timer
|
||
casting = true
|
||
lastSwingMain, swingDurationMain, mainSwingOffset = nil, nil, nil
|
||
lastSwingOff, swingDurationOff = nil, nil
|
||
swingTriggerUpdate()
|
||
end
|
||
elseif event == "PLAYER_REGEN_DISABLED" then
|
||
isAttacking = true
|
||
elseif event == "PLAYER_REGEN_ENABLED" then
|
||
isAttacking = nil
|
||
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_REGEN_DISABLED");
|
||
swingTimerFrame:RegisterEvent("PLAYER_REGEN_ENABLED");
|
||
swingTimerFrame:RegisterEvent("PLAYER_EQUIPMENT_CHANGED");
|
||
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
|
||
Private.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
|
||
self:Cancel(id)
|
||
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,
|
||
Cancel = function(self, id)
|
||
if self.handles[id] then
|
||
timer:CancelTimer(self.handles[id])
|
||
self.handles[id] = nil
|
||
self.expirationTime[id] = nil
|
||
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.readyTime[id] = self.expirationTime[id]
|
||
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 = GetSpellInfo(name or 0);
|
||
gcdSpellName = name;
|
||
gcdSpellIcon = icon;
|
||
if not WeakAuras.IsPaused() then
|
||
Private.ScanEvents("GCD_UPDATE")
|
||
end
|
||
end
|
||
end
|
||
elseif(event == "UNIT_INVENTORY_CHANGED" 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;
|
||
Private.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);
|
||
Private.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);
|
||
Private.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 > 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
|
||
local oldRole = watchUnitChange.unitRoles[unit]
|
||
local newRole = WeakAuras.LGT:GetUnitRole(unit)
|
||
if oldRole ~= newRole then
|
||
eventsToSend["UNIT_ROLE_CHANGED_" .. unit] = unit
|
||
watchUnitChange.unitRoles[unit] = newRole
|
||
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,
|
||
PARTY_MEMBERS_CHANGED = 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,
|
||
RAID_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
|
||
}
|
||
|
||
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
|
||
Private.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
|
||
|
||
function Private.ExecEnv.CheckTotemIcon(totemIcon, triggerTotemIcon, operator)
|
||
if not triggerTotemIcon then
|
||
return true
|
||
end
|
||
return (totemIcon == triggerTotemIcon) == (operator == "==")
|
||
end
|
||
|
||
-- Queueable Spells
|
||
local queueableSpells
|
||
local classQueueableSpells = {
|
||
["WARRIOR"] = {
|
||
(select(1, GetSpellInfo(78))), -- Heroic Strike
|
||
(select(1, GetSpellInfo(845))), -- Cleave
|
||
},
|
||
["HUNTER"] = {
|
||
(select(1, GetSpellInfo(2973))), -- Raptor Strike
|
||
},
|
||
["DRUID"] = {
|
||
(select(1, GetSpellInfo(6807))), -- Maul
|
||
},
|
||
["DEATHKNIGHT"] = {
|
||
(select(1, GetSpellInfo(56815))), -- Rune Strike
|
||
},
|
||
}
|
||
local class = select(2, UnitClass("player"))
|
||
queueableSpells = classQueueableSpells[class]
|
||
|
||
local queuedSpellFrame
|
||
function WeakAuras.WatchForQueuedSpell()
|
||
if not queuedSpellFrame then
|
||
queuedSpellFrame = CreateFrame("Frame")
|
||
Private.frames["Queued Spell Handler"] = queuedSpellFrame
|
||
queuedSpellFrame:RegisterEvent("CURRENT_SPELL_CAST_CHANGED")
|
||
|
||
queuedSpellFrame:SetScript("OnEvent", function(self)
|
||
local newQueuedSpell
|
||
if queueableSpells then
|
||
for _, spellName in ipairs(queueableSpells) do
|
||
if IsCurrentSpell(spellName) then
|
||
newQueuedSpell = spellName
|
||
break
|
||
end
|
||
end
|
||
end
|
||
if newQueuedSpell ~= self.queuedSpell then
|
||
self.queuedSpell = newQueuedSpell
|
||
Private.ScanEvents("WA_UNIT_QUEUED_SPELL_CHANGED", "player")
|
||
end
|
||
end)
|
||
end
|
||
end
|
||
|
||
function WeakAuras.GetQueuedSpell()
|
||
return queuedSpellFrame and queuedSpellFrame.queuedSpell
|
||
end
|
||
|
||
function WeakAuras.GetSpellCost(powerTypeToCheck)
|
||
local spellName = UnitCastingInfo("player")
|
||
if not spellName then -- not casting so check if it is queued
|
||
spellName = WeakAuras.GetQueuedSpell()
|
||
end
|
||
if spellName then
|
||
local _, _, _, powerCost, _, powerType = GetSpellInfo(spellName);
|
||
if powerType and powerCost then
|
||
if powerType == powerTypeToCheck then
|
||
return powerCost;
|
||
end
|
||
end
|
||
end
|
||
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
|
||
Private.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")
|
||
Private.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
|
||
Private.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();
|
||
Private.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
|
||
Private.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
|
||
Private.ScanEvents("NP_SHOW", self, name)
|
||
Private.StopProfileSystem("nameplatetrigger")
|
||
end
|
||
|
||
local function nameplateHide(self)
|
||
Private.StartProfileSystem("nameplatetrigger")
|
||
visibleNameplates[self] = nil
|
||
Private.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(Private.ScanEvents, 0.2, "ITEM_COUNT_UPDATE");
|
||
timer:ScheduleTimer(Private.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
|
||
|
||
-- LibSpecWrapper
|
||
-- We always register, because it's probably not that often called, and ScanEvents checks
|
||
-- early if anyone wants the event
|
||
Private.LibGroupTalentsWrapper.Register(function(unit)
|
||
WeakAuras.ScanEvents("UNIT_SPEC_CHANGED_" .. unit, unit)
|
||
end)
|
||
|
||
do
|
||
local scheduled_scans = {};
|
||
|
||
local function doScan(fireTime, event)
|
||
scheduled_scans[event][fireTime] = nil;
|
||
Private.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;
|
||
Private.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 or 0);
|
||
return true
|
||
elseif prototype.hasItemID then
|
||
GameTooltip:SetHyperlink("item:"..(trigger.itemName or 0)..":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"
|
||
},
|
||
itemInRange = {
|
||
display = WeakAuras.newFeatureString .. L["Item in Range"],
|
||
hidden = true,
|
||
type = "bool",
|
||
test = function(state, needle)
|
||
if not state or not state.itemId or not state.show or not UnitExists('target') then
|
||
return false
|
||
end
|
||
if InCombatLockdown() and not UnitCanAttack('player', 'target') then
|
||
return false
|
||
end
|
||
return C_Item.IsItemInRange(state.itemId, 'target') == (needle == 1)
|
||
end,
|
||
events = { "PLAYER_TARGET_CHANGED", "WA_SPELL_RANGECHECK", }
|
||
},
|
||
}
|
||
|
||
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
|
||
|
||
if prototype.hasItemID then
|
||
result.itemInRange = commonConditions.itemInRange
|
||
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);
|