6761 lines
218 KiB
Lua
6761 lines
218 KiB
Lua
local internalVersion = 29;
|
|
|
|
-- Lua APIs
|
|
local insert = table.insert
|
|
|
|
-- WoW APIs
|
|
local GetTalentInfo, IsAddOnLoaded, InCombatLockdown = GetTalentInfo, IsAddOnLoaded, InCombatLockdown
|
|
local LoadAddOn, UnitName, GetRealmName, UnitRace, UnitFactionGroup, IsInRaid
|
|
= LoadAddOn, UnitName, GetRealmName, UnitRace, UnitFactionGroup, IsInRaid
|
|
local UnitClass, UnitExists, UnitGUID, UnitAffectingCombat, GetInstanceInfo, IsInInstance
|
|
= UnitClass, UnitExists, UnitGUID, UnitAffectingCombat, GetInstanceInfo, IsInInstance
|
|
local UnitIsUnit, GetRaidRosterInfo, UnitInVehicle, UnitHasVehicleUI, GetSpellInfo
|
|
= UnitIsUnit, GetRaidRosterInfo, UnitInVehicle, UnitHasVehicleUI, GetSpellInfo
|
|
local SendChatMessage, GetChannelName, UnitInBattleground, UnitInRaid, UnitInParty, GetTime, GetSpellLink, GetItemInfo
|
|
= SendChatMessage, GetChannelName, UnitInBattleground, UnitInRaid, UnitInParty, GetTime, GetSpellLink, GetItemInfo
|
|
local CreateFrame, IsShiftKeyDown, GetScreenWidth, GetScreenHeight, GetCursorPosition, UpdateAddOnCPUUsage, GetFrameCPUUsage, debugprofilestop
|
|
= CreateFrame, IsShiftKeyDown, GetScreenWidth, GetScreenHeight, GetCursorPosition, UpdateAddOnCPUUsage, GetFrameCPUUsage, debugprofilestop
|
|
local debugstack, IsSpellKnown = debugstack, IsSpellKnown
|
|
local GetNumTalentTabs, GetNumTalents = GetNumTalentTabs, GetNumTalents
|
|
|
|
local ADDON_NAME = "WeakAuras"
|
|
local WeakAuras = WeakAuras
|
|
local versionString = WeakAuras.versionString
|
|
local prettyPrint = WeakAuras.prettyPrint
|
|
|
|
WeakAurasTimers = setmetatable({}, {__tostring=function() return "WeakAuras" end})
|
|
LibStub("AceTimer-3.0"):Embed(WeakAurasTimers)
|
|
|
|
WeakAuras.maxTimerDuration = 604800; -- A week, in seconds
|
|
WeakAuras.maxUpTime = 4294967; -- 2^32 / 1000
|
|
|
|
function WeakAuras:Mixin(object, ...)
|
|
for i = 1, select("#", ...) do
|
|
local mixin = select(i, ...);
|
|
for k, v in pairs(mixin) do
|
|
object[k] = v;
|
|
end
|
|
end
|
|
return object;
|
|
end
|
|
|
|
function WeakAurasTimers:ScheduleTimerFixed(func, delay, ...)
|
|
if (delay < WeakAuras.maxTimerDuration) then
|
|
if delay + GetTime() > WeakAuras.maxUpTime then
|
|
WeakAuras.prettyPrint(WeakAuras.L["Can't schedule timer with %i, due to a World of Warcraft Bug with high computer uptime. (Uptime: %i). Please restart your Computer."]:format(delay, GetTime()))
|
|
return
|
|
end
|
|
return self:ScheduleTimer(func, delay, ...);
|
|
end
|
|
end
|
|
|
|
local LDB = LibStub:GetLibrary("LibDataBroker-1.1")
|
|
local LDBIcon = LibStub("LibDBIcon-1.0")
|
|
local LCG = LibStub("LibCustomGlow-1.0")
|
|
local LGF = LibStub("LibGetFrame-1.0")
|
|
local timer = WeakAurasTimers
|
|
WeakAuras.timer = timer
|
|
|
|
local L = WeakAuras.L
|
|
|
|
local loginQueue = {}
|
|
local queueshowooc;
|
|
|
|
function WeakAuras.InternalVersion()
|
|
return internalVersion;
|
|
end
|
|
|
|
function WeakAuras.LoadOptions(msg)
|
|
if not(IsAddOnLoaded("WeakAurasOptions")) then
|
|
if not WeakAuras.IsLoginFinished() then
|
|
prettyPrint(WeakAuras.LoginMessage())
|
|
loginQueue[#loginQueue + 1] = WeakAuras.OpenOptions
|
|
elseif InCombatLockdown() then
|
|
-- inform the user and queue ooc
|
|
prettyPrint(L["Options will finish loading after combat ends."])
|
|
queueshowooc = msg or "";
|
|
WeakAuras.frames["Addon Initialization Handler"]:RegisterEvent("PLAYER_REGEN_ENABLED")
|
|
return false;
|
|
else
|
|
local loaded, reason = LoadAddOn("WeakAurasOptions");
|
|
if not(loaded) then
|
|
reason = string.lower("|cffff2020" .. _G["ADDON_" .. reason] .. "|r.")
|
|
print(WeakAuras.printPrefix .. "Options could not be loaded, the addon is " .. reason);
|
|
return false;
|
|
end
|
|
end
|
|
end
|
|
return true;
|
|
end
|
|
|
|
function WeakAuras.OpenOptions(msg)
|
|
if WeakAuras.NeedToRepairDatabase() then
|
|
StaticPopup_Show("WEAKAURAS_CONFIRM_REPAIR", nil, nil, {reason = "downgrade"})
|
|
elseif (WeakAuras.IsLoginFinished() and WeakAuras.LoadOptions(msg)) then
|
|
WeakAuras.ToggleOptions(msg);
|
|
end
|
|
end
|
|
|
|
function WeakAuras.PrintHelp()
|
|
print(L["Usage:"])
|
|
print(L["/wa help - Show this message"])
|
|
print(L["/wa minimap - Toggle the minimap icon"])
|
|
print(L["/wa pstart - Start profiling. Optionally include a duration in seconds after which profiling automatically stops. To profile the next combat/encounter, pass a \"combat\" or \"encounter\" argument."])
|
|
print(L["/wa pstop - Finish profiling"])
|
|
print(L["/wa pprint - Show the results from the most recent profiling"])
|
|
print(L["/wa repair - Repair tool"])
|
|
print(L["If you require additional assistance, please open a ticket on GitHub or visit our Discord at https://discord.gg/wa2!"])
|
|
end
|
|
|
|
SLASH_WEAKAURAS1, SLASH_WEAKAURAS2 = "/weakauras", "/wa";
|
|
function SlashCmdList.WEAKAURAS(input)
|
|
local args, msg = {}
|
|
for v in string.gmatch(input, "%S+") do
|
|
if not msg then
|
|
msg = v
|
|
else
|
|
insert(args, v)
|
|
end
|
|
end
|
|
if msg == "pstart" then
|
|
WeakAuras.StartProfile(args[1]);
|
|
elseif msg == "pstop" then
|
|
WeakAuras.StopProfile();
|
|
elseif msg == "pprint" then
|
|
WeakAuras.PrintProfile();
|
|
elseif msg == "pcancel" then
|
|
WeakAuras.CancelScheduledProfile()
|
|
elseif msg == "minimap" then
|
|
WeakAuras.ToggleMinimap();
|
|
elseif msg == "help" then
|
|
WeakAuras.PrintHelp();
|
|
elseif msg == "repair" then
|
|
StaticPopup_Show("WEAKAURAS_CONFIRM_REPAIR", nil, nil, {reason = "user"})
|
|
else
|
|
WeakAuras.OpenOptions(msg);
|
|
end
|
|
end
|
|
if not WeakAuras.IsCorrectVersion() then return end
|
|
|
|
function WeakAuras.ApplyToDataOrChildData(data, func, ...)
|
|
if data.controlledChildren then
|
|
for index, childId in ipairs(data.controlledChildren) do
|
|
local childData = WeakAuras.GetData(childId)
|
|
func(childData, ...)
|
|
end
|
|
return true
|
|
else
|
|
func(data, ...)
|
|
end
|
|
end
|
|
|
|
function WeakAuras.ToggleMinimap()
|
|
WeakAurasSaved.minimap.hide = not WeakAurasSaved.minimap.hide
|
|
if WeakAurasSaved.minimap.hide then
|
|
LDBIcon:Hide("WeakAuras");
|
|
prettyPrint(L["Use /wa minimap to show the minimap icon again"])
|
|
else
|
|
LDBIcon:Show("WeakAuras");
|
|
end
|
|
end
|
|
|
|
BINDING_HEADER_WEAKAURAS = ADDON_NAME
|
|
BINDING_NAME_WEAKAURASTOGGLE = L["Toggle Options Window"]
|
|
BINDING_NAME_WEAKAURASPROFILINGTOGGLE = L["Toggle Performance Profiling Window"]
|
|
BINDING_NAME_WEAKAURASPRINTPROFILING = L["Print Profiling Results"]
|
|
|
|
-- An alias for WeakAurasSaved, the SavedVariables
|
|
-- Noteable properties:
|
|
-- debug: If set to true, WeakAura.debug() outputs messages to the chat frame
|
|
-- displays: All aura settings, keyed on their id
|
|
local db;
|
|
|
|
local registeredFromAddons;
|
|
-- List of addons that registered displays
|
|
WeakAuras.addons = {};
|
|
local addons = WeakAuras.addons;
|
|
|
|
-- A list of tutorials, filled in by the WeakAuras_Tutorials addon by calling RegisterTutorial
|
|
WeakAuras.tutorials = {};
|
|
local tutorials = WeakAuras.tutorials;
|
|
|
|
-- used if an addon tries to register a display under an id that the user already has a display with that id
|
|
WeakAuras.collisions = {};
|
|
local collisions = WeakAuras.collisions;
|
|
|
|
-- While true no events are handled. E.g. WeakAuras is paused while the Options dialog is open
|
|
local paused = true;
|
|
local importing = false;
|
|
|
|
-- squelches actions and sounds from auras. is used e.g. to prevent lots of actions/sounds from triggering
|
|
-- on login or after closing the options dialog
|
|
local squelch_actions = true;
|
|
|
|
-- Load functions, keyed on id
|
|
local loadFuncs = {};
|
|
-- Load functions for the Options window that ignore various load options
|
|
local loadFuncsForOptions = {};
|
|
-- Mapping of events to ids, contains true if a aura should be checked for a certain event
|
|
local loadEvents = {}
|
|
|
|
-- Check Conditions Functions, keyed on id
|
|
local checkConditions = {};
|
|
WeakAuras.checkConditions = checkConditions
|
|
|
|
-- Dynamic Condition functions to run. keyed on event and id
|
|
local dynamicConditions = {};
|
|
|
|
-- Global Dynamic Condition Funcs, keyed on the event
|
|
local globalDynamicConditionFuncs = {};
|
|
|
|
-- All regions keyed on id, has properties: region, regionType, also see clones
|
|
WeakAuras.regions = {};
|
|
local regions = WeakAuras.regions;
|
|
WeakAuras.auras = {};
|
|
local auras = WeakAuras.auras;
|
|
WeakAuras.events = {};
|
|
local events = WeakAuras.events;
|
|
|
|
-- keyed on id, contains bool indicating whether the aura is loaded
|
|
WeakAuras.loaded = {};
|
|
local loaded = WeakAuras.loaded;
|
|
|
|
WeakAuras.specificBosses = {};
|
|
local specificBosses = WeakAuras.specificBosses;
|
|
WeakAuras.specificUnits = {};
|
|
local specificUnits = WeakAuras.specificUnits;
|
|
|
|
-- contains regions for clones
|
|
WeakAuras.clones = {};
|
|
local clones = WeakAuras.clones;
|
|
|
|
-- Unused regions that are kept around for clones
|
|
WeakAuras.clonePool = {};
|
|
local clonePool = WeakAuras.clonePool;
|
|
|
|
-- One table per regionType, see RegisterRegionType, notable properties: create, modify and default
|
|
WeakAuras.regionTypes = {};
|
|
local regionTypes = WeakAuras.regionTypes;
|
|
|
|
WeakAuras.subRegionTypes = {}
|
|
local subRegionTypes = WeakAuras.subRegionTypes
|
|
|
|
-- One table per regionType, see RegisterRegionOptions
|
|
WeakAuras.regionOptions = {};
|
|
local regionOptions = WeakAuras.regionOptions;
|
|
|
|
WeakAuras.subRegionOptions = {}
|
|
local subRegionOptions = WeakAuras.subRegionOptions
|
|
|
|
-- Maps from trigger type to trigger system
|
|
WeakAuras.triggerTypes = {};
|
|
local triggerTypes = WeakAuras.triggerTypes;
|
|
|
|
-- Maps from trigger type to a functin that can create options for the trigger
|
|
WeakAuras.triggerTypesOptions = {};
|
|
local triggerTypesOptions = WeakAuras.triggerTypesOptions;
|
|
|
|
|
|
-- Trigger State, updated by trigger systems, then applied to regions by UpdatedTriggerState
|
|
-- keyed on id, triggernum, cloneid
|
|
-- cloneid can be a empty string
|
|
|
|
-- Noteable properties:
|
|
-- changed: Whether this trigger state was recently changed and its properties
|
|
-- need to be applied to a region. The glue code resets this
|
|
-- after syncing the region to the trigger state
|
|
-- show: Whether the region for this trigger state should be shown
|
|
-- progressType: Either "timed", "static"
|
|
-- duration: The duration if the progressType is timed
|
|
-- expirationTime: The expirationTime if the progressType is timed
|
|
-- autoHide: If the aura should be hidden on expiring
|
|
-- value: The value if the progressType is static
|
|
-- total: The total if the progressType is static
|
|
-- inverse: The static values should be interpreted inversely
|
|
-- name: The name information
|
|
-- icon: The icon information
|
|
-- texture: The texture information
|
|
-- stacks: The stacks information
|
|
-- index: The index of the buff/debuff for the buff trigger system, used to set the tooltip
|
|
-- spellId: spellId of the buff/debuff, used to set the tooltip
|
|
|
|
WeakAuras.triggerState = {}
|
|
local triggerState = WeakAuras.triggerState;
|
|
|
|
-- Fallback states
|
|
local fallbacksStates = {};
|
|
|
|
-- List of all trigger systems, contains each system once
|
|
WeakAuras.triggerSystems = {}
|
|
local triggerSystems = WeakAuras.triggerSystems;
|
|
|
|
WeakAuras.forceable_events = {};
|
|
|
|
local from_files = {};
|
|
|
|
local timers = {}; -- Timers for autohiding, keyed on id, triggernum, cloneid
|
|
WeakAuras.timers = timers;
|
|
|
|
local loaded_events = {};
|
|
WeakAuras.loaded_events = loaded_events;
|
|
local loaded_auras = {};
|
|
WeakAuras.loaded_auras = loaded_auras;
|
|
|
|
-- Animations
|
|
WeakAuras.animations = {};
|
|
local animations = WeakAuras.animations;
|
|
WeakAuras.pending_controls = {};
|
|
local pending_controls = WeakAuras.pending_controls;
|
|
|
|
WeakAuras.raidUnits = {};
|
|
WeakAuras.partyUnits = {};
|
|
do
|
|
for i=1,40 do
|
|
WeakAuras.raidUnits[i] = "raid"..i
|
|
end
|
|
for i=1,4 do
|
|
WeakAuras.partyUnits[i] = "party"..i
|
|
end
|
|
end
|
|
local playerLevel = UnitLevel("player");
|
|
|
|
WeakAuras.currentInstanceType = "none"
|
|
|
|
-- Custom Action Functions, keyed on id, "init" / "start" / "finish"
|
|
WeakAuras.customActionsFunctions = {};
|
|
|
|
-- Custom Functions used in conditions, keyed on id, condition number, "changes", property number
|
|
WeakAuras.customConditionsFunctions = {};
|
|
|
|
local anim_function_strings = WeakAuras.anim_function_strings;
|
|
local anim_presets = WeakAuras.anim_presets;
|
|
local load_prototype = WeakAuras.load_prototype;
|
|
|
|
local levelColors = {
|
|
[0] = "|cFFFFFFFF",
|
|
[1] = "|cFF40FF40",
|
|
[2] = "|cFF6060FF",
|
|
[3] = "|cFFFF4040"
|
|
};
|
|
|
|
function WeakAuras.debug(msg, level)
|
|
if(db.debug) then
|
|
level = (level and levelColors[level] and level) or 2;
|
|
msg = (type(msg) == "string" and msg) or (msg and "Invalid debug message of type "..type(msg)) or "Debug message not specified";
|
|
DEFAULT_CHAT_FRAME:AddMessage(levelColors[level]..msg);
|
|
end
|
|
end
|
|
local debug = WeakAuras.debug;
|
|
|
|
function WeakAuras.validate(input, default)
|
|
for field, defaultValue in pairs(default) do
|
|
if(type(defaultValue) == "table" and type(input[field]) ~= "table") then
|
|
input[field] = {};
|
|
elseif(input[field] == nil) then
|
|
input[field] = defaultValue;
|
|
elseif(type(input[field]) ~= type(defaultValue)) then
|
|
input[field] = defaultValue;
|
|
end
|
|
if(type(input[field]) == "table") then
|
|
WeakAuras.validate(input[field], defaultValue);
|
|
end
|
|
end
|
|
end
|
|
|
|
function WeakAuras.RegisterRegionType(name, createFunction, modifyFunction, default, properties, validate)
|
|
if not(name) then
|
|
error("Improper arguments to WeakAuras.RegisterRegionType - name is not defined", 2);
|
|
elseif(type(name) ~= "string") then
|
|
error("Improper arguments to WeakAuras.RegisterRegionType - name is not a string", 2);
|
|
elseif not(createFunction) then
|
|
error("Improper arguments to WeakAuras.RegisterRegionType - creation function is not defined", 2);
|
|
elseif(type(createFunction) ~= "function") then
|
|
error("Improper arguments to WeakAuras.RegisterRegionType - creation function is not a function", 2);
|
|
elseif not(modifyFunction) then
|
|
error("Improper arguments to WeakAuras.RegisterRegionType - modification function is not defined", 2);
|
|
elseif(type(modifyFunction) ~= "function") then
|
|
error("Improper arguments to WeakAuras.RegisterRegionType - modification function is not a function", 2)
|
|
elseif not(default) then
|
|
error("Improper arguments to WeakAuras.RegisterRegionType - default options are not defined", 2);
|
|
elseif(type(default) ~= "table") then
|
|
error("Improper arguments to WeakAuras.RegisterRegionType - default options are not a table", 2);
|
|
elseif(type(default) ~= "table" and type(default) ~= "nil") then
|
|
error("Improper arguments to WeakAuras.RegisterRegionType - properties options are not a table", 2);
|
|
elseif(regionTypes[name]) then
|
|
error("Improper arguments to WeakAuras.RegisterRegionType - region type \""..name.."\" already defined", 2);
|
|
else
|
|
regionTypes[name] = {
|
|
create = createFunction,
|
|
modify = modifyFunction,
|
|
default = default,
|
|
validate = validate,
|
|
properties = properties,
|
|
};
|
|
end
|
|
end
|
|
|
|
function WeakAuras.RegisterSubRegionType(name, displayName, supportFunction, createFunction, modifyFunction, onAcquire, onRelease, default, addDefaultsForNewAura, properties, supportsAdd)
|
|
if not(name) then
|
|
error("Improper arguments to WeakAuras.RegisterSubRegionType - name is not defined", 2);
|
|
elseif(type(name) ~= "string") then
|
|
error("Improper arguments to WeakAuras.RegisterSubRegionType - name is not a string", 2);
|
|
elseif not(displayName) then
|
|
error("Improper arguments to WeakAuras.RegisterSubRegionType - display name is not defined".." "..name, 2);
|
|
elseif(type(displayName) ~= "string") then
|
|
error("Improper arguments to WeakAuras.RegisterSubRegionType - display name is not a string", 2);
|
|
elseif not(supportFunction) then
|
|
error("Improper arguments to WeakAuras.RegisterSubRegionType - support function is not defined", 2);
|
|
elseif(type(supportFunction) ~= "function") then
|
|
error("Improper arguments to WeakAuras.RegisterSubRegionType - support function is not a function", 2);
|
|
elseif not(createFunction) then
|
|
error("Improper arguments to WeakAuras.RegisterSubRegionType - creation function is not defined", 2);
|
|
elseif(type(createFunction) ~= "function") then
|
|
error("Improper arguments to WeakAuras.RegisterSubRegionType - creation function is not a function", 2);
|
|
elseif not(modifyFunction) then
|
|
error("Improper arguments to WeakAuras.RegisterSubRegionType - modification function is not defined", 2);
|
|
elseif(type(modifyFunction) ~= "function") then
|
|
error("Improper arguments to WeakAuras.RegisterSubRegionType - modification function is not a function", 2)
|
|
elseif not(onAcquire) then
|
|
error("Improper arguments to WeakAuras.RegisterSubRegionType - onAcquire function is not defined", 2);
|
|
elseif(type(onAcquire) ~= "function") then
|
|
error("Improper arguments to WeakAuras.RegisterSubRegionType - onAcquire function is not a function", 2)
|
|
elseif not(onRelease) then
|
|
error("Improper arguments to WeakAuras.RegisterSubRegionType - onRelease function is not defined", 2);
|
|
elseif(type(onRelease) ~= "function") then
|
|
error("Improper arguments to WeakAuras.RegisterSubRegionType - onRelease function is not a function", 2)
|
|
elseif not(default) then
|
|
error("Improper arguments to WeakAuras.RegisterSubRegionType - default options are not defined", 2);
|
|
elseif(type(default) ~= "table" and type(default) ~= "function") then
|
|
error("Improper arguments to WeakAuras.RegisterSubRegionType - default options are not a table or a function", 2);
|
|
elseif(addDefaultsForNewAura and type(addDefaultsForNewAura) ~= "function") then
|
|
error("Improper arguments to WeakAuras.RegisterSubRegionType - addDefaultsForNewAura function is not nil or a function", 2)
|
|
elseif(subRegionTypes[name]) then
|
|
error("Improper arguments to WeakAuras.RegisterSubRegionType - region type \""..name.."\" already defined", 2);
|
|
else
|
|
local pool = CreateObjectPool(createFunction)
|
|
|
|
subRegionTypes[name] = {
|
|
displayName = displayName,
|
|
supports = supportFunction,
|
|
modify = modifyFunction,
|
|
default = default,
|
|
addDefaultsForNewAura = addDefaultsForNewAura,
|
|
properties = properties,
|
|
supportsAdd = supportsAdd == nil or supportsAdd,
|
|
acquire = function()
|
|
local subRegion = pool:Acquire()
|
|
onAcquire(subRegion)
|
|
subRegion.type = name
|
|
return subRegion
|
|
end,
|
|
release = function(subRegion)
|
|
onRelease(subRegion)
|
|
pool:Release(subRegion)
|
|
end
|
|
};
|
|
end
|
|
end
|
|
|
|
function WeakAuras.RegisterRegionOptions(name, createFunction, icon, displayName, createThumbnail, modifyThumbnail, description, templates, getAnchors)
|
|
if not(name) then
|
|
error("Improper arguments to WeakAuras.RegisterRegionOptions - name is not defined", 2);
|
|
elseif(type(name) ~= "string") then
|
|
error("Improper arguments to WeakAuras.RegisterRegionOptions - name is not a string", 2);
|
|
elseif not(createFunction) then
|
|
error("Improper arguments to WeakAuras.RegisterRegionOptions - creation function is not defined", 2);
|
|
elseif(type(createFunction) ~= "function") then
|
|
error("Improper arguments to WeakAuras.RegisterRegionOptions - creation function is not a function", 2);
|
|
elseif not(icon) then
|
|
error("Improper arguments to WeakAuras.RegisterRegionOptions - icon is not defined", 2);
|
|
elseif not(type(icon) == "string" or type(icon) == "function") then
|
|
error("Improper arguments to WeakAuras.RegisterRegionOptions - icon is not a string or a function", 2)
|
|
elseif not(displayName) then
|
|
error("Improper arguments to WeakAuras.RegisterRegionOptions - display name is not defined".." "..name, 2);
|
|
elseif(type(displayName) ~= "string") then
|
|
error("Improper arguments to WeakAuras.RegisterRegionOptions - display name is not a string", 2);
|
|
elseif (getAnchors and type(getAnchors) ~= "function") then
|
|
error("Improper arguments to WeakAuras.RegisterRegionOptions - anchors is not a function", 2);
|
|
elseif(regionOptions[name]) then
|
|
error("Improper arguments to WeakAuras.RegisterRegionOptions - region type \""..name.."\" already defined", 2);
|
|
else
|
|
if (type(icon) == "function") then
|
|
-- We only want to create one icon and reparent it as needed
|
|
icon = icon()
|
|
icon:Hide()
|
|
end
|
|
|
|
local acquireThumbnail, releaseThumbnail
|
|
if createThumbnail and modifyThumbnail then
|
|
local thumbnailPool = CreateObjectPool(createThumbnail)
|
|
acquireThumbnail = function(parent, data)
|
|
local thumbnail, newObject = thumbnailPool:Acquire()
|
|
thumbnail:Show()
|
|
modifyThumbnail(parent, thumbnail, data)
|
|
return thumbnail
|
|
end
|
|
releaseThumbnail = function(thumbnail)
|
|
thumbnail:Hide()
|
|
thumbnailPool:Release(thumbnail)
|
|
end
|
|
end
|
|
regionOptions[name] = {
|
|
create = createFunction,
|
|
icon = icon,
|
|
displayName = displayName,
|
|
createThumbnail = createThumbnail,
|
|
modifyThumbnail = modifyThumbnail,
|
|
acquireThumbnail = acquireThumbnail,
|
|
releaseThumbnail = releaseThumbnail,
|
|
description = description,
|
|
templates = templates,
|
|
getAnchors = getAnchors
|
|
};
|
|
end
|
|
end
|
|
|
|
function WeakAuras.RegisterSubRegionOptions(name, createFunction, description)
|
|
if not(name) then
|
|
error("Improper arguments to WeakAuras.RegisterSubRegionOptions - name is not defined", 2);
|
|
elseif(type(name) ~= "string") then
|
|
error("Improper arguments to WeakAuras.RegisterSubRegionOptions - name is not a string", 2);
|
|
elseif not(createFunction) then
|
|
error("Improper arguments to WeakAuras.RegisterSubRegionOptions - creation function is not defined", 2);
|
|
elseif(type(createFunction) ~= "function") then
|
|
error("Improper arguments to WeakAuras.RegisterSubRegionOptions - creation function is not a function", 2);
|
|
elseif(subRegionOptions[name]) then
|
|
error("Improper arguments to WeakAuras.RegisterSubRegionOptions - region type \""..name.."\" already defined", 2);
|
|
else
|
|
subRegionOptions[name] = {
|
|
create = createFunction,
|
|
description = description,
|
|
};
|
|
end
|
|
end
|
|
|
|
-- This function is replaced in WeakAurasOptions.lua
|
|
function WeakAuras.IsOptionsOpen()
|
|
return false;
|
|
end
|
|
|
|
function WeakAuras.ParseNumber(numString)
|
|
if not(numString and type(numString) == "string") then
|
|
if(type(numString) == "number") then
|
|
return numString, "notastring";
|
|
else
|
|
return nil;
|
|
end
|
|
elseif(numString:sub(-1) == "%") then
|
|
local percent = tonumber(numString:sub(1, -2));
|
|
if(percent) then
|
|
return percent / 100, "percent";
|
|
else
|
|
return nil;
|
|
end
|
|
else
|
|
-- Matches any string with two integers separated by a forward slash
|
|
-- Captures the two integers
|
|
local _, _, numerator, denominator = numString:find("(%d+)%s*/%s*(%d+)");
|
|
numerator, denominator = tonumber(numerator), tonumber(denominator);
|
|
if(numerator and denominator) then
|
|
if(denominator == 0) then
|
|
return nil;
|
|
else
|
|
return numerator / denominator, "fraction";
|
|
end
|
|
else
|
|
local num = tonumber(numString)
|
|
if(num) then
|
|
if(math.floor(num) ~= num) then
|
|
return num, "decimal";
|
|
else
|
|
return num, "whole";
|
|
end
|
|
else
|
|
return nil;
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Used for the load function, could be simplified a bit
|
|
-- It used to be also used for the generic trigger system
|
|
function WeakAuras.ConstructFunction(prototype, trigger, skipOptional)
|
|
local input = {"event"};
|
|
local required = {};
|
|
local tests = {};
|
|
local debug = {};
|
|
local events = {}
|
|
local init;
|
|
if(prototype.init) then
|
|
init = prototype.init(trigger);
|
|
else
|
|
init = "";
|
|
end
|
|
for index, arg in pairs(prototype.args) do
|
|
local enable = arg.type ~= "collpase";
|
|
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);
|
|
end
|
|
if (arg.optional and skipOptional) then
|
|
-- Do nothing
|
|
elseif(arg.hidden or arg.type == "tristate" or arg.type == "toggle" or arg.type == "tristatestring"
|
|
or (arg.type == "multiselect" and trigger["use_"..name] ~= nil)
|
|
or ((trigger["use_"..name] or arg.required) and trigger[name])) then
|
|
if(arg.init and arg.init ~= "arg") then
|
|
init = init.."local "..name.." = "..arg.init.."\n";
|
|
end
|
|
local number = trigger[name] and tonumber(trigger[name]);
|
|
local test;
|
|
if(arg.type == "tristate") then
|
|
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
|
|
elseif(arg.type == "tristatestring") then
|
|
if(trigger["use_"..name] == false) then
|
|
test = "("..name.. "~=".. (number or string.format("%q", trigger[name] or "")) .. ")"
|
|
elseif(trigger["use_"..name]) then
|
|
test = "("..name.. "==".. (number or string.format("%q", trigger[name] or "")) .. ")"
|
|
end
|
|
elseif(arg.type == "multiselect") then
|
|
if(trigger["use_"..name] == false) then -- multi selection
|
|
local any = false;
|
|
if (trigger[name] and trigger[name].multi) then
|
|
test = "(";
|
|
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
|
|
if(any) then
|
|
test = test:sub(1, -5);
|
|
else
|
|
test = "(false";
|
|
end
|
|
test = test..")";
|
|
end
|
|
elseif(trigger["use_"..name]) then -- single selection
|
|
local value = trigger[name] and trigger[name].single;
|
|
if not arg.test then
|
|
test = trigger[name] and trigger[name].single and "("..name.."=="..(tonumber(value) or "[["..value.."]]")..")";
|
|
else
|
|
test = trigger[name] and trigger[name].single and "("..arg.test:format(tonumber(value) or "[["..value.."]]")..")";
|
|
end
|
|
end
|
|
elseif(arg.type == "toggle") then
|
|
if(trigger["use_"..name]) then
|
|
if(arg.test) then
|
|
test = "("..arg.test:format(trigger[name])..")";
|
|
else
|
|
test = name;
|
|
end
|
|
end
|
|
elseif (arg.type == "spell") then
|
|
if arg.showExactOption then
|
|
test = "("..arg.test:format(trigger[name], tostring(trigger["use_exact_" .. name]) or "false") ..")";
|
|
else
|
|
test = "("..arg.test:format(trigger[name])..")";
|
|
end
|
|
elseif(arg.test) then
|
|
test = "("..arg.test:format(trigger[name])..")";
|
|
elseif(arg.type == "longstring" and trigger[name.."_operator"]) then
|
|
if(trigger[name.."_operator"] == "==") then
|
|
test = "("..name.."==[["..trigger[name].."]])";
|
|
else
|
|
test = "("..name..":"..trigger[name.."_operator"]:format(trigger[name])..")";
|
|
end
|
|
else
|
|
if(type(trigger[name]) == "table") then
|
|
trigger[name] = "error";
|
|
end
|
|
test = "("..name..(trigger[name.."_operator"] or "==")..(number or "[["..(trigger[name] or "").."]]")..")";
|
|
end
|
|
if(arg.required) then
|
|
tinsert(required, test);
|
|
else
|
|
tinsert(tests, test);
|
|
end
|
|
|
|
if test and arg.events then
|
|
for index, event in ipairs(arg.events) do
|
|
events[event] = true
|
|
end
|
|
end
|
|
|
|
if(arg.debug) then
|
|
tinsert(debug, arg.debug:format(trigger[name]));
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local ret = "return function("..table.concat(input, ", ")..")\n";
|
|
ret = ret..(init or "");
|
|
ret = ret..(#debug > 0 and table.concat(debug, "\n") or "");
|
|
ret = ret.."if(";
|
|
ret = ret..((#required > 0) and table.concat(required, " and ").." and " or "");
|
|
ret = ret..(#tests > 0 and table.concat(tests, " and ") or "true");
|
|
ret = ret..") then\n";
|
|
if(#debug > 0) then
|
|
ret = ret.."print('ret: true');\n";
|
|
end
|
|
ret = ret.."return true else return false end end";
|
|
|
|
return ret, events;
|
|
end
|
|
|
|
function WeakAuras.GetActiveConditions(id, cloneId)
|
|
triggerState[id].activatedConditions[cloneId] = triggerState[id].activatedConditions[cloneId] or {};
|
|
return triggerState[id].activatedConditions[cloneId];
|
|
end
|
|
|
|
local function formatValueForAssignment(vtype, value, pathToCustomFunction)
|
|
if (value == nil) then
|
|
value = false;
|
|
end
|
|
if (vtype == "bool") then
|
|
return value and tostring(value) or "false";
|
|
elseif(vtype == "number") then
|
|
return value and tostring(value) or "0";
|
|
elseif (vtype == "list") then
|
|
return type(value) == "string" and string.format("%q", value) or "nil";
|
|
elseif(vtype == "color") then
|
|
if (value and type(value) == "table") then
|
|
return string.format("{%s, %s, %s, %s}", tostring(value[1]), tostring(value[2]), tostring(value[3]), tostring(value[4]));
|
|
end
|
|
return "{1, 1, 1, 1}";
|
|
elseif(vtype == "chat") then
|
|
if (value and type(value) == "table") then
|
|
return string.format("{message_type = %q, message = %q, message_dest = %q, message_channel = %q, message_custom = %s}",
|
|
tostring(value.message_type), tostring(value.message or ""),
|
|
tostring(value.message_dest), tostring(value.message_channel),
|
|
pathToCustomFunction);
|
|
end
|
|
elseif(vtype == "sound") then
|
|
if (value and type(value) == "table") then
|
|
return string.format("{ sound = %q, sound_channel = %q, sound_path = %q, sound_kit_id = %q, sound_type = %q, %s}",
|
|
tostring(value.sound or ""), tostring(value.sound_channel or ""), tostring(value.sound_path or ""),
|
|
tostring(value.sound_kit_id or ""), tostring(value.sound_type or ""),
|
|
value.sound_repeat and "sound_repeat = " .. tostring(value.sound_repeat) or "nil");
|
|
end
|
|
elseif(vtype == "customcode") then
|
|
return string.format("%s", pathToCustomFunction);
|
|
elseif vtype == "glowexternal" then
|
|
if (value and type(value) == "table") then
|
|
return ([[{ glow_action = %q, glow_frame_type = %q, glow_type = %q,
|
|
glow_frame = %q, use_glow_color = %s, glow_color = {%s, %s, %s, %s},
|
|
glow_lines = %d, glow_frequency = %f, glow_length = %f, glow_thickness = %f, glow_XOffset = %f, glow_YOffset = %f,
|
|
glow_scale = %f, glow_border = %s }]]):format(
|
|
value.glow_action or "",
|
|
value.glow_frame_type or "",
|
|
value.glow_type or "",
|
|
value.glow_frame or "",
|
|
value.use_glow_color and "true" or "false",
|
|
type(value.glow_color) == "table" and tostring(value.glow_color[1]) or "1",
|
|
type(value.glow_color) == "table" and tostring(value.glow_color[2]) or "1",
|
|
type(value.glow_color) == "table" and tostring(value.glow_color[3]) or "1",
|
|
type(value.glow_color) == "table" and tostring(value.glow_color[4]) or "1",
|
|
value.glow_lines or 8,
|
|
value.glow_frequency or 0.25,
|
|
value.glow_length or 10,
|
|
value.glow_thickness or 1,
|
|
value.glow_XOffset or 0,
|
|
value.glow_YOffset or 0,
|
|
value.glow_scale or 1,
|
|
value.glow_border and "true" or "false"
|
|
)
|
|
end
|
|
end
|
|
return "nil";
|
|
end
|
|
|
|
local function formatValueForCall(type, property)
|
|
if (type == "bool" or type == "number" or type == "list") then
|
|
return "propertyChanges['" .. property .. "']";
|
|
elseif (type == "color") then
|
|
local pcp = "propertyChanges['" .. property .. "']";
|
|
return pcp .. "[1], " .. pcp .. "[2], " .. pcp .. "[3], " .. pcp .. "[4]";
|
|
end
|
|
return "nil";
|
|
end
|
|
|
|
local conditionChecksTimers = {};
|
|
conditionChecksTimers.recheckTime = {};
|
|
conditionChecksTimers.recheckHandle = {};
|
|
|
|
function WeakAuras.scheduleConditionCheck(time, id, cloneId)
|
|
conditionChecksTimers.recheckTime[id] = conditionChecksTimers.recheckTime[id] or {}
|
|
conditionChecksTimers.recheckHandle[id] = conditionChecksTimers.recheckHandle[id] or {};
|
|
|
|
if (conditionChecksTimers.recheckTime[id][cloneId] and conditionChecksTimers.recheckTime[id][cloneId] > time) then
|
|
timer:CancelTimer(conditionChecksTimers.recheckHandle[id][cloneId]);
|
|
conditionChecksTimers.recheckHandle[id][cloneId] = nil;
|
|
end
|
|
|
|
if (conditionChecksTimers.recheckHandle[id][cloneId] == nil) then
|
|
conditionChecksTimers.recheckHandle[id][cloneId] = timer:ScheduleTimerFixed(function()
|
|
conditionChecksTimers.recheckHandle[id][cloneId] = nil;
|
|
local region;
|
|
if(cloneId and cloneId ~= "") then
|
|
region = clones[id] and clones[id][cloneId];
|
|
else
|
|
region = WeakAuras.regions[id].region;
|
|
end
|
|
if (region and region.toShow) then
|
|
checkConditions[id](region);
|
|
end
|
|
end, time - GetTime())
|
|
conditionChecksTimers.recheckTime[id][cloneId] = time;
|
|
end
|
|
end
|
|
|
|
local customConditionTestFunctions = {};
|
|
|
|
function WeakAuras.CallCustomConditionTest(testFunctionNumber, ...)
|
|
local result = customConditionTestFunctions[testFunctionNumber](...)
|
|
return result
|
|
end
|
|
|
|
local function CreateTestForCondition(input, allConditionsTemplate, usedStates)
|
|
local trigger = input and input.trigger;
|
|
local variable = input and input.variable;
|
|
local op = input and input.op;
|
|
local value = input and input.value;
|
|
|
|
local check = nil;
|
|
local recheckCode = nil;
|
|
|
|
if (variable == "AND" or variable == "OR") then
|
|
local test = {};
|
|
if (input.checks) then
|
|
for i, subcheck in ipairs(input.checks) do
|
|
local subtest, subrecheckCode = CreateTestForCondition(subcheck, allConditionsTemplate, usedStates);
|
|
if (subtest) then
|
|
tinsert(test, "(" .. subtest .. ")");
|
|
end
|
|
if (subrecheckCode) then
|
|
recheckCode = recheckCode or "";
|
|
recheckCode = recheckCode .. subrecheckCode;
|
|
end
|
|
end
|
|
end
|
|
if (next(test)) then
|
|
if (variable == "AND") then
|
|
check = table.concat(test, " and ");
|
|
else
|
|
check = table.concat(test, " or ");
|
|
end
|
|
end
|
|
end
|
|
|
|
if (trigger and variable and value) then
|
|
usedStates[trigger] = true;
|
|
|
|
local conditionTemplate = allConditionsTemplate[trigger] and allConditionsTemplate[trigger][variable];
|
|
local ctype = conditionTemplate and conditionTemplate.type;
|
|
local test = conditionTemplate and conditionTemplate.test;
|
|
|
|
local stateCheck = "state[" .. trigger .. "] and state[" .. trigger .. "].show and ";
|
|
local stateVariableCheck = "state[" .. trigger .. "]." .. variable .. "~= nil and ";
|
|
if (test) then
|
|
if (value) then
|
|
tinsert(customConditionTestFunctions, test);
|
|
local testFunctionNumber = #(customConditionTestFunctions);
|
|
local valueString = type(value) == "string" and "[[" .. value .. "]]" or value;
|
|
local opString = type(op) == "string" and "[[" .. op .. "]]" or op;
|
|
check = "state and WeakAuras.CallCustomConditionTest(" .. testFunctionNumber .. ", state[" .. trigger .. "], " .. valueString .. ", " .. (opString or "nil") .. ")";
|
|
end
|
|
elseif (ctype == "number" and op) then
|
|
local v = tonumber(value)
|
|
if (v) then
|
|
check = stateCheck .. stateVariableCheck .. "state[" .. trigger .. "]." .. variable .. op .. v;
|
|
end
|
|
elseif (ctype == "timer" and op) then
|
|
if (op == "==") then
|
|
check = stateCheck .. stateVariableCheck .. "abs(state[" .. trigger .. "]." ..variable .. "- now -" .. value .. ") < 0.05";
|
|
else
|
|
check = stateCheck .. stateVariableCheck .. "state[" .. trigger .. "]." .. variable .. "- now" .. op .. value;
|
|
end
|
|
elseif (ctype == "select" and op) then
|
|
if (tonumber(value)) then
|
|
check = stateCheck .. stateVariableCheck .. "state[" .. trigger .. "]." .. variable .. op .. tonumber(value);
|
|
else
|
|
check = stateCheck .. stateVariableCheck .. "state[" .. trigger .. "]." .. variable .. op .. "'" .. value .. "'";
|
|
end
|
|
elseif (ctype == "bool") then
|
|
local rightSide = value == 0 and "false" or "true";
|
|
check = stateCheck .. stateVariableCheck .. "state[" .. trigger .. "]." .. variable .. "==" .. rightSide
|
|
elseif (ctype == "string") then
|
|
if(op == "==") then
|
|
check = stateCheck .. stateVariableCheck .. "state[" .. trigger .. "]." .. variable .. " == [[" .. value .. "]]";
|
|
elseif (op == "find('%s')") then
|
|
check = stateCheck .. stateVariableCheck .. "state[" .. trigger .. "]." .. variable .. ":find([[" .. value .. "]], 1, true)";
|
|
elseif (op == "match('%s')") then
|
|
check = stateCheck .. stateVariableCheck .. "state[" .. trigger .. "]." .. variable .. ":match([[" .. value .. "]], 1, true)";
|
|
end
|
|
end
|
|
|
|
if (ctype == "timer" and value) then
|
|
recheckCode = " nextTime = state[" .. trigger .. "] and state[" .. trigger .. "]." .. variable .. " and (state[" .. trigger .. "]." .. variable .. " -" .. value .. ")\n";
|
|
recheckCode = recheckCode .. " if (nextTime and (not recheckTime or nextTime < recheckTime) and nextTime >= now) then\n"
|
|
recheckCode = recheckCode .. " recheckTime = nextTime\n";
|
|
recheckCode = recheckCode .. " end\n"
|
|
end
|
|
end
|
|
|
|
return check, recheckCode;
|
|
end
|
|
|
|
local function CreateCheckCondition(ret, condition, conditionNumber, allConditionsTemplate, debug)
|
|
local usedStates = {};
|
|
local check, recheckCode = CreateTestForCondition(condition.check, allConditionsTemplate, usedStates);
|
|
if (check) then
|
|
ret = ret .. " state = region.states\n"
|
|
ret = ret .. " if (" .. check .. ") then\n";
|
|
ret = ret .. " newActiveConditions[" .. conditionNumber .. "] = true;\n";
|
|
ret = ret .. " end\n";
|
|
end
|
|
if (recheckCode) then
|
|
ret = ret .. recheckCode;
|
|
end
|
|
if (check or recheckCode) then
|
|
ret = ret .. "\n";
|
|
end
|
|
return ret;
|
|
end
|
|
|
|
local function ParseProperty(property)
|
|
local subIndex, prop = string.match(property, "^sub%.(%d*).(.*)")
|
|
if subIndex then
|
|
return tonumber(subIndex), prop
|
|
else
|
|
return nil, property
|
|
end
|
|
end
|
|
|
|
local function GetBaseProperty(data, property, start)
|
|
if (not data) then
|
|
return nil;
|
|
end
|
|
|
|
local subIndex, prop = ParseProperty(property)
|
|
if subIndex then
|
|
return GetBaseProperty(data.subRegions[subIndex], prop, start)
|
|
end
|
|
|
|
start = start or 1;
|
|
local next = string.find(property, ".", start, true);
|
|
if (next) then
|
|
return GetBaseProperty(data[string.sub(property, start, next - 1)], property, next + 1);
|
|
end
|
|
|
|
local key = string.sub(property, start);
|
|
return data[key] or data[tonumber(key)];
|
|
end
|
|
|
|
local function CreateDeactivateCondition(ret, condition, conditionNumber, data, properties, usedProperties, debug)
|
|
if (condition.changes) then
|
|
ret = ret .. " if (activatedConditions[".. conditionNumber .. "] and not newActiveConditions[" .. conditionNumber .. "]) then\n"
|
|
if (debug) then ret = ret .. " print('Deactivating condition " .. conditionNumber .. "' )\n"; end
|
|
for changeNum, change in ipairs(condition.changes) do
|
|
if (change.property) then
|
|
local propertyData = properties and properties[change.property]
|
|
if (propertyData and propertyData.type and propertyData.setter) then
|
|
usedProperties[change.property] = true;
|
|
ret = ret .. " propertyChanges['" .. change.property .. "'] = " .. formatValueForAssignment(propertyData.type, GetBaseProperty(data, change.property)) .. "\n";
|
|
if (debug) then ret = ret .. " print('- " .. change.property .. " " ..formatValueForAssignment(propertyData.type, GetBaseProperty(data, change.property)) .. "')\n"; end
|
|
end
|
|
end
|
|
end
|
|
ret = ret .. " end\n"
|
|
end
|
|
|
|
return ret;
|
|
end
|
|
|
|
local function CreateActivateCondition(ret, id, condition, conditionNumber, properties, debug)
|
|
if (condition.changes) then
|
|
ret = ret .. " if (newActiveConditions[" .. conditionNumber .. "]) then\n"
|
|
ret = ret .. " if (not activatedConditions[".. conditionNumber .. "]) then\n"
|
|
if (debug) then ret = ret .. " print('Activating condition " .. conditionNumber .. "' )\n"; end
|
|
-- non active => active
|
|
for changeNum, change in ipairs(condition.changes) do
|
|
if (change.property) then
|
|
local propertyData = properties and properties[change.property]
|
|
if (propertyData and propertyData.type) then
|
|
if (propertyData.setter) then
|
|
ret = ret .. " propertyChanges['" .. change.property .. "'] = " .. formatValueForAssignment(propertyData.type, change.value) .. "\n";
|
|
if (debug) then ret = ret .. " print('- " .. change.property .. " " .. formatValueForAssignment(propertyData.type, change.value) .. "')\n"; end
|
|
elseif (propertyData.action) then
|
|
local pathToCustomFunction = "nil";
|
|
if (WeakAuras.customConditionsFunctions[id]
|
|
and WeakAuras.customConditionsFunctions[id][conditionNumber]
|
|
and WeakAuras.customConditionsFunctions[id][conditionNumber].changes
|
|
and WeakAuras.customConditionsFunctions[id][conditionNumber].changes[changeNum]) then
|
|
pathToCustomFunction = string.format("WeakAuras.customConditionsFunctions[%q][%s].changes[%s]", id, conditionNumber, changeNum);
|
|
end
|
|
ret = ret .. " region:" .. propertyData.action .. "(" .. formatValueForAssignment(propertyData.type, change.value, pathToCustomFunction) .. ")" .. "\n";
|
|
if (debug) then ret = ret .. " print('# " .. propertyData.action .. "(" .. formatValueForAssignment(propertyData.type, change.value, pathToCustomFunction) .. "')\n"; end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
ret = ret .. " else\n"
|
|
-- active => active, only override properties
|
|
for changeNum, change in ipairs(condition.changes) do
|
|
if (change.property) then
|
|
local propertyData = properties and properties[change.property]
|
|
if (propertyData and propertyData.type and propertyData.setter) then
|
|
ret = ret .. " if(propertyChanges['" .. change.property .. "'] ~= nil) then\n"
|
|
ret = ret .. " propertyChanges['" .. change.property .. "'] = " .. formatValueForAssignment(propertyData.type, change.value) .. "\n";
|
|
if (debug) then ret = ret .. " print('- " .. change.property .. " " .. formatValueForAssignment(propertyData.type, change.value) .. "')\n"; end
|
|
ret = ret .. " end\n"
|
|
end
|
|
end
|
|
end
|
|
ret = ret .. " end\n"
|
|
ret = ret .. " end\n"
|
|
ret = ret .. "\n";
|
|
ret = ret .. " activatedConditions[".. conditionNumber .. "] = newActiveConditions[" .. conditionNumber .. "]\n";
|
|
end
|
|
|
|
return ret;
|
|
end
|
|
|
|
function WeakAuras.LoadCustomActionFunctions(data)
|
|
local id = data.id;
|
|
WeakAuras.customActionsFunctions[id] = {};
|
|
|
|
if (data.actions) then
|
|
if (data.actions.init and data.actions.init.do_custom and data.actions.init.custom) then
|
|
local func = WeakAuras.LoadFunction("return function() "..(data.actions.init.custom).."\n end", id, "initialization");
|
|
WeakAuras.customActionsFunctions[id]["init"] = func;
|
|
end
|
|
|
|
if (data.actions.start) then
|
|
if (data.actions.start.do_custom and data.actions.start.custom) then
|
|
local func = WeakAuras.LoadFunction("return function() "..(data.actions.start.custom).."\n end", id, "show action");
|
|
WeakAuras.customActionsFunctions[id]["start"] = func;
|
|
end
|
|
|
|
if (data.actions.start.do_message and data.actions.start.message_custom) then
|
|
local func = WeakAuras.LoadFunction("return "..(data.actions.start.message_custom), id, "show message");
|
|
WeakAuras.customActionsFunctions[id]["start_message"] = func;
|
|
end
|
|
end
|
|
|
|
if (data.actions.finish) then
|
|
if (data.actions.finish.do_custom and data.actions.finish.custom) then
|
|
local func = WeakAuras.LoadFunction("return function() "..(data.actions.finish.custom).."\n end", id, "hide action");
|
|
WeakAuras.customActionsFunctions[id]["finish"] = func;
|
|
end
|
|
|
|
if (data.actions.finish.do_message and data.actions.finish.message_custom) then
|
|
local func = WeakAuras.LoadFunction("return "..(data.actions.finish.message_custom), id, "hide message");
|
|
WeakAuras.customActionsFunctions[id]["finish_message"] = func;
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function WeakAuras.GetProperties(data)
|
|
local properties;
|
|
local propertiesFunction = WeakAuras.regionTypes[data.regionType] and WeakAuras.regionTypes[data.regionType].properties;
|
|
if (type(propertiesFunction) == "function") then
|
|
properties = propertiesFunction(data);
|
|
elseif propertiesFunction then
|
|
properties = CopyTable(propertiesFunction);
|
|
else
|
|
properties = {}
|
|
end
|
|
|
|
if data.subRegions then
|
|
local subIndex = {}
|
|
for index, subRegion in ipairs(data.subRegions) do
|
|
local subRegionTypeData = WeakAuras.subRegionTypes[subRegion.type];
|
|
local propertiesFunction = subRegionTypeData and subRegionTypeData.properties
|
|
local subProperties;
|
|
if (type(propertiesFunction) == "function") then
|
|
subProperties = propertiesFunction(data, subRegion);
|
|
elseif propertiesFunction then
|
|
subProperties = CopyTable(propertiesFunction)
|
|
end
|
|
|
|
if subProperties then
|
|
for key, property in pairs(subProperties) do
|
|
subIndex[key] = subIndex[key] and subIndex[key] + 1 or 1
|
|
property.display = { subIndex[key] .. ". " .. subRegionTypeData.displayName, property.display, property.defaultProperty }
|
|
properties["sub." .. index .. "." .. key ] = property;
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
return properties;
|
|
end
|
|
|
|
function WeakAuras.LoadConditionPropertyFunctions(data)
|
|
local id = data.id;
|
|
if (data.conditions) then
|
|
WeakAuras.customConditionsFunctions[id] = {};
|
|
for conditionNumber, condition in ipairs(data.conditions) do
|
|
if (condition.changes) then
|
|
for changeIndex, change in ipairs(condition.changes) do
|
|
if ( (change.property == "chat" or change.property == "customcode") and type(change.value) == "table" and change.value.custom) then
|
|
local custom = change.value.custom;
|
|
local prefix, suffix;
|
|
if (change.property == "chat") then
|
|
prefix, suffix = "return ", "";
|
|
else
|
|
prefix, suffix = "return function()", "\nend";
|
|
end
|
|
local customFunc = WeakAuras.LoadFunction(prefix .. custom .. suffix, id, "condition");
|
|
if (customFunc) then
|
|
WeakAuras.customConditionsFunctions[id][conditionNumber] = WeakAuras.customConditionsFunctions[id][conditionNumber] or {};
|
|
WeakAuras.customConditionsFunctions[id][conditionNumber].changes = WeakAuras.customConditionsFunctions[id][conditionNumber].changes or {};
|
|
WeakAuras.customConditionsFunctions[id][conditionNumber].changes[changeIndex] = customFunc;
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local globalConditions =
|
|
{
|
|
["incombat"] = {
|
|
display = L["In Combat"],
|
|
type = "bool",
|
|
events = {"PLAYER_REGEN_ENABLED", "PLAYER_REGEN_DISABLED"},
|
|
globalStateUpdate = function(state)
|
|
state.incombat = UnitAffectingCombat("player");
|
|
end
|
|
},
|
|
["hastarget"] = {
|
|
display = L["Has Target"],
|
|
type = "bool",
|
|
events = {"PLAYER_TARGET_CHANGED", "PLAYER_ENTERING_WORLD"},
|
|
globalStateUpdate = function(state)
|
|
state.hastarget = UnitExists("target");
|
|
end
|
|
},
|
|
["attackabletarget"] = {
|
|
display = L["Attackable Target"],
|
|
type = "bool",
|
|
events = {"PLAYER_TARGET_CHANGED", "UNIT_FACTION"},
|
|
globalStateUpdate = function(state)
|
|
state.attackabletarget = UnitCanAttack("player", "target");
|
|
end
|
|
},
|
|
}
|
|
|
|
function WeakAuras.GetGlobalConditions()
|
|
return globalConditions;
|
|
end
|
|
|
|
function WeakAuras.ConstructConditionFunction(data)
|
|
local debug = false;
|
|
if (not data.conditions or #data.conditions == 0) then
|
|
return nil;
|
|
end
|
|
|
|
local usedProperties = {};
|
|
|
|
local allConditionsTemplate = WeakAuras.GetTriggerConditions(data);
|
|
allConditionsTemplate[-1] = WeakAuras.GetGlobalConditions();
|
|
|
|
local ret = "";
|
|
ret = ret .. "local newActiveConditions = {};\n"
|
|
ret = ret .. "local propertyChanges = {};\n"
|
|
ret = ret .. "local nextTime;\n"
|
|
ret = ret .. "return function(region, hideRegion)\n";
|
|
if (debug) then ret = ret .. " print('check conditions for:', region.id, region.cloneId)\n"; end
|
|
ret = ret .. " local id = region.id\n";
|
|
ret = ret .. " local cloneId = region.cloneId or ''\n";
|
|
ret = ret .. " local activatedConditions = WeakAuras.GetActiveConditions(id, cloneId)\n";
|
|
ret = ret .. " wipe(newActiveConditions)\n";
|
|
ret = ret .. " local recheckTime;\n"
|
|
ret = ret .. " local now = GetTime();\n"
|
|
|
|
local normalConditionCount = data.conditions and #data.conditions;
|
|
-- First Loop gather which conditions are active
|
|
ret = ret .. " if (not hideRegion) then\n"
|
|
if (data.conditions) then
|
|
for conditionNumber, condition in ipairs(data.conditions) do
|
|
ret = CreateCheckCondition(ret, condition, conditionNumber, allConditionsTemplate, debug)
|
|
end
|
|
end
|
|
ret = ret .. " end\n";
|
|
|
|
ret = ret .. " if (recheckTime) then\n"
|
|
ret = ret .. " WeakAuras.scheduleConditionCheck(recheckTime, id, cloneId);\n"
|
|
ret = ret .. " end\n"
|
|
|
|
local properties = WeakAuras.GetProperties(data);
|
|
|
|
-- Now build a property + change list
|
|
-- Second Loop deals with conditions that are no longer active
|
|
ret = ret .. " wipe(propertyChanges)\n"
|
|
if (data.conditions) then
|
|
for conditionNumber, condition in ipairs(data.conditions) do
|
|
ret = CreateDeactivateCondition(ret, condition, conditionNumber, data, properties, usedProperties, debug)
|
|
end
|
|
end
|
|
ret = ret .. "\n";
|
|
|
|
-- Third Loop deals with conditions that are newly active
|
|
if (data.conditions) then
|
|
for conditionNumber, condition in ipairs(data.conditions) do
|
|
ret = CreateActivateCondition(ret, data.id, condition, conditionNumber, properties, debug)
|
|
end
|
|
end
|
|
|
|
-- Last apply changes to region
|
|
for property, _ in pairs(usedProperties) do
|
|
ret = ret .. " if(propertyChanges['" .. property .. "'] ~= nil) then\n"
|
|
local arg1 = "";
|
|
if (properties[property].arg1) then
|
|
if (type(properties[property].arg1) == "number") then
|
|
arg1 = tostring(properties[property].arg1) .. ", ";
|
|
else
|
|
arg1 = "'" .. properties[property].arg1 .. "', ";
|
|
end
|
|
end
|
|
|
|
local base = "region:"
|
|
local subIndex = ParseProperty(property)
|
|
if subIndex then
|
|
base = "region.subRegions[" .. subIndex .. "]:"
|
|
end
|
|
|
|
ret = ret .. " " .. base .. properties[property].setter .. "(" .. arg1 .. formatValueForCall(properties[property].type, property) .. ")\n";
|
|
if (debug) then ret = ret .. " print('Calling " .. properties[property].setter .. " with', " .. arg1 .. formatValueForCall(properties[property].type, property) .. ")\n"; end
|
|
ret = ret .. " end\n";
|
|
end
|
|
ret = ret .. "end\n";
|
|
|
|
return ret;
|
|
end
|
|
|
|
|
|
local dynamicConditionsFrame = nil;
|
|
|
|
local globalConditionAllState = {
|
|
[""] = {
|
|
show = true;
|
|
}
|
|
};
|
|
|
|
local globalConditionState = globalConditionAllState[""];
|
|
|
|
function WeakAuras.GetGlobalConditionState()
|
|
return globalConditionAllState;
|
|
end
|
|
|
|
local function runDynamicConditionFunctions(funcs)
|
|
for id in pairs(funcs) do
|
|
if (triggerState[id] and triggerState[id].show and checkConditions[id]) then
|
|
local activeTriggerState = WeakAuras.GetTriggerStateForTrigger(id, triggerState[id].activeTrigger);
|
|
for cloneId, state in pairs(activeTriggerState) do
|
|
local region = WeakAuras.GetRegion(id, cloneId);
|
|
checkConditions[id](region, false);
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local function handleDynamicConditions(self, event)
|
|
if (globalDynamicConditionFuncs[event]) then
|
|
for i, func in ipairs(globalDynamicConditionFuncs[event]) do
|
|
func(globalConditionState);
|
|
end
|
|
end
|
|
if (dynamicConditions[event]) then
|
|
runDynamicConditionFunctions(dynamicConditions[event]);
|
|
end
|
|
end
|
|
|
|
local lastDynamicConditionsUpdateCheck;
|
|
local function handleDynamicConditionsOnUpdate(self)
|
|
handleDynamicConditions(self, "FRAME_UPDATE");
|
|
if (not lastDynamicConditionsUpdateCheck or GetTime() - lastDynamicConditionsUpdateCheck > 0.2) then
|
|
lastDynamicConditionsUpdateCheck = GetTime();
|
|
handleDynamicConditions(self, "WA_SPELL_RANGECHECK");
|
|
end
|
|
end
|
|
|
|
local registeredGlobalFunctions = {};
|
|
|
|
local function EvaluateCheckForRegisterForGlobalConditions(id, check, allConditionsTemplate, register)
|
|
local trigger = check and check.trigger;
|
|
local variable = check and check.variable;
|
|
|
|
if (trigger == -2) then
|
|
if (check.checks) then
|
|
for _, subcheck in ipairs(check.checks) do
|
|
EvaluateCheckForRegisterForGlobalConditions(id, subcheck, allConditionsTemplate, register);
|
|
end
|
|
end
|
|
elseif (trigger and variable) then
|
|
local conditionTemplate = allConditionsTemplate[trigger] and allConditionsTemplate[trigger][variable];
|
|
if (conditionTemplate and conditionTemplate.events) then
|
|
for _, event in ipairs(conditionTemplate.events) do
|
|
if (not dynamicConditions[event]) then
|
|
register[event] = true;
|
|
dynamicConditions[event] = {};
|
|
end
|
|
dynamicConditions[event][id] = true;
|
|
end
|
|
|
|
if (conditionTemplate.globalStateUpdate and not registeredGlobalFunctions[variable]) then
|
|
registeredGlobalFunctions[variable] = true;
|
|
for _, event in ipairs(conditionTemplate.events) do
|
|
globalDynamicConditionFuncs[event] = globalDynamicConditionFuncs[event] or {};
|
|
tinsert(globalDynamicConditionFuncs[event], conditionTemplate.globalStateUpdate);
|
|
end
|
|
conditionTemplate.globalStateUpdate(globalConditionState);
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function WeakAuras.RegisterForGlobalConditions(id)
|
|
local data = WeakAuras.GetData(id);
|
|
for event, conditonFunctions in pairs(dynamicConditions) do
|
|
conditonFunctions.id = nil;
|
|
end
|
|
|
|
local register = {};
|
|
if (data.conditions) then
|
|
local allConditionsTemplate = WeakAuras.GetTriggerConditions(data);
|
|
allConditionsTemplate[-1] = WeakAuras.GetGlobalConditions();
|
|
|
|
for conditionNumber, condition in ipairs(data.conditions) do
|
|
EvaluateCheckForRegisterForGlobalConditions(id, condition.check, allConditionsTemplate, register);
|
|
end
|
|
end
|
|
|
|
if (next(register) and not dynamicConditionsFrame) then
|
|
dynamicConditionsFrame = CreateFrame("FRAME");
|
|
dynamicConditionsFrame:SetScript("OnEvent", handleDynamicConditions);
|
|
WeakAuras.frames["Rerun Conditions Frame"] = dynamicConditionsFrame
|
|
end
|
|
|
|
for event in pairs(register) do
|
|
if (event == "FRAME_UPDATE" or event == "WA_SPELL_RANGECHECK") then
|
|
if (not dynamicConditionsFrame.onUpdate) then
|
|
dynamicConditionsFrame:SetScript("OnUpdate", handleDynamicConditionsOnUpdate);
|
|
dynamicConditionsFrame.onUpdate = true;
|
|
end
|
|
else
|
|
dynamicConditionsFrame:RegisterEvent(event);
|
|
end
|
|
end
|
|
end
|
|
|
|
function WeakAuras.UnregisterForGlobalConditions(id)
|
|
for event, condFuncs in pairs(dynamicConditions) do
|
|
condFuncs[id] = nil;
|
|
end
|
|
end
|
|
|
|
WeakAuras.talent_types_specific = {}
|
|
function WeakAuras.CreateTalentCache()
|
|
local _, player_class = UnitClass("player")
|
|
|
|
WeakAuras.talent_types_specific[player_class] = WeakAuras.talent_types_specific[player_class] or {};
|
|
|
|
for tab = 1, GetNumTalentTabs() do
|
|
for num_talent = 1, GetNumTalents(tab) do
|
|
local talentName, talentIcon = GetTalentInfo(tab, num_talent);
|
|
local talentId = (tab - 1)*MAX_NUM_TALENTS+num_talent
|
|
if (talentName and talentIcon) then
|
|
WeakAuras.talent_types_specific[player_class][talentId] = "|T"..talentIcon..":0|t "..talentName
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function WeakAuras.CountWagoUpdates()
|
|
local WeakAurasSaved = WeakAurasSaved
|
|
local updatedSlugs, updatedSlugsCount = {}, 0
|
|
for id, aura in pairs(WeakAurasSaved.displays) do
|
|
if not aura.ignoreWagoUpdate and aura.url and aura.url ~= "" then
|
|
local slug, version = aura.url:match("wago.io/([^/]+)/([0-9]+)")
|
|
if not slug and not version then
|
|
slug = aura.url:match("wago.io/([^/]+)$")
|
|
version = 1
|
|
end
|
|
if slug and version then
|
|
local wago = WeakAurasCompanion.slugs[slug]
|
|
if wago and wago.wagoVersion
|
|
and tonumber(wago.wagoVersion) > (
|
|
aura.skipWagoUpdate and tonumber(aura.skipWagoUpdate) or tonumber(version)
|
|
)
|
|
then
|
|
if not updatedSlugs[slug] then
|
|
updatedSlugs[slug] = true
|
|
updatedSlugsCount = updatedSlugsCount + 1
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
return updatedSlugsCount
|
|
end
|
|
|
|
local function tooltip_draw()
|
|
local tooltip = GameTooltip;
|
|
tooltip:ClearLines();
|
|
tooltip:AddDoubleLine("WeakAuras", versionString);
|
|
if WeakAurasCompanion then
|
|
local count = WeakAuras.CountWagoUpdates()
|
|
if count > 0 then
|
|
tooltip:AddLine(" ");
|
|
tooltip:AddLine((L["There are %i updates to your auras ready to be installed!"]):format(count));
|
|
end
|
|
end
|
|
tooltip:AddLine(" ");
|
|
tooltip:AddLine(L["|cffeda55fLeft-Click|r to toggle showing the main window."], 0.2, 1, 0.2);
|
|
if not WeakAuras.IsOptionsOpen() then
|
|
if paused then
|
|
tooltip:AddLine("|cFFFF0000"..L["Paused"].." - "..L["Shift-Click to resume addon execution."], 0.2, 1, 0.2);
|
|
else
|
|
tooltip:AddLine(L["|cffeda55fShift-Click|r to pause addon execution."], 0.2, 1, 0.2);
|
|
end
|
|
end
|
|
tooltip:AddLine(L["|cffeda55fRight-Click|r to toggle performance profiling window."], 0.2, 1, 0.2);
|
|
tooltip:AddLine(L["|cffeda55fMiddle-Click|r to toggle the minimap icon on or off."], 0.2, 1, 0.2);
|
|
tooltip:Show();
|
|
end
|
|
|
|
local colorFrame = CreateFrame("frame");
|
|
WeakAuras.frames["LDB Icon Recoloring"] = colorFrame;
|
|
|
|
local colorElapsed = 0;
|
|
local colorDelay = 2;
|
|
local r, g, b = 0.8, 0, 1;
|
|
local r2, g2, b2 = random(2)-1, random(2)-1, random(2)-1;
|
|
|
|
local tooltip_update_frame = CreateFrame("FRAME");
|
|
WeakAuras.frames["LDB Tooltip Updater"] = tooltip_update_frame;
|
|
|
|
-- function copied from LibDBIcon-1.0.lua
|
|
local function getAnchors(frame)
|
|
local x, y = frame:GetCenter()
|
|
if not x or not y then return "CENTER" end
|
|
local hhalf = (x > UIParent:GetWidth()*2/3) and "RIGHT" or (x < UIParent:GetWidth()/3) and "LEFT" or ""
|
|
local vhalf = (y > UIParent:GetHeight()/2) and "TOP" or "BOTTOM"
|
|
return vhalf..hhalf, frame, (vhalf == "TOP" and "BOTTOM" or "TOP")..hhalf
|
|
end
|
|
|
|
local Broker_WeakAuras;
|
|
Broker_WeakAuras = LDB:NewDataObject("WeakAuras", {
|
|
type = "launcher",
|
|
text = "WeakAuras",
|
|
icon = "Interface\\AddOns\\WeakAuras\\Media\\Textures\\icon.blp",
|
|
OnClick = function(self, button)
|
|
if button == 'LeftButton' then
|
|
if(IsShiftKeyDown()) then
|
|
if not(WeakAuras.IsOptionsOpen()) then
|
|
WeakAuras.Toggle();
|
|
end
|
|
else
|
|
WeakAuras.OpenOptions();
|
|
end
|
|
elseif(button == 'MiddleButton') then
|
|
WeakAuras.ToggleMinimap();
|
|
else
|
|
WeakAuras.RealTimeProfilingWindow:Toggle()
|
|
end
|
|
tooltip_draw()
|
|
end,
|
|
OnEnter = function(self)
|
|
colorFrame:SetScript("OnUpdate", function(self, elaps)
|
|
colorElapsed = colorElapsed + elaps;
|
|
if(colorElapsed > colorDelay) then
|
|
colorElapsed = colorElapsed - colorDelay;
|
|
r, g, b = r2, g2, b2;
|
|
r2, g2, b2 = random(2)-1, random(2)-1, random(2)-1;
|
|
end
|
|
Broker_WeakAuras.iconR = r + (r2 - r) * colorElapsed / colorDelay;
|
|
Broker_WeakAuras.iconG = g + (g2 - g) * colorElapsed / colorDelay;
|
|
Broker_WeakAuras.iconB = b + (b2 - b) * colorElapsed / colorDelay;
|
|
end);
|
|
local elapsed = 0;
|
|
local delay = 1;
|
|
tooltip_update_frame:SetScript("OnUpdate", function(self, elap)
|
|
elapsed = elapsed + elap;
|
|
if(elapsed > delay) then
|
|
elapsed = 0;
|
|
tooltip_draw();
|
|
end
|
|
end);
|
|
GameTooltip:SetOwner(self, "ANCHOR_NONE");
|
|
GameTooltip:SetPoint(getAnchors(self))
|
|
tooltip_draw();
|
|
end,
|
|
OnLeave = function(self)
|
|
colorFrame:SetScript("OnUpdate", nil);
|
|
tooltip_update_frame:SetScript("OnUpdate", nil);
|
|
GameTooltip:Hide();
|
|
end,
|
|
iconR = 0.6,
|
|
iconG = 0,
|
|
iconB = 1
|
|
});
|
|
|
|
|
|
do -- Archive stuff
|
|
local Archivist = select(2, ...).Archivist
|
|
function WeakAuras.OpenArchive()
|
|
if Archivist:IsInitialized() then
|
|
return Archivist
|
|
else
|
|
if not IsAddOnLoaded("WeakAurasArchive") then
|
|
local ok, reason = LoadAddOn("WeakAurasArchive")
|
|
if not ok then
|
|
error("Could not load WeakAuras Archive, reason: |cFFFF00" .. (reason or "UNKNOWN"))
|
|
end
|
|
end
|
|
Archivist:Initialize(WeakAurasArchive)
|
|
end
|
|
return Archivist
|
|
end
|
|
|
|
function WeakAuras.LoadFromArchive(storeType, storeID)
|
|
local Archivist = WeakAuras.OpenArchive()
|
|
return Archivist:Load(storeType, storeID)
|
|
end
|
|
end
|
|
|
|
local loginFinished, loginMessage = false, L["Options will open after the login process has completed."]
|
|
|
|
function WeakAuras.IsLoginFinished()
|
|
return loginFinished
|
|
end
|
|
|
|
function WeakAuras.LoginMessage()
|
|
return loginMessage
|
|
end
|
|
|
|
function WeakAuras.Login(initialTime, takeNewSnapshots)
|
|
local loginThread = coroutine.create(function()
|
|
WeakAuras.Pause();
|
|
|
|
if db.history then
|
|
local histRepo = WeakAuras.LoadFromArchive("Repository", "history")
|
|
local migrationRepo = WeakAuras.LoadFromArchive("Repository", "migration")
|
|
for uid, hist in pairs(db.history) do
|
|
local histStore = histRepo:Set(uid, hist.data)
|
|
local migrationStore = migrationRepo:Set(uid, hist.migration)
|
|
coroutine.yield()
|
|
end
|
|
-- history is now in archive so we can shrink WeakAurasSaved
|
|
db.history = nil
|
|
coroutine.yield();
|
|
end
|
|
|
|
local toAdd = {};
|
|
loginFinished = false
|
|
loginMessage = L["Options will open after the login process has completed."]
|
|
for id, data in pairs(db.displays) do
|
|
if(id ~= data.id) then
|
|
print("|cFF8800FFWeakAuras|r detected a corrupt entry in WeakAuras saved displays - '"..tostring(id).."' vs '"..tostring(data.id).."'" );
|
|
data.id = id;
|
|
end
|
|
tinsert(toAdd, data);
|
|
end
|
|
coroutine.yield();
|
|
|
|
WeakAuras.AddMany(toAdd, takeNewSnapshots);
|
|
coroutine.yield();
|
|
WeakAuras.AddManyFromAddons(from_files);
|
|
WeakAuras.RegisterDisplay = WeakAuras.AddFromAddon;
|
|
coroutine.yield();
|
|
WeakAuras.ResolveCollisions(function() registeredFromAddons = true; end);
|
|
coroutine.yield();
|
|
|
|
for _, triggerSystem in pairs(triggerSystems) do
|
|
if (triggerSystem.AllAdded) then
|
|
triggerSystem.AllAdded();
|
|
coroutine.yield();
|
|
end
|
|
end
|
|
|
|
-- check in case of a disconnect during an encounter.
|
|
if (db.CurrentEncounter) then
|
|
WeakAuras.CheckForPreviousEncounter()
|
|
end
|
|
coroutine.yield();
|
|
WeakAuras.RegisterLoadEvents();
|
|
WeakAuras.Resume();
|
|
coroutine.yield();
|
|
|
|
local nextCallback = loginQueue[1];
|
|
while nextCallback do
|
|
tremove(loginQueue, 1);
|
|
if type(nextCallback) == 'table' then
|
|
nextCallback[1](unpack(nextCallback[2]))
|
|
else
|
|
nextCallback()
|
|
end
|
|
coroutine.yield();
|
|
nextCallback = loginQueue[1];
|
|
end
|
|
|
|
loginFinished = true
|
|
WeakAuras.ResumeAllDynamicGroups();
|
|
end)
|
|
|
|
if initialTime then
|
|
local startTime = debugprofilestop()
|
|
local finishTime = debugprofilestop()
|
|
local ok, msg
|
|
-- hard limit seems to be 19 seconds. We'll do 15 for now.
|
|
while coroutine.status(loginThread) ~= 'dead' and finishTime - startTime < 15000 do
|
|
ok, msg = coroutine.resume(loginThread)
|
|
finishTime = debugprofilestop()
|
|
end
|
|
if coroutine.status(loginThread) ~= 'dead' then
|
|
WeakAuras.dynFrame:AddAction('login', loginThread)
|
|
end
|
|
if not ok then
|
|
loginMessage = L["WeakAuras has encountered an error during the login process. Please report this issue at https://github.com/WeakAuras/Weakauras2/issues/new."]
|
|
.. "\nMessage:" .. msg
|
|
geterrorhandler()(msg .. '\n' .. debugstack(loginThread))
|
|
end
|
|
else
|
|
WeakAuras.dynFrame:AddAction('login', loginThread)
|
|
end
|
|
end
|
|
|
|
local frame = CreateFrame("FRAME", "WeakAurasFrame", UIParent);
|
|
WeakAuras.frames["WeakAuras Main Frame"] = frame;
|
|
frame:SetAllPoints(UIParent);
|
|
local loadedFrame = CreateFrame("FRAME");
|
|
WeakAuras.frames["Addon Initialization Handler"] = loadedFrame;
|
|
loadedFrame:RegisterEvent("ADDON_LOADED");
|
|
loadedFrame:RegisterEvent("PLAYER_LOGIN");
|
|
loadedFrame:RegisterEvent("PLAYER_ENTERING_WORLD");
|
|
loadedFrame:RegisterEvent("ACTIVE_TALENT_GROUP_CHANGED");
|
|
loadedFrame:SetScript("OnEvent", function(self, event, addon)
|
|
if(event == "ADDON_LOADED") then
|
|
if(addon == ADDON_NAME) then
|
|
WeakAurasSaved = WeakAurasSaved or {};
|
|
db = WeakAurasSaved;
|
|
|
|
-- Defines the action squelch period after login
|
|
-- Stored in SavedVariables so it can be changed by the user if they find it necessary
|
|
db.login_squelch_time = db.login_squelch_time or 10;
|
|
|
|
-- Deprecated fields with *lots* of data, clear them out
|
|
db.iconCache = nil;
|
|
db.iconHash = nil;
|
|
db.tempIconCache = nil;
|
|
db.dynamicIconCache = db.dynamicIconCache or {};
|
|
|
|
db.displays = db.displays or {};
|
|
db.registered = db.registered or {};
|
|
|
|
WeakAuras.UpdateCurrentInstanceType();
|
|
WeakAuras.SyncParentChildRelationships();
|
|
local isFirstUIDValidation = db.dbVersion == nil or db.dbVersion < 26;
|
|
WeakAuras.ValidateUniqueDataIds(isFirstUIDValidation);
|
|
|
|
if db.lastArchiveClear == nil then
|
|
db.lastArchiveClear = time();
|
|
elseif db.lastArchiveClear < time() - 86400 then
|
|
WeakAuras.CleanArchive(db.historyCutoff, db.migrationCutoff);
|
|
end
|
|
db.minimap = db.minimap or { hide = false };
|
|
LDBIcon:Register("WeakAuras", Broker_WeakAuras, db.minimap);
|
|
end
|
|
elseif(event == "PLAYER_LOGIN") then
|
|
local dbIsValid, takeNewSnapshots
|
|
if not db.dbVersion or db.dbVersion < internalVersion then
|
|
-- db is out of date, will run any necessary migrations in AddMany
|
|
db.dbVersion = internalVersion
|
|
db.lastUpgrade = time()
|
|
dbIsValid = true
|
|
takeNewSnapshots = true
|
|
elseif db.dbVersion > internalVersion then
|
|
-- user has downgraded past a forwards-incompatible migration
|
|
dbIsValid = false
|
|
else
|
|
-- db has same version as code, can commit to login
|
|
dbIsValid = true
|
|
end
|
|
if dbIsValid then
|
|
-- run login thread for up to 15 seconds, then defer to dynFrame
|
|
WeakAuras.Login(15000, takeNewSnapshots)
|
|
else
|
|
-- db isn't valid. Request permission to run repair tool before logging in
|
|
StaticPopup_Show("WEAKAURAS_CONFIRM_REPAIR", nil, nil, {reason = "downgrade"})
|
|
end
|
|
else
|
|
local callback
|
|
if(event == "PLAYER_ENTERING_WORLD") then
|
|
-- Schedule events that need to be handled some time after login
|
|
local now = GetTime()
|
|
callback = function()
|
|
local elapsed = GetTime() - now
|
|
local remainingSquelch = db.login_squelch_time - elapsed
|
|
if remainingSquelch > 0 then
|
|
timer:ScheduleTimer(function() squelch_actions = false; end, remainingSquelch); -- No sounds while loading
|
|
end
|
|
WeakAuras.CreateTalentCache() -- It seems that GetTalentInfo might give info about whatever class was previously being played, until PLAYER_ENTERING_WORLD
|
|
WeakAuras.UpdateCurrentInstanceType();
|
|
end
|
|
elseif(event == "ACTIVE_TALENT_GROUP_CHANGED") then
|
|
callback = WeakAuras.CreateTalentCache;
|
|
elseif(event == "PLAYER_REGEN_ENABLED") then
|
|
callback = function()
|
|
if (queueshowooc) then
|
|
WeakAuras.OpenOptions(queueshowooc)
|
|
queueshowooc = nil
|
|
WeakAuras.frames["Addon Initialization Handler"]:UnregisterEvent("PLAYER_REGEN_ENABLED")
|
|
end
|
|
end
|
|
end
|
|
if WeakAuras.IsLoginFinished() then
|
|
callback()
|
|
else
|
|
loginQueue[#loginQueue + 1] = callback
|
|
end
|
|
end
|
|
end);
|
|
|
|
function WeakAuras.SetImporting(b)
|
|
importing = b;
|
|
WeakAuras.RefreshTooltipButtons()
|
|
end
|
|
|
|
function WeakAuras.IsImporting()
|
|
return importing;
|
|
end
|
|
|
|
function WeakAuras.IsPaused()
|
|
return paused;
|
|
end
|
|
|
|
function WeakAuras.Pause()
|
|
-- Forcibly hide all displays, and clear all trigger information (it will be restored on .Resume() due to forced events)
|
|
for id, region in pairs(regions) do
|
|
region.region:Collapse(); -- ticket 366
|
|
end
|
|
|
|
for id, cloneList in pairs(clones) do
|
|
for cloneId, clone in pairs(cloneList) do
|
|
clone:Collapse();
|
|
end
|
|
end
|
|
|
|
paused = true;
|
|
end
|
|
|
|
function WeakAuras.Resume()
|
|
paused = false;
|
|
WeakAuras.ScanAll();
|
|
for _, regionData in pairs(regions) do
|
|
if regionData.region.Resume then
|
|
regionData.region:Resume(true)
|
|
end
|
|
end
|
|
end
|
|
|
|
function WeakAuras.Toggle()
|
|
if(paused) then
|
|
WeakAuras.Resume();
|
|
else
|
|
WeakAuras.Pause();
|
|
end
|
|
end
|
|
|
|
function WeakAuras.SquelchingActions()
|
|
return squelch_actions;
|
|
end
|
|
|
|
function WeakAuras.PauseAllDynamicGroups()
|
|
for id, region in pairs(regions) do
|
|
if (region.region.Suspend) then
|
|
region.region:Suspend();
|
|
end
|
|
end
|
|
end
|
|
|
|
function WeakAuras.ResumeAllDynamicGroups()
|
|
for id, region in pairs(regions) do
|
|
if (region.region.Resume) then
|
|
region.region:Resume();
|
|
end
|
|
end
|
|
end
|
|
|
|
function WeakAuras.ScanAll()
|
|
WeakAuras.PauseAllDynamicGroups();
|
|
|
|
for id, region in pairs(regions) do
|
|
region.region:Collapse();
|
|
end
|
|
|
|
for id, cloneList in pairs(clones) do
|
|
for cloneId, clone in pairs(cloneList) do
|
|
clone:Collapse();
|
|
end
|
|
end
|
|
|
|
WeakAuras.ResumeAllDynamicGroups();
|
|
WeakAuras.ReloadAll();
|
|
end
|
|
|
|
-- encounter stuff
|
|
function WeakAuras.StoreBossGUIDs()
|
|
WeakAuras.StartProfileSystem("boss_guids")
|
|
if (WeakAuras.CurrentEncounter and WeakAuras.CurrentEncounter.boss_guids) then
|
|
for i = 1, 5 do
|
|
if (UnitExists ("boss" .. i)) then
|
|
local guid = UnitGUID ("boss" .. i)
|
|
if (guid) then
|
|
WeakAuras.CurrentEncounter.boss_guids [guid] = true
|
|
end
|
|
end
|
|
end
|
|
db.CurrentEncounter = WeakAuras.CurrentEncounter
|
|
end
|
|
WeakAuras.StopProfileSystem("boss_guids")
|
|
end
|
|
|
|
function WeakAuras.CheckForPreviousEncounter()
|
|
if (UnitAffectingCombat ("player") or InCombatLockdown()) then
|
|
for i = 1, 5 do
|
|
if (UnitExists ("boss" .. i)) then
|
|
local guid = UnitGUID ("boss" .. i)
|
|
if (guid and db.CurrentEncounter.boss_guids [guid]) then
|
|
-- we are in the same encounter
|
|
WeakAuras.CurrentEncounter = db.CurrentEncounter
|
|
return true
|
|
end
|
|
end
|
|
end
|
|
db.CurrentEncounter = nil
|
|
else
|
|
db.CurrentEncounter = nil
|
|
end
|
|
end
|
|
|
|
function WeakAuras.DestroyEncounterTable()
|
|
if (WeakAuras.CurrentEncounter) then
|
|
wipe(WeakAuras.CurrentEncounter)
|
|
end
|
|
WeakAuras.CurrentEncounter = nil
|
|
db.CurrentEncounter = nil
|
|
end
|
|
|
|
function WeakAuras.CreateEncounterTable(encounter_id)
|
|
local _, _, _, _, _, _, _, ZoneMapID = GetInstanceInfo()
|
|
WeakAuras.CurrentEncounter = {
|
|
id = encounter_id,
|
|
zone_id = ZoneMapID,
|
|
boss_guids = {},
|
|
}
|
|
timer:ScheduleTimer(WeakAuras.StoreBossGUIDs, 2)
|
|
|
|
return WeakAuras.CurrentEncounter
|
|
end
|
|
|
|
local encounterScriptsDeferred = {}
|
|
local function LoadEncounterInitScriptsImpl(id)
|
|
if (WeakAuras.currentInstanceType ~= "raid") then
|
|
return
|
|
end
|
|
if (id) then
|
|
local data = db.displays[id]
|
|
if (data and data.load.use_encounterid and not WeakAuras.IsEnvironmentInitialized(id) and data.actions.init and data.actions.init.do_custom) then
|
|
WeakAuras.ActivateAuraEnvironment(id)
|
|
WeakAuras.ActivateAuraEnvironment(nil)
|
|
end
|
|
encounterScriptsDeferred[id] = nil
|
|
else
|
|
for id, data in pairs(db.displays) do
|
|
if (data.load.use_encounterid and not WeakAuras.IsEnvironmentInitialized(id) and data.actions.init and data.actions.init.do_custom) then
|
|
WeakAuras.ActivateAuraEnvironment(id)
|
|
WeakAuras.ActivateAuraEnvironment(nil)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function WeakAuras.LoadEncounterInitScripts(id)
|
|
if not WeakAuras.IsLoginFinished() then
|
|
if encounterScriptsDeferred[id] then
|
|
return
|
|
end
|
|
loginQueue[#loginQueue + 1] = {LoadEncounterInitScriptsImpl, {id}}
|
|
encounterScriptsDeferred[id] = true
|
|
return
|
|
end
|
|
LoadEncounterInitScriptsImpl(id)
|
|
end
|
|
|
|
function WeakAuras.UpdateCurrentInstanceType(instanceType)
|
|
if (not IsInInstance()) then
|
|
WeakAuras.currentInstanceType = "none"
|
|
else
|
|
WeakAuras.currentInstanceType = instanceType or select (2, GetInstanceInfo())
|
|
end
|
|
end
|
|
|
|
local pausedOptionsProcessing = false;
|
|
function WeakAuras.pauseOptionsProcessing(enable)
|
|
pausedOptionsProcessing = enable;
|
|
end
|
|
|
|
function WeakAuras.IsOptionsProcessingPaused()
|
|
return pausedOptionsProcessing;
|
|
end
|
|
|
|
function WeakAuras.GroupType()
|
|
if (IsInRaid()) then
|
|
return "raid";
|
|
end
|
|
if (IsInGroup()) then
|
|
return "group";
|
|
end
|
|
return "solo";
|
|
end
|
|
|
|
local function GetInstanceTypeAndSize()
|
|
local size, difficulty
|
|
local inInstance, Type = IsInInstance()
|
|
local _, instanceType, difficultyIndex = GetInstanceInfo()
|
|
if (inInstance) then
|
|
local ZoneMapID = GetCurrentMapAreaID()
|
|
size = Type
|
|
if Type ~= "pvp" and Type ~= "arena" then
|
|
local difficultyInfo = WeakAuras.difficulty_info[difficultyIndex]
|
|
if difficultyInfo then
|
|
size, difficulty = difficultyInfo.size, difficultyInfo.difficulty
|
|
end
|
|
end
|
|
return size, difficulty, instanceType, ZoneMapID
|
|
end
|
|
return "none", "none", nil, nil
|
|
end
|
|
|
|
function WeakAuras.InstanceType()
|
|
return GetInstanceTypeAndSize(), nil
|
|
end
|
|
|
|
function WeakAuras.InstanceDifficulty()
|
|
return select(2, GetInstanceTypeAndSize())
|
|
end
|
|
|
|
local toLoad = {}
|
|
local toUnload = {};
|
|
local function scanForLoadsImpl(toCheck, event, arg1, ...)
|
|
if (WeakAuras.IsOptionsProcessingPaused()) then
|
|
return;
|
|
end
|
|
|
|
toCheck = toCheck or loadEvents[event or "SCAN_ALL"]
|
|
|
|
if (event == "PLAYER_LEVEL_UP") then
|
|
playerLevel = arg1;
|
|
end
|
|
|
|
if toCheck == nil or next(toCheck) == nil then
|
|
return
|
|
end
|
|
|
|
local player, realm, zone = UnitName("player"), GetRealmName(), GetRealZoneText();
|
|
local _, race = UnitRace("player")
|
|
local faction = UnitFactionGroup("player")
|
|
|
|
local _, class = UnitClass("player");
|
|
|
|
local incombat = UnitAffectingCombat("player") -- or UnitAffectingCombat("pet");
|
|
local vehicle = UnitInVehicle("player") or UnitOnTaxi("player")
|
|
local vehicleUi = UnitHasVehicleUI("player")
|
|
|
|
local size, difficulty, instanceType = GetInstanceTypeAndSize()
|
|
WeakAuras.UpdateCurrentInstanceType(instanceType)
|
|
|
|
local group = WeakAuras.GroupType()
|
|
|
|
local changed = 0;
|
|
local shouldBeLoaded, couldBeLoaded;
|
|
wipe(toLoad);
|
|
wipe(toUnload);
|
|
|
|
for id in pairs(toCheck) do
|
|
local data = WeakAuras.GetData(id)
|
|
if (data and not data.controlledChildren) then
|
|
local loadFunc = loadFuncs[id];
|
|
local loadOpt = loadFuncsForOptions[id];
|
|
shouldBeLoaded = loadFunc and loadFunc("ScanForLoads_Auras", incombat, vehicle, vehicleUi, group, player, realm, class, race, faction, playerLevel, zone, size, difficulty);
|
|
couldBeLoaded = loadOpt and loadOpt("ScanForLoads_Auras", incombat, vehicle, vehicleUi, group, player, realm, class, race, faction, playerLevel, zone, size, difficulty);
|
|
|
|
if(shouldBeLoaded and not loaded[id]) then
|
|
changed = changed + 1;
|
|
toLoad[id] = true;
|
|
end
|
|
|
|
if(loaded[id] and not shouldBeLoaded) then
|
|
toUnload[id] = true;
|
|
changed = changed + 1;
|
|
end
|
|
if(shouldBeLoaded) then
|
|
loaded[id] = true;
|
|
elseif(couldBeLoaded) then
|
|
loaded[id] = false;
|
|
else
|
|
loaded[id] = nil;
|
|
end
|
|
end
|
|
end
|
|
|
|
if(changed > 0 and not paused) then
|
|
WeakAuras.LoadDisplays(toLoad, event, arg1, ...);
|
|
WeakAuras.UnloadDisplays(toUnload, event, arg1, ...);
|
|
WeakAuras.FinishLoadUnload();
|
|
end
|
|
|
|
for id, data in pairs(db.displays) do
|
|
if(data.controlledChildren) then
|
|
if(#data.controlledChildren > 0) then
|
|
local any_loaded;
|
|
for index, childId in pairs(data.controlledChildren) do
|
|
if(loaded[childId] ~= nil) then
|
|
any_loaded = true;
|
|
break;
|
|
end
|
|
end
|
|
loaded[id] = any_loaded;
|
|
else
|
|
loaded[id] = true;
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
if (WeakAuras.afterScanForLoads) then -- Hook for Options
|
|
WeakAuras.afterScanForLoads();
|
|
end
|
|
wipe(toLoad);
|
|
wipe(toUnload)
|
|
end
|
|
|
|
function WeakAuras.ScanForLoads(toCheck, event, arg1, ...)
|
|
if not WeakAuras.IsLoginFinished() then
|
|
return
|
|
end
|
|
scanForLoadsImpl(toCheck, event, arg1, ...)
|
|
end
|
|
|
|
local loadFrame = CreateFrame("FRAME");
|
|
WeakAuras.loadFrame = loadFrame;
|
|
WeakAuras.frames["Display Load Handling"] = loadFrame;
|
|
|
|
loadFrame:RegisterEvent("PLAYER_TALENT_UPDATE");
|
|
loadFrame:RegisterEvent("PLAYER_DIFFICULTY_CHANGED");
|
|
loadFrame:RegisterEvent("VEHICLE_UPDATE");
|
|
|
|
loadFrame:RegisterEvent("PARTY_MEMBERS_CHANGED");
|
|
loadFrame:RegisterEvent("RAID_ROSTER_UPDATE");
|
|
loadFrame:RegisterEvent("ZONE_CHANGED");
|
|
loadFrame:RegisterEvent("ZONE_CHANGED_INDOORS");
|
|
loadFrame:RegisterEvent("ZONE_CHANGED_NEW_AREA");
|
|
loadFrame:RegisterEvent("PLAYER_LEVEL_UP");
|
|
loadFrame:RegisterEvent("PLAYER_REGEN_DISABLED");
|
|
loadFrame:RegisterEvent("PLAYER_REGEN_ENABLED");
|
|
loadFrame:RegisterEvent("SPELLS_CHANGED");
|
|
loadFrame:RegisterEvent("UNIT_INVENTORY_CHANGED")
|
|
loadFrame:RegisterEvent("PLAYER_EQUIPMENT_CHANGED")
|
|
|
|
local unitLoadFrame = CreateFrame("FRAME");
|
|
WeakAuras.unitLoadFrame = unitLoadFrame;
|
|
WeakAuras.frames["Display Load Handling 2"] = unitLoadFrame;
|
|
|
|
unitLoadFrame:RegisterEvent("UNIT_FLAGS");
|
|
unitLoadFrame:RegisterEvent("UNIT_ENTERED_VEHICLE");
|
|
unitLoadFrame:RegisterEvent("UNIT_EXITED_VEHICLE");
|
|
|
|
function WeakAuras.RegisterLoadEvents()
|
|
loadFrame:SetScript("OnEvent", function(frame, ...)
|
|
WeakAuras.StartProfileSystem("load");
|
|
WeakAuras.ScanForLoads(nil, ...)
|
|
WeakAuras.StopProfileSystem("load");
|
|
end);
|
|
|
|
--[[
|
|
C_Timer.NewTicker(0.5, function()
|
|
WeakAuras.StartProfileSystem("load");
|
|
local zoneId = C_Map.GetBestMapForUnit("player");
|
|
if loadFrame.zoneId ~= zoneId then
|
|
WeakAuras.ScanForLoads(nil, "ZONE_CHANGED")
|
|
loadFrame.zoneId = zoneId;
|
|
end
|
|
WeakAuras.StopProfileSystem("load");
|
|
end)
|
|
]]
|
|
|
|
unitLoadFrame:SetScript("OnEvent", function(frame, e, arg1, ...)
|
|
WeakAuras.StartProfileSystem("load");
|
|
if (arg1 == "player") then
|
|
WeakAuras.ScanForLoads(nil, e, arg1, ...)
|
|
end
|
|
WeakAuras.StopProfileSystem("load");
|
|
end);
|
|
end
|
|
|
|
function WeakAuras.ReloadAll()
|
|
WeakAuras.UnloadAll();
|
|
scanForLoadsImpl();
|
|
end
|
|
|
|
function WeakAuras.UnloadAll()
|
|
-- Even though auras are collapsed, their finish animation can be running
|
|
for id in pairs(loaded) do
|
|
WeakAuras.CancelAnimation(WeakAuras.regions[id].region, true, true, true, true, true, true)
|
|
if clones[id] then
|
|
for cloneId, region in pairs(clones[id]) do
|
|
WeakAuras.CancelAnimation(region, true, true, true, true, true, true)
|
|
end
|
|
end
|
|
end
|
|
|
|
for _, v in pairs(triggerState) do
|
|
for i = 1, v.numTriggers do
|
|
if (v[i]) then
|
|
wipe(v[i]);
|
|
end
|
|
end
|
|
end
|
|
|
|
for _, aura in pairs(timers) do
|
|
for _, trigger in pairs(aura) do
|
|
for _, record in pairs(trigger) do
|
|
if (record.handle) then
|
|
timer:CancelTimer(record.handle);
|
|
end
|
|
end
|
|
end
|
|
end
|
|
wipe(timers);
|
|
|
|
for id in pairs(conditionChecksTimers.recheckTime) do
|
|
if (conditionChecksTimers.recheckHandle[id]) then
|
|
for _, v in pairs(conditionChecksTimers.recheckHandle[id]) do
|
|
timer:CancelTimer(v);
|
|
end
|
|
end
|
|
end
|
|
wipe(conditionChecksTimers.recheckTime);
|
|
wipe(conditionChecksTimers.recheckHandle);
|
|
|
|
for _, triggerSystem in pairs(triggerSystems) do
|
|
triggerSystem.UnloadAll();
|
|
end
|
|
wipe(loaded);
|
|
end
|
|
|
|
function WeakAuras.LoadDisplays(toLoad, ...)
|
|
for id in pairs(toLoad) do
|
|
WeakAuras.RegisterForGlobalConditions(id);
|
|
triggerState[id].triggers = {};
|
|
triggerState[id].triggerCount = 0;
|
|
triggerState[id].show = false;
|
|
triggerState[id].activeTrigger = nil;
|
|
triggerState[id].activatedConditions = {};
|
|
end
|
|
for _, triggerSystem in pairs(triggerSystems) do
|
|
triggerSystem.LoadDisplays(toLoad, ...);
|
|
end
|
|
end
|
|
|
|
function WeakAuras.UnloadDisplays(toUnload, ...)
|
|
for _, triggerSystem in pairs(triggerSystems) do
|
|
triggerSystem.UnloadDisplays(toUnload, ...);
|
|
end
|
|
|
|
for id in pairs(toUnload) do
|
|
-- Even though auras are collapsed, their finish animation can be running
|
|
WeakAuras.CancelAnimation(WeakAuras.regions[id].region, true, true, true, true, true, true)
|
|
if clones[id] then
|
|
for cloneId, region in pairs(clones[id]) do
|
|
WeakAuras.CancelAnimation(region, true, true, true, true, true, true)
|
|
end
|
|
end
|
|
|
|
for i = 1, triggerState[id].numTriggers do
|
|
if (triggerState[id][i]) then
|
|
wipe(triggerState[id][i]);
|
|
end
|
|
end
|
|
triggerState[id].show = nil;
|
|
triggerState[id].activeTrigger = nil;
|
|
|
|
if (timers[id]) then
|
|
for _, trigger in pairs(timers[id]) do
|
|
for _, record in pairs(trigger) do
|
|
if (record.handle) then
|
|
timer:CancelTimer(record.handle);
|
|
end
|
|
end
|
|
end
|
|
timers[id] = nil;
|
|
end
|
|
|
|
conditionChecksTimers.recheckTime[id] = nil;
|
|
if (conditionChecksTimers.recheckHandle[id]) then
|
|
for _, v in pairs(conditionChecksTimers.recheckHandle[id]) do
|
|
timer:CancelTimer(v);
|
|
end
|
|
end
|
|
conditionChecksTimers.recheckHandle[id] = nil;
|
|
WeakAuras.UnregisterForGlobalConditions(id);
|
|
|
|
WeakAuras.regions[id].region:Collapse();
|
|
WeakAuras.CollapseAllClones(id);
|
|
end
|
|
end
|
|
|
|
function WeakAuras.FinishLoadUnload()
|
|
for _, triggerSystem in pairs(triggerSystems) do
|
|
triggerSystem.FinishLoadUnload();
|
|
end
|
|
end
|
|
|
|
-- transient cache of uid => id
|
|
-- eventually, the database will be migrated to index by uid
|
|
-- and this mapping will become redundant
|
|
-- this cache is loaded lazily via pAdd()
|
|
local UIDtoID = {}
|
|
|
|
function WeakAuras.GetDataByUID(uid)
|
|
return WeakAuras.GetData(UIDtoID[uid])
|
|
end
|
|
|
|
function WeakAuras.Delete(data)
|
|
local id = data.id;
|
|
if(data.parent) then
|
|
local parentData = db.displays[data.parent];
|
|
if(parentData and parentData.controlledChildren) then
|
|
for index, childId in pairs(parentData.controlledChildren) do
|
|
if(childId == id) then
|
|
tremove(parentData.controlledChildren, index);
|
|
end
|
|
end
|
|
if parentData.sortHybridTable then
|
|
parentData.sortHybridTable[id] = nil
|
|
end
|
|
WeakAuras.ClearAuraEnvironment(data.parent);
|
|
end
|
|
end
|
|
|
|
UIDtoID[data.uid] = nil
|
|
if(data.controlledChildren) then
|
|
for index, childId in pairs(data.controlledChildren) do
|
|
local childData = db.displays[childId];
|
|
if(childData) then
|
|
childData.parent = nil;
|
|
WeakAuras.Add(childData);
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
regions[id].region:Collapse()
|
|
WeakAuras.CollapseAllClones(id);
|
|
|
|
WeakAuras.CancelAnimation(WeakAuras.regions[id].region, true, true, true, true, true, true)
|
|
|
|
if clones[id] then
|
|
for cloneId, region in pairs(clones[id]) do
|
|
WeakAuras.CancelAnimation(region, true, true, true, true, true, true)
|
|
end
|
|
end
|
|
|
|
regions[id].region:SetScript("OnUpdate", nil);
|
|
regions[id].region:SetScript("OnShow", nil);
|
|
regions[id].region:SetScript("OnHide", nil);
|
|
regions[id].region:Hide();
|
|
|
|
db.registered[id] = nil;
|
|
if(WeakAuras.importDisplayButtons and WeakAuras.importDisplayButtons[id]) then
|
|
local button = WeakAuras.importDisplayButtons[id];
|
|
button.checkbox:SetChecked(false);
|
|
if(button.updateChecked) then
|
|
button.updateChecked();
|
|
end
|
|
end
|
|
|
|
for _, triggerSystem in pairs(triggerSystems) do
|
|
triggerSystem.Delete(id);
|
|
end
|
|
|
|
regions[id].region = nil;
|
|
regions[id] = nil;
|
|
loaded[id] = nil;
|
|
loadFuncs[id] = nil;
|
|
loadFuncsForOptions[id] = nil;
|
|
for event, eventData in pairs(loadEvents) do
|
|
eventData[id] = nil
|
|
end
|
|
checkConditions[id] = nil;
|
|
conditionChecksTimers.recheckTime[id] = nil;
|
|
if (conditionChecksTimers.recheckHandle[id]) then
|
|
for cloneId, v in pairs(conditionChecksTimers.recheckHandle[id]) do
|
|
timer:CancelTimer(v);
|
|
end
|
|
end
|
|
conditionChecksTimers.recheckHandle[id] = nil;
|
|
|
|
db.displays[id] = nil;
|
|
|
|
WeakAuras.DeleteAuraEnvironment(id)
|
|
triggerState[id] = nil;
|
|
|
|
if (WeakAuras.mouseFrame) then
|
|
WeakAuras.mouseFrame:delete(id);
|
|
end
|
|
|
|
WeakAuras.customActionsFunctions[id] = nil;
|
|
WeakAuras.customConditionsFunctions[id] = nil;
|
|
|
|
for event, funcs in pairs(dynamicConditions) do
|
|
funcs[id] = nil;
|
|
end
|
|
|
|
WeakAuras.frameLevels[id] = nil;
|
|
|
|
WeakAuras.DeleteCollapsedData(id)
|
|
end
|
|
|
|
function WeakAuras.Rename(data, newid)
|
|
local oldid = data.id;
|
|
if(data.parent) then
|
|
local parentData = db.displays[data.parent];
|
|
if(parentData.controlledChildren) then
|
|
for index, childId in pairs(parentData.controlledChildren) do
|
|
if(childId == data.id) then
|
|
parentData.controlledChildren[index] = newid;
|
|
end
|
|
end
|
|
if parentData.sortHybridTable and parentData.sortHybridTable[oldid] then
|
|
parentData.sortHybridTable[newid] = true
|
|
parentData.sortHybridTable[oldid] = nil
|
|
end
|
|
end
|
|
local parentRegion = WeakAuras.GetRegion(data.parent)
|
|
if parentRegion and parentRegion.ReloadControlledChildren then
|
|
parentRegion:ReloadControlledChildren()
|
|
end
|
|
end
|
|
|
|
UIDtoID[data.uid] = newid
|
|
regions[newid] = regions[oldid];
|
|
regions[oldid] = nil;
|
|
regions[newid].region.id = newid;
|
|
|
|
for _, triggerSystem in pairs(triggerSystems) do
|
|
triggerSystem.Rename(oldid, newid);
|
|
end
|
|
|
|
loaded[newid] = loaded[oldid];
|
|
loaded[oldid] = nil;
|
|
loadFuncs[newid] = loadFuncs[oldid];
|
|
loadFuncs[oldid] = nil;
|
|
|
|
loadFuncsForOptions[newid] = loadFuncsForOptions[oldid]
|
|
loadFuncsForOptions[oldid] = nil;
|
|
|
|
for event, eventData in pairs(loadEvents) do
|
|
eventData[newid] = eventData[oldid]
|
|
eventData[oldid] = nil
|
|
end
|
|
|
|
checkConditions[newid] = checkConditions[oldid];
|
|
checkConditions[oldid] = nil;
|
|
|
|
conditionChecksTimers.recheckTime[newid] = conditionChecksTimers.recheckTime[oldid];
|
|
conditionChecksTimers.recheckTime[oldid] = nil;
|
|
|
|
conditionChecksTimers.recheckHandle[newid] = conditionChecksTimers.recheckHandle[oldid];
|
|
conditionChecksTimers.recheckHandle[oldid] = nil;
|
|
|
|
timers[newid] = timers[oldid];
|
|
timers[oldid] = nil;
|
|
|
|
triggerState[newid] = triggerState[oldid];
|
|
triggerState[oldid] = nil;
|
|
|
|
WeakAuras.RenameAuraEnvironment(oldid, newid)
|
|
|
|
db.displays[newid] = db.displays[oldid];
|
|
db.displays[oldid] = nil;
|
|
|
|
if(clones[oldid]) then
|
|
clones[newid] = clones[oldid];
|
|
clones[oldid] = nil;
|
|
for cloneid, clone in pairs(clones[newid]) do
|
|
clone.id = newid;
|
|
end
|
|
end
|
|
|
|
db.displays[newid].id = newid;
|
|
|
|
if(data.controlledChildren) then
|
|
for index, childId in pairs(data.controlledChildren) do
|
|
local childData = db.displays[childId];
|
|
if(childData) then
|
|
childData.parent = data.id;
|
|
end
|
|
end
|
|
if regions[newid].ReloadControlledChildren then
|
|
regions[newid]:ReloadControlledChildren()
|
|
end
|
|
end
|
|
|
|
for key, animation in pairs(animations) do
|
|
if animation.name == oldid then
|
|
animation.name = newid;
|
|
end
|
|
end
|
|
|
|
if (WeakAuras.mouseFrame) then
|
|
WeakAuras.mouseFrame:rename(oldid, newid);
|
|
end
|
|
|
|
WeakAuras.customActionsFunctions[newid] = WeakAuras.customActionsFunctions[oldid];
|
|
WeakAuras.customActionsFunctions[oldid] = nil;
|
|
|
|
WeakAuras.customConditionsFunctions[newid] = WeakAuras.customConditionsFunctions[oldid];
|
|
WeakAuras.customConditionsFunctions[oldid] = nil;
|
|
|
|
for event, funcs in pairs(dynamicConditions) do
|
|
funcs[newid] = funcs[oldid]
|
|
funcs[oldid] = nil;
|
|
end
|
|
|
|
WeakAuras.frameLevels[newid] = WeakAuras.frameLevels[oldid];
|
|
WeakAuras.frameLevels[oldid] = nil;
|
|
|
|
WeakAuras.ProfileRenameAura(oldid, newid);
|
|
|
|
WeakAuras.RenameCollapsedData(oldid, newid)
|
|
end
|
|
|
|
function WeakAuras.Convert(data, newType)
|
|
local id = data.id;
|
|
regions[id].region:SetScript("OnUpdate", nil);
|
|
regions[id].region:Hide();
|
|
WeakAuras.EndEvent(id, 0, true);
|
|
|
|
WeakAuras.FakeStatesFor(id, false)
|
|
|
|
regions[id].region = nil;
|
|
regions[id] = nil;
|
|
|
|
data.regionType = newType;
|
|
WeakAuras.Add(data);
|
|
WeakAuras.ResetCollapsed(id)
|
|
|
|
WeakAuras.FakeStatesFor(id, true)
|
|
|
|
local parentRegion = WeakAuras.GetRegion(data.parent)
|
|
if parentRegion and parentRegion.ReloadControlledChildren then
|
|
parentRegion:ReloadControlledChildren()
|
|
end
|
|
end
|
|
|
|
function WeakAuras.DeepCopy(source, dest)
|
|
local function recurse(source, dest)
|
|
for i,v in pairs(source) do
|
|
if(type(v) == "table") then
|
|
dest[i] = type(dest[i]) == "table" and dest[i] or {};
|
|
recurse(v, dest[i]);
|
|
else
|
|
dest[i] = v;
|
|
end
|
|
end
|
|
end
|
|
recurse(source, dest);
|
|
end
|
|
|
|
function WeakAuras.RegisterAddon(addon, displayName, description, icon)
|
|
if(addons[addon]) then
|
|
addons[addon].displayName = displayName;
|
|
addons[addon].description = description;
|
|
addons[addon].icon = icon;
|
|
addons[addon].displays = addons[addon].displays or {};
|
|
else
|
|
addons[addon] = {
|
|
displayName = displayName,
|
|
description = description,
|
|
icon = icon,
|
|
displays = {}
|
|
};
|
|
end
|
|
end
|
|
|
|
function WeakAuras.RegisterDisplay(addon, data, force)
|
|
tinsert(from_files, {addon, data, force});
|
|
end
|
|
|
|
function WeakAuras.AddManyFromAddons(table)
|
|
for _, addData in ipairs(table) do
|
|
WeakAuras.AddFromAddon(addData[1], addData[2], addData[3]);
|
|
end
|
|
end
|
|
|
|
function WeakAuras.AddFromAddon(addon, data, force)
|
|
local id = data.id;
|
|
if(id and addons[addon]) then
|
|
addons[addon].displays[id] = data;
|
|
if(db.registered[id]) then
|
|
-- This display was already registered
|
|
-- It is unnecessary to add it again
|
|
elseif(force and not db.registered[id] == false) then
|
|
if(db.displays[id]) then
|
|
-- ID collision
|
|
collisions[id] = {addon, data};
|
|
else
|
|
db.registered[id] = addon;
|
|
WeakAuras.Add(data);
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function WeakAuras.CollisionResolved(addon, data, force)
|
|
WeakAuras.AddFromAddon(addon, data, force);
|
|
end
|
|
|
|
function WeakAuras.IsDefinedByAddon(id)
|
|
return db.registered[id];
|
|
end
|
|
|
|
function WeakAuras.ResolveCollisions(onFinished)
|
|
local num = 0;
|
|
for id, _ in pairs(collisions) do
|
|
num = num + 1;
|
|
end
|
|
|
|
if(num > 0) then
|
|
local baseText;
|
|
local buttonText;
|
|
if(registeredFromAddons) then
|
|
if(num == 1) then
|
|
baseText = L["Resolve collisions dialog singular"];
|
|
buttonText = L["Done"];
|
|
else
|
|
baseText = L["Resolve collisions dialog"];
|
|
buttonText = L["Next"];
|
|
end
|
|
else
|
|
if(num == 1) then
|
|
baseText = L["Resolve collisions dialog startup singular"];
|
|
buttonText = L["Done"];
|
|
else
|
|
baseText = L["Resolve collisions dialog startup"];
|
|
buttonText = L["Next"];
|
|
end
|
|
end
|
|
|
|
local numResolved = 0;
|
|
local currentId = next(collisions);
|
|
|
|
local function UpdateText(popup)
|
|
popup.text:SetText(baseText..(numResolved or "error").."/"..(num or "error"));
|
|
end
|
|
|
|
StaticPopupDialogs["WEAKAURAS_RESOLVE_COLLISIONS"] = {
|
|
text = baseText,
|
|
button1 = buttonText,
|
|
OnAccept = function(self)
|
|
-- Do the collision resolution
|
|
local newId = self.editBox:GetText();
|
|
if(WeakAuras.OptionsFrame and WeakAuras.OptionsFrame() and WeakAuras.displayButtons and WeakAuras.displayButtons[currentId]) then
|
|
WeakAuras.displayButtons[currentId].callbacks.OnRenameAction(newId)
|
|
else
|
|
local data = WeakAuras.GetData(currentId);
|
|
if(data) then
|
|
WeakAuras.Rename(data, newId);
|
|
else
|
|
print("|cFF8800FFWeakAuras|r: Data not found");
|
|
end
|
|
end
|
|
|
|
WeakAuras.CollisionResolved(collisions[currentId][1], collisions[currentId][2], true);
|
|
numResolved = numResolved + 1;
|
|
|
|
-- Get the next id to resolve
|
|
currentId = next(collisions, currentId);
|
|
if(currentId) then
|
|
-- There is another conflict to resolve - hook OnHide to reshow the dialog as soon as it hides
|
|
self:SetScript("OnHide", function(self)
|
|
self:Show();
|
|
UpdateText(self);
|
|
self.editBox:SetText(currentId);
|
|
self:SetScript("OnHide", nil);
|
|
if not(next(collisions, currentId)) then
|
|
self.button1:SetText(L["Done"]);
|
|
end
|
|
end);
|
|
else
|
|
self.editBox:SetScript("OnTextChanged", nil);
|
|
wipe(collisions);
|
|
if(onFinished) then
|
|
onFinished();
|
|
end
|
|
end
|
|
end,
|
|
hasEditBox = true,
|
|
hasWideEditBox = true,
|
|
hideOnEscape = true,
|
|
whileDead = true,
|
|
showAlert = true,
|
|
timeout = 0,
|
|
preferredindex = STATICPOPUP_NUMDIALOGS
|
|
};
|
|
|
|
local popup = StaticPopup_Show("WEAKAURAS_RESOLVE_COLLISIONS");
|
|
popup.editBox:SetScript("OnTextChanged", function(self)
|
|
local newid = self:GetText();
|
|
if(collisions[newid] or db.displays[newid]) then
|
|
popup.button1:Disable();
|
|
else
|
|
popup.button1:Enable();
|
|
end
|
|
end);
|
|
popup.editBox:SetText(currentId);
|
|
popup.text:SetJustifyH("left");
|
|
popup.icon:SetTexture("Interface\\Addons\\WeakAuras\\Media\\Textures\\icon.blp");
|
|
popup.icon:SetVertexColor(0.833, 0, 1);
|
|
|
|
UpdateText(popup);
|
|
elseif(onFinished) then
|
|
onFinished();
|
|
end
|
|
end
|
|
|
|
StaticPopupDialogs["WEAKAURAS_CONFIRM_REPAIR"] = {
|
|
text = "",
|
|
button1 = L["Repair"],
|
|
button2 = L["Cancel"],
|
|
OnAccept = function(self)
|
|
WeakAuras.RepairDatabase()
|
|
end,
|
|
OnShow = function(self)
|
|
if self.data.reason == "user" then
|
|
self.text:SetText(L["Manual Repair Confirmation Dialog"]:format(WeakAuras.LastUpgrade()))
|
|
else
|
|
self.text:SetText(L["Automatic Repair Confirmation Dialog"]:format(WeakAuras.LastUpgrade()))
|
|
end
|
|
end,
|
|
OnCancel = function(self)
|
|
if self.data.reason ~= "user" then
|
|
WeakAuras.Login()
|
|
end
|
|
end,
|
|
whileDead = true,
|
|
showAlert = true,
|
|
timeout = 0,
|
|
preferredindex = STATICPOPUP_NUMDIALOGS
|
|
}
|
|
|
|
function WeakAuras.LastUpgrade()
|
|
return db.lastUpgrade and date(nil, db.lastUpgrade) or "unknown"
|
|
end
|
|
|
|
function WeakAuras.NeedToRepairDatabase()
|
|
return db.dbVersion and db.dbVersion > WeakAuras.InternalVersion()
|
|
end
|
|
|
|
function WeakAuras.RepairDatabase(loginAfter)
|
|
local coro = coroutine.create(function()
|
|
WeakAuras.SetImporting(true)
|
|
-- set db version to current code version
|
|
db.dbVersion = WeakAuras.InternalVersion()
|
|
-- reinstall snapshots from history
|
|
local newDB = WeakAuras:Mixin({}, db.displays)
|
|
coroutine.yield()
|
|
for id, data in pairs(db.displays) do
|
|
local snapshot = WeakAuras.GetMigrationSnapshot(data.uid)
|
|
if snapshot then
|
|
newDB[id] = nil
|
|
newDB[snapshot.id] = snapshot
|
|
coroutine.yield()
|
|
end
|
|
end
|
|
db.displays = newDB
|
|
WeakAuras.SetImporting(false)
|
|
-- finally, login
|
|
WeakAuras.Login()
|
|
end)
|
|
WeakAuras.dynFrame:AddAction("repair", coro)
|
|
end
|
|
|
|
local function ModernizeAnimation(animation)
|
|
if (type(animation) ~= "string") then
|
|
return nil;
|
|
end
|
|
return animation:gsub("^%s*return%s*", "");
|
|
end
|
|
|
|
local function ModernizeAnimations(animations)
|
|
if (not animations) then
|
|
return;
|
|
end
|
|
animations.alphaFunc = ModernizeAnimation(animations.alphaFunc);
|
|
animations.translateFunc = ModernizeAnimation(animations.translateFunc);
|
|
animations.scaleFunc = ModernizeAnimation(animations.scaleFunc);
|
|
animations.rotateFunc = ModernizeAnimation(animations.rotateFunc);
|
|
animations.colorFunc = ModernizeAnimation(animations.colorFunc);
|
|
end
|
|
|
|
local modelMigration = CreateFrame("PlayerModel")
|
|
|
|
-- Takes as input a table of display data and attempts to update it to be compatible with the current version
|
|
function WeakAuras.Modernize(data)
|
|
if (not data.internalVersion) then
|
|
data.internalVersion = 1;
|
|
end
|
|
|
|
-- Version 2 was introduced April 2018 in Legion
|
|
if (data.internalVersion < 2) then
|
|
-- Add trigger count
|
|
if not data.numTriggers then
|
|
data.numTriggers = 1 + (data.additional_triggers and #data.additional_triggers or 0)
|
|
end
|
|
|
|
local load = data.load;
|
|
|
|
if (not load.ingroup) then
|
|
load.ingroup = {};
|
|
if (load.use_ingroup == true) then
|
|
load.ingroup.single = nil;
|
|
load.ingroup.multi = {
|
|
["group"] = true,
|
|
["raid"] = true
|
|
};
|
|
load.use_ingroup = false;
|
|
elseif (load.use_ingroup == false) then
|
|
load.ingroup.single = "solo";
|
|
load.ingroup.multi = {};
|
|
load.use_ingroup = true;
|
|
end
|
|
end
|
|
|
|
|
|
-- Convert load options into single/multi format
|
|
for index, prototype in pairs(WeakAuras.load_prototype.args) do
|
|
local protoname = prototype.name;
|
|
if(prototype.type == "multiselect") then
|
|
if(not load[protoname] or type(load[protoname]) ~= "table") then
|
|
local value = load[protoname];
|
|
load[protoname] = {};
|
|
if(value) then
|
|
load[protoname].single = value;
|
|
end
|
|
end
|
|
load[protoname].multi = load[protoname].multi or {};
|
|
elseif(load[protoname] and type(load[protoname]) == "table") then
|
|
load[protoname] = nil;
|
|
end
|
|
end
|
|
|
|
-- upgrade from singleselecting talents to multi select, see ticket 52
|
|
if (type(load.talent) == "number") then
|
|
local talent = load.talent;
|
|
load.talent = {};
|
|
load.talent.single = talent;
|
|
load.talent.multi = {}
|
|
end
|
|
|
|
|
|
--upgrade to support custom trigger combination logic
|
|
if (data.disjunctive == true) then
|
|
data.disjunctive = "any";
|
|
end
|
|
if(data.disjunctive == false) then
|
|
data.disjunctive = "all";
|
|
end
|
|
|
|
-- Change English-language class tokens to locale-agnostic versions
|
|
local class_agnosticize = {
|
|
["Death Knight"] = "DEATHKNIGHT",
|
|
["Druid"] = "DRUID",
|
|
["Hunter"] = "HUNTER",
|
|
["Mage"] = "MAGE",
|
|
["Monk"] = "MONK",
|
|
["Paladin"] = "PALADIN",
|
|
["Priest"] = "PRIEST",
|
|
["Rogue"] = "ROGUE",
|
|
["Shaman"] = "SHAMAN",
|
|
["Warlock"] = "WARLOCK",
|
|
["Warrior"] = "WARRIOR"
|
|
};
|
|
|
|
if(load.class.single) then
|
|
load.class.single = class_agnosticize[load.class.single] or load.class.single;
|
|
end
|
|
|
|
if(load.class.multi) then
|
|
for i,v in pairs(load.class.multi) do
|
|
if(class_agnosticize[i]) then
|
|
load.class.multi[class_agnosticize[i]] = true;
|
|
load.class.multi[i] = nil;
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Add dynamic text info to Progress Bars
|
|
-- Also convert custom displayText to new displayText
|
|
if(data.regionType == "aurabar") then
|
|
data.displayTextLeft = data.displayTextLeft or (not data.auto and data.displayText) or "%n";
|
|
data.displayTextRight = data.displayTextRight or "%p";
|
|
|
|
if (data.barInFront ~= nil) then
|
|
data.borderInFront = not data.barInFront;
|
|
data.backdropInFront = not data.barInFront;
|
|
data.barInFront = nil;
|
|
end
|
|
end
|
|
|
|
if(data.regionType == "icon") then
|
|
if (data.cooldownTextEnabled == nil) then
|
|
data.cooldownTextEnabled = true;
|
|
end
|
|
if (data.displayStacks) then
|
|
data.text1Enabled = true;
|
|
data.text1 = data.displayStacks;
|
|
data.displayStacks = nil;
|
|
data.text1Color = data.textColor;
|
|
data.textColor = nil;
|
|
data.text1Point = data.stacksPoint;
|
|
data.stacksPoint = nil;
|
|
data.text1Containment = data.stacksContainment;
|
|
data.stacksContainment = nil;
|
|
data.text1Font = data.font;
|
|
data.font = nil;
|
|
data.text1FontSize = data.fontSize;
|
|
data.fontSize = nil;
|
|
data.text1FontFlags = data.fontFlags;
|
|
data.fontFlags = nil;
|
|
|
|
data.text2Enabled = false;
|
|
data.text2 = "%p";
|
|
data.text2Color = {1, 1, 1, 1};
|
|
data.text2Point = "CENTER";
|
|
data.text2Containment = "INSIDE";
|
|
data.text2Font = "Friz Quadrata TT";
|
|
data.text2FontSize = 24;
|
|
data.text2FontFlags = "OUTLINE";
|
|
end
|
|
end
|
|
|
|
-- Upgrade some old variables
|
|
if data.regionType == "aurabar" then
|
|
-- "border" changed to "borderEdge"
|
|
if data.border and type(data.border) ~= "boolean" then
|
|
data.borderEdge = data.border;
|
|
data.border = data.borderEdge ~= "None";
|
|
end
|
|
-- Multiple text settings
|
|
if data.textColor then
|
|
if not data.timerColor then
|
|
data.timerColor = {};
|
|
data.timerColor[1] = data.textColor[1];
|
|
data.timerColor[2] = data.textColor[2];
|
|
data.timerColor[3] = data.textColor[3];
|
|
data.timerColor[4] = data.textColor[4];
|
|
end
|
|
if not data.stacksColor then
|
|
data.stacksColor = {};
|
|
data.stacksColor[1] = data.textColor[1];
|
|
data.stacksColor[2] = data.textColor[2];
|
|
data.stacksColor[3] = data.textColor[3];
|
|
data.stacksColor[4] = data.textColor[4];
|
|
end
|
|
end
|
|
-- Multiple text settings
|
|
if data.font then
|
|
if not data.textFont then
|
|
data.textFont = data.font;
|
|
end
|
|
if not data.timerFont then
|
|
data.timerFont = data.font;
|
|
end
|
|
if not data.stacksFont then
|
|
data.stacksFont = data.font;
|
|
end
|
|
|
|
data.font = nil;
|
|
end
|
|
-- Multiple text settings
|
|
if data.fontSize then
|
|
if not data.textSize then
|
|
data.textSize = data.fontSize;
|
|
end
|
|
if not data.timerSize then
|
|
data.timerSize = data.fontSize;
|
|
end
|
|
if not data.stacksSize then
|
|
data.stacksSize = data.fontSize;
|
|
end
|
|
|
|
data.fontSize = nil;
|
|
end
|
|
|
|
-- fontFlags (outline)
|
|
if not data.fontFlags then
|
|
data.fontFlags = "OUTLINE";
|
|
end
|
|
end
|
|
|
|
if data.regionType == "text" then
|
|
if (type(data.outline) == "boolean") then
|
|
data.outline = data.outline and "OUTLINE" or "None";
|
|
end
|
|
end
|
|
|
|
if data.regionType == "model" then
|
|
if (data.api == nil) then
|
|
data.api = false;
|
|
end
|
|
end
|
|
|
|
if (data.regionType == "progresstexture") then
|
|
if (not data.version or data.version < 2) then
|
|
if (data.orientation == "CLOCKWISE") then
|
|
if (data.inverse) then
|
|
data.startAngle, data.endAngle = 360 - data.endAngle, 360 - data.startAngle;
|
|
data.orientation = (data.orientation == "CLOCKWISE") and "ANTICLOCKWISE" or "CLOCKWISE";
|
|
end
|
|
elseif (data.orientation == "ANTICLOCKWISE") then
|
|
data.startAngle, data.endAngle = 360 - data.endAngle, 360 - data.startAngle;
|
|
if (data.inverse) then
|
|
data.orientation = (data.orientation == "CLOCKWISE") and "ANTICLOCKWISE" or "CLOCKWISE";
|
|
end
|
|
end
|
|
data.version = 2;
|
|
end
|
|
end
|
|
|
|
if (not data.activeTriggerMode) then
|
|
data.activeTriggerMode = 0;
|
|
end
|
|
|
|
if (data.sort == "hybrid") then
|
|
if (not data.hybridPosition) then
|
|
data.hybridPosition = "hybridLast";
|
|
end
|
|
if (not data.hybridSortMode) then
|
|
data.hybridSortMode = "descending";
|
|
end
|
|
end
|
|
|
|
if (data.conditions) then
|
|
for conditionIndex, condition in ipairs(data.conditions) do
|
|
if (not condition.check) then
|
|
condition.check = {
|
|
["trigger"] = condition.trigger,
|
|
["variable"] = condition.condition,
|
|
["op"] = condition.op,
|
|
["value"] = condition.value
|
|
};
|
|
condition.trigger = nil;
|
|
condition.condition = nil;
|
|
condition.op = nil;
|
|
condition.value = nil;
|
|
end
|
|
end
|
|
end
|
|
ModernizeAnimations(data.animation and data.animation.start);
|
|
ModernizeAnimations(data.animation and data.animation.main);
|
|
ModernizeAnimations(data.animation and data.animation.finish);
|
|
end -- End of V1 => V2
|
|
|
|
-- Version 3 was introduced April 2018 in Legion
|
|
if (data.internalVersion < 3) then
|
|
if (data.parent) then
|
|
local parentData = WeakAuras.GetData(data.parent);
|
|
if(parentData and parentData.regionType == "dynamicgroup") then
|
|
-- Version 3 allowed for offsets for dynamic groups, before that they were ignored
|
|
-- Thus reset them in the V2 to V3 upgrade
|
|
data.xOffset = 0;
|
|
data.yOffset = 0;
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Version 4 was introduced July 2018 in BfA
|
|
if (data.internalVersion < 4) then
|
|
if (data.conditions) then
|
|
for conditionIndex, condition in ipairs(data.conditions) do
|
|
if (condition.check) then
|
|
local triggernum = condition.check.trigger;
|
|
if (triggernum) then
|
|
local trigger;
|
|
if (triggernum == 0) then
|
|
trigger = data.trigger;
|
|
elseif(data.additional_triggers and data.additional_triggers[triggernum]) then
|
|
trigger = data.additional_triggers[triggernum].trigger;
|
|
end
|
|
if (trigger and trigger.event == "Cooldown Progress (Spell)") then
|
|
if (condition.check.variable == "stacks") then
|
|
condition.check.variable = "charges";
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Version 5 was introduced July 2018 in BFA
|
|
if data.internalVersion < 5 then
|
|
-- this is to fix hybrid sorting
|
|
if data.sortHybridTable then
|
|
if data.controlledChildren then
|
|
local newSortTable = {}
|
|
for index, isHybrid in pairs(data.sortHybridTable) do
|
|
local childID = data.controlledChildren[index]
|
|
if childID then
|
|
newSortTable[childID] = isHybrid
|
|
end
|
|
end
|
|
data.sortHybridTable = newSortTable
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Version 6 was introduced July 30, 2018 in BFA
|
|
-- Changes were entirely within triggers, so no code runs here
|
|
|
|
-- Version 7 was introduced September 1, 2018 in BFA
|
|
-- Triggers were cleaned up into a 1-indexed array
|
|
|
|
if data.internalVersion < 7 then
|
|
|
|
-- migrate trigger data
|
|
data.triggers = data.additional_triggers or {}
|
|
tinsert(data.triggers, 1, {
|
|
trigger = data.trigger or {},
|
|
untrigger = data.untrigger or {},
|
|
})
|
|
data.additional_triggers = nil
|
|
data.trigger = nil
|
|
data.untrigger = nil
|
|
data.numTriggers = nil
|
|
data.triggers.customTriggerLogic = data.customTriggerLogic
|
|
data.customTriggerLogic = nil
|
|
local activeTriggerMode = data.activeTriggerMode or WeakAuras.trigger_modes.first_active
|
|
if activeTriggerMode ~= WeakAuras.trigger_modes.first_active then
|
|
activeTriggerMode = activeTriggerMode + 1
|
|
end
|
|
data.triggers.activeTriggerMode = activeTriggerMode
|
|
data.activeTriggerMode = nil
|
|
data.triggers.disjunctive = data.disjunctive
|
|
data.disjunctive = nil
|
|
-- migrate condition trigger references
|
|
local function recurseRepairChecks(checks)
|
|
if not checks then return end
|
|
for _, check in pairs(checks) do
|
|
if check.trigger and check.trigger >= 0 then
|
|
check.trigger = check.trigger + 1
|
|
end
|
|
recurseRepairChecks(check.checks)
|
|
end
|
|
end
|
|
for _, condition in pairs(data.conditions) do
|
|
if condition.check.trigger and condition.check.trigger >= 0 then
|
|
condition.check.trigger = condition.check.trigger + 1
|
|
end
|
|
recurseRepairChecks(condition.check.checks)
|
|
end
|
|
end
|
|
|
|
-- Version 8 was introduced in September 2018
|
|
-- Changes are in PreAdd
|
|
|
|
-- Version 9 was introduced in September 2018
|
|
if data.internalVersion < 9 then
|
|
local function repairCheck(check)
|
|
if check and check.variable == "buffed" then
|
|
local trigger = check.trigger and data.triggers[check.trigger] and data.triggers[check.trigger].trigger;
|
|
if (trigger) then
|
|
if(trigger.buffShowOn == "showOnActive") then
|
|
check.variable = "show";
|
|
elseif (trigger.buffShowOn == "showOnMissing") then
|
|
check.variable = "show";
|
|
check.value = check.value == 0 and 1 or 0;
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local function recurseRepairChecks(checks)
|
|
if not checks then return end
|
|
for _, check in pairs(checks) do
|
|
repairCheck(check);
|
|
recurseRepairChecks(check.checks);
|
|
end
|
|
end
|
|
for _, condition in pairs(data.conditions) do
|
|
repairCheck(condition.check);
|
|
recurseRepairChecks(condition.check.checks);
|
|
end
|
|
end
|
|
|
|
-- Version 10 is skipped, due to a bad migration script (see https://github.com/WeakAuras/WeakAuras2/pull/1091)
|
|
|
|
-- Version 11 was introduced in January 2019
|
|
if data.internalVersion < 11 then
|
|
if data.url and data.url ~= "" then
|
|
local slug, version = data.url:match("wago.io/([^/]+)/([0-9]+)")
|
|
if not slug and not version then
|
|
version = 1
|
|
end
|
|
if version and tonumber(version) then
|
|
data.version = tonumber(version)
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Version 13 was introduced March 2019 in BFA
|
|
if data.internalVersion < 13 then
|
|
if data.regionType == "dynamicgroup" then
|
|
local selfPoints = {
|
|
default = "CENTER",
|
|
RIGHT = function(data)
|
|
if data.align == "LEFT" then
|
|
return "TOPLEFT"
|
|
elseif data.align == "RIGHT" then
|
|
return "BOTTOMLEFT"
|
|
else
|
|
return "LEFT"
|
|
end
|
|
end,
|
|
LEFT = function(data)
|
|
if data.align == "LEFT" then
|
|
return "TOPRIGHT"
|
|
elseif data.align == "RIGHT" then
|
|
return "BOTTOMRIGHT"
|
|
else
|
|
return "RIGHT"
|
|
end
|
|
end,
|
|
UP = function(data)
|
|
if data.align == "LEFT" then
|
|
return "BOTTOMLEFT"
|
|
elseif data.align == "RIGHT" then
|
|
return "BOTTOMRIGHT"
|
|
else
|
|
return "BOTTOM"
|
|
end
|
|
end,
|
|
DOWN = function(data)
|
|
if data.align == "LEFT" then
|
|
return "TOPLEFT"
|
|
elseif data.align == "RIGHT" then
|
|
return "TOPRIGHT"
|
|
else
|
|
return "TOP"
|
|
end
|
|
end,
|
|
HORIZONTAL = function(data)
|
|
if data.align == "LEFT" then
|
|
return "TOP"
|
|
elseif data.align == "RIGHT" then
|
|
return "BOTTOM"
|
|
else
|
|
return "CENTER"
|
|
end
|
|
end,
|
|
VERTICAL = function(data)
|
|
if data.align == "LEFT" then
|
|
return "LEFT"
|
|
elseif data.align == "RIGHT" then
|
|
return "RIGHT"
|
|
else
|
|
return "CENTER"
|
|
end
|
|
end,
|
|
CIRCLE = "CENTER",
|
|
COUNTERCIRCLE = "CENTER",
|
|
}
|
|
local selfPoint = selfPoints[data.grow or "DOWN"] or selfPoints.DOWN
|
|
if type(selfPoint) == "function" then
|
|
selfPoint = selfPoint(data)
|
|
end
|
|
data.selfPoint = selfPoint
|
|
end
|
|
end
|
|
|
|
-- Version 14 was introduced March 2019 in BFA
|
|
if data.internalVersion < 14 then
|
|
if data.triggers then
|
|
for triggerId, triggerData in pairs(data.triggers) do
|
|
if type(triggerData) == "table"
|
|
and triggerData.trigger
|
|
and triggerData.trigger.debuffClass
|
|
and type(triggerData.trigger.debuffClass) == "string"
|
|
and triggerData.trigger.debuffClass ~= ""
|
|
then
|
|
local idx = triggerData.trigger.debuffClass
|
|
data.triggers[triggerId].trigger.debuffClass = { [idx] = true }
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Version 15 was introduced April 2019 in BFA
|
|
if data.internalVersion < 15 then
|
|
if data.triggers then
|
|
for triggerId, triggerData in ipairs(data.triggers) do
|
|
if triggerData.trigger.type == "status" and triggerData.trigger.event == "Spell Known" then
|
|
triggerData.trigger.use_exact_spellName = true
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Version 16 was introduced May 2019 in BFA
|
|
if data.internalVersion < 16 then
|
|
-- second conversion: migrate name/realm conditions to tristate
|
|
if data.load.use_name == false then
|
|
data.load.use_name = nil
|
|
end
|
|
if data.load.use_realm == false then
|
|
data.load.use_realm = nil
|
|
end
|
|
end
|
|
|
|
-- Version 18 was a migration for stance/form trigger, but deleted later because of migration issue
|
|
|
|
-- Version 20 was introduced July 2019 in BFA
|
|
if data.internalVersion < 20 then
|
|
if data.regionType == "icon" then
|
|
local convertPoint = function(containment, point)
|
|
if not point or point == "CENTER" then
|
|
return "CENTER"
|
|
elseif containment == "INSIDE" then
|
|
return "INNER_" .. point
|
|
elseif containment == "OUTSIDE" then
|
|
return "OUTER_" .. point
|
|
end
|
|
end
|
|
|
|
local text1 = {
|
|
["type"] = "subtext",
|
|
text_visible = data.text1Enabled ~= false,
|
|
text_color = data.text1Color,
|
|
text_text = data.text1,
|
|
text_font = data.text1Font,
|
|
text_fontSize = data.text1FontSize,
|
|
text_fontType = data.text1FontFlags,
|
|
text_selfPoint = "AUTO",
|
|
text_anchorPoint = convertPoint(data.text1Containment, data.text1Point),
|
|
anchorXOffset = 0,
|
|
anchorYOffset = 0,
|
|
text_shadowColor = { 0, 0, 0, 1},
|
|
text_shadowXOffset = 0,
|
|
text_shadowYOffset = 0,
|
|
}
|
|
|
|
local usetext2 = data.text2Enabled
|
|
|
|
local text2 = {
|
|
["type"] = "subtext",
|
|
text_visible = data.text2Enabled or false,
|
|
text_color = data.text2Color,
|
|
text_text = data.text2,
|
|
text_font = data.text2Font,
|
|
text_fontSize = data.text2FontSize,
|
|
text_fontType = data.text2FontFlags,
|
|
text_selfPoint = "AUTO",
|
|
text_anchorPoint = convertPoint(data.text2Containment, data.text2Point),
|
|
anchorXOffset = 0,
|
|
anchorYOffset = 0,
|
|
text_shadowColor = { 0, 0, 0, 1},
|
|
text_shadowXOffset = 0,
|
|
text_shadowYOffset = 0,
|
|
}
|
|
|
|
data.text1Enabled = nil
|
|
data.text1Color = nil
|
|
data.text1 = nil
|
|
data.text1Font = nil
|
|
data.text1FontSize = nil
|
|
data.text1FontFlags = nil
|
|
data.text1Containment = nil
|
|
data.text1Point = nil
|
|
|
|
data.text2Enabled = nil
|
|
data.text2Color = nil
|
|
data.text2 = nil
|
|
data.text2Font = nil
|
|
data.text2FontSize = nil
|
|
data.text2FontFlags = nil
|
|
data.text2Containment = nil
|
|
data.text2Point = nil
|
|
|
|
local propertyRenames = {
|
|
text1Color = "sub.1.text_color",
|
|
text1FontSize = "sub.1.text_fontSize",
|
|
text2Color = "sub.2.text_color",
|
|
text2FontSize = "sub.2.text_fontSize"
|
|
}
|
|
|
|
tinsert(data.subRegions, text1)
|
|
if (usetext2) then
|
|
tinsert(data.subRegions, text2)
|
|
end
|
|
|
|
if (data.conditions) then
|
|
for conditionIndex, condition in ipairs(data.conditions) do
|
|
for changeIndex, change in ipairs(condition.changes) do
|
|
if propertyRenames[change.property] then
|
|
change.property = propertyRenames[change.property]
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Version 20 was introduced May 2019 in BFA
|
|
if data.internalVersion < 20 then
|
|
if data.regionType == "aurabar" then
|
|
local orientationToPostion = {
|
|
HORIZONTAL_INVERSE = { "INNER_LEFT", "INNER_RIGHT" },
|
|
HORIZONTAL = { "INNER_RIGHT", "INNER_LEFT" },
|
|
VERTICAL_INVERSE = { "INNER_BOTTOM", "INNER_TOP" },
|
|
VERTICAL = {"INNER_TOP", "INNER_BOTTOM"}
|
|
}
|
|
|
|
local positions = orientationToPostion[data.orientation] or { "INNER_LEFT", "INNER_RIGHT" }
|
|
|
|
local text1 = {
|
|
["type"] = "subtext",
|
|
text_visible = data.timer,
|
|
text_color = data.timerColor,
|
|
text_text = data.displayTextRight,
|
|
text_font = data.timerFont,
|
|
text_fontSize = data.timerSize,
|
|
text_fontType = data.timerFlags,
|
|
text_selfPoint = "AUTO",
|
|
text_anchorPoint = positions[1],
|
|
anchorXOffset = 0,
|
|
anchorYOffset = 0,
|
|
text_shadowColor = { 0, 0, 0, 1},
|
|
text_shadowXOffset = 1,
|
|
text_shadowYOffset = -1,
|
|
rotateText = data.rotateText
|
|
}
|
|
|
|
local text2 = {
|
|
["type"] = "subtext",
|
|
text_visible = data.text,
|
|
text_color = data.textColor,
|
|
text_text = data.displayTextLeft,
|
|
text_font = data.textFont,
|
|
text_fontSize = data.textSize,
|
|
text_fontType = data.textFlags,
|
|
text_selfPoint = "AUTO",
|
|
text_anchorPoint = positions[2],
|
|
anchorXOffset = 0,
|
|
anchorYOffset = 0,
|
|
text_shadowColor = { 0, 0, 0, 1},
|
|
text_shadowXOffset = 1,
|
|
text_shadowYOffset = -1,
|
|
rotateText = data.rotateText
|
|
}
|
|
|
|
local text3 = {
|
|
["type"] = "subtext",
|
|
text_visible = data.stacks,
|
|
text_color = data.stacksColor,
|
|
text_text = "%s",
|
|
text_font = data.stacksFont,
|
|
text_fontSize = data.stacksSize,
|
|
text_fontType = data.stacksFlags,
|
|
text_selfPoint = "AUTO",
|
|
text_anchorPoint = "ICON_CENTER",
|
|
anchorXOffset = 0,
|
|
anchorYOffset = 0,
|
|
text_shadowColor = { 0, 0, 0, 1},
|
|
text_shadowXOffset = 1,
|
|
text_shadowYOffset = -1,
|
|
rotateText = data.rotateText
|
|
}
|
|
|
|
data.timer = nil
|
|
data.textColor = nil
|
|
data.displayTextRight = nil
|
|
data.textFont = nil
|
|
data.textSize = nil
|
|
data.textFlags = nil
|
|
data.text = nil
|
|
data.timerColor = nil
|
|
data.displayTextLeft = nil
|
|
data.timerFont = nil
|
|
data.timerSize = nil
|
|
data.timerFlags = nil
|
|
data.stacks = nil
|
|
data.stacksColor = nil
|
|
data.stacksFont = nil
|
|
data.stacksSize = nil
|
|
data.stacksFlags = nil
|
|
data.rotateText = nil
|
|
|
|
local propertyRenames = {
|
|
timerColor = "sub.1.text_color",
|
|
timerSize = "sub.1.text_fontSize",
|
|
textColor = "sub.2.text_color",
|
|
textSize = "sub.2.text_fontSize",
|
|
stacksColor = "sub.3.text_color",
|
|
stacksSize = "sub.3.text_fontSize",
|
|
}
|
|
|
|
data.subRegions = data.subRegions or {}
|
|
tinsert(data.subRegions, text1)
|
|
tinsert(data.subRegions, text2)
|
|
tinsert(data.subRegions, text3)
|
|
|
|
if (data.conditions) then
|
|
for conditionIndex, condition in ipairs(data.conditions) do
|
|
for changeIndex, change in ipairs(condition.changes) do
|
|
if propertyRenames[change.property] then
|
|
change.property = propertyRenames[change.property]
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
end
|
|
end
|
|
|
|
if data.internalVersion < 21 then
|
|
if data.regionType == "dynamicgroup" then
|
|
data.border = data.background and data.background ~= "None"
|
|
data.borderEdge = data.border
|
|
data.borderBackdrop = data.background ~= "None" and data.background
|
|
data.borderInset = data.backgroundInset
|
|
data.background = nil
|
|
data.backgroundInset = nil
|
|
end
|
|
end
|
|
|
|
if data.internalVersion < 22 then
|
|
if data.regionType == "aurabar" then
|
|
data.subRegions = data.subRegions or {}
|
|
|
|
local border = {
|
|
["type"] = "subborder",
|
|
border_visible = data.border,
|
|
border_color = data.borderColor,
|
|
border_edge = data.borderEdge,
|
|
border_offset = data.borderOffset,
|
|
border_size = data.borderSize,
|
|
border_anchor = "bar",
|
|
}
|
|
|
|
data.border = nil
|
|
data.borderColor = nil
|
|
data.borderEdge = nil
|
|
data.borderOffset = nil
|
|
data.borderInset = nil
|
|
data.borderSize = nil
|
|
if data.borderInFront then
|
|
tinsert(data.subRegions, border)
|
|
else
|
|
tinsert(data.subRegions, 1, border)
|
|
end
|
|
|
|
local propertyRenames = {
|
|
borderColor = "sub.".. #data.subRegions..".border_color",
|
|
}
|
|
|
|
if (data.conditions) then
|
|
for conditionIndex, condition in ipairs(data.conditions) do
|
|
for changeIndex, change in ipairs(condition.changes) do
|
|
if propertyRenames[change.property] then
|
|
change.property = propertyRenames[change.property]
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
if data.internalVersion < 23 then
|
|
if data.triggers then
|
|
for triggerId, triggerData in ipairs(data.triggers) do
|
|
local trigger = triggerData.trigger
|
|
-- Stance/Form/Aura form field type changed from type="select" to type="multiselect"
|
|
if trigger and trigger.type == "status" and trigger.event == "Stance/Form/Aura" then
|
|
local value = trigger.form
|
|
if type(value) ~= "table" then
|
|
if trigger.use_form == false then
|
|
if value then
|
|
trigger.form = { multi = { [value] = true } }
|
|
else
|
|
trigger.form = { multi = { } }
|
|
end
|
|
elseif trigger.use_form then
|
|
trigger.form = { single = value }
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
if data.internalVersion < 24 then
|
|
if data.triggers then
|
|
for triggerId, triggerData in ipairs(data.triggers) do
|
|
local trigger = triggerData.trigger
|
|
if trigger and trigger.type == "status" and trigger.event == "Weapon Enchant" then
|
|
if trigger.use_inverse then
|
|
trigger.showOn = "showOnMissing"
|
|
else
|
|
trigger.showOn = "showOnActive"
|
|
end
|
|
trigger.use_inverse = nil
|
|
if not trigger.use_weapon then
|
|
trigger.use_weapon = "true"
|
|
trigger.weapon = "main"
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
if data.internalVersion < 25 then
|
|
if data.regionType == "icon" then
|
|
data.subRegions = data.subRegions or {}
|
|
-- Need to check if glow is needed
|
|
|
|
local prefix = "sub.".. #data.subRegions + 1 .. "."
|
|
-- For Conditions
|
|
local propertyRenames = {
|
|
glow = prefix .. "glow",
|
|
useGlowColor = prefix .. "useGlowColor",
|
|
glowColor = prefix .. "glowColor",
|
|
glowType = prefix .. "glowType",
|
|
glowLines = prefix .. "glowLines",
|
|
glowFrequency = prefix .. "glowFrequency",
|
|
glowLength = prefix .. "glowLength",
|
|
glowThickness = prefix .. "glowThickness",
|
|
glowScale = prefix .. "glowScale",
|
|
glowBorder = prefix .. "glowBorder",
|
|
glowXOffset = prefix .. "glowXOffset",
|
|
glowYOffset = prefix .. "glowYOffset",
|
|
}
|
|
|
|
local needsGlow = data.glow
|
|
if (not needsGlow and data.conditions) then
|
|
for conditionIndex, condition in ipairs(data.conditions) do
|
|
for changeIndex, change in ipairs(condition.changes) do
|
|
if propertyRenames[change.property] then
|
|
needsGlow = true
|
|
break
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
if needsGlow then
|
|
local glow = {
|
|
["type"] = "subglow",
|
|
glow = data.glow,
|
|
useGlowColor = data.useGlowColor,
|
|
glowColor = data.glowColor,
|
|
glowType = data.glowType,
|
|
glowLines = data.glowLines,
|
|
glowFrequency = data.glowFrequency,
|
|
glowLength = data.glowLength,
|
|
glowThickness = data.glowThickness,
|
|
glowScale = data.glowScale,
|
|
glowBorder = data.glowBorder,
|
|
glowXOffset = data.glowXOffset,
|
|
glowYOffset = data.glowYOffset,
|
|
}
|
|
tinsert(data.subRegions, glow)
|
|
end
|
|
|
|
data.glow = nil
|
|
data.useglowColor = nil
|
|
data.useGlowColor = nil
|
|
data.glowColor = nil
|
|
data.glowType = nil
|
|
data.glowLines = nil
|
|
data.glowFrequency = nil
|
|
data.glowLength = nil
|
|
data.glowThickness = nil
|
|
data.glowScale = nil
|
|
data.glowBorder = nil
|
|
data.glowXOffset = nil
|
|
data.glowYOffset = nil
|
|
|
|
if (data.conditions) then
|
|
for conditionIndex, condition in ipairs(data.conditions) do
|
|
for changeIndex, change in ipairs(condition.changes) do
|
|
if propertyRenames[change.property] then
|
|
change.property = propertyRenames[change.property]
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
if data.internalVersion < 26 then
|
|
if data.conditions then
|
|
for conditionIndex, condition in ipairs(data.conditions) do
|
|
for changeIndex, change in ipairs(condition.changes) do
|
|
if change.property == "xOffset" or change.property == "yOffset" then
|
|
change.value = (change.value or 0) - (data[change.property] or 0)
|
|
change.property = change.property .. "Relative"
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
if data.internalVersion < 28 then
|
|
if data.actions then
|
|
if data.actions.start and data.actions.start.do_glow then
|
|
data.actions.start.glow_frame_type = "FRAMESELECTOR"
|
|
end
|
|
if data.actions.finish and data.actions.finish.do_glow then
|
|
data.actions.finish.glow_frame_type = "FRAMESELECTOR"
|
|
end
|
|
end
|
|
end
|
|
|
|
if data.internalVersion < 29 then
|
|
if data.actions then
|
|
if data.actions.start
|
|
and data.actions.start.do_glow
|
|
and data.actions.start.glow_type == nil
|
|
then
|
|
data.actions.start.glow_type = "buttonOverlay"
|
|
end
|
|
if data.actions.finish
|
|
and data.actions.finish.do_glow
|
|
and data.actions.finish.glow_type == nil
|
|
then
|
|
data.actions.finish.glow_type = "buttonOverlay"
|
|
end
|
|
end
|
|
end
|
|
|
|
for _, triggerSystem in pairs(triggerSystems) do
|
|
triggerSystem.Modernize(data);
|
|
end
|
|
|
|
data.internalVersion = max(data.internalVersion or 0, internalVersion);
|
|
end
|
|
|
|
function WeakAuras.ValidateUniqueDataIds(silent)
|
|
-- ensure that there are no duplicated uids anywhere in the database
|
|
local seenUIDs = {}
|
|
for _, data in pairs(db.displays) do
|
|
if type(data.uid) == "string" then
|
|
if seenUIDs[data.uid] then
|
|
if not silent then
|
|
prettyPrint("duplicate uid \""..data.uid.."\" detected in saved variables between \""..data.id.."\" and \""..seenUIDs[data.uid].id.."\".")
|
|
end
|
|
data.uid = WeakAuras.GenerateUniqueID()
|
|
seenUIDs[data.uid] = data
|
|
else
|
|
seenUIDs[data.uid] = data
|
|
end
|
|
elseif data.uid ~= nil then
|
|
if not silent then
|
|
prettyPrint("invalid uid detected in saved variables for \""..data.id.."\"")
|
|
end
|
|
data.uid = WeakAuras.GenerateUniqueID()
|
|
seenUIDs[data.uid] = data
|
|
end
|
|
end
|
|
for uid, data in pairs(seenUIDs) do
|
|
UIDtoID[uid] = data.id
|
|
end
|
|
end
|
|
|
|
function WeakAuras.SyncParentChildRelationships(silent)
|
|
-- 1. Find all auras where data.parent ~= nil or data.controlledChildren ~= nil
|
|
-- If an aura has both, then remove data.parent
|
|
-- If an aura has data.parent which doesn't exist, then remove data.parent
|
|
-- If an aura has data.parent which doesn't have data.controledChildren, then remove data.parent
|
|
-- 2. For each aura with data.controlledChildren, iterate through the list of children and remove entries where:
|
|
-- The child doesn't exist in the database
|
|
-- The child ID is duplicated in data.controlledChildren (only the first will be kept)
|
|
-- The child's data.parent points to a different parent
|
|
-- Otherwise, mark the child as having a valid parent relationship
|
|
-- 3. For each aura with data.parent, remove data.parent if it was not marked to have a valid relationship in 2.
|
|
local parents = {}
|
|
local children = {}
|
|
local childHasParent = {}
|
|
for id, data in pairs(db.displays) do
|
|
if data.parent then
|
|
if data.controlledChildren then
|
|
if not silent then
|
|
prettyPrint("detected corruption in saved variables: "..id.." is a group that thinks it's a parent.")
|
|
end
|
|
-- A display cannot have both children and a parent
|
|
data.parent = nil
|
|
parents[id] = data
|
|
elseif not db.displays[data.parent] then
|
|
if not(silent) then
|
|
prettyPrint("detected corruption in saved variables: "..id.." has a nonexistent parent.")
|
|
end
|
|
data.parent = nil
|
|
elseif not db.displays[data.parent].controlledChildren then
|
|
if not silent then
|
|
prettyPrint("detected corruption in saved variables: "..id.." thinks "..data.parent..
|
|
" controls it, but "..data.parent.." is not a group.")
|
|
end
|
|
data.parent = nil
|
|
else
|
|
children[id] = data
|
|
end
|
|
elseif data.controlledChildren then
|
|
parents[id] = data
|
|
end
|
|
end
|
|
|
|
for id, data in pairs(parents) do
|
|
local groupChildren = {}
|
|
local childrenToRemove = {}
|
|
for index, childID in ipairs(data.controlledChildren) do
|
|
local child = children[childID]
|
|
if not child then
|
|
if not silent then
|
|
prettyPrint("detected corruption in saved variables: "..id.." thinks it controls "..childID.." which doesn't exist.")
|
|
end
|
|
childrenToRemove[index] = true
|
|
elseif child.parent ~= id then
|
|
if not silent then
|
|
prettyPrint("detected corruption in saved variables: "..id.." thinks it controls "..childID.." which it does not.")
|
|
end
|
|
childrenToRemove[index] = true
|
|
elseif groupChildren[childID] then
|
|
if not silent then
|
|
prettyPrint("detected corruption in saved variables: "..id.." has "..childID.." as a child in multiple positions.")
|
|
end
|
|
childrenToRemove[index] = true
|
|
else
|
|
groupChildren[childID] = index
|
|
childHasParent[childID] = true
|
|
end
|
|
end
|
|
if next(childrenToRemove) ~= nil then
|
|
for i = #data.controlledChildren, 1, -1 do
|
|
if childrenToRemove[i] then
|
|
tremove(data.controlledChildren, i)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
for id, data in pairs(children) do
|
|
if not childHasParent[id] then
|
|
if not silent then
|
|
prettyPrint("detected corruption in saved variables: "..id.." should be controlled by "..data.parent.." but isn't.")
|
|
end
|
|
local parent = parents[data.parent]
|
|
tinsert(parent.controlledChildren, id)
|
|
end
|
|
end
|
|
end
|
|
|
|
function WeakAuras.AddMany(table, takeSnapshots)
|
|
local idtable = {};
|
|
for _, data in ipairs(table) do
|
|
idtable[data.id] = data;
|
|
end
|
|
local loaded = {};
|
|
local function load(id, depends)
|
|
local data = idtable[id];
|
|
if(data.parent) then
|
|
if(idtable[data.parent]) then
|
|
if(tContains(depends, data.parent)) then
|
|
error("Circular dependency in WeakAuras.AddMany between "..table.concat(depends, ", "));
|
|
else
|
|
if not(loaded[data.parent]) then
|
|
local dependsOut = {};
|
|
for i,v in pairs(depends) do
|
|
dependsOut[i] = v;
|
|
end
|
|
tinsert(dependsOut, data.parent);
|
|
load(data.parent, dependsOut);
|
|
end
|
|
end
|
|
else
|
|
data.parent = nil;
|
|
end
|
|
end
|
|
if not(loaded[id]) then
|
|
WeakAuras.Add(data, takeSnapshots);
|
|
coroutine.yield();
|
|
loaded[id] = true;
|
|
end
|
|
end
|
|
local groups = {}
|
|
for id, data in pairs(idtable) do
|
|
load(id, {});
|
|
if data.regionType == "dynamicgroup" or data.regionType == "group" then
|
|
groups[data] = true
|
|
end
|
|
end
|
|
for data in pairs(groups) do
|
|
if data.type == "dynamicgroup" then
|
|
regions[data.id].region:ReloadControlledChildren()
|
|
else
|
|
WeakAuras.Add(data)
|
|
end
|
|
coroutine.yield();
|
|
end
|
|
end
|
|
|
|
local function customOptionIsValid(option)
|
|
if not option.type then
|
|
return false
|
|
elseif WeakAuras.author_option_classes[option.type] == "simple" then
|
|
if not option.key
|
|
or not option.name
|
|
or not option.default == nil then
|
|
return false
|
|
end
|
|
elseif WeakAuras.author_option_classes[option.type] == "group" then
|
|
if not option.key
|
|
or not option.name
|
|
or not option.default == nil
|
|
or not option.subOptions then
|
|
return false
|
|
end
|
|
end
|
|
return true
|
|
end
|
|
|
|
local function validateUserConfig(data, options, config)
|
|
local authorOptionKeys, corruptOptions = {}, {}
|
|
for index, option in ipairs(options) do
|
|
if not customOptionIsValid(option) then
|
|
prettyPrint(data.id .. " Custom Option #" .. index .. " in " .. data.id .. " has been detected as corrupt, and has been deleted.")
|
|
corruptOptions[index] = true
|
|
else
|
|
local optionClass = WeakAuras.author_option_classes[option.type]
|
|
if optionClass == "simple" then
|
|
if not option.key then
|
|
option.key = WeakAuras.GenerateUniqeID()
|
|
end
|
|
authorOptionKeys[option.key] = index
|
|
if config[option.key] == nil then
|
|
if type(option.default) ~= "table" then
|
|
config[option.key] = option.default
|
|
else
|
|
config[option.key] = CopyTable(option.default)
|
|
end
|
|
end
|
|
elseif optionClass == "group" then
|
|
authorOptionKeys[option.key] = "group"
|
|
local subOptions = option.subOptions
|
|
if type(config[option.key]) ~= "table" then
|
|
config[option.key] = {}
|
|
end
|
|
local subConfig = config[option.key]
|
|
if option.groupType == "array" then
|
|
for k, v in pairs(subConfig) do
|
|
if type(k) ~= "number" or type(v) ~= "table" then
|
|
-- if k was not a number, then this was a simple group before
|
|
-- if v is not a table, then this was likely a color option
|
|
wipe(subConfig) -- second iteration will fill table with defaults
|
|
break
|
|
end
|
|
end
|
|
if option.limitType == "fixed" then
|
|
for i = #subConfig + 1, option.size do
|
|
-- add missing entries
|
|
subConfig[i] = {}
|
|
end
|
|
end
|
|
if option.limitType ~= "none" then
|
|
for i = option.size + 1, #subConfig do
|
|
-- remove excess entries
|
|
subConfig[i] = nil
|
|
end
|
|
end
|
|
for _, toValidate in pairs(subConfig) do
|
|
validateUserConfig(data, subOptions, toValidate)
|
|
end
|
|
else
|
|
if type(next(subConfig)) ~= "string" then
|
|
-- either there are no sub options, in which case this is a noop
|
|
-- or this group was previously an array, in which case we need to wipe
|
|
wipe(subConfig)
|
|
end
|
|
validateUserConfig(data, subOptions, subConfig)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
for i = #options, 1, -1 do
|
|
if corruptOptions[i] then
|
|
tremove(options, i)
|
|
end
|
|
end
|
|
for key, value in pairs(config) do
|
|
if not authorOptionKeys[key] then
|
|
config[key] = nil
|
|
elseif authorOptionKeys[key] ~= "group" then
|
|
local option = options[authorOptionKeys[key]]
|
|
if type(value) ~= type(option.default) then
|
|
-- if type mismatch then we know that it can't be right
|
|
if type(option.default) ~= "table" then
|
|
config[key] = option.default
|
|
else
|
|
config[key] = CopyTable(option.default)
|
|
end
|
|
elseif option.type == "input" and option.useLength then
|
|
config[key] = config[key]:sub(1, option.length)
|
|
elseif option.type == "number" or option.type == "range" then
|
|
if (option.max and option.max < value) or (option.min and option.min > value) then
|
|
config[key] = option.default
|
|
else
|
|
if option.type == "number" and option.step then
|
|
local min = option.min or 0
|
|
config[key] = option.step * Round((value - min)/option.step) + min
|
|
end
|
|
end
|
|
elseif option.type == "select" then
|
|
if value < 1 or value > #option.values then
|
|
config[key] = option.default
|
|
end
|
|
elseif option.type == "multiselect" then
|
|
local multiselect = config[key]
|
|
for i, v in ipairs(multiselect) do
|
|
if option.default[i] ~= nil then
|
|
if type(v) ~= "boolean" then
|
|
multiselect[i] = option.default[i]
|
|
end
|
|
else
|
|
multiselect[i] = nil
|
|
end
|
|
end
|
|
for i, v in ipairs(option.default) do
|
|
if type(multiselect[i]) ~= "boolean" then
|
|
multiselect[i] = v
|
|
end
|
|
end
|
|
elseif option.type == "color" then
|
|
for i = 1, 4 do
|
|
local c = config[key][i]
|
|
if type(c) ~= "number" or c < 0 or c > 1 then
|
|
config[key] = option.default
|
|
break
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local function removeSpellNames(data)
|
|
local trigger
|
|
for i = 1, #data.triggers do
|
|
trigger = data.triggers[i].trigger
|
|
if trigger and trigger.type == "aura" then
|
|
if type(trigger.spellName) == "number" then
|
|
trigger.realSpellName = GetSpellInfo(trigger.spellName) or trigger.realSpellName
|
|
end
|
|
if (trigger.spellId) then
|
|
trigger.name = GetSpellInfo(trigger.spellId) or trigger.name;
|
|
end
|
|
if (trigger.spellIds) then
|
|
for i = 1, 10 do
|
|
if (trigger.spellIds[i]) then
|
|
trigger.names = trigger.names or {};
|
|
trigger.names[i] = GetSpellInfo(trigger.spellIds[i]) or trigger.names[i];
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local oldDataStub = {
|
|
-- note: this is the minimal data stub which prevents false positives in WeakAuras.diff upon reimporting an aura.
|
|
-- pending a refactor of other code which adds unnecessary fields, it is possible to shrink it
|
|
trigger = {
|
|
type = "aura",
|
|
names = {},
|
|
event = "Health",
|
|
subeventPrefix = "SPELL",
|
|
subeventSuffix = "_CAST_START",
|
|
spellIds = {},
|
|
unit = "player",
|
|
debuffType = "HELPFUL",
|
|
},
|
|
numTriggers = 1,
|
|
untrigger = {},
|
|
load = {
|
|
size = {
|
|
multi = {},
|
|
},
|
|
spec = {
|
|
multi = {},
|
|
},
|
|
class = {
|
|
multi = {},
|
|
},
|
|
},
|
|
actions = {
|
|
init = {},
|
|
start = {},
|
|
finish = {},
|
|
},
|
|
animation = {
|
|
start = {
|
|
type = "none",
|
|
duration_type = "seconds",
|
|
},
|
|
main = {
|
|
type = "none",
|
|
duration_type = "seconds",
|
|
},
|
|
finish = {
|
|
type = "none",
|
|
duration_type = "seconds",
|
|
},
|
|
},
|
|
conditions = {},
|
|
}
|
|
|
|
local oldDataStub2 = {
|
|
-- note: this is the minimal data stub which prevents false positives in WeakAuras.diff upon reimporting an aura.
|
|
-- pending a refactor of other code which adds unnecessary fields, it is possible to shrink it
|
|
triggers = {
|
|
{
|
|
trigger = {
|
|
type = "aura",
|
|
names = {},
|
|
event = "Health",
|
|
subeventPrefix = "SPELL",
|
|
subeventSuffix = "_CAST_START",
|
|
spellIds = {},
|
|
unit = "player",
|
|
debuffType = "HELPFUL",
|
|
},
|
|
untrigger = {},
|
|
},
|
|
},
|
|
load = {
|
|
size = {
|
|
multi = {},
|
|
},
|
|
spec = {
|
|
multi = {},
|
|
},
|
|
class = {
|
|
multi = {},
|
|
},
|
|
},
|
|
actions = {
|
|
init = {},
|
|
start = {},
|
|
finish = {},
|
|
},
|
|
animation = {
|
|
start = {
|
|
type = "none",
|
|
duration_type = "seconds",
|
|
},
|
|
main = {
|
|
type = "none",
|
|
duration_type = "seconds",
|
|
},
|
|
finish = {
|
|
type = "none",
|
|
duration_type = "seconds",
|
|
},
|
|
},
|
|
conditions = {},
|
|
}
|
|
|
|
function WeakAuras.PreAdd(data)
|
|
-- Readd what Compress removed before version 8
|
|
if (not data.internalVersion or data.internalVersion < 7) then
|
|
WeakAuras.validate(data, oldDataStub)
|
|
elseif (data.internalVersion < 8) then
|
|
WeakAuras.validate(data, oldDataStub2)
|
|
end
|
|
|
|
local default = data.regionType and WeakAuras.regionTypes[data.regionType] and WeakAuras.regionTypes[data.regionType].default
|
|
if default then
|
|
WeakAuras.validate(data, default)
|
|
end
|
|
|
|
local regionValidate = data.regionType and WeakAuras.regionTypes[data.regionType] and WeakAuras.regionTypes[data.regionType].validate
|
|
if regionValidate then
|
|
regionValidate(data)
|
|
end
|
|
|
|
if data.subRegions then
|
|
local result = {}
|
|
for index, subRegionData in ipairs(data.subRegions) do
|
|
local subType = subRegionData.type
|
|
if subType and WeakAuras.subRegionTypes[subType] then
|
|
-- If it is not supported, then drop it
|
|
if WeakAuras.subRegionTypes[subType].supports(data.regionType) then
|
|
local default = WeakAuras.subRegionTypes[subType].default
|
|
if type(default) == "function" then
|
|
default = default(data.regionType)
|
|
end
|
|
if default then
|
|
WeakAuras.validate(subRegionData, default)
|
|
end
|
|
|
|
tinsert(result, subRegionData)
|
|
end
|
|
else
|
|
-- Unknown sub type is because the user didn't restart their client
|
|
-- so keep it
|
|
tinsert(result, subRegionData)
|
|
end
|
|
end
|
|
data.subRegions = result
|
|
end
|
|
|
|
WeakAuras.Modernize(data);
|
|
WeakAuras.validate(data, WeakAuras.data_stub);
|
|
validateUserConfig(data, data.authorOptions, data.config)
|
|
removeSpellNames(data)
|
|
data.init_started = nil
|
|
data.init_completed = nil
|
|
data.expanded = nil
|
|
end
|
|
|
|
local function pAdd(data, simpleChange)
|
|
local id = data.id;
|
|
if not(id) then
|
|
error("Improper arguments to WeakAuras.Add - id not defined");
|
|
return;
|
|
end
|
|
|
|
data.uid = data.uid or WeakAuras.GenerateUniqueID()
|
|
local otherID = UIDtoID[data.uid]
|
|
if not otherID then
|
|
UIDtoID[data.uid] = id
|
|
elseif otherID ~= id then
|
|
-- duplicate uid
|
|
data.uid = WeakAuras.GenerateUniqueID()
|
|
UIDtoID[data.uid] = id
|
|
end
|
|
|
|
if simpleChange then
|
|
db.displays[id] = data
|
|
WeakAuras.SetRegion(data)
|
|
WeakAuras.UpdatedTriggerState(id)
|
|
else
|
|
if (data.controlledChildren) then
|
|
WeakAuras.ClearAuraEnvironment(id);
|
|
if data.parent then
|
|
WeakAuras.ClearAuraEnvironment(data.parent);
|
|
end
|
|
db.displays[id] = data;
|
|
WeakAuras.SetRegion(data);
|
|
else
|
|
local visible
|
|
if (WeakAuras.IsOptionsOpen()) then
|
|
visible = WeakAuras.FakeStatesFor(id, false)
|
|
else
|
|
if (WeakAuras.regions[id] and WeakAuras.regions[id].region) then
|
|
WeakAuras.regions[id].region:Collapse()
|
|
else
|
|
WeakAuras.CollapseAllClones(id)
|
|
end
|
|
end
|
|
|
|
WeakAuras.ClearAuraEnvironment(id);
|
|
if data.parent then
|
|
WeakAuras.ClearAuraEnvironment(data.parent);
|
|
end
|
|
|
|
db.displays[id] = data;
|
|
|
|
if (not data.triggers.activeTriggerMode or data.triggers.activeTriggerMode > #data.triggers) then
|
|
data.triggers.activeTriggerMode = WeakAuras.trigger_modes.first_active;
|
|
end
|
|
|
|
for _, triggerSystem in pairs(triggerSystems) do
|
|
triggerSystem.Add(data);
|
|
end
|
|
|
|
local loadFuncStr, events = WeakAuras.ConstructFunction(load_prototype, data.load);
|
|
for event, eventData in pairs(loadEvents) do
|
|
eventData[id] = nil
|
|
end
|
|
for event in pairs(events) do
|
|
loadEvents[event] = loadEvents[event] or {}
|
|
loadEvents[event][id] = true
|
|
end
|
|
loadEvents["SCAN_ALL"] = loadEvents["SCAN_ALL"] or {}
|
|
loadEvents["SCAN_ALL"][id] = true
|
|
|
|
local loadForOptionsFuncStr = WeakAuras.ConstructFunction(load_prototype, data.load, true);
|
|
local loadFunc = WeakAuras.LoadFunction(loadFuncStr, id, "load");
|
|
local loadForOptionsFunc = WeakAuras.LoadFunction(loadForOptionsFuncStr, id, "options load");
|
|
local triggerLogicFunc;
|
|
if data.triggers.disjunctive == "custom" then
|
|
triggerLogicFunc = WeakAuras.LoadFunction("return "..(data.triggers.customTriggerLogic or ""), id, "trigger combination");
|
|
end
|
|
WeakAuras.LoadCustomActionFunctions(data);
|
|
WeakAuras.LoadConditionPropertyFunctions(data);
|
|
local checkConditionsFuncStr = WeakAuras.ConstructConditionFunction(data);
|
|
local checkCondtionsFunc = checkConditionsFuncStr and WeakAuras.LoadFunction(checkConditionsFuncStr, id, "condition checks");
|
|
debug(id.." - Load", 1);
|
|
debug(loadFuncStr);
|
|
|
|
loadFuncs[id] = loadFunc;
|
|
loadFuncsForOptions[id] = loadForOptionsFunc;
|
|
checkConditions[id] = checkCondtionsFunc;
|
|
clones[id] = clones[id] or {};
|
|
|
|
if (timers[id]) then
|
|
for _, trigger in pairs(timers[id]) do
|
|
for _, record in pairs(trigger) do
|
|
if (record.handle) then
|
|
timer:CancelTimer(record.handle);
|
|
end
|
|
end
|
|
end
|
|
timers[id] = nil;
|
|
end
|
|
|
|
local region = WeakAuras.SetRegion(data);
|
|
|
|
triggerState[id] = {
|
|
disjunctive = data.triggers.disjunctive or "all",
|
|
numTriggers = #data.triggers,
|
|
activeTriggerMode = data.triggers.activeTriggerMode or WeakAuras.trigger_modes.first_active,
|
|
triggerLogicFunc = triggerLogicFunc,
|
|
triggers = {},
|
|
triggerCount = 0,
|
|
activatedConditions = {},
|
|
};
|
|
|
|
WeakAuras.LoadEncounterInitScripts(id);
|
|
|
|
if (WeakAuras.IsOptionsOpen()) then
|
|
WeakAuras.FakeStatesFor(id, visible)
|
|
end
|
|
|
|
if not(paused) then
|
|
WeakAuras.ScanForLoads({[id] = true});
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function WeakAuras.Add(data, takeSnapshot, simpleChange)
|
|
local snapshot
|
|
if takeSnapshot or (data.internalVersion or 0) < internalVersion then
|
|
snapshot = CopyTable(data)
|
|
end
|
|
if takeSnapshot then
|
|
WeakAuras.SetMigrationSnapshot(data.uid, snapshot)
|
|
end
|
|
WeakAuras.PreAdd(data)
|
|
pAdd(data, simpleChange)
|
|
end
|
|
|
|
function WeakAuras.SetRegion(data, cloneId)
|
|
local regionType = data.regionType;
|
|
if not(regionType) then
|
|
error("Improper arguments to WeakAuras.SetRegion - regionType not defined");
|
|
else
|
|
if(not regionTypes[regionType]) then
|
|
regionType = "fallback";
|
|
print("Improper arguments to WeakAuras.CreateRegion - regionType \""..data.regionType.."\" is not supported");
|
|
end
|
|
|
|
local id = data.id;
|
|
if not(id) then
|
|
error("Improper arguments to WeakAuras.SetRegion - id not defined");
|
|
else
|
|
local region;
|
|
if(cloneId) then
|
|
region = clones[id][cloneId];
|
|
if (not region or region.regionType ~= data.regionType) then
|
|
if (region) then
|
|
clonePool[region.regionType] = clonePool[region.regionType] or {};
|
|
tinsert(clonePool[region.regionType], region);
|
|
region:Hide();
|
|
end
|
|
if(clonePool[data.regionType] and clonePool[data.regionType][1]) then
|
|
clones[id][cloneId] = tremove(clonePool[data.regionType]);
|
|
else
|
|
local clone = regionTypes[data.regionType].create(frame, data);
|
|
clone.regionType = data.regionType;
|
|
clone:Hide();
|
|
clones[id][cloneId] = clone;
|
|
end
|
|
region = clones[id][cloneId];
|
|
end
|
|
else
|
|
if((not regions[id]) or (not regions[id].region) or regions[id].regionType ~= regionType) then
|
|
region = regionTypes[regionType].create(frame, data);
|
|
region.regionType = regionType;
|
|
regions[id] = {
|
|
regionType = regionType,
|
|
region = region
|
|
};
|
|
if regionType ~= "dynamicgroup" and regionType ~= "group" then
|
|
region.toShow = false
|
|
region:Hide()
|
|
else
|
|
region.toShow = true
|
|
end
|
|
else
|
|
region = regions[id].region;
|
|
end
|
|
end
|
|
region.id = id;
|
|
region.cloneId = cloneId or "";
|
|
WeakAuras.validate(data, regionTypes[regionType].default);
|
|
|
|
local parent = frame;
|
|
if(data.parent) then
|
|
if(regions[data.parent]) then
|
|
parent = regions[data.parent].region;
|
|
else
|
|
data.parent = nil;
|
|
end
|
|
end
|
|
local loginFinished = WeakAuras.IsLoginFinished();
|
|
local anim_cancelled = loginFinished and WeakAuras.CancelAnimation(region, true, true, true, true, true, true);
|
|
|
|
regionTypes[regionType].modify(parent, region, data);
|
|
WeakAuras.regionPrototype.AddSetDurationInfo(region);
|
|
WeakAuras.regionPrototype.AddExpandFunction(data, region, cloneId, parent, parent.regionType)
|
|
|
|
|
|
data.animation = data.animation or {};
|
|
data.animation.start = data.animation.start or {type = "none"};
|
|
data.animation.main = data.animation.main or {type = "none"};
|
|
data.animation.finish = data.animation.finish or {type = "none"};
|
|
if(WeakAuras.CanHaveDuration(data)) then
|
|
data.animation.start.duration_type = data.animation.start.duration_type or "seconds";
|
|
data.animation.main.duration_type = data.animation.main.duration_type or "seconds";
|
|
data.animation.finish.duration_type = data.animation.finish.duration_type or "seconds";
|
|
else
|
|
data.animation.start.duration_type = "seconds";
|
|
data.animation.main.duration_type = "seconds";
|
|
data.animation.finish.duration_type = "seconds";
|
|
end
|
|
|
|
if(cloneId) then
|
|
clonePool[regionType] = clonePool[regionType] or {};
|
|
end
|
|
if(anim_cancelled) then
|
|
WeakAuras.Animate("display", data, "main", data.animation.main, region, false, nil, true, cloneId);
|
|
end
|
|
return region;
|
|
end
|
|
end
|
|
end
|
|
|
|
function WeakAuras.EnsureClone(id, cloneId)
|
|
clones[id] = clones[id] or {};
|
|
if not(clones[id][cloneId]) then
|
|
local data = WeakAuras.GetData(id);
|
|
WeakAuras.SetRegion(data, cloneId);
|
|
clones[id][cloneId].justCreated = true;
|
|
end
|
|
return clones[id][cloneId];
|
|
end
|
|
|
|
function WeakAuras.GetRegion(id, cloneId)
|
|
if(cloneId and cloneId ~= "") then
|
|
return WeakAuras.EnsureClone(id, cloneId);
|
|
end
|
|
return WeakAuras.regions[id] and WeakAuras.regions[id].region;
|
|
end
|
|
|
|
function WeakAuras.CollapseAllClones(id, triggernum)
|
|
if(clones[id]) then
|
|
for i,v in pairs(clones[id]) do
|
|
v:Collapse();
|
|
end
|
|
end
|
|
end
|
|
|
|
function WeakAuras.SetAllStatesHidden(id, triggernum)
|
|
local triggerState = WeakAuras.GetTriggerStateForTrigger(id, triggernum);
|
|
local changed = false
|
|
for id, state in pairs(triggerState) do
|
|
changed = changed or state.show
|
|
state.show = false;
|
|
state.changed = true;
|
|
end
|
|
return changed
|
|
end
|
|
|
|
function WeakAuras.SetAllStatesHiddenExcept(id, triggernum, list)
|
|
local triggerState = WeakAuras.GetTriggerStateForTrigger(id, triggernum);
|
|
for cloneId, state in pairs(triggerState) do
|
|
if (not (list[cloneId])) then
|
|
state.show = false;
|
|
state.changed = true;
|
|
end
|
|
end
|
|
end
|
|
|
|
function WeakAuras.ReleaseClone(id, cloneId, regionType)
|
|
if (not clones[id]) then
|
|
return;
|
|
end
|
|
local region = clones[id][cloneId];
|
|
clones[id][cloneId] = nil;
|
|
clonePool[regionType][#clonePool[regionType] + 1] = region;
|
|
end
|
|
|
|
function WeakAuras.HandleChatAction(message_type, message, message_dest, message_channel, r, g, b, region, customFunc)
|
|
if (message:find('%%')) then
|
|
message = WeakAuras.ReplacePlaceHolders(message, region, customFunc);
|
|
end
|
|
if(message_type == "PRINT") then
|
|
DEFAULT_CHAT_FRAME:AddMessage(message, r or 1, g or 1, b or 1);
|
|
elseif(message_type == "COMBAT") then
|
|
if(CombatText_AddMessage) then
|
|
CombatText_AddMessage(message, COMBAT_TEXT_SCROLL_FUNCTION, r or 1, g or 1, b or 1);
|
|
end
|
|
elseif(message_type == "WHISPER") then
|
|
if(message_dest) then
|
|
if(message_dest == "target" or message_dest == "'target'" or message_dest == "\"target\"" or message_dest == "%t" or message_dest == "'%t'" or message_dest == "\"%t\"") then
|
|
pcall(function() SendChatMessage(message, "WHISPER", nil, UnitName("target")) end);
|
|
else
|
|
pcall(function() SendChatMessage(message, "WHISPER", nil, message_dest) end);
|
|
end
|
|
end
|
|
elseif(message_type == "CHANNEL") then
|
|
local channel = message_channel and tonumber(message_channel);
|
|
if(GetChannelName(channel)) then
|
|
pcall(function() SendChatMessage(message, "CHANNEL", nil, channel) end);
|
|
end
|
|
elseif(message_type == "SMARTRAID") then
|
|
local isInstanceGroup = IsInGroup(LE_PARTY_CATEGORY_INSTANCE)
|
|
if UnitInBattleground("player") then
|
|
pcall(function() SendChatMessage(message, "CHAT_MSG_BATTLEGROUND") end)
|
|
elseif UnitInRaid("player") then
|
|
pcall(function() SendChatMessage(message, "RAID") end)
|
|
elseif UnitInParty("player") then
|
|
if isInstanceGroup then
|
|
pcall(function() SendChatMessage(message, "CHAT_MSG_BATTLEGROUND") end)
|
|
else
|
|
pcall(function() SendChatMessage(message, "PARTY") end)
|
|
end
|
|
else
|
|
if IsInInstance() then
|
|
pcall(function() SendChatMessage(message, "SAY") end)
|
|
end
|
|
end
|
|
elseif(message_type == "SAY" or message_type == "YELL") then
|
|
if IsInInstance() then
|
|
pcall(function() SendChatMessage(message, message_type, nil, nil) end)
|
|
end
|
|
else
|
|
pcall(function() SendChatMessage(message, message_type, nil, nil) end);
|
|
end
|
|
end
|
|
|
|
local function actionGlowStop(actions, frame, id)
|
|
if not frame.__WAGlowFrame then return end
|
|
if actions.glow_type == "buttonOverlay" then
|
|
LCG.ButtonGlow_Stop(frame.__WAGlowFrame)
|
|
elseif actions.glow_type == "Pixel" then
|
|
LCG.PixelGlow_Stop(frame.__WAGlowFrame, id)
|
|
elseif actions.glow_type == "ACShine" then
|
|
LCG.AutoCastGlow_Stop(frame.__WAGlowFrame, id)
|
|
end
|
|
end
|
|
|
|
local function actionGlowStart(actions, frame, id)
|
|
if not frame.__WAGlowFrame then
|
|
frame.__WAGlowFrame = CreateFrame("Frame", nil, frame)
|
|
frame.__WAGlowFrame:SetAllPoints(frame)
|
|
frame.__WAGlowFrame:SetSize(frame:GetSize())
|
|
end
|
|
local glow_frame = frame.__WAGlowFrame
|
|
if glow_frame:GetWidth() < 1 or glow_frame:GetHeight() < 1 then
|
|
actionGlowStop(actions, frame)
|
|
return
|
|
end
|
|
local color = actions.use_glow_color and actions.glow_color or nil
|
|
if actions.glow_type == "buttonOverlay" then
|
|
LCG.ButtonGlow_Start(glow_frame, color)
|
|
elseif actions.glow_type == "Pixel" then
|
|
LCG.PixelGlow_Start(
|
|
glow_frame,
|
|
color,
|
|
actions.glow_lines,
|
|
actions.glow_frequency,
|
|
actions.glow_length,
|
|
actions.glow_thickness,
|
|
actions.glow_XOffset,
|
|
actions.glow_YOffset,
|
|
actions.glow_border,
|
|
id
|
|
)
|
|
elseif actions.glow_type == "ACShine" then
|
|
LCG.AutoCastGlow_Start(
|
|
glow_frame,
|
|
color,
|
|
actions.glow_lines,
|
|
actions.glow_frequency,
|
|
actions.glow_scale,
|
|
actions.glow_XOffset,
|
|
actions.glow_YOffset,
|
|
id
|
|
)
|
|
end
|
|
end
|
|
|
|
local glow_frame_monitor
|
|
local anchor_unitframe_monitor
|
|
WeakAuras.dyngroup_unitframe_monitor = {}
|
|
do
|
|
local function frame_monitor_callback(event, frame, unit)
|
|
local new_frame
|
|
local update_frame = event == "FRAME_UNIT_UPDATE"
|
|
if type(glow_frame_monitor) == "table" then
|
|
for region, data in pairs(glow_frame_monitor) do
|
|
if region.state and region.state.unit == unit
|
|
and (data.frame ~= frame) == update_frame
|
|
then
|
|
if not new_frame then
|
|
new_frame = WeakAuras.GetUnitFrame(unit) or WeakAuras.HiddenFrames
|
|
end
|
|
if new_frame and new_frame ~= data.frame then
|
|
local id = region.id .. (region.cloneId or "")
|
|
-- remove previous glow
|
|
actionGlowStop(data.actions, data.frame, id)
|
|
-- apply the glow to new_frame
|
|
data.frame = new_frame
|
|
actionGlowStart(data.actions, data.frame, id)
|
|
-- update hidefunc
|
|
local region = region
|
|
region.active_glows_hidefunc[data.frame] = function()
|
|
actionGlowStop(data.actions, data.frame, id)
|
|
glow_frame_monitor[region] = nil
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
if type(anchor_unitframe_monitor) == "table" then
|
|
for region, data in pairs(anchor_unitframe_monitor) do
|
|
if region.state and region.state.unit == unit
|
|
and (data.frame ~= frame) == update_frame
|
|
then
|
|
if not new_frame then
|
|
new_frame = WeakAuras.GetUnitFrame(unit) or WeakAuras.HiddenFrames
|
|
end
|
|
if new_frame and new_frame ~= data.frame then
|
|
WeakAuras.AnchorFrame(data.data, region, data.parent)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
for regionData, data_frame in pairs(WeakAuras.dyngroup_unitframe_monitor) do
|
|
if regionData.region and regionData.region.state and regionData.region.state.unit == unit
|
|
and (data_frame ~= frame) == update_frame
|
|
then
|
|
if not new_frame then
|
|
new_frame = WeakAuras.GetUnitFrame(unit) or WeakAuras.HiddenFrames
|
|
end
|
|
if new_frame and new_frame ~= data_frame then
|
|
regionData.controlPoint:ReAnchor(new_frame)
|
|
if regionData.shown and new_frame ~= WeakAuras.HiddenFrames then
|
|
regionData.controlPoint:Show()
|
|
else
|
|
regionData.controlPoint:Hide()
|
|
end
|
|
WeakAuras.dyngroup_unitframe_monitor[regionData] = new_frame
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
LGF.RegisterCallback("WeakAuras", "FRAME_UNIT_UPDATE", frame_monitor_callback)
|
|
LGF.RegisterCallback("WeakAuras", "FRAME_UNIT_REMOVED", frame_monitor_callback)
|
|
end
|
|
|
|
function WeakAuras.HandleGlowAction(actions, region)
|
|
if actions.glow_action
|
|
and (
|
|
(actions.glow_frame_type == "UNITFRAME" and region.state.unit)
|
|
or (actions.glow_frame_type == "FRAMESELECTOR" and actions.glow_frame)
|
|
)
|
|
then
|
|
local glow_frame
|
|
local original_glow_frame
|
|
if actions.glow_frame_type == "FRAMESELECTOR" then
|
|
if actions.glow_frame:sub(1, 10) == "WeakAuras:" then
|
|
local frame_name = actions.glow_frame:sub(11)
|
|
if regions[frame_name] then
|
|
glow_frame = regions[frame_name].region
|
|
end
|
|
else
|
|
glow_frame = WeakAuras.GetSanitizedGlobal(actions.glow_frame)
|
|
end
|
|
elseif actions.glow_frame_type == "UNITFRAME" and region.state.unit then
|
|
glow_frame = WeakAuras.GetUnitFrame(region.state.unit)
|
|
end
|
|
|
|
if glow_frame then
|
|
local id = region.id .. (region.cloneId or "")
|
|
if actions.glow_action == "show" then
|
|
-- remove previous glow
|
|
if region.active_glows_hidefunc
|
|
and region.active_glows_hidefunc[glow_frame]
|
|
then
|
|
region.active_glows_hidefunc[glow_frame]()
|
|
end
|
|
-- start glow
|
|
actionGlowStart(actions, glow_frame, id)
|
|
-- make unglow function & monitor unitframe changes
|
|
region.active_glows_hidefunc = region.active_glows_hidefunc or {}
|
|
if actions.glow_frame_type == "UNITFRAME" then
|
|
glow_frame_monitor = glow_frame_monitor or {}
|
|
glow_frame_monitor[region] = {
|
|
actions = actions,
|
|
frame = glow_frame
|
|
}
|
|
region.active_glows_hidefunc[glow_frame] = function()
|
|
actionGlowStop(actions, glow_frame, id)
|
|
glow_frame_monitor[region] = nil
|
|
end
|
|
else
|
|
region.active_glows_hidefunc[glow_frame] = function()
|
|
actionGlowStop(actions, glow_frame, id)
|
|
end
|
|
end
|
|
elseif actions.glow_action == "hide"
|
|
and region.active_glows_hidefunc
|
|
and region.active_glows_hidefunc[glow_frame]
|
|
then
|
|
region.active_glows_hidefunc[glow_frame]()
|
|
region.active_glows_hidefunc[glow_frame] = nil
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function WeakAuras.PerformActions(data, when, region)
|
|
if (paused or WeakAuras.IsOptionsOpen()) then
|
|
return;
|
|
end;
|
|
local actions;
|
|
if(when == "start") then
|
|
actions = data.actions.start;
|
|
elseif(when == "finish") then
|
|
actions = data.actions.finish;
|
|
else
|
|
return;
|
|
end
|
|
|
|
if(actions.do_message and actions.message_type and actions.message) then
|
|
local customFunc = WeakAuras.customActionsFunctions[data.id][when .. "_message"];
|
|
WeakAuras.HandleChatAction(actions.message_type, actions.message, actions.message_dest, actions.message_channel, actions.r, actions.g, actions.b, region, customFunc);
|
|
end
|
|
|
|
if (actions.stop_sound) then
|
|
if (region.SoundStop) then
|
|
region:SoundStop();
|
|
end
|
|
end
|
|
|
|
if(actions.do_sound and actions.sound) then
|
|
if (region.SoundPlay) then
|
|
region:SoundPlay(actions);
|
|
end
|
|
end
|
|
|
|
if(actions.do_custom and actions.custom) then
|
|
local func = WeakAuras.customActionsFunctions[data.id][when]
|
|
if func then
|
|
WeakAuras.ActivateAuraEnvironment(region.id, region.cloneId, region.state, region.states);
|
|
xpcall(func, geterrorhandler());
|
|
WeakAuras.ActivateAuraEnvironment(nil);
|
|
end
|
|
end
|
|
|
|
-- Apply start glow actions even if squelch_actions is true, but don't apply finish glow actions
|
|
if actions.do_glow then
|
|
WeakAuras.HandleGlowAction(actions, region)
|
|
end
|
|
|
|
-- remove all glows on finish
|
|
if when == "finish" and actions.hide_all_glows and region.active_glows_hidefunc then
|
|
for _, hideFunc in pairs(region.active_glows_hidefunc) do
|
|
hideFunc()
|
|
end
|
|
wipe(region.active_glows_hidefunc)
|
|
end
|
|
if when == "finish" and type(anchor_unitframe_monitor) == "table" then
|
|
anchor_unitframe_monitor[region] = nil
|
|
end
|
|
end
|
|
|
|
local function noopErrorHandler() end
|
|
|
|
local updatingAnimations;
|
|
local last_update = GetTime();
|
|
function WeakAuras.UpdateAnimations()
|
|
WeakAuras.StartProfileSystem("animations");
|
|
local errorHandler = WeakAuras.IsOptionsOpen() and noopErrorHandler or geterrorhandler()
|
|
for groupId, groupRegion in pairs(pending_controls) do
|
|
pending_controls[groupId] = nil;
|
|
groupRegion:DoPositionChildren();
|
|
end
|
|
local time = GetTime();
|
|
local elapsed = time - last_update;
|
|
last_update = time;
|
|
local num = 0;
|
|
for id, anim in pairs(animations) do
|
|
WeakAuras.StartProfileAura(anim.name);
|
|
num = num + 1;
|
|
local finished = false;
|
|
if(anim.duration_type == "seconds") then
|
|
if anim.duration > 0 then
|
|
anim.progress = anim.progress + (elapsed / anim.duration);
|
|
else
|
|
anim.progress = anim.progress + (elapsed / 1);
|
|
end
|
|
if(anim.progress >= 1) then
|
|
anim.progress = 1;
|
|
finished = true;
|
|
end
|
|
elseif(anim.duration_type == "relative") then
|
|
local state = anim.region.state;
|
|
if (not state
|
|
or (state.progressType == "timed" and state.duration < 0.01)
|
|
or (state.progressType == "static" and state.value < 0.01)) then
|
|
anim.progress = 0;
|
|
if(anim.type == "start" or anim.type == "finish") then
|
|
finished = true;
|
|
end
|
|
else
|
|
local relativeProgress = 0;
|
|
if(state.progressType == "static") then
|
|
relativeProgress = state.value / state.total;
|
|
elseif (state.progressType == "timed") then
|
|
relativeProgress = 1 - ((state.expirationTime - time) / state.duration);
|
|
end
|
|
relativeProgress = state.inverse and (1 - relativeProgress) or relativeProgress;
|
|
anim.progress = relativeProgress / anim.duration
|
|
local iteration = math.floor(anim.progress);
|
|
--anim.progress = anim.progress - iteration;
|
|
if not(anim.iteration) then
|
|
anim.iteration = iteration;
|
|
elseif(anim.iteration ~= iteration) then
|
|
anim.iteration = nil;
|
|
finished = true;
|
|
end
|
|
end
|
|
else
|
|
anim.progress = 1;
|
|
end
|
|
local progress = anim.inverse and (1 - anim.progress) or anim.progress;
|
|
progress = anim.easeFunc(progress, anim.easeStrength or 3)
|
|
WeakAuras.ActivateAuraEnvironment(anim.name, anim.cloneId, anim.region.state, anim.region.states);
|
|
if(anim.translateFunc) then
|
|
if (anim.region.SetOffsetAnim) then
|
|
local x, y = anim.translateFunc(progress, 0, 0, anim.dX, anim.dY);
|
|
anim.region:SetOffsetAnim(x, y);
|
|
else
|
|
anim.region:ClearAllPoints();
|
|
local x, y = anim.translateFunc(progress, anim.startX, anim.startY, anim.dX, anim.dY);
|
|
anim.region:SetPoint(anim.selfPoint, anim.anchor, anim.anchorPoint, x, y);
|
|
end
|
|
end
|
|
if(anim.alphaFunc) then
|
|
local alpha = anim.alphaFunc(progress, anim.startAlpha, anim.dAlpha);
|
|
if (anim.region.SetAnimAlpha) then
|
|
anim.region:SetAnimAlpha(alpha);
|
|
else
|
|
anim.region:SetAlpha(alpha);
|
|
end
|
|
end
|
|
if(anim.scaleFunc) then
|
|
local scaleX, scaleY = anim.scaleFunc(progress, 1, 1, anim.scaleX, anim.scaleY);
|
|
if(anim.region.Scale) then
|
|
anim.region:Scale(scaleX, scaleY);
|
|
else
|
|
anim.region:SetWidth(anim.startWidth * scaleX);
|
|
anim.region:SetHeight(anim.startHeight * scaleY);
|
|
end
|
|
end
|
|
if(anim.rotateFunc and anim.region.Rotate) then
|
|
local rotate = anim.rotateFunc(progress, anim.startRotation, anim.rotate);
|
|
anim.region:Rotate(rotate);
|
|
end
|
|
if(anim.colorFunc and anim.region.ColorAnim) then
|
|
local startR, startG, startB, startA = anim.region:GetColor();
|
|
startR, startG, startB, startA = startR or 1, startG or 1, startB or 1, startA or 1;
|
|
local r, g, b, a = anim.colorFunc(progress, startR, startG, startB, startA, anim.colorR, anim.colorG, anim.colorB, anim.colorA);
|
|
anim.region:ColorAnim(r, g, b, a);
|
|
end
|
|
WeakAuras.ActivateAuraEnvironment(nil);
|
|
if(finished) then
|
|
if not(anim.loop) then
|
|
if (anim.region.SetOffsetAnim) then
|
|
anim.region:SetOffsetAnim(0, 0);
|
|
else
|
|
if(anim.startX) then
|
|
anim.region:SetPoint(anim.selfPoint, anim.anchor, anim.anchorPoint, anim.startX, anim.startY);
|
|
end
|
|
end
|
|
if (anim.region.SetAnimAlpha) then
|
|
anim.region:SetAnimAlpha(nil);
|
|
elseif(anim.startAlpha) then
|
|
anim.region:SetAlpha(anim.startAlpha);
|
|
end
|
|
if(anim.startWidth) then
|
|
if(anim.region.Scale) then
|
|
anim.region:Scale(1, 1);
|
|
else
|
|
anim.region:SetWidth(anim.startWidth);
|
|
anim.region:SetHeight(anim.startHeight);
|
|
end
|
|
end
|
|
if(anim.startRotation) then
|
|
if(anim.region.Rotate) then
|
|
anim.region:Rotate(anim.startRotation);
|
|
end
|
|
end
|
|
if(anim.region.ColorAnim) then
|
|
anim.region:ColorAnim(nil);
|
|
end
|
|
animations[id] = nil;
|
|
end
|
|
|
|
if(anim.loop) then
|
|
WeakAuras.Animate(anim.namespace, anim.data, anim.type, anim.anim, anim.region, anim.inverse, anim.onFinished, anim.loop, anim.cloneId);
|
|
elseif(anim.onFinished) then
|
|
anim.onFinished();
|
|
end
|
|
end
|
|
WeakAuras.StopProfileAura(anim.name);
|
|
end
|
|
-- XXX: I tried to have animations only update if there are actually animation data to animate upon.
|
|
-- This caused all start animations to break, and I couldn't figure out why.
|
|
-- May revisit at a later time.
|
|
--[[
|
|
if(num == 0) then
|
|
WeakAuras.debug("Animation stopped", 3);
|
|
frame:SetScript("OnUpdate", nil);
|
|
updatingAnimations = nil;
|
|
updatingAnimations = nil;
|
|
end
|
|
]]--
|
|
|
|
WeakAuras.StopProfileSystem("animations");
|
|
end
|
|
|
|
function WeakAuras.RegisterGroupForPositioning(id, region)
|
|
pending_controls[id] = region
|
|
updatingAnimations = true
|
|
frame:SetScript("OnUpdate", WeakAuras.UpdateAnimations)
|
|
end
|
|
|
|
function WeakAuras.Animate(namespace, data, type, anim, region, inverse, onFinished, loop, cloneId)
|
|
local id = data.id;
|
|
local key = tostring(region);
|
|
local valid;
|
|
if(anim and anim.type == "custom" and (anim.use_translate or anim.use_alpha or (anim.use_scale and region.Scale) or (anim.use_rotate and region.Rotate) or (anim.use_color and region.Color))) then
|
|
valid = true;
|
|
elseif(anim and anim.type == "preset" and anim.preset and anim_presets[anim.preset]) then
|
|
anim = anim_presets[anim.preset];
|
|
valid = true;
|
|
end
|
|
if(valid) then
|
|
local progress, duration, selfPoint, anchor, anchorPoint, startX, startY, startAlpha, startWidth, startHeight, startRotation, easeType, easeStrength;
|
|
local translateFunc, alphaFunc, scaleFunc, rotateFunc, colorFunc, easeFunc;
|
|
if(animations[key]) then
|
|
if(animations[key].type == type and not loop) then
|
|
return "no replace";
|
|
end
|
|
anim.x = anim.x or 0;
|
|
anim.y = anim.y or 0;
|
|
selfPoint, anchor, anchorPoint, startX, startY = animations[key].selfPoint, animations[key].anchor, animations[key].anchorPoint, animations[key].startX, animations[key].startY;
|
|
anim.alpha = anim.alpha or 0;
|
|
startAlpha = animations[key].startAlpha;
|
|
anim.scalex = anim.scalex or 1;
|
|
anim.scaley = anim.scaley or 1;
|
|
startWidth, startHeight = animations[key].startWidth, animations[key].startHeight;
|
|
anim.rotate = anim.rotate or 0;
|
|
startRotation = animations[key].startRotation;
|
|
anim.colorR = anim.colorR or 1;
|
|
anim.colorG = anim.colorG or 1;
|
|
anim.colorB = anim.colorB or 1;
|
|
anim.colorA = anim.colorA or 1;
|
|
else
|
|
anim.x = anim.x or 0;
|
|
anim.y = anim.y or 0;
|
|
if not region.SetOffsetAnim then
|
|
selfPoint, anchor, anchorPoint, startX, startY = region:GetPoint(1);
|
|
end
|
|
anim.alpha = anim.alpha or 0;
|
|
startAlpha = region:GetAlpha();
|
|
anim.scalex = anim.scalex or 1;
|
|
anim.scaley = anim.scaley or 1;
|
|
startWidth, startHeight = region:GetWidth(), region:GetHeight();
|
|
anim.rotate = anim.rotate or 0;
|
|
startRotation = region.GetRotation and region:GetRotation() or 0;
|
|
anim.colorR = anim.colorR or 1;
|
|
anim.colorG = anim.colorG or 1;
|
|
anim.colorB = anim.colorB or 1;
|
|
anim.colorA = anim.colorA or 1;
|
|
end
|
|
|
|
if(anim.use_translate) then
|
|
if not(anim.translateType == "custom" and anim.translateFunc) then
|
|
anim.translateType = anim.translateType or "straightTranslate";
|
|
anim.translateFunc = anim_function_strings[anim.translateType]
|
|
end
|
|
if (anim.translateFunc) then
|
|
translateFunc = WeakAuras.LoadFunction("return " .. anim.translateFunc, id, "translate animation");
|
|
else
|
|
if (region.SetOffsetAnim) then
|
|
region:SetOffsetAnim(0, 0);
|
|
else
|
|
region:SetPoint(selfPoint, anchor, anchorPoint, startX, startY);
|
|
end
|
|
end
|
|
else
|
|
if (region.SetOffsetAnim) then
|
|
region:SetOffsetAnim(0, 0);
|
|
else
|
|
region:SetPoint(selfPoint, anchor, anchorPoint, startX, startY);
|
|
end
|
|
end
|
|
if(anim.use_alpha) then
|
|
if not(anim.alphaType == "custom" and anim.alphaFunc) then
|
|
anim.alphaType = anim.alphaType or "straight";
|
|
anim.alphaFunc = anim_function_strings[anim.alphaType]
|
|
end
|
|
if (anim.alphaFunc) then
|
|
alphaFunc = WeakAuras.LoadFunction("return " .. anim.alphaFunc, id, "alpha animation");
|
|
else
|
|
if (region.SetAnimAlpha) then
|
|
region:SetAnimAlpha(nil);
|
|
else
|
|
region:SetAlpha(startAlpha);
|
|
end
|
|
end
|
|
else
|
|
if (region.SetAnimAlpha) then
|
|
region:SetAnimAlpha(nil);
|
|
else
|
|
region:SetAlpha(startAlpha);
|
|
end
|
|
end
|
|
if(anim.use_scale) then
|
|
if not(anim.scaleType == "custom" and anim.scaleFunc) then
|
|
anim.scaleType = anim.scaleType or "straightScale";
|
|
anim.scaleFunc = anim_function_strings[anim.scaleType]
|
|
end
|
|
if (anim.scaleFunc) then
|
|
scaleFunc = WeakAuras.LoadFunction("return " .. anim.scaleFunc, id, "scale animation");
|
|
else
|
|
region:Scale(1, 1);
|
|
end
|
|
elseif(region.Scale) then
|
|
region:Scale(1, 1);
|
|
end
|
|
if(anim.use_rotate) then
|
|
if not(anim.rotateType == "custom" and anim.rotateFunc) then
|
|
anim.rotateType = anim.rotateType or "straight";
|
|
anim.rotateFunc = anim_function_strings[anim.rotateType]
|
|
end
|
|
if (anim.rotateFunc) then
|
|
rotateFunc = WeakAuras.LoadFunction("return " .. anim.rotateFunc, id, "rotate animation");
|
|
else
|
|
region:Rotate(startRotation);
|
|
end
|
|
elseif(region.Rotate) then
|
|
region:Rotate(startRotation);
|
|
end
|
|
if(anim.use_color) then
|
|
if not(anim.colorType == "custom" and anim.colorFunc) then
|
|
anim.colorType = anim.colorType or "straightColor";
|
|
anim.colorFunc = anim_function_strings[anim.colorType]
|
|
end
|
|
if (anim.colorFunc) then
|
|
colorFunc = WeakAuras.LoadFunction("return " .. anim.colorFunc, id, "color animation");
|
|
else
|
|
region:ColorAnim(nil);
|
|
end
|
|
elseif(region.ColorAnim) then
|
|
region:ColorAnim(nil);
|
|
end
|
|
easeFunc = WeakAuras.anim_ease_functions[anim.easeType or "none"]
|
|
|
|
duration = WeakAuras.ParseNumber(anim.duration) or 0;
|
|
progress = 0;
|
|
if(namespace == "display" and type == "main" and not onFinished and not anim.duration_type == "relative") then
|
|
local data = WeakAuras.GetData(id);
|
|
if(data and data.parent) then
|
|
local parentRegion = regions[data.parent].region;
|
|
if(parentRegion and parentRegion.controlledRegions) then
|
|
for index, regionData in pairs(parentRegion.controlledRegions) do
|
|
local childRegion = regionData.region;
|
|
local childKey = regionData.key;
|
|
if(childKey and childKey ~= tostring(region) and animations[childKey] and animations[childKey].type == "main" and duration == animations[childKey].duration) then
|
|
progress = animations[childKey].progress;
|
|
break;
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local animation = animations[key] or {}
|
|
animations[key] = animation
|
|
|
|
animation.progress = progress
|
|
animation.startX = startX
|
|
animation.startY = startY
|
|
animation.startAlpha = startAlpha
|
|
animation.startWidth = startWidth
|
|
animation.startHeight = startHeight
|
|
animation.startRotation = startRotation
|
|
animation.dX = (anim.use_translate and anim.x)
|
|
animation.dY = (anim.use_translate and anim.y)
|
|
animation.dAlpha = (anim.use_alpha and (anim.alpha - startAlpha))
|
|
animation.scaleX = (anim.use_scale and anim.scalex)
|
|
animation.scaleY = (anim.use_scale and anim.scaley)
|
|
animation.rotate = anim.rotate
|
|
animation.colorR = (anim.use_color and anim.colorR)
|
|
animation.colorG = (anim.use_color and anim.colorG)
|
|
animation.colorB = (anim.use_color and anim.colorB)
|
|
animation.colorA = (anim.use_color and anim.colorA)
|
|
animation.translateFunc = translateFunc
|
|
animation.alphaFunc = alphaFunc
|
|
animation.scaleFunc = scaleFunc
|
|
animation.rotateFunc = rotateFunc
|
|
animation.colorFunc = colorFunc
|
|
animation.region = region
|
|
animation.selfPoint = selfPoint
|
|
animation.anchor = anchor
|
|
animation.anchorPoint = anchorPoint
|
|
animation.duration = duration
|
|
animation.duration_type = anim.duration_type or "seconds"
|
|
animation.inverse = inverse
|
|
animation.easeType = anim.easeType
|
|
animation.easeFunc = easeFunc
|
|
animation.easeStrength = anim.easeStrength
|
|
animation.type = type
|
|
animation.loop = loop
|
|
animation.onFinished = onFinished
|
|
animation.name = id
|
|
animation.cloneId = cloneId or ""
|
|
animation.namespace = namespace;
|
|
animation.data = data;
|
|
animation.anim = anim;
|
|
|
|
if not(updatingAnimations) then
|
|
frame:SetScript("OnUpdate", WeakAuras.UpdateAnimations);
|
|
updatingAnimations = true;
|
|
end
|
|
return true;
|
|
else
|
|
if(animations[key]) then
|
|
if(animations[key].type ~= type or loop) then
|
|
WeakAuras.CancelAnimation(region, true, true, true, true, true);
|
|
end
|
|
end
|
|
return false;
|
|
end
|
|
end
|
|
|
|
function WeakAuras.IsAnimating(region)
|
|
local key = tostring(region);
|
|
local anim = animations[key];
|
|
if(anim) then
|
|
return anim.type;
|
|
else
|
|
return nil;
|
|
end
|
|
end
|
|
|
|
function WeakAuras.CancelAnimation(region, resetPos, resetAlpha, resetScale, resetRotation, resetColor, doOnFinished)
|
|
local key = tostring(region);
|
|
local anim = animations[key];
|
|
|
|
if(anim) then
|
|
if(resetPos) then
|
|
if (anim.region.SetOffsetAnim) then
|
|
anim.region:SetOffsetAnim(0, 0);
|
|
else
|
|
anim.region:ClearAllPoints();
|
|
anim.region:SetPoint(anim.selfPoint, anim.anchor, anim.anchorPoint, anim.startX, anim.startY);
|
|
end
|
|
end
|
|
if(resetAlpha) then
|
|
if (anim.region.SetAnimAlpha) then
|
|
anim.region:SetAnimAlpha(nil);
|
|
else
|
|
anim.region:SetAlpha(anim.startAlpha);
|
|
end
|
|
end
|
|
if(resetScale) then
|
|
if(anim.region.Scale) then
|
|
anim.region:Scale(1, 1);
|
|
else
|
|
anim.region:SetWidth(anim.startWidth);
|
|
anim.region:SetHeight(anim.startHeight);
|
|
end
|
|
end
|
|
if(resetRotation and anim.region.Rotate) then
|
|
anim.region:Rotate(anim.startRotation);
|
|
end
|
|
if(resetColor and anim.region.ColorAnim) then
|
|
anim.region:ColorAnim(nil);
|
|
end
|
|
|
|
animations[key] = nil;
|
|
if(doOnFinished and anim.onFinished) then
|
|
anim.onFinished();
|
|
end
|
|
return true;
|
|
else
|
|
return false;
|
|
end
|
|
end
|
|
|
|
function WeakAuras.GetData(id)
|
|
return id and db.displays[id];
|
|
end
|
|
|
|
function WeakAuras.GetTriggerSystem(data, triggernum)
|
|
local triggerType = data.triggers[triggernum] and data.triggers[triggernum].trigger.type
|
|
return triggerType and triggerTypes[triggerType]
|
|
end
|
|
|
|
local function wrapTriggerSystemFunction(functionName, mode)
|
|
local func;
|
|
func = function(data, triggernum, ...)
|
|
if (not triggernum) then
|
|
return func(data, data.triggers.activeTriggerMode or -1, ...);
|
|
elseif (triggernum < 0) then
|
|
local result;
|
|
if (mode == "or") then
|
|
result = false;
|
|
for i = 1, #data.triggers do
|
|
result = result or func(data, i);
|
|
end
|
|
elseif (mode == "and") then
|
|
result = true;
|
|
for i = 1, #data.triggers do
|
|
result = result and func(data, i);
|
|
end
|
|
elseif (mode == "table") then
|
|
result = {};
|
|
for i = 1, #data.triggers do
|
|
local tmp = func(data, i);
|
|
if (tmp) then
|
|
for k, v in pairs(tmp) do
|
|
result[k] = v;
|
|
end
|
|
end
|
|
end
|
|
elseif (mode == "call") then
|
|
for i = 1, #data.triggers do
|
|
func(data, i, ...);
|
|
end
|
|
elseif (mode == "firstValue") then
|
|
result = nil;
|
|
for i = 1, #data.triggers do
|
|
local tmp = func(data, i);
|
|
if (tmp) then
|
|
result = tmp;
|
|
break;
|
|
end
|
|
end
|
|
elseif (mode == "nameAndIcon") then
|
|
for i = 1, #data.triggers do
|
|
local tmp1, tmp2 = func(data, i);
|
|
if (tmp1) then
|
|
return tmp1, tmp2;
|
|
end
|
|
end
|
|
end
|
|
return result;
|
|
else -- triggernum >= 1
|
|
local triggerSystem = WeakAuras.GetTriggerSystem(data, triggernum);
|
|
if (not triggerSystem) then
|
|
return false
|
|
end
|
|
return triggerSystem[functionName](data, triggernum, ...);
|
|
end
|
|
end
|
|
return func;
|
|
end
|
|
|
|
WeakAuras.CanHaveDuration = wrapTriggerSystemFunction("CanHaveDuration", "firstValue");
|
|
WeakAuras.CanHaveAuto = wrapTriggerSystemFunction("CanHaveAuto", "or");
|
|
WeakAuras.CanHaveClones = wrapTriggerSystemFunction("CanHaveClones", "or");
|
|
WeakAuras.CanHaveTooltip = wrapTriggerSystemFunction("CanHaveTooltip", "or");
|
|
WeakAuras.GetNameAndIcon = wrapTriggerSystemFunction("GetNameAndIcon", "nameAndIcon");
|
|
WeakAuras.GetTriggerDescription = wrapTriggerSystemFunction("GetTriggerDescription", "call");
|
|
|
|
local wrappedGetOverlayInfo = wrapTriggerSystemFunction("GetOverlayInfo", "table");
|
|
|
|
WeakAuras.GetAdditionalProperties = function(data, triggernum, ...)
|
|
local additionalProperties = ""
|
|
for i = 1, #data.triggers do
|
|
local triggerSystem = WeakAuras.GetTriggerSystem(data, i);
|
|
if (triggerSystem) then
|
|
local add = triggerSystem.GetAdditionalProperties(data, i)
|
|
if (add and add ~= "") then
|
|
if additionalProperties ~= "" then
|
|
additionalProperties = additionalProperties .. "\n"
|
|
end
|
|
additionalProperties = additionalProperties .. add;
|
|
end
|
|
end
|
|
end
|
|
|
|
if additionalProperties ~= "" then
|
|
additionalProperties = "\n\n"
|
|
.. L["Additional Trigger Replacements"] .. "\n"
|
|
.. additionalProperties .. "\n\n"
|
|
.. L["The trigger number is optional, and uses the trigger providing dynamic information if not specified."]
|
|
end
|
|
return additionalProperties
|
|
end
|
|
|
|
function WeakAuras.GetOverlayInfo(data, triggernum)
|
|
local overlayInfo;
|
|
if (data.controlledChildren) then
|
|
overlayInfo = {};
|
|
for index, childId in pairs(data.controlledChildren) do
|
|
local childData = WeakAuras.GetData(childId);
|
|
local tmp = wrappedGetOverlayInfo(childData, triggernum);
|
|
if (tmp) then
|
|
for k, v in pairs(tmp) do
|
|
overlayInfo[k] = v;
|
|
end
|
|
end
|
|
end
|
|
else
|
|
overlayInfo = wrappedGetOverlayInfo(data, triggernum);
|
|
end
|
|
return overlayInfo;
|
|
end
|
|
|
|
function WeakAuras.GetTriggerConditions(data)
|
|
local conditions = {};
|
|
for i = 1, #data.triggers do
|
|
local triggerSystem = WeakAuras.GetTriggerSystem(data, i);
|
|
if (triggerSystem) then
|
|
conditions[i] = triggerSystem.GetTriggerConditions(data, i);
|
|
conditions[i] = conditions[i] or {};
|
|
conditions[i].show = {
|
|
display = L["Active"],
|
|
type = "bool",
|
|
test = function(state, needle)
|
|
return (state and state.id and triggerState[state.id].triggers[i] or false) == (needle == 1);
|
|
end
|
|
}
|
|
end
|
|
end
|
|
return conditions;
|
|
end
|
|
|
|
function WeakAuras.CreateFallbackState(id, triggernum)
|
|
fallbacksStates[id] = fallbacksStates[id] or {};
|
|
fallbacksStates[id][triggernum] = fallbacksStates[id][triggernum] or {};
|
|
|
|
local states = fallbacksStates[id][triggernum];
|
|
states[""] = states[""] or {};
|
|
local state = states[""];
|
|
|
|
local data = db.displays[id];
|
|
local triggerSystem = WeakAuras.GetTriggerSystem(data, triggernum);
|
|
if (triggerSystem) then
|
|
triggerSystem.CreateFallbackState(data, triggernum, state)
|
|
state.trigger = data.triggers[triggernum].trigger
|
|
state.triggernum = triggernum
|
|
else
|
|
state.show = true;
|
|
state.changed = true;
|
|
state.progressType = "timed";
|
|
state.duration = 0;
|
|
state.expirationTime = math.huge;
|
|
end
|
|
|
|
state.id = id
|
|
|
|
return states;
|
|
end
|
|
|
|
function WeakAuras.CanShowNameInfo(data)
|
|
if(data.regionType == "aurabar" or data.regionType == "icon" or data.regionType == "text") then
|
|
return true;
|
|
else
|
|
return false;
|
|
end
|
|
end
|
|
|
|
function WeakAuras.CanShowStackInfo(data)
|
|
if(data.regionType == "aurabar" or data.regionType == "icon" or data.regionType == "text") then
|
|
return true;
|
|
else
|
|
return false;
|
|
end
|
|
end
|
|
|
|
function WeakAuras.CorrectSpellName(input)
|
|
local inputId = tonumber(input);
|
|
if(inputId) then
|
|
local name = GetSpellInfo(inputId);
|
|
if(name) then
|
|
return inputId;
|
|
else
|
|
return nil;
|
|
end
|
|
elseif(input) then
|
|
local link;
|
|
if(input:sub(1,1) == "\124") then
|
|
link = input;
|
|
else
|
|
link = GetSpellLink(input);
|
|
end
|
|
if(link) and link ~= "" then
|
|
local itemId = link:match("spell:(%d+)");
|
|
return tonumber(itemId);
|
|
end
|
|
end
|
|
end
|
|
|
|
function WeakAuras.CorrectItemName(input)
|
|
local inputId = tonumber(input);
|
|
if(inputId) then
|
|
return inputId;
|
|
elseif(input) then
|
|
local _, link = GetItemInfo(input);
|
|
if(link) then
|
|
local itemId = link:match("item:(%d+)");
|
|
return tonumber(itemId);
|
|
else
|
|
return nil;
|
|
end
|
|
end
|
|
end
|
|
|
|
local currentTooltipRegion;
|
|
local currentTooltipOwner;
|
|
function WeakAuras.UpdateMouseoverTooltip(region)
|
|
if(region == currentTooltipRegion) then
|
|
WeakAuras.ShowMouseoverTooltip(currentTooltipRegion, currentTooltipOwner);
|
|
end
|
|
end
|
|
|
|
function WeakAuras.ShowMouseoverTooltip(region, owner)
|
|
currentTooltipRegion = region;
|
|
currentTooltipOwner = owner;
|
|
|
|
GameTooltip:SetOwner(owner, "ANCHOR_NONE");
|
|
GameTooltip:SetPoint("LEFT", owner, "RIGHT");
|
|
GameTooltip:ClearLines();
|
|
|
|
local triggerType;
|
|
if (region.state) then
|
|
triggerType = region.state.trigger.type;
|
|
end
|
|
|
|
local triggerSystem = triggerType and triggerTypes[triggerType];
|
|
if (not triggerSystem) then
|
|
GameTooltip:Hide();
|
|
return;
|
|
end
|
|
|
|
if (triggerSystem.SetToolTip(region.state.trigger, region.state)) then
|
|
GameTooltip:Show();
|
|
else
|
|
GameTooltip:Hide();
|
|
end
|
|
end
|
|
|
|
function WeakAuras.HideTooltip()
|
|
currentTooltipRegion = nil;
|
|
currentTooltipOwner = nil;
|
|
GameTooltip:Hide();
|
|
end
|
|
|
|
do
|
|
local hiddenTooltip;
|
|
function WeakAuras.GetHiddenTooltip()
|
|
if not(hiddenTooltip) then
|
|
hiddenTooltip = CreateFrame("GameTooltip", "WeakAurasTooltip", nil, "GameTooltipTemplate");
|
|
hiddenTooltip:SetOwner(WorldFrame, "ANCHOR_NONE");
|
|
end
|
|
return hiddenTooltip;
|
|
end
|
|
end
|
|
|
|
function WeakAuras.GetAuraTooltipInfo(unit, index, filter)
|
|
local tooltip = WeakAuras.GetHiddenTooltip();
|
|
tooltip:ClearLines();
|
|
tooltip:SetUnitAura(unit, index, filter);
|
|
local tooltipTextLine = select(5, tooltip:GetRegions())
|
|
|
|
local tooltipText = tooltipTextLine and tooltipTextLine:GetObjectType() == "FontString" and tooltipTextLine:GetText() or "";
|
|
local debuffType = "none";
|
|
local found = false;
|
|
local tooltipSize = {};
|
|
if(tooltipText) then
|
|
for t in tooltipText:gmatch("(%d[%d%.,]*)") do
|
|
if (LARGE_NUMBER_SEPERATOR == ",") then
|
|
t = t:gsub(",", "");
|
|
else
|
|
t = t:gsub("%.", "");
|
|
t = t:gsub(",", ".");
|
|
end
|
|
tinsert(tooltipSize, tonumber(t));
|
|
end
|
|
end
|
|
|
|
if (#tooltipSize) then
|
|
return tooltipText, debuffType, unpack(tooltipSize);
|
|
else
|
|
return tooltipText, debuffType, 0;
|
|
end
|
|
end
|
|
|
|
local FrameTimes = {};
|
|
function WeakAuras.ProfileFrames(all)
|
|
UpdateAddOnCPUUsage();
|
|
for name, frame in pairs(WeakAuras.frames) do
|
|
local FrameTime = GetFrameCPUUsage(frame);
|
|
FrameTimes[name] = FrameTimes[name] or 0;
|
|
if(all or FrameTime > FrameTimes[name]) then
|
|
print("|cFFFF0000"..name.."|r -", FrameTime, "-", FrameTime - FrameTimes[name]);
|
|
end
|
|
FrameTimes[name] = FrameTime;
|
|
end
|
|
end
|
|
|
|
local DisplayTimes = {};
|
|
function WeakAuras.ProfileDisplays(all)
|
|
UpdateAddOnCPUUsage();
|
|
for id, regionData in pairs(WeakAuras.regions) do
|
|
local DisplayTime = GetFrameCPUUsage(regionData.region, true);
|
|
DisplayTimes[id] = DisplayTimes[id] or 0;
|
|
if(all or DisplayTime > DisplayTimes[id]) then
|
|
print("|cFFFF0000"..id.."|r -", DisplayTime, "-", DisplayTime - DisplayTimes[id]);
|
|
end
|
|
DisplayTimes[id] = DisplayTime;
|
|
end
|
|
end
|
|
|
|
function WeakAuras.RegisterTutorial(name, displayName, description, icon, steps, order)
|
|
tutorials[name] = {
|
|
name = name,
|
|
displayName = displayName,
|
|
description = description,
|
|
icon = icon,
|
|
steps = steps,
|
|
order = order
|
|
};
|
|
end
|
|
|
|
function WeakAuras.ValueFromPath(data, path)
|
|
if not data then
|
|
return nil
|
|
end
|
|
if(#path == 1) then
|
|
return data[path[1]];
|
|
else
|
|
local reducedPath = {};
|
|
for i=2,#path do
|
|
reducedPath[i-1] = path[i];
|
|
end
|
|
return WeakAuras.ValueFromPath(data[path[1]], reducedPath);
|
|
end
|
|
end
|
|
|
|
function WeakAuras.ValueToPath(data, path, value)
|
|
if not data then
|
|
return
|
|
end
|
|
if(#path == 1) then
|
|
data[path[1]] = value;
|
|
else
|
|
local reducedPath = {};
|
|
for i=2,#path do
|
|
reducedPath[i-1] = path[i];
|
|
end
|
|
WeakAuras.ValueToPath(data[path[1]], reducedPath, value);
|
|
end
|
|
end
|
|
|
|
function WeakAuras.FixGroupChildrenOrderForGroup(data)
|
|
local frameLevel = 5;
|
|
for i=1, #data.controlledChildren do
|
|
WeakAuras.SetFrameLevel(data.controlledChildren[i], frameLevel);
|
|
frameLevel = frameLevel + 4;
|
|
end
|
|
end
|
|
|
|
WeakAuras.frameLevels = {};
|
|
function WeakAuras.SetFrameLevel(id, frameLevel)
|
|
if (WeakAuras.frameLevels[id] == frameLevel) then
|
|
return;
|
|
end
|
|
if (WeakAuras.regions[id] and WeakAuras.regions[id].region) then
|
|
WeakAuras.ApplyFrameLevel(WeakAuras.regions[id].region, frameLevel)
|
|
end
|
|
if (clones[id]) then
|
|
for i,v in pairs(clones[id]) do
|
|
WeakAuras.ApplyFrameLevel(v, frameLevel)
|
|
end
|
|
end
|
|
WeakAuras.frameLevels[id] = frameLevel;
|
|
end
|
|
|
|
function WeakAuras.GetFrameLevelFor(id)
|
|
return WeakAuras.frameLevels[id] or 5;
|
|
end
|
|
|
|
function WeakAuras.ApplyFrameLevel(region, frameLevel)
|
|
frameLevel = frameLevel or WeakAuras.GetFrameLevelFor(region.id)
|
|
region:SetFrameLevel(frameLevel)
|
|
if region.subRegions then
|
|
for index, subRegion in pairs(region.subRegions) do
|
|
subRegion:SetFrameLevel(frameLevel + index + 1)
|
|
end
|
|
end
|
|
end
|
|
|
|
function WeakAuras.EnsureString(input)
|
|
if (input == nil) then
|
|
return "";
|
|
end
|
|
return tostring(input);
|
|
end
|
|
|
|
-- Handle coroutines
|
|
local dynFrame = {};
|
|
do
|
|
-- Internal data
|
|
dynFrame.frame = CreateFrame("frame");
|
|
dynFrame.update = {};
|
|
dynFrame.size = 0;
|
|
|
|
-- Add an action to be resumed via OnUpdate
|
|
function dynFrame.AddAction(self, name, func)
|
|
if not name then
|
|
name = string.format("NIL", dynFrame.size+1);
|
|
end
|
|
|
|
if not dynFrame.update[name] then
|
|
dynFrame.update[name] = func;
|
|
dynFrame.size = dynFrame.size + 1
|
|
dynFrame.frame:Show();
|
|
end
|
|
end
|
|
|
|
-- Remove an action from OnUpdate
|
|
function dynFrame.RemoveAction(self, name)
|
|
if dynFrame.update[name] then
|
|
dynFrame.update[name] = nil;
|
|
dynFrame.size = dynFrame.size - 1
|
|
if dynFrame.size == 0 then
|
|
dynFrame.frame:Hide();
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Setup frame
|
|
dynFrame.frame:Hide();
|
|
dynFrame.frame:SetScript("OnUpdate", function(self, elapsed)
|
|
-- Start timing
|
|
local start = debugprofilestop();
|
|
local hasData = true;
|
|
|
|
-- Resume as often as possible (Limit to 16ms per frame -> 60 FPS)
|
|
while (debugprofilestop() - start < 16 and hasData) do
|
|
-- Stop loop without data
|
|
hasData = false;
|
|
|
|
-- Resume all coroutines
|
|
for name, func in pairs(dynFrame.update) do
|
|
-- Loop has data
|
|
hasData = true;
|
|
|
|
-- Resume or remove
|
|
if coroutine.status(func) ~= "dead" then
|
|
local ok, msg = coroutine.resume(func)
|
|
if not ok then
|
|
geterrorhandler()(msg .. '\n' .. debugstack(func))
|
|
end
|
|
else
|
|
dynFrame:RemoveAction(name);
|
|
end
|
|
end
|
|
end
|
|
end);
|
|
end
|
|
|
|
WeakAuras.dynFrame = dynFrame;
|
|
|
|
function WeakAuras.SetDynamicIconCache(name, spellId, icon)
|
|
db.dynamicIconCache[name] = db.dynamicIconCache[name] or {};
|
|
db.dynamicIconCache[name][spellId] = icon;
|
|
end
|
|
|
|
function WeakAuras.GetDynamicIconCache(name)
|
|
if (db.dynamicIconCache[name]) then
|
|
local fallback = nil;
|
|
for spellId, icon in pairs(db.dynamicIconCache[name]) do
|
|
fallback = icon;
|
|
if (type(spellId) == "number" and IsSpellKnown(spellId)) then -- TODO save this information?
|
|
return db.dynamicIconCache[name][spellId];
|
|
end
|
|
end
|
|
return fallback;
|
|
end
|
|
|
|
if WeakAuras.spellCache then
|
|
return WeakAuras.spellCache.GetIcon(name);
|
|
end
|
|
return nil;
|
|
end
|
|
|
|
function WeakAuras.RegisterTriggerSystem(types, triggerSystem)
|
|
for _, v in ipairs(types) do
|
|
triggerTypes[v] = triggerSystem;
|
|
end
|
|
tinsert(triggerSystems, triggerSystem);
|
|
end
|
|
|
|
function WeakAuras.RegisterTriggerSystemOptions(types, func)
|
|
for _, v in ipairs(types) do
|
|
triggerTypesOptions[v] = func;
|
|
end
|
|
end
|
|
|
|
function WeakAuras.GetTriggerStateForTrigger(id, triggernum)
|
|
if (triggernum == -1) then
|
|
return WeakAuras.GetGlobalConditionState();
|
|
end
|
|
triggerState[id][triggernum] = triggerState[id][triggernum] or {}
|
|
return triggerState[id][triggernum];
|
|
end
|
|
|
|
|
|
do
|
|
local visibleFakeStates = {}
|
|
|
|
local UpdateFakeTimesHandle
|
|
|
|
local function UpdateFakeTimers()
|
|
local t = GetTime()
|
|
for id, triggers in pairs(triggerState) do
|
|
local changed = false
|
|
for triggernum, triggerData in ipairs(triggers) do
|
|
for id, state in pairs(triggerData) do
|
|
if state.progressType == "timed" and state.expirationTime and state.expirationTime < t and state.duration and state.duration > 0 then
|
|
state.expirationTime = state.expirationTime + state.duration
|
|
state.changed = true
|
|
changed = true
|
|
end
|
|
end
|
|
end
|
|
if changed then
|
|
WeakAuras.UpdatedTriggerState(id)
|
|
end
|
|
end
|
|
end
|
|
|
|
function WeakAuras.SetFakeStates()
|
|
if UpdateFakeTimesHandle then
|
|
return
|
|
end
|
|
|
|
for id, states in pairs(triggerState) do
|
|
local changed
|
|
for triggernum in ipairs(states) do
|
|
changed = WeakAuras.SetAllStatesHidden(id, triggernum) or changed
|
|
end
|
|
if changed then
|
|
WeakAuras.UpdatedTriggerState(id)
|
|
end
|
|
end
|
|
UpdateFakeTimesHandle = timer:ScheduleRepeatingTimer(UpdateFakeTimers, 1)
|
|
end
|
|
|
|
function WeakAuras.ClearFakeStates()
|
|
timer:CancelTimer(UpdateFakeTimesHandle)
|
|
for id in pairs(triggerState) do
|
|
WeakAuras.FakeStatesFor(id, false)
|
|
end
|
|
end
|
|
|
|
function WeakAuras.FakeStatesFor(id, visible)
|
|
if visibleFakeStates[id] == visible then
|
|
return visibleFakeStates[id]
|
|
end
|
|
if visible then
|
|
visibleFakeStates[id] = true
|
|
WeakAuras.UpdateFakeStatesFor(id)
|
|
else
|
|
visibleFakeStates[id] = false
|
|
if triggerState[id] then
|
|
local changed = false
|
|
for triggernum, state in ipairs(triggerState[id]) do
|
|
changed = WeakAuras.SetAllStatesHidden(id, triggernum) or changed
|
|
end
|
|
if changed then
|
|
WeakAuras.UpdatedTriggerState(id)
|
|
end
|
|
end
|
|
end
|
|
return not visibleFakeStates[id]
|
|
end
|
|
|
|
function WeakAuras.UpdateFakeStatesFor(id)
|
|
if (WeakAuras.IsOptionsOpen() and visibleFakeStates[id]) then
|
|
local data = WeakAuras.GetData(id)
|
|
if (data) then
|
|
for triggernum, trigger in ipairs(data.triggers) do
|
|
WeakAuras.SetAllStatesHidden(id, triggernum)
|
|
local triggerSystem = WeakAuras.GetTriggerSystem(data, triggernum)
|
|
if triggerSystem and triggerSystem.CreateFakeStates then
|
|
triggerSystem.CreateFakeStates(id, triggernum)
|
|
end
|
|
end
|
|
WeakAuras.UpdatedTriggerState(id)
|
|
if WeakAuras.GetMoverSizerId() == id then
|
|
WeakAuras.SetMoverSizer(id)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local function startStopTimers(id, cloneId, triggernum, state)
|
|
if (state.show) then
|
|
if (state.autoHide and state.duration and state.duration > 0) then -- autohide, update timer
|
|
timers[id] = timers[id] or {};
|
|
timers[id][triggernum] = timers[id][triggernum] or {};
|
|
timers[id][triggernum][cloneId] = timers[id][triggernum][cloneId] or {};
|
|
local record = timers[id][triggernum][cloneId];
|
|
if (state.expirationTime == nil) then
|
|
state.expirationTime = GetTime() + state.duration;
|
|
end
|
|
if (record.expirationTime ~= state.expirationTime or record.state ~= state) then
|
|
if (record.handle ~= nil) then
|
|
timer:CancelTimer(record.handle);
|
|
end
|
|
|
|
record.handle = timer:ScheduleTimerFixed(
|
|
function()
|
|
if (state.show ~= false and state.show ~= nil) then
|
|
state.show = false;
|
|
state.changed = true;
|
|
WeakAuras.UpdatedTriggerState(id);
|
|
end
|
|
end,
|
|
state.expirationTime - GetTime());
|
|
record.expirationTime = state.expirationTime;
|
|
record.state = state
|
|
end
|
|
else -- no auto hide, delete timer
|
|
if (timers[id] and timers[id][triggernum] and timers[id][triggernum][cloneId]) then
|
|
local record = timers[id][triggernum][cloneId];
|
|
if (record.handle) then
|
|
timer:CancelTimer(record.handle);
|
|
end
|
|
record.handle = nil;
|
|
record.expirationTime = nil;
|
|
record.state = nil
|
|
end
|
|
end
|
|
else -- not shown
|
|
if(timers[id] and timers[id][triggernum] and timers[id][triggernum][cloneId]) then
|
|
local record = timers[id][triggernum][cloneId];
|
|
if (record.handle) then
|
|
timer:CancelTimer(record.handle);
|
|
end
|
|
record.handle = nil;
|
|
record.expirationTime = nil;
|
|
record.state = nil
|
|
end
|
|
end
|
|
end
|
|
|
|
local function ApplyStateToRegion(id, cloneId, region, parent)
|
|
region:Update();
|
|
|
|
region.subRegionEvents:Notify("Update")
|
|
|
|
WeakAuras.UpdateMouseoverTooltip(region);
|
|
region:Expand();
|
|
if parent and parent.ActivateChild then
|
|
parent:ActivateChild(id, cloneId)
|
|
end
|
|
end
|
|
|
|
-- Fallbacks if the states are empty
|
|
local emptyState = {};
|
|
emptyState[""] = {};
|
|
|
|
local function applyToTriggerStateTriggers(stateShown, id, triggernum)
|
|
if (stateShown and not triggerState[id].triggers[triggernum]) then
|
|
triggerState[id].triggers[triggernum] = true;
|
|
triggerState[id].triggerCount = triggerState[id].triggerCount + 1;
|
|
return true;
|
|
elseif (not stateShown and triggerState[id].triggers[triggernum]) then
|
|
triggerState[id].triggers[triggernum] = false;
|
|
triggerState[id].triggerCount = triggerState[id].triggerCount - 1;
|
|
return true;
|
|
end
|
|
return false;
|
|
end
|
|
|
|
local function evaluateTriggerStateTriggers(id)
|
|
local result = false;
|
|
WeakAuras.ActivateAuraEnvironment(id);
|
|
|
|
if WeakAuras.IsOptionsOpen() then
|
|
-- While the options are open ignore the combination function
|
|
return triggerState[id].triggerCount > 0
|
|
end
|
|
|
|
if (triggerState[id].disjunctive == "any" and triggerState[id].triggerCount > 0) then
|
|
result = true;
|
|
elseif(triggerState[id].disjunctive == "all" and triggerState[id].triggerCount == triggerState[id].numTriggers) then
|
|
result = true;
|
|
else
|
|
if (triggerState[id].disjunctive == "custom" and triggerState[id].triggerLogicFunc) then
|
|
result = triggerState[id].triggerLogicFunc(triggerState[id].triggers);
|
|
end
|
|
end
|
|
|
|
WeakAuras.ActivateAuraEnvironment(nil);
|
|
return result;
|
|
end
|
|
|
|
local function ApplyStatesToRegions(id, activeTrigger, states)
|
|
-- Show new clones
|
|
local data = WeakAuras.GetData(id)
|
|
local parent
|
|
if data and data.parent then
|
|
parent = WeakAuras.GetRegion(data.parent)
|
|
end
|
|
if parent and parent.Suspend then
|
|
parent:Suspend()
|
|
end
|
|
for cloneId, state in pairs(states) do
|
|
if (state.show) then
|
|
local region = WeakAuras.GetRegion(id, cloneId);
|
|
local applyChanges = not region.toShow or state.changed or region.state ~= state
|
|
region.state = state
|
|
region.states = region.states or {}
|
|
local needsTimerTick = false
|
|
for triggernum = -1, triggerState[id].numTriggers do
|
|
local triggerState
|
|
if triggernum == activeTrigger then
|
|
triggerState = state
|
|
else
|
|
local triggerStates = WeakAuras.GetTriggerStateForTrigger(id, triggernum)
|
|
triggerState = triggerStates[cloneId] or triggerStates[""] or {}
|
|
end
|
|
applyChanges = applyChanges or region.states[triggernum] ~= triggerState or (triggerState and triggerState.changed)
|
|
region.states[triggernum] = triggerState
|
|
needsTimerTick = needsTimerTick or (triggerState and triggerState.show and triggerState.progressType == "timed")
|
|
end
|
|
|
|
region:SetTriggerProvidesTimer(needsTimerTick)
|
|
|
|
if (applyChanges) then
|
|
ApplyStateToRegion(id, cloneId, region, parent);
|
|
if (checkConditions[id]) then
|
|
checkConditions[id](region, not state.show);
|
|
end
|
|
end
|
|
end
|
|
end
|
|
if parent and parent.Resume then
|
|
parent:Resume()
|
|
end
|
|
end
|
|
|
|
function WeakAuras.UpdatedTriggerState(id)
|
|
if (not triggerState[id]) then
|
|
return;
|
|
end
|
|
|
|
local changed = false;
|
|
for triggernum = 1, triggerState[id].numTriggers do
|
|
triggerState[id][triggernum] = triggerState[id][triggernum] or {};
|
|
|
|
local anyStateShown = false;
|
|
|
|
for cloneId, state in pairs(triggerState[id][triggernum]) do
|
|
state.trigger = db.displays[id].triggers[triggernum] and db.displays[id].triggers[triggernum].trigger;
|
|
state.triggernum = triggernum;
|
|
state.id = id;
|
|
|
|
if (state.changed) then
|
|
startStopTimers(id, cloneId, triggernum, state);
|
|
end
|
|
anyStateShown = anyStateShown or state.show;
|
|
end
|
|
-- Update triggerState.triggers
|
|
changed = applyToTriggerStateTriggers(anyStateShown, id, triggernum) or changed;
|
|
end
|
|
|
|
-- Figure out whether we should be shown or not
|
|
local show = triggerState[id].show;
|
|
|
|
|
|
if (changed or show == nil) then
|
|
show = evaluateTriggerStateTriggers(id);
|
|
end
|
|
|
|
-- Figure out which subtrigger is active, and if it changed
|
|
local newActiveTrigger = triggerState[id].activeTriggerMode;
|
|
if (newActiveTrigger == WeakAuras.trigger_modes.first_active) then
|
|
-- Mode is first active trigger, so find a active trigger
|
|
for i = 1, triggerState[id].numTriggers do
|
|
if (triggerState[id].triggers[i]) then
|
|
newActiveTrigger = i;
|
|
break;
|
|
end
|
|
end
|
|
end
|
|
|
|
local oldShow = triggerState[id].show;
|
|
triggerState[id].activeTrigger = newActiveTrigger;
|
|
triggerState[id].show = show;
|
|
|
|
local activeTriggerState = WeakAuras.GetTriggerStateForTrigger(id, newActiveTrigger);
|
|
if (not next(activeTriggerState)) then
|
|
if (show) then
|
|
activeTriggerState = WeakAuras.CreateFallbackState(id, newActiveTrigger)
|
|
else
|
|
activeTriggerState = emptyState;
|
|
end
|
|
elseif (show) then
|
|
local needsFallback = true;
|
|
for cloneId, state in pairs(activeTriggerState) do
|
|
if (state.show) then
|
|
needsFallback = false;
|
|
break;
|
|
end
|
|
end
|
|
if (needsFallback) then
|
|
activeTriggerState = WeakAuras.CreateFallbackState(id, newActiveTrigger);
|
|
end
|
|
end
|
|
|
|
local region;
|
|
-- Now apply
|
|
if (show and not oldShow) then -- Hide => Show
|
|
ApplyStatesToRegions(id, newActiveTrigger, activeTriggerState);
|
|
elseif (not show and oldShow) then -- Show => Hide
|
|
for cloneId, clone in pairs(clones[id]) do
|
|
clone:Collapse()
|
|
end
|
|
WeakAuras.regions[id].region:Collapse()
|
|
elseif (show and oldShow) then -- Already shown, update regions
|
|
-- Hide old clones
|
|
for cloneId, clone in pairs(clones[id]) do
|
|
if (not activeTriggerState[cloneId] or not activeTriggerState[cloneId].show) then
|
|
clone:Collapse()
|
|
end
|
|
end
|
|
if (not activeTriggerState[""] or not activeTriggerState[""].show) then
|
|
WeakAuras.regions[id].region:Collapse()
|
|
end
|
|
-- Show new states
|
|
ApplyStatesToRegions(id, newActiveTrigger, activeTriggerState);
|
|
end
|
|
|
|
for triggernum = 1, triggerState[id].numTriggers do
|
|
triggerState[id][triggernum] = triggerState[id][triggernum] or {};
|
|
for cloneId, state in pairs(triggerState[id][triggernum]) do
|
|
if (not state.show) then
|
|
triggerState[id][triggernum][cloneId] = nil;
|
|
end
|
|
state.changed = false;
|
|
end
|
|
end
|
|
end
|
|
|
|
function WeakAuras.RunCustomTextFunc(region, customFunc)
|
|
if not customFunc then
|
|
return nil
|
|
end
|
|
local state = region.state
|
|
WeakAuras.ActivateAuraEnvironment(region.id, region.cloneId, region.state, region.states);
|
|
local progress = WeakAuras.dynamic_texts.p.func(state, region)
|
|
local dur = WeakAuras.dynamic_texts.t.func(state, region)
|
|
local name = WeakAuras.dynamic_texts.n.func(state, region)
|
|
local icon = WeakAuras.dynamic_texts.i.func(state, region)
|
|
local stacks = WeakAuras.dynamic_texts.s.func(state, region)
|
|
|
|
local expirationTime
|
|
local duration
|
|
if state then
|
|
if state.progressType == "timed" then
|
|
expirationTime = state.expirationTime
|
|
duration = state.duration
|
|
else
|
|
expirationTime = state.total
|
|
duration = state.value
|
|
end
|
|
end
|
|
|
|
local custom = {customFunc(expirationTime or math.huge, duration or 0, progress, dur, name, icon, stacks)}
|
|
WeakAuras.ActivateAuraEnvironment(nil)
|
|
return custom
|
|
end
|
|
|
|
local function ReplaceValuePlaceHolders(textStr, region, customFunc, state)
|
|
local regionValues = region.values;
|
|
local value;
|
|
if string.sub(textStr, 1, 1) == "c" then
|
|
local custom
|
|
if customFunc then
|
|
custom = WeakAuras.RunCustomTextFunc(region, customFunc)
|
|
else
|
|
custom = region.values.custom
|
|
end
|
|
|
|
local index = tonumber(textStr:match("^c(%d+)$") or 1)
|
|
if custom then
|
|
value = WeakAuras.EnsureString(custom[index])
|
|
end
|
|
else
|
|
local variable = WeakAuras.dynamic_texts[textStr];
|
|
if (not variable) then
|
|
return nil;
|
|
end
|
|
value = variable.func(state, region)
|
|
end
|
|
return value or "";
|
|
end
|
|
|
|
-- States:
|
|
-- 0 Normal state, text is just appened to result. Can transition to percent start state 1 via %
|
|
-- 1 Percent start state, entered via %. Can transition to via { to braced, via % to normal, AZaz09 to percent rest state
|
|
-- 2 Percent rest state, stay in it via AZaz09, transition to normal on anything else
|
|
-- 3 Braced state, } transitions to normal, everything else stay in braced state
|
|
local function nextState(char, state)
|
|
if state == 0 then -- Normal State
|
|
if char == 37 then -- % sign
|
|
return 1 -- Enter Percent state
|
|
end
|
|
return 0
|
|
elseif state == 1 then -- Percent Start State
|
|
if char == 37 then -- % sign
|
|
return 0 -- Return to normal state
|
|
elseif char == 123 then -- { sign
|
|
return 3 -- Enter Braced state
|
|
elseif (char >= 48 and char <= 57) or (char >= 65 and char <= 90) or (char >= 97 and char <= 122) or char == 46 then
|
|
-- 0-9a-zA-Z or dot character
|
|
return 2 -- Enter Percent rest state
|
|
end
|
|
return 0 -- % followed by non alpha-numeric. Back to normal state
|
|
elseif state == 2 then
|
|
if (char >= 48 and char <= 57) or (char >= 65 and char <= 90) or (char >= 97 and char <= 122) or char == 46 then
|
|
return 2 -- Continue in same state
|
|
end
|
|
if char == 37 then
|
|
return 1 -- End of %, but also start of new %
|
|
end
|
|
return 0 -- Back to normal
|
|
elseif state == 3 then
|
|
if char == 125 then -- } closing brace
|
|
return 0 -- Back to normal
|
|
end
|
|
return 3
|
|
end
|
|
-- Shouldn't happen
|
|
return state
|
|
end
|
|
|
|
local function ContainsPlaceHolders(textStr, symbolFunc)
|
|
if not textStr then
|
|
return false
|
|
end
|
|
|
|
if textStr:find("\\n") then
|
|
return true
|
|
end
|
|
|
|
local endPos = textStr:len();
|
|
local state = 0
|
|
local currentPos = 1
|
|
local start = 1
|
|
while currentPos <= endPos do
|
|
local char = string.byte(textStr, currentPos);
|
|
local nextState = nextState(char, state)
|
|
|
|
if state == 1 then -- Last char was a %
|
|
if char == 123 then
|
|
start = currentPos + 1
|
|
else
|
|
start = currentPos
|
|
end
|
|
elseif state == 2 or state == 3 then
|
|
if nextState == 0 or nextState == 1 then
|
|
local symbol = string.sub(textStr, start, currentPos - 1)
|
|
if symbolFunc(symbol) then
|
|
return true
|
|
end
|
|
end
|
|
end
|
|
|
|
state = nextState
|
|
currentPos = currentPos + 1
|
|
end
|
|
|
|
if state == 2 then
|
|
local symbol = string.sub(textStr, start, currentPos - 1)
|
|
if symbolFunc(symbol) then
|
|
return true
|
|
end
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
function WeakAuras.ContainsCustomPlaceHolder(textStr)
|
|
return ContainsPlaceHolders(textStr, function(symbol)
|
|
return string.match(symbol, "^c%d*$")
|
|
end)
|
|
end
|
|
|
|
function WeakAuras.ContainsPlaceHolders(textStr, toCheck)
|
|
return ContainsPlaceHolders(textStr, function(symbol)
|
|
if symbol:len() == 1 and toCheck:find(symbol, 1, true) then
|
|
return true
|
|
end
|
|
|
|
local _, last = symbol:find("^%d+%.")
|
|
if not last then
|
|
return false
|
|
end
|
|
|
|
symbol = symbol:sub(last + 1)
|
|
if symbol:len() == 1 and toCheck:find(symbol, 1, true) then
|
|
return true
|
|
end
|
|
end)
|
|
end
|
|
|
|
function WeakAuras.ContainsAnyPlaceHolders(textStr)
|
|
return ContainsPlaceHolders(textStr, function(symbol) return true end)
|
|
end
|
|
|
|
local function ValueForSymbol(symbol, region, customFunc, regionState, regionStates)
|
|
local triggerNum, sym = string.match(symbol, "(.+)%.(.+)")
|
|
triggerNum = triggerNum and tonumber(triggerNum)
|
|
if triggerNum and sym then
|
|
if regionStates and regionStates[triggerNum] then
|
|
if regionStates[triggerNum][sym] then
|
|
return tostring(regionStates[triggerNum][sym]) or ""
|
|
else
|
|
local value = ReplaceValuePlaceHolders(sym, region, customFunc, regionStates[triggerNum]);
|
|
return value or ""
|
|
end
|
|
end
|
|
return ""
|
|
elseif regionState and regionState[symbol] then
|
|
return regionState.show and tostring(regionState[symbol]) or ""
|
|
else
|
|
local value = regionState and ReplaceValuePlaceHolders(symbol, region, customFunc, regionState);
|
|
return value or ""
|
|
end
|
|
end
|
|
|
|
function WeakAuras.ReplacePlaceHolders(textStr, region, customFunc)
|
|
local regionValues = region.values;
|
|
local regionState = region.state;
|
|
local regionStates = region.states;
|
|
if (not regionState and not regionValues) then
|
|
return;
|
|
end
|
|
local endPos = textStr:len();
|
|
if (endPos < 2) then
|
|
textStr = textStr:gsub("\\n", "\n");
|
|
return textStr;
|
|
end
|
|
|
|
if (endPos == 2) then
|
|
if string.byte(textStr, 1) == 37 then
|
|
local value = ReplaceValuePlaceHolders(string.sub(textStr, 2), region, customFunc, regionState);
|
|
if (value) then
|
|
textStr = tostring(value);
|
|
end
|
|
end
|
|
textStr = textStr:gsub("\\n", "\n");
|
|
return textStr;
|
|
end
|
|
|
|
local result = ""
|
|
local currentPos = 1 -- Position of the "cursor"
|
|
local state = 0
|
|
local start = 1 -- Start of whatever "word" we are currently considering, doesn't include % or {} symbols
|
|
|
|
while currentPos <= endPos do
|
|
local char = string.byte(textStr, currentPos);
|
|
if state == 0 then -- Normal State
|
|
if char == 37 then -- % sign
|
|
if currentPos > start then
|
|
result = result .. string.sub(textStr, start, currentPos - 1)
|
|
end
|
|
end
|
|
elseif state == 1 then -- Percent Start State
|
|
if char == 37 then
|
|
start = currentPos
|
|
elseif char == 123 then
|
|
start = currentPos + 1
|
|
elseif (char >= 48 and char <= 57) or (char >= 65 and char <= 90) or (char >= 97 and char <= 122) or char == 46 then
|
|
-- 0-9a-zA-Z or dot character
|
|
start = currentPos
|
|
else
|
|
start = currentPos
|
|
end
|
|
elseif state == 2 then -- Percent Rest State
|
|
if (char >= 48 and char <= 57) or (char >= 65 and char <= 90) or (char >= 97 and char <= 122) or char == 46 then
|
|
-- 0-9a-zA-Z or dot character
|
|
else -- End of variable
|
|
local symbol = string.sub(textStr, start, currentPos - 1)
|
|
result = result .. ValueForSymbol(symbol, region, customFunc, regionState, regionStates)
|
|
|
|
if char == 37 then
|
|
else
|
|
start = currentPos
|
|
end
|
|
end
|
|
elseif state == 3 then
|
|
if char == 125 then -- } closing brace
|
|
local symbol = string.sub(textStr, start, currentPos - 1)
|
|
result = result .. ValueForSymbol(symbol, region, customFunc, regionState, regionStates)
|
|
start = currentPos + 1
|
|
end
|
|
end
|
|
state = nextState(char, state)
|
|
currentPos = currentPos + 1
|
|
end
|
|
|
|
if state == 0 and currentPos > start then
|
|
result = result .. string.sub(textStr, start, currentPos - 1)
|
|
elseif state == 2 and currentPos > start then
|
|
local symbol = string.sub(textStr, start, currentPos - 1)
|
|
result = result .. ValueForSymbol(symbol, region, customFunc, regionState, regionStates)
|
|
elseif state == 1 then
|
|
result = result .. "%"
|
|
end
|
|
|
|
textStr = result:gsub("\\n", "\n");
|
|
return textStr;
|
|
end
|
|
|
|
function WeakAuras.IsTriggerActive(id)
|
|
local active = triggerState[id];
|
|
return active and active.show;
|
|
end
|
|
|
|
-- Attach to Cursor/Frames code
|
|
-- Very simple function to convert a hsv angle to a color with
|
|
-- value hardcoded to 1 and saturation hardcoded to 0.75
|
|
local function colorWheel(angle)
|
|
local hh = angle / 60;
|
|
local i = floor(hh);
|
|
local ff = hh - i;
|
|
local p = 0;
|
|
local q = 0.75 * (1.0 - ff);
|
|
local t = 0.75 * ff;
|
|
if (i == 0) then
|
|
return 0.75, t, p;
|
|
elseif (i == 1) then
|
|
return q, 0.75, p;
|
|
elseif (i == 2) then
|
|
return p, 0.75, t;
|
|
elseif (i == 3) then
|
|
return p, q, 0.75;
|
|
elseif (i == 4) then
|
|
return t, p, 0.75;
|
|
else
|
|
return 0.75, p, q;
|
|
end
|
|
end
|
|
|
|
local function xPositionNextToOptions()
|
|
local xOffset;
|
|
local optionsFrame = WeakAuras.OptionsFrame();
|
|
local centerX = (optionsFrame:GetLeft() + optionsFrame:GetRight()) / 2;
|
|
if (centerX > GetScreenWidth() / 2) then
|
|
if (optionsFrame:GetLeft() > 400) then
|
|
xOffset = optionsFrame:GetLeft() - 200;
|
|
else
|
|
xOffset = optionsFrame:GetLeft() / 2;
|
|
end
|
|
else
|
|
if (GetScreenWidth() - optionsFrame:GetRight() > 400 ) then
|
|
xOffset = optionsFrame:GetRight() + 200;
|
|
else
|
|
xOffset = (GetScreenWidth() + optionsFrame:GetRight()) / 2;
|
|
end
|
|
end
|
|
return xOffset;
|
|
end
|
|
|
|
local mouseFrame;
|
|
local function ensureMouseFrame()
|
|
if (mouseFrame) then
|
|
return;
|
|
end
|
|
mouseFrame = CreateFrame("FRAME", "WeakAurasAttachToMouseFrame", UIParent);
|
|
mouseFrame.attachedVisibleFrames = {};
|
|
mouseFrame:SetWidth(1);
|
|
mouseFrame:SetHeight(1);
|
|
|
|
local moverFrame = CreateFrame("FRAME", "WeakAurasMousePointerFrame", mouseFrame);
|
|
mouseFrame.moverFrame = moverFrame;
|
|
moverFrame:SetPoint("TOPLEFT", mouseFrame, "CENTER");
|
|
moverFrame:SetWidth(32);
|
|
moverFrame:SetHeight(32);
|
|
moverFrame:SetFrameStrata("FULLSCREEN"); -- above settings dialog
|
|
|
|
moverFrame:EnableMouse(true)
|
|
moverFrame:SetScript("OnMouseDown", function()
|
|
mouseFrame:SetMovable(true);
|
|
mouseFrame:StartMoving()
|
|
end);
|
|
moverFrame:SetScript("OnMouseUp", function()
|
|
mouseFrame:StopMovingOrSizing();
|
|
mouseFrame:SetMovable(false);
|
|
local xOffset = mouseFrame:GetRight() - GetScreenWidth();
|
|
local yOffset = mouseFrame:GetTop() - GetScreenHeight();
|
|
db.mousePointerFrame = db.mousePointerFrame or {};
|
|
db.mousePointerFrame.xOffset = xOffset;
|
|
db.mousePointerFrame.yOffset = yOffset;
|
|
end);
|
|
moverFrame.colorWheelAnimation = function()
|
|
local angle = ((GetTime() - moverFrame.startTime) % 5) / 5 * 360;
|
|
moverFrame.texture:SetVertexColor(colorWheel(angle));
|
|
end;
|
|
local texture = moverFrame:CreateTexture(nil, "BACKGROUND");
|
|
moverFrame.texture = texture;
|
|
texture:SetAllPoints(moverFrame);
|
|
texture:SetTexture("Interface\\Cursor\\Point");
|
|
|
|
local label = moverFrame:CreateFontString(nil, "BACKGROUND", "GameFontHighlightSmall")
|
|
label:SetJustifyH("LEFT")
|
|
label:SetJustifyV("TOP")
|
|
label:SetPoint("TOPLEFT", moverFrame, "BOTTOMLEFT");
|
|
label:SetText("WeakAuras Anchor");
|
|
|
|
moverFrame:Hide();
|
|
|
|
mouseFrame.OptionsOpened = function()
|
|
if (db.mousePointerFrame) then
|
|
-- Restore from settings
|
|
mouseFrame:ClearAllPoints();
|
|
mouseFrame:SetPoint("TOPRIGHT", UIParent, "TOPRIGHT", db.mousePointerFrame.xOffset, db.mousePointerFrame.yOffset);
|
|
else
|
|
-- Fnd a suitable position
|
|
local optionsFrame = WeakAuras.OptionsFrame();
|
|
local yOffset = (optionsFrame:GetTop() + optionsFrame:GetBottom()) / 2;
|
|
local xOffset = xPositionNextToOptions();
|
|
-- We use the top right, because the main frame usees the top right as the reference too
|
|
mouseFrame:ClearAllPoints();
|
|
mouseFrame:SetPoint("TOPRIGHT", UIParent, "TOPRIGHT", xOffset - GetScreenWidth(), yOffset - GetScreenHeight());
|
|
end
|
|
-- Change the color of the mouse cursor
|
|
moverFrame.startTime = GetTime();
|
|
moverFrame:SetScript("OnUpdate", moverFrame.colorWheelAnimation);
|
|
mouseFrame:SetScript("OnUpdate", nil);
|
|
end
|
|
|
|
mouseFrame.moveWithMouse = function()
|
|
local scale = 1 / UIParent:GetEffectiveScale();
|
|
local x, y = GetCursorPosition();
|
|
mouseFrame:SetPoint("CENTER", UIParent, "BOTTOMLEFT", x * scale, y * scale);
|
|
end
|
|
|
|
mouseFrame.OptionsClosed = function()
|
|
moverFrame:Hide();
|
|
mouseFrame:ClearAllPoints();
|
|
mouseFrame:SetScript("OnUpdate", mouseFrame.moveWithMouse);
|
|
moverFrame:SetScript("OnUpdate", nil);
|
|
wipe(mouseFrame.attachedVisibleFrames);
|
|
end
|
|
|
|
mouseFrame.expand = function(self, id)
|
|
local data = WeakAuras.GetData(id);
|
|
if (data.anchorFrameType == "MOUSE") then
|
|
self.attachedVisibleFrames[id] = true;
|
|
self:updateVisible();
|
|
end
|
|
end
|
|
|
|
mouseFrame.collapse = function(self, id)
|
|
self.attachedVisibleFrames[id] = nil;
|
|
self:updateVisible();
|
|
end
|
|
|
|
mouseFrame.rename = function(self, oldid, newid)
|
|
self.attachedVisibleFrames[newid] = self.attachedVisibleFrames[oldid];
|
|
self.attachedVisibleFrames[oldid] = nil;
|
|
self:updateVisible();
|
|
end
|
|
|
|
mouseFrame.delete = function(self, id)
|
|
self.attachedVisibleFrames[id] = nil;
|
|
self:updateVisible();
|
|
end
|
|
|
|
mouseFrame.anchorFrame = function(self, id, anchorFrameType)
|
|
if (anchorFrameType == "MOUSE") then
|
|
self.attachedVisibleFrames[id] = true;
|
|
else
|
|
self.attachedVisibleFrames[id] = nil;
|
|
end
|
|
self:updateVisible();
|
|
end
|
|
|
|
mouseFrame.updateVisible = function(self)
|
|
if (not WeakAuras.IsOptionsOpen()) then
|
|
return;
|
|
end
|
|
|
|
if (next(self.attachedVisibleFrames)) then
|
|
mouseFrame.moverFrame:Show();
|
|
else
|
|
mouseFrame.moverFrame:Hide();
|
|
end
|
|
end
|
|
|
|
if (WeakAuras.IsOptionsOpen()) then
|
|
mouseFrame:OptionsOpened();
|
|
else
|
|
mouseFrame:OptionsClosed();
|
|
end
|
|
|
|
WeakAuras.mouseFrame = mouseFrame;
|
|
end
|
|
|
|
local postPonedAnchors = {};
|
|
local anchorTimer
|
|
|
|
local function tryAnchorAgain()
|
|
local delayed = postPonedAnchors;
|
|
postPonedAnchors = {};
|
|
anchorTimer = nil;
|
|
|
|
for id, _ in pairs(delayed) do
|
|
local data = WeakAuras.GetData(id);
|
|
local region = WeakAuras.GetRegion(id);
|
|
if (data and region) then
|
|
local parent = frame;
|
|
if (data.parent and regions[data.parent]) then
|
|
parent = regions[data.parent].region;
|
|
end
|
|
WeakAuras.AnchorFrame(data, region, parent);
|
|
end
|
|
end
|
|
end
|
|
|
|
local function postponeAnchor(id)
|
|
postPonedAnchors[id] = true;
|
|
if (not anchorTimer) then
|
|
anchorTimer = timer:ScheduleTimer(tryAnchorAgain, 1);
|
|
end
|
|
end
|
|
|
|
local HiddenFrames = CreateFrame("FRAME", "WeakAurasHiddenFrames")
|
|
HiddenFrames:Hide()
|
|
WeakAuras.HiddenFrames = HiddenFrames
|
|
|
|
local function GetAnchorFrame(data, region, parent)
|
|
local id = region.id
|
|
local anchorFrameType = data.anchorFrameType
|
|
local anchorFrameFrame = data.anchorFrameFrame
|
|
if not id then return end
|
|
|
|
if (mouseFrame) then
|
|
mouseFrame:anchorFrame(id, anchorFrameType);
|
|
end
|
|
|
|
if (anchorFrameType == "SCREEN") then
|
|
return parent;
|
|
end
|
|
|
|
if (anchorFrameType == "MOUSE") then
|
|
ensureMouseFrame();
|
|
mouseFrame:anchorFrame(id, anchorFrameType);
|
|
return mouseFrame;
|
|
end
|
|
|
|
if (anchorFrameType == "UNITFRAME") then
|
|
local unit = region.state.unit
|
|
if unit then
|
|
local frame = WeakAuras.GetUnitFrame(unit) or WeakAuras.HiddenFrames
|
|
if frame then
|
|
anchor_unitframe_monitor = anchor_unitframe_monitor or {}
|
|
anchor_unitframe_monitor[region] = {
|
|
data = data,
|
|
parent = parent,
|
|
frame = frame
|
|
}
|
|
return frame
|
|
end
|
|
end
|
|
end
|
|
|
|
if (anchorFrameType == "SELECTFRAME" and anchorFrameFrame) then
|
|
if(anchorFrameFrame:sub(1, 10) == "WeakAuras:") then
|
|
local frame_name = anchorFrameFrame:sub(11);
|
|
if (frame_name == id) then
|
|
return parent;
|
|
end
|
|
if(regions[frame_name]) then
|
|
return regions[frame_name].region;
|
|
end
|
|
postponeAnchor(id);
|
|
else
|
|
if (WeakAuras.GetSanitizedGlobal(anchorFrameFrame)) then
|
|
return WeakAuras.GetSanitizedGlobal(anchorFrameFrame);
|
|
end
|
|
postponeAnchor(id);
|
|
return parent;
|
|
end
|
|
end
|
|
|
|
if (anchorFrameType == "CUSTOM" and region.customAnchorFunc) then
|
|
WeakAuras.StartProfileSystem("custom region anchor")
|
|
WeakAuras.StartProfileAura(region.id)
|
|
WeakAuras.ActivateAuraEnvironment(region.id, region.cloneId, region.state)
|
|
local ok, frame = xpcall(region.customAnchorFunc, geterrorhandler())
|
|
WeakAuras.ActivateAuraEnvironment()
|
|
WeakAuras.StopProfileSystem("custom region anchor")
|
|
WeakAuras.StopProfileAura(region.id)
|
|
if ok and frame then
|
|
return frame
|
|
elseif WeakAuras.IsOptionsOpen() then
|
|
return parent
|
|
else
|
|
return HiddenFrames
|
|
end
|
|
end
|
|
-- Fallback
|
|
return parent;
|
|
end
|
|
|
|
local anchorFrameDeferred = {}
|
|
|
|
function WeakAuras.AnchorFrame(data, region, parent)
|
|
if data.anchorFrameType == "CUSTOM"
|
|
and (data.regionType == "group" or data.regionType == "dynamicgroup")
|
|
and not WeakAuras.IsLoginFinished()
|
|
and not anchorFrameDeferred[data.id]
|
|
then
|
|
loginQueue[#loginQueue + 1] = {WeakAuras.AnchorFrame, {data, region, parent}}
|
|
anchorFrameDeferred[data.id] = true
|
|
else
|
|
local anchorParent = GetAnchorFrame(data, region, parent);
|
|
if not anchorParent then return end
|
|
if (data.anchorFrameParent or data.anchorFrameParent == nil
|
|
or data.anchorFrameType == "SCREEN" or data.anchorFrameType == "MOUSE") then
|
|
region:SetParent(anchorParent);
|
|
else
|
|
region:SetParent(frame);
|
|
end
|
|
|
|
region:SetAnchor(data.selfPoint, anchorParent, data.anchorPoint);
|
|
|
|
if(data.frameStrata == 1) then
|
|
region:SetFrameStrata(region:GetParent():GetFrameStrata());
|
|
else
|
|
region:SetFrameStrata(WeakAuras.frame_strata_types[data.frameStrata]);
|
|
end
|
|
WeakAuras.ApplyFrameLevel(region)
|
|
anchorFrameDeferred[data.id] = nil
|
|
end
|
|
end
|
|
|
|
function WeakAuras.FindUnusedId(prefix)
|
|
prefix = prefix or "New"
|
|
local num = 2;
|
|
local id = prefix
|
|
while(db.displays[id]) do
|
|
id = prefix .. " " .. num;
|
|
num = num + 1;
|
|
end
|
|
return id
|
|
end
|
|
|
|
function WeakAuras.SetModel(frame, model_path, isUnit, isDisplayInfo)
|
|
if isDisplayInfo then
|
|
frame:SetDisplayInfo(tonumber(model_path))
|
|
elseif isUnit then
|
|
frame.SetUnit(model_path)
|
|
else
|
|
frame:SetModel(model_path)
|
|
end
|
|
end
|
|
|
|
function WeakAuras.IsCLEUSubevent(subevent)
|
|
if WeakAuras.subevent_prefix_types[subevent] then
|
|
return true
|
|
else
|
|
for prefix in pairs(WeakAuras.subevent_prefix_types) do
|
|
if subevent:match(prefix) then
|
|
local suffix = subevent:sub(#prefix + 1)
|
|
if WeakAuras.subevent_suffix_types[suffix] then
|
|
return true
|
|
end
|
|
end
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
|
|
-- SafeToNumber converts a string to number, but only if it fits into a unsigned 32bit integer
|
|
-- The C api often takes only 32bit values, and complains if passed a value outside
|
|
function WeakAuras.SafeToNumber(input)
|
|
local nr = tonumber(input)
|
|
return nr and (nr < 2147483648 and nr > -2147483649) and nr or nil
|
|
end
|
|
|
|
local textSymbols = {
|
|
["{rt1}"] = "|TInterface\\TargetingFrame\\UI-RaidTargetingIcon_1:0|t",
|
|
["{rt2}"] = "|TInterface\\TargetingFrame\\UI-RaidTargetingIcon_2:0|t ",
|
|
["{rt3}"] = "|TInterface\\TargetingFrame\\UI-RaidTargetingIcon_3:0|t ",
|
|
["{rt4}"] = "|TInterface\\TargetingFrame\\UI-RaidTargetingIcon_4:0|t ",
|
|
["{rt5}"] = "|TInterface\\TargetingFrame\\UI-RaidTargetingIcon_5:0|t ",
|
|
["{rt6}"] = "|TInterface\\TargetingFrame\\UI-RaidTargetingIcon_6:0|t ",
|
|
["{rt7}"] = "|TInterface\\TargetingFrame\\UI-RaidTargetingIcon_7:0|t ",
|
|
["{rt8}"] = "|TInterface\\TargetingFrame\\UI-RaidTargetingIcon_8:0|t "
|
|
}
|
|
|
|
function WeakAuras.ReplaceRaidMarkerSymbols(txt)
|
|
local start = 1
|
|
|
|
while true do
|
|
local firstChar = txt:find("{", start, true)
|
|
if not firstChar then
|
|
return txt
|
|
end
|
|
local lastChar = txt:find("}", firstChar, true)
|
|
if not lastChar then
|
|
return txt
|
|
end
|
|
local replace = textSymbols[txt:sub(firstChar, lastChar)]
|
|
if replace then
|
|
txt = txt:sub(1, firstChar - 1) .. replace .. txt:sub(lastChar + 1)
|
|
start = firstChar + #replace
|
|
else
|
|
start = lastChar
|
|
end
|
|
end
|
|
end
|
|
|
|
function WeakAuras.ReplaceLocalizedRaidMarkers(txt)
|
|
local start = 1
|
|
|
|
while true do
|
|
local firstChar = txt:find("{", start, true)
|
|
if not firstChar then
|
|
return txt
|
|
end
|
|
local lastChar = txt:find("}", firstChar, true)
|
|
if not lastChar then
|
|
return txt
|
|
end
|
|
|
|
local symbol = strlower(txt:sub(firstChar + 1, lastChar - 1))
|
|
if ICON_TAG_LIST[symbol] then
|
|
local replace = "rt" .. ICON_TAG_LIST[symbol]
|
|
if replace then
|
|
txt = txt:sub(1, firstChar) .. replace .. txt:sub(lastChar)
|
|
start = firstChar + #replace
|
|
else
|
|
start = lastChar
|
|
end
|
|
else
|
|
start = lastChar
|
|
end
|
|
end
|
|
end
|
|
|
|
local trackableUnits = {}
|
|
trackableUnits["player"] = true
|
|
trackableUnits["target"] = true
|
|
trackableUnits["focus"] = true
|
|
trackableUnits["pet"] = true
|
|
trackableUnits["vehicle"] = true
|
|
|
|
for i = 1, 5 do
|
|
trackableUnits["arena" .. i] = true
|
|
trackableUnits["arenapet" .. i] = true
|
|
end
|
|
|
|
for i = 1, 4 do
|
|
trackableUnits["boss" .. i] = true
|
|
trackableUnits["party" .. i] = true
|
|
trackableUnits["partypet" .. i] = true
|
|
end
|
|
|
|
for i = 1, 40 do
|
|
trackableUnits["raid" .. i] = true
|
|
trackableUnits["raidpet" .. i] = true
|
|
end
|
|
|
|
|
|
function WeakAuras.UntrackableUnit(unit)
|
|
return not trackableUnits[unit]
|
|
end
|