· 6 years ago · Mar 23, 2020, 08:04 PM
1local internalVersion = 26;
2
3-- WoW APIs
4local GetTalentInfo, IsAddOnLoaded, InCombatLockdown = GetTalentInfo, IsAddOnLoaded, InCombatLockdown
5local LoadAddOn, UnitName, GetRealmName, UnitRace, UnitFactionGroup, IsInRaid
6 = LoadAddOn, UnitName, GetRealmName, UnitRace, UnitFactionGroup, IsInRaid
7local UnitClass, UnitExists, UnitGUID, UnitAffectingCombat, GetInstanceInfo, IsInInstance
8 = UnitClass, UnitExists, UnitGUID, UnitAffectingCombat, GetInstanceInfo, IsInInstance
9local UnitIsUnit, GetRaidRosterInfo, GetSpecialization, UnitInVehicle, UnitHasVehicleUI, GetSpellInfo
10 = UnitIsUnit, GetRaidRosterInfo, GetSpecialization, UnitInVehicle, UnitHasVehicleUI, GetSpellInfo
11local SendChatMessage, GetChannelName, UnitInBattleground, UnitInRaid, UnitInParty, GetTime, GetSpellLink, GetItemInfo
12 = SendChatMessage, GetChannelName, UnitInBattleground, UnitInRaid, UnitInParty, GetTime, GetSpellLink, GetItemInfo
13local CreateFrame, IsShiftKeyDown, GetScreenWidth, GetScreenHeight, GetCursorPosition, UpdateAddOnCPUUsage, GetFrameCPUUsage, debugprofilestop
14 = CreateFrame, IsShiftKeyDown, GetScreenWidth, GetScreenHeight, GetCursorPosition, UpdateAddOnCPUUsage, GetFrameCPUUsage, debugprofilestop
15local debugstack, IsSpellKnown, GetFileIDFromPath = debugstack, IsSpellKnown, GetFileIDFromPath
16local GetNumTalentTabs, GetNumTalents = GetNumTalentTabs, GetNumTalents
17
18local ADDON_NAME = "WeakAuras"
19local WeakAuras = WeakAuras
20local versionString = WeakAuras.versionString
21local prettyPrint = WeakAuras.prettyPrint
22
23WeakAurasTimers = setmetatable({}, {__tostring=function() return "WeakAuras" end})
24LibStub("AceTimer-3.0"):Embed(WeakAurasTimers)
25
26WeakAuras.maxTimerDuration = 604800; -- A week, in seconds
27WeakAuras.maxUpTime = 4294967; -- 2^32 / 1000
28
29function WeakAurasTimers:ScheduleTimerFixed(func, delay, ...)
30 if (delay < WeakAuras.maxTimerDuration) then
31 if delay + GetTime() > WeakAuras.maxUpTime then
32 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()))
33 return
34 end
35 return self:ScheduleTimer(func, delay, ...);
36 end
37end
38
39local LDB = LibStub:GetLibrary("LibDataBroker-1.1")
40local LDBIcon = LibStub("LibDBIcon-1.0")
41local LCG = LibStub("LibCustomGlow-1.0")
42local timer = WeakAurasTimers
43WeakAuras.timer = timer
44
45local L = WeakAuras.L
46
47local loginQueue = {}
48local queueshowooc;
49
50function WeakAuras.InternalVersion()
51 return internalVersion;
52end
53
54function WeakAuras.LoadOptions(msg)
55 if not(IsAddOnLoaded("WeakAurasOptions")) then
56 if not WeakAuras.IsLoginFinished() then
57 prettyPrint(WeakAuras.LoginMessage())
58 loginQueue[#loginQueue + 1] = WeakAuras.OpenOptions
59 elseif InCombatLockdown() then
60 -- inform the user and queue ooc
61 prettyPrint(L["Options will finish loading after combat ends."])
62 queueshowooc = msg or "";
63 WeakAuras.frames["Addon Initialization Handler"]:RegisterEvent("PLAYER_REGEN_ENABLED")
64 return false;
65 else
66 local loaded, reason = LoadAddOn("WeakAurasOptions");
67 if not(loaded) then
68 reason = string.lower("|cffff2020" .. _G["ADDON_" .. reason] .. "|r.")
69 print(WeakAuras.printPrefix .. "Options could not be loaded, the addon is " .. reason);
70 return false;
71 end
72 end
73 end
74 return true;
75end
76
77function WeakAuras.OpenOptions(msg)
78 if WeakAuras.NeedToRepairDatabase() then
79 StaticPopup_Show("WEAKAURAS_CONFIRM_REPAIR", nil, nil, {reason = "downgrade"})
80 elseif (WeakAuras.IsLoginFinished() and WeakAuras.LoadOptions(msg)) then
81 WeakAuras.ToggleOptions(msg);
82 end
83end
84
85function WeakAuras.PrintHelp()
86 print(L["Usage:"])
87 print(L["/wa help - Show this message"])
88 print(L["/wa minimap - Toggle the minimap icon"])
89 print(L["/wa pstart - Start profiling"])
90 print(L["/wa pstop - Finish profiling"])
91 print(L["/wa pprint - Show the results from the most recent profiling"])
92 print(L["/wa repair - Repair tool"])
93 print(L["If you require additional assistance, please open a ticket on GitHub or visit our Discord at https://discord.gg/wa2!"])
94end
95
96SLASH_WEAKAURAS1, SLASH_WEAKAURAS2 = "/weakauras", "/wa";
97function SlashCmdList.WEAKAURAS(msg)
98 if not WeakAuras.IsCorrectVersion() then
99 prettyPrint(WeakAuras.wrongTargetMessage)
100 return
101 end
102 msg = string.lower(msg)
103 if msg == "pstart" then
104 WeakAuras.StartProfile();
105 elseif msg == "pstop" then
106 WeakAuras.StopProfile();
107 elseif msg == "pprint" then
108 WeakAuras.PrintProfile();
109 elseif msg == "minimap" then
110 WeakAuras.ToggleMinimap();
111 elseif msg == "help" then
112 WeakAuras.PrintHelp();
113 elseif msg == "repair" then
114 StaticPopup_Show("WEAKAURAS_CONFIRM_REPAIR", nil, nil, {reason = "user"})
115 else
116 WeakAuras.OpenOptions(msg);
117 end
118end
119if not WeakAuras.IsCorrectVersion() then return end
120
121function WeakAuras.ApplyToDataOrChildData(data, func, ...)
122 if data.controlledChildren then
123 for index, childId in ipairs(data.controlledChildren) do
124 local childData = WeakAuras.GetData(childId)
125 func(childData, ...)
126 end
127 return true
128 else
129 func(data, ...)
130 end
131end
132
133function WeakAuras.ToggleMinimap()
134 WeakAurasSaved.minimap.hide = not WeakAurasSaved.minimap.hide
135 if WeakAurasSaved.minimap.hide then
136 LDBIcon:Hide("WeakAuras");
137 prettyPrint(L["Use /wa minimap to show the minimap icon again"])
138 else
139 LDBIcon:Show("WeakAuras");
140 end
141end
142
143BINDING_HEADER_WEAKAURAS = ADDON_NAME
144BINDING_NAME_WEAKAURASTOGGLE = L["Toggle Options Window"]
145BINDING_NAME_WEAKAURASSTARTPROFILING = L["Start Profiling"]
146BINDING_NAME_WEAKAURASSTOPPROFILING = L["Stop Profiling"]
147BINDING_NAME_WEAKAURASPRINTPROFILING = L["Print Profiling Results"]
148
149-- An alias for WeakAurasSaved, the SavedVariables
150-- Noteable properties:
151-- debug: If set to true, WeakAura.debug() outputs messages to the chat frame
152-- displays: All aura settings, keyed on their id
153local db;
154
155local registeredFromAddons;
156-- List of addons that registered displays
157WeakAuras.addons = {};
158local addons = WeakAuras.addons;
159
160-- A list of tutorials, filled in by the WeakAuras_Tutorials addon by calling RegisterTutorial
161WeakAuras.tutorials = {};
162local tutorials = WeakAuras.tutorials;
163
164-- used if an addon tries to register a display under an id that the user already has a display with that id
165WeakAuras.collisions = {};
166local collisions = WeakAuras.collisions;
167
168-- While true no events are handled. E.g. WeakAuras is paused while the Options dialog is open
169local paused = true;
170local importing = false;
171
172-- squelches actions and sounds from auras. is used e.g. to prevent lots of actions/sounds from triggering
173-- on login or after closing the options dialog
174local squelch_actions = true;
175local in_loading_screen = false;
176
177-- Load functions, keyed on id
178local loadFuncs = {};
179-- Load functions for the Options window that ignore various load options
180local loadFuncsForOptions = {};
181-- Mapping of events to ids, contains true if a aura should be checked for a certain event
182local loadEvents = {}
183
184-- Check Conditions Functions, keyed on id
185local checkConditions = {};
186WeakAuras.checkConditions = checkConditions
187
188-- Dynamic Condition functions to run. keyed on event and id
189local dynamicConditions = {};
190
191-- Global Dynamic Condition Funcs, keyed on the event
192local globalDynamicConditionFuncs = {};
193
194-- All regions keyed on id, has properties: region, regionType, also see clones
195WeakAuras.regions = {};
196local regions = WeakAuras.regions;
197WeakAuras.auras = {};
198local auras = WeakAuras.auras;
199WeakAuras.events = {};
200local events = WeakAuras.events;
201
202-- keyed on id, contains bool indicating whether the aura is loaded
203WeakAuras.loaded = {};
204local loaded = WeakAuras.loaded;
205
206WeakAuras.specificBosses = {};
207local specificBosses = WeakAuras.specificBosses;
208WeakAuras.specificUnits = {};
209local specificUnits = WeakAuras.specificUnits;
210
211-- contains regions for clones
212WeakAuras.clones = {};
213local clones = WeakAuras.clones;
214
215-- Unused regions that are kept around for clones
216WeakAuras.clonePool = {};
217local clonePool = WeakAuras.clonePool;
218
219-- One table per regionType, see RegisterRegionType, notable properties: create, modify and default
220WeakAuras.regionTypes = {};
221local regionTypes = WeakAuras.regionTypes;
222
223WeakAuras.subRegionTypes = {}
224local subRegionTypes = WeakAuras.subRegionTypes
225
226-- One table per regionType, see RegisterRegionOptions
227WeakAuras.regionOptions = {};
228local regionOptions = WeakAuras.regionOptions;
229
230WeakAuras.subRegionOptions = {}
231local subRegionOptions = WeakAuras.subRegionOptions
232
233-- Maps from trigger type to trigger system
234WeakAuras.triggerTypes = {};
235local triggerTypes = WeakAuras.triggerTypes;
236
237-- Maps from trigger type to a functin that can create options for the trigger
238WeakAuras.triggerTypesOptions = {};
239local triggerTypesOptions = WeakAuras.triggerTypesOptions;
240
241
242-- Trigger State, updated by trigger systems, then applied to regions by UpdatedTriggerState
243-- keyed on id, triggernum, cloneid
244-- cloneid can be a empty string
245
246-- Noteable properties:
247-- changed: Whether this trigger state was recently changed and its properties
248-- need to be applied to a region. The glue code resets this
249-- after syncing the region to the trigger state
250-- show: Whether the region for this trigger state should be shown
251-- progressType: Either "timed", "static"
252-- duration: The duration if the progressType is timed
253-- expirationTime: The expirationTime if the progressType is timed
254-- autoHide: If the aura should be hidden on expiring
255-- value: The value if the progressType is static
256-- total: The total if the progressType is static
257-- inverse: The static values should be interpreted inversely
258-- name: The name information
259-- icon: The icon information
260-- texture: The texture information
261-- stacks: The stacks information
262-- index: The index of the buff/debuff for the buff trigger system, used to set the tooltip
263-- spellId: spellId of the buff/debuff, used to set the tooltip
264
265WeakAuras.triggerState = {}
266local triggerState = WeakAuras.triggerState;
267
268-- Fallback states
269local fallbacksStates = {};
270
271-- List of all trigger systems, contains each system once
272WeakAuras.triggerSystems = {}
273local triggerSystems = WeakAuras.triggerSystems;
274
275WeakAuras.forceable_events = {};
276
277local from_files = {};
278
279local timers = {}; -- Timers for autohiding, keyed on id, triggernum, cloneid
280WeakAuras.timers = timers;
281
282local loaded_events = {};
283WeakAuras.loaded_events = loaded_events;
284local loaded_auras = {};
285WeakAuras.loaded_auras = loaded_auras;
286
287-- Animations
288WeakAuras.animations = {};
289local animations = WeakAuras.animations;
290WeakAuras.pending_controls = {};
291local pending_controls = WeakAuras.pending_controls;
292
293WeakAuras.frames = {};
294
295WeakAuras.raidUnits = {};
296WeakAuras.partyUnits = {};
297do
298 for i=1,40 do
299 WeakAuras.raidUnits[i] = "raid"..i
300 end
301 for i=1,4 do
302 WeakAuras.partyUnits[i] = "party"..i
303 end
304end
305local playerLevel = UnitLevel("player");
306
307WeakAuras.currentInstanceType = "none"
308
309-- Custom Action Functions, keyed on id, "init" / "start" / "finish"
310WeakAuras.customActionsFunctions = {};
311
312-- Custom Functions used in conditions, keyed on id, condition number, "changes", property number
313WeakAuras.customConditionsFunctions = {};
314
315local anim_function_strings = WeakAuras.anim_function_strings;
316local anim_presets = WeakAuras.anim_presets;
317local load_prototype = WeakAuras.load_prototype;
318
319local levelColors = {
320 [0] = "|cFFFFFFFF",
321 [1] = "|cFF40FF40",
322 [2] = "|cFF6060FF",
323 [3] = "|cFFFF4040"
324};
325
326function WeakAuras.debug(msg, level)
327 if(db.debug) then
328 level = (level and levelColors[level] and level) or 2;
329 msg = (type(msg) == "string" and msg) or (msg and "Invalid debug message of type "..type(msg)) or "Debug message not specified";
330 DEFAULT_CHAT_FRAME:AddMessage(levelColors[level]..msg);
331 end
332end
333local debug = WeakAuras.debug;
334
335function WeakAuras.validate(input, default)
336 for field, defaultValue in pairs(default) do
337 if(type(defaultValue) == "table" and type(input[field]) ~= "table") then
338 input[field] = {};
339 elseif(input[field] == nil) then
340 input[field] = defaultValue;
341 elseif(type(input[field]) ~= type(defaultValue)) then
342 input[field] = defaultValue;
343 end
344 if(type(input[field]) == "table") then
345 WeakAuras.validate(input[field], defaultValue);
346 end
347 end
348end
349
350function WeakAuras.RegisterRegionType(name, createFunction, modifyFunction, default, properties, validate)
351 if not(name) then
352 error("Improper arguments to WeakAuras.RegisterRegionType - name is not defined", 2);
353 elseif(type(name) ~= "string") then
354 error("Improper arguments to WeakAuras.RegisterRegionType - name is not a string", 2);
355 elseif not(createFunction) then
356 error("Improper arguments to WeakAuras.RegisterRegionType - creation function is not defined", 2);
357 elseif(type(createFunction) ~= "function") then
358 error("Improper arguments to WeakAuras.RegisterRegionType - creation function is not a function", 2);
359 elseif not(modifyFunction) then
360 error("Improper arguments to WeakAuras.RegisterRegionType - modification function is not defined", 2);
361 elseif(type(modifyFunction) ~= "function") then
362 error("Improper arguments to WeakAuras.RegisterRegionType - modification function is not a function", 2)
363 elseif not(default) then
364 error("Improper arguments to WeakAuras.RegisterRegionType - default options are not defined", 2);
365 elseif(type(default) ~= "table") then
366 error("Improper arguments to WeakAuras.RegisterRegionType - default options are not a table", 2);
367 elseif(type(default) ~= "table" and type(default) ~= "nil") then
368 error("Improper arguments to WeakAuras.RegisterRegionType - properties options are not a table", 2);
369 elseif(regionTypes[name]) then
370 error("Improper arguments to WeakAuras.RegisterRegionType - region type \""..name.."\" already defined", 2);
371 else
372 regionTypes[name] = {
373 create = createFunction,
374 modify = modifyFunction,
375 default = default,
376 validate = validate,
377 properties = properties,
378 };
379 end
380end
381
382function WeakAuras.RegisterSubRegionType(name, displayName, supportFunction, createFunction, modifyFunction, onAcquire, onRelease, default, addDefaultsForNewAura, properties, supportsAdd)
383 if not(name) then
384 error("Improper arguments to WeakAuras.RegisterSubRegionType - name is not defined", 2);
385 elseif(type(name) ~= "string") then
386 error("Improper arguments to WeakAuras.RegisterSubRegionType - name is not a string", 2);
387 elseif not(displayName) then
388 error("Improper arguments to WeakAuras.RegisterSubRegionType - display name is not defined".." "..name, 2);
389 elseif(type(displayName) ~= "string") then
390 error("Improper arguments to WeakAuras.RegisterSubRegionType - display name is not a string", 2);
391 elseif not(supportFunction) then
392 error("Improper arguments to WeakAuras.RegisterSubRegionType - support function is not defined", 2);
393 elseif(type(supportFunction) ~= "function") then
394 error("Improper arguments to WeakAuras.RegisterSubRegionType - support function is not a function", 2);
395 elseif not(createFunction) then
396 error("Improper arguments to WeakAuras.RegisterSubRegionType - creation function is not defined", 2);
397 elseif(type(createFunction) ~= "function") then
398 error("Improper arguments to WeakAuras.RegisterSubRegionType - creation function is not a function", 2);
399 elseif not(modifyFunction) then
400 error("Improper arguments to WeakAuras.RegisterSubRegionType - modification function is not defined", 2);
401 elseif(type(modifyFunction) ~= "function") then
402 error("Improper arguments to WeakAuras.RegisterSubRegionType - modification function is not a function", 2)
403 elseif not(onAcquire) then
404 error("Improper arguments to WeakAuras.RegisterSubRegionType - onAcquire function is not defined", 2);
405 elseif(type(onAcquire) ~= "function") then
406 error("Improper arguments to WeakAuras.RegisterSubRegionType - onAcquire function is not a function", 2)
407 elseif not(onRelease) then
408 error("Improper arguments to WeakAuras.RegisterSubRegionType - onRelease function is not defined", 2);
409 elseif(type(onRelease) ~= "function") then
410 error("Improper arguments to WeakAuras.RegisterSubRegionType - onRelease function is not a function", 2)
411 elseif not(default) then
412 error("Improper arguments to WeakAuras.RegisterSubRegionType - default options are not defined", 2);
413 elseif(type(default) ~= "table" and type(default) ~= "function") then
414 error("Improper arguments to WeakAuras.RegisterSubRegionType - default options are not a table or a function", 2);
415 elseif(addDefaultsForNewAura and type(addDefaultsForNewAura) ~= "function") then
416 error("Improper arguments to WeakAuras.RegisterSubRegionType - addDefaultsForNewAura function is not nil or a function", 2)
417 elseif(subRegionTypes[name]) then
418 error("Improper arguments to WeakAuras.RegisterSubRegionType - region type \""..name.."\" already defined", 2);
419 else
420 local pool = CreateObjectPool(createFunction)
421
422 subRegionTypes[name] = {
423 displayName = displayName,
424 supports = supportFunction,
425 modify = modifyFunction,
426 default = default,
427 addDefaultsForNewAura = addDefaultsForNewAura,
428 properties = properties,
429 supportsAdd = supportsAdd == nil or supportsAdd,
430 acquire = function()
431 local subRegion = pool:Acquire()
432 onAcquire(subRegion)
433 subRegion.type = name
434 return subRegion
435 end,
436 release = function(subRegion)
437 onRelease(subRegion)
438 pool:Release(subRegion)
439 end
440 };
441 end
442end
443
444function WeakAuras.RegisterRegionOptions(name, createFunction, icon, displayName, createThumbnail, modifyThumbnail, description, templates, getAnchors)
445 if not(name) then
446 error("Improper arguments to WeakAuras.RegisterRegionOptions - name is not defined", 2);
447 elseif(type(name) ~= "string") then
448 error("Improper arguments to WeakAuras.RegisterRegionOptions - name is not a string", 2);
449 elseif not(createFunction) then
450 error("Improper arguments to WeakAuras.RegisterRegionOptions - creation function is not defined", 2);
451 elseif(type(createFunction) ~= "function") then
452 error("Improper arguments to WeakAuras.RegisterRegionOptions - creation function is not a function", 2);
453 elseif not(icon) then
454 error("Improper arguments to WeakAuras.RegisterRegionOptions - icon is not defined", 2);
455 elseif not(type(icon) == "string" or type(icon) == "function") then
456 error("Improper arguments to WeakAuras.RegisterRegionOptions - icon is not a string or a function", 2)
457 elseif not(displayName) then
458 error("Improper arguments to WeakAuras.RegisterRegionOptions - display name is not defined".." "..name, 2);
459 elseif(type(displayName) ~= "string") then
460 error("Improper arguments to WeakAuras.RegisterRegionOptions - display name is not a string", 2);
461 elseif (getAnchors and type(getAnchors) ~= "function") then
462 error("Improper arguments to WeakAuras.RegisterRegionOptions - anchors is not a function", 2);
463 elseif(regionOptions[name]) then
464 error("Improper arguments to WeakAuras.RegisterRegionOptions - region type \""..name.."\" already defined", 2);
465 else
466 if (type(icon) == "function") then
467 -- We only want to create one icon and reparent it as needed
468 icon = icon()
469 icon:Hide()
470 end
471
472 local acquireThumbnail, releaseThumbnail
473 if createThumbnail and modifyThumbnail then
474 local thumbnailPool = CreateObjectPool(createThumbnail)
475 acquireThumbnail = function(parent, data)
476 local thumbnail, newObject = thumbnailPool:Acquire()
477 thumbnail:Show()
478 modifyThumbnail(parent, thumbnail, data)
479 return thumbnail
480 end
481 releaseThumbnail = function(thumbnail)
482 thumbnail:Hide()
483 thumbnailPool:Release(thumbnail)
484 end
485 end
486 regionOptions[name] = {
487 create = createFunction,
488 icon = icon,
489 displayName = displayName,
490 createThumbnail = createThumbnail,
491 modifyThumbnail = modifyThumbnail,
492 acquireThumbnail = acquireThumbnail,
493 releaseThumbnail = releaseThumbnail,
494 description = description,
495 templates = templates,
496 getAnchors = getAnchors
497 };
498 end
499end
500
501function WeakAuras.RegisterSubRegionOptions(name, createFunction, description)
502 if not(name) then
503 error("Improper arguments to WeakAuras.RegisterSubRegionOptions - name is not defined", 2);
504 elseif(type(name) ~= "string") then
505 error("Improper arguments to WeakAuras.RegisterSubRegionOptions - name is not a string", 2);
506 elseif not(createFunction) then
507 error("Improper arguments to WeakAuras.RegisterSubRegionOptions - creation function is not defined", 2);
508 elseif(type(createFunction) ~= "function") then
509 error("Improper arguments to WeakAuras.RegisterSubRegionOptions - creation function is not a function", 2);
510 elseif(subRegionOptions[name]) then
511 error("Improper arguments to WeakAuras.RegisterSubRegionOptions - region type \""..name.."\" already defined", 2);
512 else
513 subRegionOptions[name] = {
514 create = createFunction,
515 description = description,
516 };
517 end
518end
519
520-- This function is replaced in WeakAurasOptions.lua
521function WeakAuras.IsOptionsOpen()
522 return false;
523end
524
525function WeakAuras.ParseNumber(numString)
526 if not(numString and type(numString) == "string") then
527 if(type(numString) == "number") then
528 return numString, "notastring";
529 else
530 return nil;
531 end
532 elseif(numString:sub(-1) == "%") then
533 local percent = tonumber(numString:sub(1, -2));
534 if(percent) then
535 return percent / 100, "percent";
536 else
537 return nil;
538 end
539 else
540 -- Matches any string with two integers separated by a forward slash
541 -- Captures the two integers
542 local _, _, numerator, denominator = numString:find("(%d+)%s*/%s*(%d+)");
543 numerator, denominator = tonumber(numerator), tonumber(denominator);
544 if(numerator and denominator) then
545 if(denominator == 0) then
546 return nil;
547 else
548 return numerator / denominator, "fraction";
549 end
550 else
551 local num = tonumber(numString)
552 if(num) then
553 if(math.floor(num) ~= num) then
554 return num, "decimal";
555 else
556 return num, "whole";
557 end
558 else
559 return nil;
560 end
561 end
562 end
563end
564
565-- Used for the load function, could be simplified a bit
566-- It used to be also used for the generic trigger system
567function WeakAuras.ConstructFunction(prototype, trigger, skipOptional)
568 local input = {"event"};
569 local required = {};
570 local tests = {};
571 local debug = {};
572 local events = {}
573 local init;
574 if(prototype.init) then
575 init = prototype.init(trigger);
576 else
577 init = "";
578 end
579 for index, arg in pairs(prototype.args) do
580 local enable = arg.type ~= "collpase";
581 if(type(arg.enable) == "function") then
582 enable = arg.enable(trigger);
583 elseif type(arg.enable) == "boolean" then
584 enable = arg.enable
585 end
586 if(enable) then
587 local name = arg.name;
588 if not(arg.name or arg.hidden) then
589 tinsert(input, "_");
590 else
591 if(arg.init == "arg") then
592 tinsert(input, name);
593 end
594 if (arg.optional and skipOptional) then
595 -- Do nothing
596 elseif(arg.hidden or arg.type == "tristate" or arg.type == "toggle" or arg.type == "tristatestring"
597 or (arg.type == "multiselect" and trigger["use_"..name] ~= nil)
598 or ((trigger["use_"..name] or arg.required) and trigger[name])) then
599 if(arg.init and arg.init ~= "arg") then
600 init = init.."local "..name.." = "..arg.init.."\n";
601 end
602 local number = trigger[name] and tonumber(trigger[name]);
603 local test;
604 if(arg.type == "tristate") then
605 if(trigger["use_"..name] == false) then
606 test = "(not "..name..")";
607 elseif(trigger["use_"..name]) then
608 if(arg.test) then
609 test = "("..arg.test:format(trigger[name])..")";
610 else
611 test = name;
612 end
613 end
614 elseif(arg.type == "tristatestring") then
615 if(trigger["use_"..name] == false) then
616 test = "("..name.. "~=".. (number or string.format("%q", trigger[name] or "")) .. ")"
617 elseif(trigger["use_"..name]) then
618 test = "("..name.. "==".. (number or string.format("%q", trigger[name] or "")) .. ")"
619 end
620 elseif(arg.type == "multiselect") then
621 if(trigger["use_"..name] == false) then -- multi selection
622 local any = false;
623 if (trigger[name] and trigger[name].multi) then
624 test = "(";
625 for value, _ in pairs(trigger[name].multi) do
626 if not arg.test then
627 test = test..name.."=="..(tonumber(value) or "[["..value.."]]").." or ";
628 else
629 test = test..arg.test:format(tonumber(value) or "[["..value.."]]").." or ";
630 end
631 any = true;
632 end
633 if(any) then
634 test = test:sub(1, -5);
635 else
636 test = "(false";
637 end
638 test = test..")";
639 end
640 elseif(trigger["use_"..name]) then -- single selection
641 local value = trigger[name] and trigger[name].single;
642 if not arg.test then
643 test = trigger[name] and trigger[name].single and "("..name.."=="..(tonumber(value) or "[["..value.."]]")..")";
644 else
645 test = trigger[name] and trigger[name].single and "("..arg.test:format(tonumber(value) or "[["..value.."]]")..")";
646 end
647 end
648 elseif(arg.type == "toggle") then
649 if(trigger["use_"..name]) then
650 if(arg.test) then
651 test = "("..arg.test:format(trigger[name])..")";
652 else
653 test = name;
654 end
655 end
656 elseif (arg.type == "spell") then
657 if arg.showExactOption then
658 test = "("..arg.test:format(trigger[name], tostring(trigger["use_exact_" .. name]) or "false") ..")";
659 else
660 test = "("..arg.test:format(trigger[name])..")";
661 end
662 elseif(arg.test) then
663 test = "("..arg.test:format(trigger[name])..")";
664 elseif(arg.type == "longstring" and trigger[name.."_operator"]) then
665 if(trigger[name.."_operator"] == "==") then
666 test = "("..name.."==[["..trigger[name].."]])";
667 else
668 test = "("..name..":"..trigger[name.."_operator"]:format(trigger[name])..")";
669 end
670 else
671 if(type(trigger[name]) == "table") then
672 trigger[name] = "error";
673 end
674 test = "("..name..(trigger[name.."_operator"] or "==")..(number or "[["..(trigger[name] or "").."]]")..")";
675 end
676 if(arg.required) then
677 tinsert(required, test);
678 else
679 tinsert(tests, test);
680 end
681
682 if test and arg.events then
683 for index, event in ipairs(arg.events) do
684 events[event] = true
685 end
686 end
687
688 if(arg.debug) then
689 tinsert(debug, arg.debug:format(trigger[name]));
690 end
691 end
692 end
693 end
694 end
695
696 local ret = "return function("..table.concat(input, ", ")..")\n";
697 ret = ret..(init or "");
698 ret = ret..(#debug > 0 and table.concat(debug, "\n") or "");
699 ret = ret.."if(";
700 ret = ret..((#required > 0) and table.concat(required, " and ").." and " or "");
701 ret = ret..(#tests > 0 and table.concat(tests, " and ") or "true");
702 ret = ret..") then\n";
703 if(#debug > 0) then
704 ret = ret.."print('ret: true');\n";
705 end
706 ret = ret.."return true else return false end end";
707
708 return ret, events;
709end
710
711function WeakAuras.GetActiveConditions(id, cloneId)
712 triggerState[id].activatedConditions[cloneId] = triggerState[id].activatedConditions[cloneId] or {};
713 return triggerState[id].activatedConditions[cloneId];
714end
715
716local function formatValueForAssignment(vtype, value, pathToCustomFunction)
717 if (value == nil) then
718 value = false;
719 end
720 if (vtype == "bool") then
721 return value and tostring(value) or "false";
722 elseif(vtype == "number") then
723 return value and tostring(value) or "0";
724 elseif (vtype == "list") then
725 return type(value) == "string" and string.format("%q", value) or "nil";
726 elseif(vtype == "color") then
727 if (value and type(value) == "table") then
728 return string.format("{%s, %s, %s, %s}", tostring(value[1]), tostring(value[2]), tostring(value[3]), tostring(value[4]));
729 end
730 return "{1, 1, 1, 1}";
731 elseif(vtype == "chat") then
732 if (value and type(value) == "table") then
733 return string.format("{message_type = %q, message = %q, message_dest = %q, message_channel = %q, message_custom = %s}",
734 tostring(value.message_type), tostring(value.message or ""),
735 tostring(value.message_dest), tostring(value.message_channel),
736 pathToCustomFunction);
737 end
738 elseif(vtype == "sound") then
739 if (value and type(value) == "table") then
740 return string.format("{ sound = %q, sound_channel = %q, sound_path = %q, sound_kit_id = %q, sound_type = %q, %s}",
741 tostring(value.sound or ""), tostring(value.sound_channel or ""), tostring(value.sound_path or ""),
742 tostring(value.sound_kit_id or ""), tostring(value.sound_type or ""),
743 value.sound_repeat and "sound_repeat = " .. tostring(value.sound_repeat) or "nil");
744 end
745 elseif(vtype == "customcode") then
746 return string.format("%s", pathToCustomFunction);
747 end
748 return "nil";
749end
750
751local function formatValueForCall(type, property)
752 if (type == "bool" or type == "number" or type == "list") then
753 return "propertyChanges['" .. property .. "']";
754 elseif (type == "color") then
755 local pcp = "propertyChanges['" .. property .. "']";
756 return pcp .. "[1], " .. pcp .. "[2], " .. pcp .. "[3], " .. pcp .. "[4]";
757 end
758 return "nil";
759end
760
761local conditionChecksTimers = {};
762conditionChecksTimers.recheckTime = {};
763conditionChecksTimers.recheckHandle = {};
764
765function WeakAuras.scheduleConditionCheck(time, id, cloneId)
766 conditionChecksTimers.recheckTime[id] = conditionChecksTimers.recheckTime[id] or {}
767 conditionChecksTimers.recheckHandle[id] = conditionChecksTimers.recheckHandle[id] or {};
768
769 if (conditionChecksTimers.recheckTime[id][cloneId] and conditionChecksTimers.recheckTime[id][cloneId] > time) then
770 timer:CancelTimer(conditionChecksTimers.recheckHandle[id][cloneId]);
771 conditionChecksTimers.recheckHandle[id][cloneId] = nil;
772 end
773
774 if (conditionChecksTimers.recheckHandle[id][cloneId] == nil) then
775 conditionChecksTimers.recheckHandle[id][cloneId] = timer:ScheduleTimerFixed(function()
776 conditionChecksTimers.recheckHandle[id][cloneId] = nil;
777 local region;
778 if(cloneId and cloneId ~= "") then
779 region = clones[id] and clones[id][cloneId];
780 else
781 region = WeakAuras.regions[id].region;
782 end
783 if (region and region.toShow) then
784 checkConditions[id](region);
785 end
786 end, time - GetTime())
787 conditionChecksTimers.recheckTime[id][cloneId] = time;
788 end
789end
790
791WeakAuras.customConditionTestFunctions = {};
792
793local function CreateTestForCondition(input, allConditionsTemplate, usedStates)
794 local trigger = input and input.trigger;
795 local variable = input and input.variable;
796 local op = input and input.op;
797 local value = input and input.value;
798
799 local check = nil;
800 local recheckCode = nil;
801
802 if (variable == "AND" or variable == "OR") then
803 local test = {};
804 if (input.checks) then
805 for i, subcheck in ipairs(input.checks) do
806 local subtest, subrecheckCode = CreateTestForCondition(subcheck, allConditionsTemplate, usedStates);
807 if (subtest) then
808 tinsert(test, "(" .. subtest .. ")");
809 end
810 if (subrecheckCode) then
811 recheckCode = recheckCode or "";
812 recheckCode = recheckCode .. subrecheckCode;
813 end
814 end
815 end
816 if (next(test)) then
817 if (variable == "AND") then
818 check = table.concat(test, " and ");
819 else
820 check = table.concat(test, " or ");
821 end
822 end
823 end
824
825 if (trigger and variable and value) then
826 usedStates[trigger] = true;
827
828 local conditionTemplate = allConditionsTemplate[trigger] and allConditionsTemplate[trigger][variable];
829 local ctype = conditionTemplate and conditionTemplate.type;
830 local test = conditionTemplate and conditionTemplate.test;
831
832 local stateCheck = "state[" .. trigger .. "] and state[" .. trigger .. "].show and ";
833 local stateVariableCheck = "state[" .. trigger .. "]." .. variable .. "~= nil and ";
834 if (test) then
835 if (value) then
836 tinsert(WeakAuras.customConditionTestFunctions, test);
837 local testFunctionNumber = #(WeakAuras.customConditionTestFunctions);
838 local valueString = type(value) == "string" and "[[" .. value .. "]]" or value;
839 local opString = type(op) == "string" and "[[" .. op .. "]]" or op;
840 check = "state and WeakAuras.customConditionTestFunctions[" .. testFunctionNumber .. "](state[" .. trigger .. "], " .. valueString .. ", " .. (opString or "nil") .. ")";
841 end
842 elseif (ctype == "number" and op) then
843 local v = tonumber(value)
844 if (v) then
845 check = stateCheck .. stateVariableCheck .. "state[" .. trigger .. "]." .. variable .. op .. v;
846 end
847 elseif (ctype == "timer" and op) then
848 if (op == "==") then
849 check = stateCheck .. stateVariableCheck .. "abs(state[" .. trigger .. "]." ..variable .. "- now -" .. value .. ") < 0.05";
850 else
851 check = stateCheck .. stateVariableCheck .. "state[" .. trigger .. "]." .. variable .. "- now" .. op .. value;
852 end
853 elseif (ctype == "select" and op) then
854 if (tonumber(value)) then
855 check = stateCheck .. stateVariableCheck .. "state[" .. trigger .. "]." .. variable .. op .. tonumber(value);
856 else
857 check = stateCheck .. stateVariableCheck .. "state[" .. trigger .. "]." .. variable .. op .. "'" .. value .. "'";
858 end
859 elseif (ctype == "bool") then
860 local rightSide = value == 0 and "false" or "true";
861 check = stateCheck .. stateVariableCheck .. "state[" .. trigger .. "]." .. variable .. "==" .. rightSide
862 elseif (ctype == "string") then
863 if(op == "==") then
864 check = stateCheck .. stateVariableCheck .. "state[" .. trigger .. "]." .. variable .. " == [[" .. value .. "]]";
865 elseif (op == "find('%s')") then
866 check = stateCheck .. stateVariableCheck .. "state[" .. trigger .. "]." .. variable .. ":find([[" .. value .. "]], 1, true)";
867 elseif (op == "match('%s')") then
868 check = stateCheck .. stateVariableCheck .. "state[" .. trigger .. "]." .. variable .. ":match([[" .. value .. "]], 1, true)";
869 end
870 end
871
872 if (ctype == "timer" and value) then
873 recheckCode = " nextTime = state[" .. trigger .. "] and state[" .. trigger .. "]." .. variable .. " and (state[" .. trigger .. "]." .. variable .. " -" .. value .. ")\n";
874 recheckCode = recheckCode .. " if (nextTime and (not recheckTime or nextTime < recheckTime) and nextTime >= now) then\n"
875 recheckCode = recheckCode .. " recheckTime = nextTime\n";
876 recheckCode = recheckCode .. " end\n"
877 end
878 end
879
880 return check, recheckCode;
881end
882
883local function CreateCheckCondition(ret, condition, conditionNumber, allConditionsTemplate, debug)
884 local usedStates = {};
885 local check, recheckCode = CreateTestForCondition(condition.check, allConditionsTemplate, usedStates);
886 if (check) then
887 ret = ret .. " state = region.states\n"
888 ret = ret .. " if (" .. check .. ") then\n";
889 ret = ret .. " newActiveConditions[" .. conditionNumber .. "] = true;\n";
890 ret = ret .. " end\n";
891 end
892 if (recheckCode) then
893 ret = ret .. recheckCode;
894 end
895 if (check or recheckCode) then
896 ret = ret .. "\n";
897 end
898 return ret;
899end
900
901local function ParseProperty(property)
902 local subIndex, prop = string.match(property, "^sub%.(%d*).(.*)")
903 if subIndex then
904 return tonumber(subIndex), prop
905 else
906 return nil, property
907 end
908end
909
910local function GetBaseProperty(data, property, start)
911 if (not data) then
912 return nil;
913 end
914
915 local subIndex, prop = ParseProperty(property)
916 if subIndex then
917 return GetBaseProperty(data.subRegions[subIndex], prop, start)
918 end
919
920 start = start or 1;
921 local next = string.find(property, ".", start, true);
922 if (next) then
923 return GetBaseProperty(data[string.sub(property, start, next - 1)], property, next + 1);
924 end
925
926 local key = string.sub(property, start);
927 return data[key] or data[tonumber(key)];
928end
929
930local function CreateDeactivateCondition(ret, condition, conditionNumber, data, properties, usedProperties, debug)
931 if (condition.changes) then
932 ret = ret .. " if (activatedConditions[".. conditionNumber .. "] and not newActiveConditions[" .. conditionNumber .. "]) then\n"
933 if (debug) then ret = ret .. " print('Deactivating condition " .. conditionNumber .. "' )\n"; end
934 for changeNum, change in ipairs(condition.changes) do
935 if (change.property) then
936 local propertyData = properties and properties[change.property]
937 if (propertyData and propertyData.type and propertyData.setter) then
938 usedProperties[change.property] = true;
939 ret = ret .. " propertyChanges['" .. change.property .. "'] = " .. formatValueForAssignment(propertyData.type, GetBaseProperty(data, change.property)) .. "\n";
940 if (debug) then ret = ret .. " print('- " .. change.property .. " " ..formatValueForAssignment(propertyData.type, GetBaseProperty(data, change.property)) .. "')\n"; end
941 end
942 end
943 end
944 ret = ret .. " end\n"
945 end
946
947 return ret;
948end
949
950local function CreateActivateCondition(ret, id, condition, conditionNumber, properties, debug)
951 if (condition.changes) then
952 ret = ret .. " if (newActiveConditions[" .. conditionNumber .. "]) then\n"
953 ret = ret .. " if (not activatedConditions[".. conditionNumber .. "]) then\n"
954 if (debug) then ret = ret .. " print('Activating condition " .. conditionNumber .. "' )\n"; end
955 -- non active => active
956 for changeNum, change in ipairs(condition.changes) do
957 if (change.property) then
958 local propertyData = properties and properties[change.property]
959 if (propertyData and propertyData.type) then
960 if (propertyData.setter) then
961 ret = ret .. " propertyChanges['" .. change.property .. "'] = " .. formatValueForAssignment(propertyData.type, change.value) .. "\n";
962 if (debug) then ret = ret .. " print('- " .. change.property .. " " .. formatValueForAssignment(propertyData.type, change.value) .. "')\n"; end
963 elseif (propertyData.action) then
964 local pathToCustomFunction = "nil";
965 if (WeakAuras.customConditionsFunctions[id]
966 and WeakAuras.customConditionsFunctions[id][conditionNumber]
967 and WeakAuras.customConditionsFunctions[id][conditionNumber].changes
968 and WeakAuras.customConditionsFunctions[id][conditionNumber].changes[changeNum]) then
969 pathToCustomFunction = string.format("WeakAuras.customConditionsFunctions[%q][%s].changes[%s]", id, conditionNumber, changeNum);
970 end
971 ret = ret .. " region:" .. propertyData.action .. "(" .. formatValueForAssignment(propertyData.type, change.value, pathToCustomFunction) .. ")" .. "\n";
972 if (debug) then ret = ret .. " print('# " .. propertyData.action .. "(" .. formatValueForAssignment(propertyData.type, change.value, pathToCustomFunction) .. "')\n"; end
973 end
974 end
975 end
976 end
977 ret = ret .. " else\n"
978 -- active => active, only override properties
979 for changeNum, change in ipairs(condition.changes) do
980 if (change.property) then
981 local propertyData = properties and properties[change.property]
982 if (propertyData and propertyData.type and propertyData.setter) then
983 ret = ret .. " if(propertyChanges['" .. change.property .. "'] ~= nil) then\n"
984 ret = ret .. " propertyChanges['" .. change.property .. "'] = " .. formatValueForAssignment(propertyData.type, change.value) .. "\n";
985 if (debug) then ret = ret .. " print('- " .. change.property .. " " .. formatValueForAssignment(propertyData.type, change.value) .. "')\n"; end
986 ret = ret .. " end\n"
987 end
988 end
989 end
990 ret = ret .. " end\n"
991 ret = ret .. " end\n"
992 ret = ret .. "\n";
993 ret = ret .. " activatedConditions[".. conditionNumber .. "] = newActiveConditions[" .. conditionNumber .. "]\n";
994 end
995
996 return ret;
997end
998
999function WeakAuras.LoadCustomActionFunctions(data)
1000 local id = data.id;
1001 WeakAuras.customActionsFunctions[id] = {};
1002
1003 if (data.actions) then
1004 if (data.actions.init and data.actions.init.do_custom and data.actions.init.custom) then
1005 local func = WeakAuras.LoadFunction("return function() "..(data.actions.init.custom).."\n end", id, "initialization");
1006 WeakAuras.customActionsFunctions[id]["init"] = func;
1007 end
1008
1009 if (data.actions.start) then
1010 if (data.actions.start.do_custom and data.actions.start.custom) then
1011 local func = WeakAuras.LoadFunction("return function() "..(data.actions.start.custom).."\n end", id, "show action");
1012 WeakAuras.customActionsFunctions[id]["start"] = func;
1013 end
1014
1015 if (data.actions.start.do_message and data.actions.start.message_custom) then
1016 local func = WeakAuras.LoadFunction("return "..(data.actions.start.message_custom), id, "show message");
1017 WeakAuras.customActionsFunctions[id]["start_message"] = func;
1018 end
1019 end
1020
1021 if (data.actions.finish) then
1022 if (data.actions.finish.do_custom and data.actions.finish.custom) then
1023 local func = WeakAuras.LoadFunction("return function() "..(data.actions.finish.custom).."\n end", id, "hide action");
1024 WeakAuras.customActionsFunctions[id]["finish"] = func;
1025 end
1026
1027 if (data.actions.finish.do_message and data.actions.finish.message_custom) then
1028 local func = WeakAuras.LoadFunction("return "..(data.actions.finish.message_custom), id, "hide message");
1029 WeakAuras.customActionsFunctions[id]["finish_message"] = func;
1030 end
1031 end
1032 end
1033end
1034
1035function WeakAuras.GetProperties(data)
1036 local properties;
1037 local propertiesFunction = WeakAuras.regionTypes[data.regionType] and WeakAuras.regionTypes[data.regionType].properties;
1038 if (type(propertiesFunction) == "function") then
1039 properties = propertiesFunction(data);
1040 elseif propertiesFunction then
1041 properties = CopyTable(propertiesFunction);
1042 else
1043 properties = {}
1044 end
1045
1046 if data.subRegions then
1047 local subIndex = {}
1048 for index, subRegion in ipairs(data.subRegions) do
1049 local subRegionTypeData = WeakAuras.subRegionTypes[subRegion.type];
1050 local propertiesFunction = subRegionTypeData and subRegionTypeData.properties
1051 local subProperties;
1052 if (type(propertiesFunction) == "function") then
1053 subProperties = propertiesFunction(data, subRegion);
1054 elseif propertiesFunction then
1055 subProperties = CopyTable(propertiesFunction)
1056 end
1057
1058 if subProperties then
1059 for key, property in pairs(subProperties) do
1060 subIndex[key] = subIndex[key] and subIndex[key] + 1 or 1
1061 property.display = { subIndex[key] .. ". " .. subRegionTypeData.displayName, property.display, property.defaultProperty }
1062 properties["sub." .. index .. "." .. key ] = property;
1063 end
1064 end
1065 end
1066 end
1067
1068 return properties;
1069end
1070
1071function WeakAuras.LoadConditionPropertyFunctions(data)
1072 local id = data.id;
1073 if (data.conditions) then
1074 WeakAuras.customConditionsFunctions[id] = {};
1075 for conditionNumber, condition in ipairs(data.conditions) do
1076 if (condition.changes) then
1077 for changeIndex, change in ipairs(condition.changes) do
1078 if ( (change.property == "chat" or change.property == "customcode") and type(change.value) == "table" and change.value.custom) then
1079 local custom = change.value.custom;
1080 local prefix, suffix;
1081 if (change.property == "chat") then
1082 prefix, suffix = "return ", "";
1083 else
1084 prefix, suffix = "return function()", "\nend";
1085 end
1086 local customFunc = WeakAuras.LoadFunction(prefix .. custom .. suffix, id, "condition");
1087 if (customFunc) then
1088 WeakAuras.customConditionsFunctions[id][conditionNumber] = WeakAuras.customConditionsFunctions[id][conditionNumber] or {};
1089 WeakAuras.customConditionsFunctions[id][conditionNumber].changes = WeakAuras.customConditionsFunctions[id][conditionNumber].changes or {};
1090 WeakAuras.customConditionsFunctions[id][conditionNumber].changes[changeIndex] = customFunc;
1091 end
1092 end
1093 end
1094 end
1095 end
1096 end
1097end
1098
1099local globalConditions =
1100{
1101 ["incombat"] = {
1102 display = L["In Combat"],
1103 type = "bool",
1104 events = {"PLAYER_REGEN_ENABLED", "PLAYER_REGEN_DISABLED"},
1105 globalStateUpdate = function(state)
1106 state.incombat = UnitAffectingCombat("player");
1107 end
1108 },
1109 ["hastarget"] = {
1110 display = L["Has Target"],
1111 type = "bool",
1112 events = {"PLAYER_TARGET_CHANGED", "PLAYER_ENTERING_WORLD"},
1113 globalStateUpdate = function(state)
1114 state.hastarget = UnitExists("target");
1115 end
1116 },
1117 ["attackabletarget"] = {
1118 display = L["Attackable Target"],
1119 type = "bool",
1120 events = {"PLAYER_TARGET_CHANGED", "UNIT_FACTION"},
1121 globalStateUpdate = function(state)
1122 state.attackabletarget = UnitCanAttack("player", "target");
1123 end
1124 },
1125}
1126
1127function WeakAuras.GetGlobalConditions()
1128 return globalConditions;
1129end
1130
1131function WeakAuras.ConstructConditionFunction(data)
1132 local debug = false;
1133 if (not data.conditions or #data.conditions == 0) then
1134 return nil;
1135 end
1136
1137 local usedProperties = {};
1138
1139 local allConditionsTemplate = WeakAuras.GetTriggerConditions(data);
1140 allConditionsTemplate[-1] = WeakAuras.GetGlobalConditions();
1141
1142 local ret = "";
1143 ret = ret .. "local newActiveConditions = {};\n"
1144 ret = ret .. "local propertyChanges = {};\n"
1145 ret = ret .. "local nextTime;\n"
1146 ret = ret .. "return function(region, hideRegion)\n";
1147 if (debug) then ret = ret .. " print('check conditions for:', region.id, region.cloneId)\n"; end
1148 ret = ret .. " local id = region.id\n";
1149 ret = ret .. " local cloneId = region.cloneId or ''\n";
1150 ret = ret .. " local activatedConditions = WeakAuras.GetActiveConditions(id, cloneId)\n";
1151 ret = ret .. " wipe(newActiveConditions)\n";
1152 ret = ret .. " local recheckTime;\n"
1153 ret = ret .. " local now = GetTime();\n"
1154
1155 local normalConditionCount = data.conditions and #data.conditions;
1156 -- First Loop gather which conditions are active
1157 ret = ret .. " if (not hideRegion) then\n"
1158 if (data.conditions) then
1159 for conditionNumber, condition in ipairs(data.conditions) do
1160 ret = CreateCheckCondition(ret, condition, conditionNumber, allConditionsTemplate, debug)
1161 end
1162 end
1163 ret = ret .. " end\n";
1164
1165 ret = ret .. " if (recheckTime) then\n"
1166 ret = ret .. " WeakAuras.scheduleConditionCheck(recheckTime, id, cloneId);\n"
1167 ret = ret .. " end\n"
1168
1169 local properties = WeakAuras.GetProperties(data);
1170
1171 -- Now build a property + change list
1172 -- Second Loop deals with conditions that are no longer active
1173 ret = ret .. " wipe(propertyChanges)\n"
1174 if (data.conditions) then
1175 for conditionNumber, condition in ipairs(data.conditions) do
1176 ret = CreateDeactivateCondition(ret, condition, conditionNumber, data, properties, usedProperties, debug)
1177 end
1178 end
1179 ret = ret .. "\n";
1180
1181 -- Third Loop deals with conditions that are newly active
1182 if (data.conditions) then
1183 for conditionNumber, condition in ipairs(data.conditions) do
1184 ret = CreateActivateCondition(ret, data.id, condition, conditionNumber, properties, debug)
1185 end
1186 end
1187
1188 -- Last apply changes to region
1189 for property, _ in pairs(usedProperties) do
1190 ret = ret .. " if(propertyChanges['" .. property .. "'] ~= nil) then\n"
1191 local arg1 = "";
1192 if (properties[property].arg1) then
1193 if (type(properties[property].arg1) == "number") then
1194 arg1 = tostring(properties[property].arg1) .. ", ";
1195 else
1196 arg1 = "'" .. properties[property].arg1 .. "', ";
1197 end
1198 end
1199
1200 local base = "region:"
1201 local subIndex = ParseProperty(property)
1202 if subIndex then
1203 base = "region.subRegions[" .. subIndex .. "]:"
1204 end
1205
1206 ret = ret .. " " .. base .. properties[property].setter .. "(" .. arg1 .. formatValueForCall(properties[property].type, property) .. ")\n";
1207 if (debug) then ret = ret .. " print('Calling " .. properties[property].setter .. " with', " .. arg1 .. formatValueForCall(properties[property].type, property) .. ")\n"; end
1208 ret = ret .. " end\n";
1209 end
1210 ret = ret .. "end\n";
1211
1212 return ret;
1213end
1214
1215
1216local dynamicConditionsFrame = nil;
1217
1218local globalConditionAllState = {
1219 [""] = {
1220 show = true;
1221 }
1222};
1223
1224local globalConditionState = globalConditionAllState[""];
1225
1226function WeakAuras.GetGlobalConditionState()
1227 return globalConditionAllState;
1228end
1229
1230local function runDynamicConditionFunctions(funcs)
1231 for id in pairs(funcs) do
1232 if (triggerState[id] and triggerState[id].show and checkConditions[id]) then
1233 local activeTriggerState = WeakAuras.GetTriggerStateForTrigger(id, triggerState[id].activeTrigger);
1234 for cloneId, state in pairs(activeTriggerState) do
1235 local region = WeakAuras.GetRegion(id, cloneId);
1236 checkConditions[id](region, false);
1237 end
1238 end
1239 end
1240end
1241
1242local function handleDynamicConditions(self, event)
1243 if (globalDynamicConditionFuncs[event]) then
1244 for i, func in ipairs(globalDynamicConditionFuncs[event]) do
1245 func(globalConditionState);
1246 end
1247 end
1248 if (dynamicConditions[event]) then
1249 runDynamicConditionFunctions(dynamicConditions[event]);
1250 end
1251end
1252
1253local lastDynamicConditionsUpdateCheck;
1254local function handleDynamicConditionsOnUpdate(self)
1255 handleDynamicConditions(self, "FRAME_UPDATE");
1256 if (not lastDynamicConditionsUpdateCheck or GetTime() - lastDynamicConditionsUpdateCheck > 0.2) then
1257 lastDynamicConditionsUpdateCheck = GetTime();
1258 handleDynamicConditions(self, "WA_SPELL_RANGECHECK");
1259 end
1260end
1261
1262local registeredGlobalFunctions = {};
1263
1264local function EvaluateCheckForRegisterForGlobalConditions(id, check, allConditionsTemplate, register)
1265 local trigger = check and check.trigger;
1266 local variable = check and check.variable;
1267
1268 if (trigger == -2) then
1269 if (check.checks) then
1270 for _, subcheck in ipairs(check.checks) do
1271 EvaluateCheckForRegisterForGlobalConditions(id, subcheck, allConditionsTemplate, register);
1272 end
1273 end
1274 elseif (trigger and variable) then
1275 local conditionTemplate = allConditionsTemplate[trigger] and allConditionsTemplate[trigger][variable];
1276 if (conditionTemplate and conditionTemplate.events) then
1277 for _, event in ipairs(conditionTemplate.events) do
1278 if (not dynamicConditions[event]) then
1279 register[event] = true;
1280 dynamicConditions[event] = {};
1281 end
1282 dynamicConditions[event][id] = true;
1283 end
1284
1285 if (conditionTemplate.globalStateUpdate and not registeredGlobalFunctions[variable]) then
1286 registeredGlobalFunctions[variable] = true;
1287 for _, event in ipairs(conditionTemplate.events) do
1288 globalDynamicConditionFuncs[event] = globalDynamicConditionFuncs[event] or {};
1289 tinsert(globalDynamicConditionFuncs[event], conditionTemplate.globalStateUpdate);
1290 end
1291 conditionTemplate.globalStateUpdate(globalConditionState);
1292 end
1293 end
1294 end
1295end
1296
1297function WeakAuras.RegisterForGlobalConditions(id)
1298 local data = WeakAuras.GetData(id);
1299 for event, conditonFunctions in pairs(dynamicConditions) do
1300 conditonFunctions.id = nil;
1301 end
1302
1303 local register = {};
1304 if (data.conditions) then
1305 local allConditionsTemplate = WeakAuras.GetTriggerConditions(data);
1306 allConditionsTemplate[-1] = WeakAuras.GetGlobalConditions();
1307
1308 for conditionNumber, condition in ipairs(data.conditions) do
1309 EvaluateCheckForRegisterForGlobalConditions(id, condition.check, allConditionsTemplate, register);
1310 end
1311 end
1312
1313 if (next(register) and not dynamicConditionsFrame) then
1314 dynamicConditionsFrame = CreateFrame("FRAME");
1315 dynamicConditionsFrame:SetScript("OnEvent", handleDynamicConditions);
1316 WeakAuras.frames["Rerun Conditions Frame"] = dynamicConditionsFrame
1317 end
1318
1319 for event in pairs(register) do
1320 if (event == "FRAME_UPDATE" or event == "WA_SPELL_RANGECHECK") then
1321 if (not dynamicConditionsFrame.onUpdate) then
1322 dynamicConditionsFrame:SetScript("OnUpdate", handleDynamicConditionsOnUpdate);
1323 dynamicConditionsFrame.onUpdate = true;
1324 end
1325 else
1326 dynamicConditionsFrame:RegisterEvent(event);
1327 end
1328 end
1329end
1330
1331function WeakAuras.UnregisterForGlobalConditions(id)
1332 for event, condFuncs in pairs(dynamicConditions) do
1333 condFuncs[id] = nil;
1334 end
1335end
1336
1337WeakAuras.talent_types_specific = {}
1338WeakAuras.pvp_talent_types_specific = {}
1339function WeakAuras.CreateTalentCache()
1340 local _, player_class = UnitClass("player")
1341
1342 WeakAuras.talent_types_specific[player_class] = WeakAuras.talent_types_specific[player_class] or {};
1343
1344 if WeakAuras.IsClassic() then
1345 for tab = 1, GetNumTalentTabs() do
1346 for num_talent = 1, GetNumTalents(tab) do
1347 local talentName, talentIcon = GetTalentInfo(tab, num_talent);
1348 local talentId = (tab - 1)*20+num_talent
1349 if (talentName and talentIcon) then
1350 WeakAuras.talent_types_specific[player_class][talentId] = "|T"..talentIcon..":0|t "..talentName
1351 end
1352 end
1353 end
1354 else
1355 local spec = GetSpecialization()
1356 WeakAuras.talent_types_specific[player_class][spec] = WeakAuras.talent_types_specific[player_class][spec] or {};
1357
1358 for tier = 1, MAX_TALENT_TIERS do
1359 for column = 1, NUM_TALENT_COLUMNS do
1360 -- Get name and icon info for the current talent of the current class and save it
1361 local _, talentName, talentIcon = GetTalentInfo(tier, column, 1)
1362 local talentId = (tier-1)*3+column
1363 -- Get the icon and name from the talent cache and record it in the table that will be used by WeakAurasOptions
1364 if (talentName and talentIcon) then
1365 WeakAuras.talent_types_specific[player_class][spec][talentId] = "|T"..talentIcon..":0|t "..talentName
1366 end
1367 end
1368 end
1369 end
1370end
1371
1372local pvpTalentsInitialized = false;
1373function WeakAuras.CreatePvPTalentCache()
1374 if (pvpTalentsInitialized) then return end;
1375 local _, player_class = UnitClass("player")
1376 local spec = GetSpecialization()
1377
1378 if (not player_class or not spec) then
1379 return;
1380 end
1381
1382 WeakAuras.pvp_talent_types_specific[player_class] = WeakAuras.pvp_talent_types_specific[player_class] or {};
1383 WeakAuras.pvp_talent_types_specific[player_class][spec] = WeakAuras.pvp_talent_types_specific[player_class][spec] or {};
1384
1385 local function formatTalent(talentId)
1386 local _, name, icon = GetPvpTalentInfoByID(talentId);
1387 return "|T"..icon..":0|t "..name
1388 end
1389
1390 local slotInfo = C_SpecializationInfo.GetPvpTalentSlotInfo(2);
1391 if (slotInfo) then
1392
1393 WeakAuras.pvp_talent_types_specific[player_class][spec] = {
1394 formatTalent(3589),
1395 formatTalent(3588),
1396 formatTalent(3587),
1397 nil
1398 };
1399
1400 local pvpSpecTalents = slotInfo.availableTalentIDs;
1401 for i, talentId in ipairs(pvpSpecTalents) do
1402 WeakAuras.pvp_talent_types_specific[player_class][spec][i + 3] = formatTalent(talentId);
1403 end
1404
1405 pvpTalentsInitialized = true;
1406 end
1407end
1408
1409function WeakAuras.CountWagoUpdates()
1410 local WeakAurasSaved = WeakAurasSaved
1411 local updatedSlugs, updatedSlugsCount = {}, 0
1412 for id, aura in pairs(WeakAurasSaved.displays) do
1413 if not aura.ignoreWagoUpdate and aura.url and aura.url ~= "" then
1414 local slug, version = aura.url:match("wago.io/([^/]+)/([0-9]+)")
1415 if not slug and not version then
1416 slug = aura.url:match("wago.io/([^/]+)$")
1417 version = 1
1418 end
1419 if slug and version then
1420 local wago = WeakAurasCompanion.slugs[slug]
1421 if wago and wago.wagoVersion
1422 and tonumber(wago.wagoVersion) > (
1423 aura.skipWagoUpdate and tonumber(aura.skipWagoUpdate) or tonumber(version)
1424 )
1425 then
1426 if not updatedSlugs[slug] then
1427 updatedSlugs[slug] = true
1428 updatedSlugsCount = updatedSlugsCount + 1
1429 end
1430 end
1431 end
1432 end
1433 end
1434 return updatedSlugsCount
1435end
1436
1437local function tooltip_draw()
1438 local tooltip = GameTooltip;
1439 tooltip:ClearLines();
1440 tooltip:AddDoubleLine("WeakAuras", versionString);
1441 if WeakAurasCompanion then
1442 local count = WeakAuras.CountWagoUpdates()
1443 if count > 0 then
1444 tooltip:AddLine(" ");
1445 tooltip:AddLine((L["There are %i updates to your auras ready to be installed!"]):format(count));
1446 end
1447 end
1448 tooltip:AddLine(" ");
1449 tooltip:AddLine(L["|cffeda55fLeft-Click|r to toggle showing the main window."], 0.2, 1, 0.2);
1450 if not WeakAuras.IsOptionsOpen() then
1451 if paused then
1452 tooltip:AddLine("|cFFFF0000"..L["Paused"].." - "..L["Shift-Click to resume addon execution."], 0.2, 1, 0.2);
1453 else
1454 tooltip:AddLine(L["|cffeda55fShift-Click|r to pause addon execution."], 0.2, 1, 0.2);
1455 end
1456 end
1457 tooltip:AddLine(L["|cffeda55fRight-Click|r to toggle performance profiling on or off."], 0.2, 1, 0.2);
1458 tooltip:AddLine(L["|cffeda55fShift-Right-Click|r to show profiling results."], 0.2, 1, 0.2);
1459 tooltip:AddLine(L["|cffeda55fMiddle-Click|r to toggle the minimap icon on or off."], 0.2, 1, 0.2);
1460 tooltip:Show();
1461end
1462
1463local colorFrame = CreateFrame("frame");
1464WeakAuras.frames["LDB Icon Recoloring"] = colorFrame;
1465
1466local colorElapsed = 0;
1467local colorDelay = 2;
1468local r, g, b = 0.8, 0, 1;
1469local r2, g2, b2 = random(2)-1, random(2)-1, random(2)-1;
1470
1471local tooltip_update_frame = CreateFrame("FRAME");
1472WeakAuras.frames["LDB Tooltip Updater"] = tooltip_update_frame;
1473
1474-- function copied from LibDBIcon-1.0.lua
1475local function getAnchors(frame)
1476 local x, y = frame:GetCenter()
1477 if not x or not y then return "CENTER" end
1478 local hhalf = (x > UIParent:GetWidth()*2/3) and "RIGHT" or (x < UIParent:GetWidth()/3) and "LEFT" or ""
1479 local vhalf = (y > UIParent:GetHeight()/2) and "TOP" or "BOTTOM"
1480 return vhalf..hhalf, frame, (vhalf == "TOP" and "BOTTOM" or "TOP")..hhalf
1481end
1482
1483local Broker_WeakAuras;
1484Broker_WeakAuras = LDB:NewDataObject("WeakAuras", {
1485 type = "launcher",
1486 text = "WeakAuras",
1487 icon = "Interface\\AddOns\\WeakAuras\\Media\\Textures\\icon.blp",
1488 OnClick = function(self, button)
1489 if button == 'LeftButton' then
1490 if(IsShiftKeyDown()) then
1491 if not(WeakAuras.IsOptionsOpen()) then
1492 WeakAuras.Toggle();
1493 end
1494 else
1495 WeakAuras.OpenOptions();
1496 end
1497 elseif(button == 'MiddleButton') then
1498 WeakAuras.ToggleMinimap();
1499 else
1500 if(IsShiftKeyDown()) then
1501 WeakAuras.PrintProfile();
1502 else
1503 WeakAuras.ToggleProfile();
1504 end
1505 end
1506 tooltip_draw()
1507 end,
1508 OnEnter = function(self)
1509 colorFrame:SetScript("OnUpdate", function(self, elaps)
1510 colorElapsed = colorElapsed + elaps;
1511 if(colorElapsed > colorDelay) then
1512 colorElapsed = colorElapsed - colorDelay;
1513 r, g, b = r2, g2, b2;
1514 r2, g2, b2 = random(2)-1, random(2)-1, random(2)-1;
1515 end
1516 Broker_WeakAuras.iconR = r + (r2 - r) * colorElapsed / colorDelay;
1517 Broker_WeakAuras.iconG = g + (g2 - g) * colorElapsed / colorDelay;
1518 Broker_WeakAuras.iconB = b + (b2 - b) * colorElapsed / colorDelay;
1519 end);
1520 local elapsed = 0;
1521 local delay = 1;
1522 tooltip_update_frame:SetScript("OnUpdate", function(self, elap)
1523 elapsed = elapsed + elap;
1524 if(elapsed > delay) then
1525 elapsed = 0;
1526 tooltip_draw();
1527 end
1528 end);
1529 GameTooltip:SetOwner(self, "ANCHOR_NONE");
1530 GameTooltip:SetPoint(getAnchors(self))
1531 tooltip_draw();
1532 end,
1533 OnLeave = function(self)
1534 colorFrame:SetScript("OnUpdate", nil);
1535 tooltip_update_frame:SetScript("OnUpdate", nil);
1536 GameTooltip:Hide();
1537 end,
1538 iconR = 0.6,
1539 iconG = 0,
1540 iconB = 1
1541});
1542
1543
1544do -- Archive stuff
1545 local Archivist = select(2, ...).Archivist
1546 function WeakAuras.OpenArchive()
1547 if Archivist:IsInitialized() then
1548 return Archivist
1549 else
1550 if not IsAddOnLoaded("WeakAurasArchive") then
1551 local ok, reason = LoadAddOn("WeakAurasArchive")
1552 if not ok then
1553 error("Could not load WeakAuras Archive, reason: |cFFFF00" .. (reason or "UNKNOWN"))
1554 end
1555 end
1556 Archivist:Initialize(WeakAurasArchive)
1557 end
1558 return Archivist
1559 end
1560
1561 function WeakAuras.LoadFromArchive(storeType, storeID)
1562 local Archivist = WeakAuras.OpenArchive()
1563 return Archivist:Load(storeType, storeID)
1564 end
1565end
1566
1567local loginFinished, loginMessage = false, L["Options will open after the login process has completed."]
1568
1569function WeakAuras.IsLoginFinished()
1570 return loginFinished
1571end
1572
1573function WeakAuras.LoginMessage()
1574 return loginMessage
1575end
1576
1577function WeakAuras.Login(initialTime, takeNewSnapshots)
1578 local loginThread = coroutine.create(function()
1579 WeakAuras.Pause();
1580
1581 if db.history then
1582 local histRepo = WeakAuras.LoadFromArchive("Repository", "history")
1583 local migrationRepo = WeakAuras.LoadFromArchive("Repository", "migration")
1584 for uid, hist in pairs(db.history) do
1585 local histStore = histRepo:Set(uid, hist.data)
1586 local migrationStore = migrationRepo:Set(uid, hist.migration)
1587 coroutine.yield()
1588 end
1589 -- history is now in archive so we can shrink WeakAurasSaved
1590 db.history = nil
1591 coroutine.yield();
1592 end
1593
1594 local toAdd = {};
1595 loginFinished = false
1596 loginMessage = L["Options will open after the login process has completed."]
1597 for id, data in pairs(db.displays) do
1598 if(id ~= data.id) then
1599 print("|cFF8800FFWeakAuras|r detected a corrupt entry in WeakAuras saved displays - '"..tostring(id).."' vs '"..tostring(data.id).."'" );
1600 data.id = id;
1601 end
1602 tinsert(toAdd, data);
1603 end
1604 coroutine.yield();
1605
1606 WeakAuras.AddMany(toAdd, takeNewSnapshots);
1607 coroutine.yield();
1608 WeakAuras.AddManyFromAddons(from_files);
1609 WeakAuras.RegisterDisplay = WeakAuras.AddFromAddon;
1610 coroutine.yield();
1611 WeakAuras.ResolveCollisions(function() registeredFromAddons = true; end);
1612 coroutine.yield();
1613
1614 for _, triggerSystem in pairs(triggerSystems) do
1615 if (triggerSystem.AllAdded) then
1616 triggerSystem.AllAdded();
1617 coroutine.yield();
1618 end
1619 end
1620
1621 -- check in case of a disconnect during an encounter.
1622 if (db.CurrentEncounter) then
1623 WeakAuras.CheckForPreviousEncounter()
1624 end
1625 coroutine.yield();
1626 WeakAuras.RegisterLoadEvents();
1627 WeakAuras.Resume();
1628 coroutine.yield();
1629
1630 local nextCallback = loginQueue[1];
1631 while nextCallback do
1632 tremove(loginQueue, 1);
1633 if type(nextCallback) == 'table' then
1634 nextCallback[1](unpack(nextCallback[2]))
1635 else
1636 nextCallback()
1637 end
1638 coroutine.yield();
1639 nextCallback = loginQueue[1];
1640 end
1641
1642 loginFinished = true
1643 WeakAuras.ResumeAllDynamicGroups();
1644 end)
1645
1646 if initialTime then
1647 local startTime = debugprofilestop()
1648 local finishTime = debugprofilestop()
1649 local ok, msg
1650 -- hard limit seems to be 19 seconds. We'll do 15 for now.
1651 while coroutine.status(loginThread) ~= 'dead' and finishTime - startTime < 15000 do
1652 ok, msg = coroutine.resume(loginThread)
1653 finishTime = debugprofilestop()
1654 end
1655 if coroutine.status(loginThread) ~= 'dead' then
1656 WeakAuras.dynFrame:AddAction('login', loginThread)
1657 end
1658 if not ok then
1659 loginMessage = L["WeakAuras has encountered an error during the login process. Please report this issue at https://github.com/WeakAuras/Weakauras2/issues/new."]
1660 .. "\nMessage:" .. msg
1661 geterrorhandler()(msg .. '\n' .. debugstack(loginThread))
1662 end
1663 else
1664 WeakAuras.dynFrame:AddAction('login', loginThread)
1665 end
1666end
1667
1668local frame = CreateFrame("FRAME", "WeakAurasFrame", UIParent);
1669WeakAuras.frames["WeakAuras Main Frame"] = frame;
1670frame:SetAllPoints(UIParent);
1671local loadedFrame = CreateFrame("FRAME");
1672WeakAuras.frames["Addon Initialization Handler"] = loadedFrame;
1673loadedFrame:RegisterEvent("ADDON_LOADED");
1674loadedFrame:RegisterEvent("PLAYER_LOGIN");
1675loadedFrame:RegisterEvent("PLAYER_ENTERING_WORLD");
1676loadedFrame:RegisterEvent("LOADING_SCREEN_ENABLED");
1677loadedFrame:RegisterEvent("LOADING_SCREEN_DISABLED");
1678if not WeakAuras.IsClassic() then
1679 loadedFrame:RegisterEvent("ACTIVE_TALENT_GROUP_CHANGED");
1680 loadedFrame:RegisterEvent("PLAYER_PVP_TALENT_UPDATE");
1681else
1682 loadedFrame:RegisterEvent("CHARACTER_POINTS_CHANGED");
1683 loadedFrame:RegisterEvent("SPELLS_CHANGED");
1684end
1685loadedFrame:SetScript("OnEvent", function(self, event, addon)
1686 if(event == "ADDON_LOADED") then
1687 if(addon == ADDON_NAME) then
1688 WeakAurasSaved = WeakAurasSaved or {};
1689 db = WeakAurasSaved;
1690
1691 -- Defines the action squelch period after login
1692 -- Stored in SavedVariables so it can be changed by the user if they find it necessary
1693 db.login_squelch_time = db.login_squelch_time or 10;
1694
1695 -- Deprecated fields with *lots* of data, clear them out
1696 db.iconCache = nil;
1697 db.iconHash = nil;
1698 db.tempIconCache = nil;
1699 db.dynamicIconCache = db.dynamicIconCache or {};
1700
1701 db.displays = db.displays or {};
1702 db.registered = db.registered or {};
1703
1704 WeakAuras.UpdateCurrentInstanceType();
1705 WeakAuras.SyncParentChildRelationships();
1706 local isFirstUIDValidation = db.dbVersion == nil or db.dbVersion < 26;
1707 WeakAuras.ValidateUniqueDataIds(isFirstUIDValidation);
1708
1709 if db.lastArchiveClear == nil then
1710 db.lastArchiveClear = time();
1711 elseif db.lastArchiveClear < time() - 86400 then
1712 WeakAuras.CleanArchive(db.historyCutoff, db.migrationCutoff);
1713 end
1714 db.minimap = db.minimap or { hide = false };
1715 LDBIcon:Register("WeakAuras", Broker_WeakAuras, db.minimap);
1716 end
1717 elseif(event == "PLAYER_LOGIN") then
1718 local dbIsValid, takeNewSnapshots
1719 if not db.dbVersion or db.dbVersion < internalVersion then
1720 -- db is out of date, will run any necessary migrations in AddMany
1721 db.dbVersion = internalVersion
1722 db.lastUpgrade = time()
1723 dbIsValid = true
1724 takeNewSnapshots = true
1725 elseif db.dbVersion > internalVersion then
1726 -- user has downgraded past a forwards-incompatible migration
1727 dbIsValid = false
1728 else
1729 -- db has same version as code, can commit to login
1730 dbIsValid = true
1731 end
1732 if dbIsValid then
1733 -- run login thread for up to 15 seconds, then defer to dynFrame
1734 WeakAuras.Login(15000, takeNewSnapshots)
1735 else
1736 -- db isn't valid. Request permission to run repair tool before logging in
1737 StaticPopup_Show("WEAKAURAS_CONFIRM_REPAIR", nil, nil, {reason = "downgrade"})
1738 end
1739 elseif(event == "LOADING_SCREEN_ENABLED") then
1740 in_loading_screen = true;
1741 elseif(event == "LOADING_SCREEN_DISABLED") then
1742 in_loading_screen = false;
1743 else
1744 local callback
1745 if(event == "PLAYER_ENTERING_WORLD") then
1746 -- Schedule events that need to be handled some time after login
1747 local now = GetTime()
1748 callback = function()
1749 local elapsed = GetTime() - now
1750 local remainingSquelch = db.login_squelch_time - elapsed
1751 if remainingSquelch > 0 then
1752 timer:ScheduleTimer(function() squelch_actions = false; end, remainingSquelch); -- No sounds while loading
1753 end
1754 WeakAuras.CreateTalentCache() -- It seems that GetTalentInfo might give info about whatever class was previously being played, until PLAYER_ENTERING_WORLD
1755 WeakAuras.UpdateCurrentInstanceType();
1756 WeakAuras.InitializeEncounterAndZoneLists()
1757 end
1758 elseif(event == "PLAYER_PVP_TALENT_UPDATE") then
1759 callback = WeakAuras.CreatePvPTalentCache;
1760 elseif(event == "ACTIVE_TALENT_GROUP_CHANGED" or event == "CHARACTER_POINTS_CHANGED" or event == "SPELLS_CHANGED") then
1761 callback = WeakAuras.CreateTalentCache;
1762 elseif(event == "PLAYER_REGEN_ENABLED") then
1763 callback = function()
1764 if (queueshowooc) then
1765 WeakAuras.OpenOptions(queueshowooc)
1766 queueshowooc = nil
1767 WeakAuras.frames["Addon Initialization Handler"]:UnregisterEvent("PLAYER_REGEN_ENABLED")
1768 end
1769 end
1770 end
1771 if WeakAuras.IsLoginFinished() then
1772 callback()
1773 else
1774 loginQueue[#loginQueue + 1] = callback
1775 end
1776 end
1777end);
1778
1779function WeakAuras.SetImporting(b)
1780 importing = b;
1781 WeakAuras.RefreshTooltipButtons()
1782end
1783
1784function WeakAuras.IsImporting()
1785 return importing;
1786end
1787
1788function WeakAuras.IsPaused()
1789 return paused;
1790end
1791
1792function WeakAuras.Pause()
1793 paused = true;
1794 -- Forcibly hide all displays, and clear all trigger information (it will be restored on .Resume() due to forced events)
1795 for id, region in pairs(regions) do
1796 region.region:Collapse(); -- ticket 366
1797 end
1798
1799 for id, cloneList in pairs(clones) do
1800 for cloneId, clone in pairs(cloneList) do
1801 clone:Collapse();
1802 end
1803 end
1804end
1805
1806function WeakAuras.Resume()
1807 paused = false;
1808 squelch_actions = true;
1809 WeakAuras.ScanAll();
1810 squelch_actions = false;
1811 for _, regionData in pairs(regions) do
1812 if regionData.region.Resume then
1813 regionData.region:Resume(true)
1814 end
1815 end
1816end
1817
1818function WeakAuras.Toggle()
1819 if(paused) then
1820 WeakAuras.Resume();
1821 else
1822 WeakAuras.Pause();
1823 end
1824end
1825
1826function WeakAuras.SquelchingActions()
1827 return squelch_actions;
1828end
1829
1830function WeakAuras.InLoadingScreen()
1831 return in_loading_screen;
1832end
1833
1834function WeakAuras.PauseAllDynamicGroups()
1835 for id, region in pairs(regions) do
1836 if (region.region.Suspend) then
1837 region.region:Suspend();
1838 end
1839 end
1840end
1841
1842function WeakAuras.ResumeAllDynamicGroups()
1843 for id, region in pairs(regions) do
1844 if (region.region.Resume) then
1845 region.region:Resume();
1846 end
1847 end
1848end
1849
1850function WeakAuras.ScanAll()
1851 WeakAuras.PauseAllDynamicGroups();
1852
1853 for id, region in pairs(regions) do
1854 region.region:Collapse();
1855 end
1856
1857 for id, cloneList in pairs(clones) do
1858 for cloneId, clone in pairs(cloneList) do
1859 clone:Collapse();
1860 end
1861 end
1862
1863 WeakAuras.ResumeAllDynamicGroups();
1864 WeakAuras.ReloadAll();
1865end
1866
1867-- encounter stuff
1868function WeakAuras.StoreBossGUIDs()
1869 WeakAuras.StartProfileSystem("boss_guids")
1870 if (WeakAuras.CurrentEncounter and WeakAuras.CurrentEncounter.boss_guids) then
1871 for i = 1, 5 do
1872 if (UnitExists ("boss" .. i)) then
1873 local guid = UnitGUID ("boss" .. i)
1874 if (guid) then
1875 WeakAuras.CurrentEncounter.boss_guids [guid] = true
1876 end
1877 end
1878 end
1879 db.CurrentEncounter = WeakAuras.CurrentEncounter
1880 end
1881 WeakAuras.StopProfileSystem("boss_guids")
1882end
1883
1884function WeakAuras.CheckForPreviousEncounter()
1885 if (UnitAffectingCombat ("player") or InCombatLockdown()) then
1886 for i = 1, 5 do
1887 if (UnitExists ("boss" .. i)) then
1888 local guid = UnitGUID ("boss" .. i)
1889 if (guid and db.CurrentEncounter.boss_guids [guid]) then
1890 -- we are in the same encounter
1891 WeakAuras.CurrentEncounter = db.CurrentEncounter
1892 return true
1893 end
1894 end
1895 end
1896 db.CurrentEncounter = nil
1897 else
1898 db.CurrentEncounter = nil
1899 end
1900end
1901
1902function WeakAuras.DestroyEncounterTable()
1903 if (WeakAuras.CurrentEncounter) then
1904 wipe(WeakAuras.CurrentEncounter)
1905 end
1906 WeakAuras.CurrentEncounter = nil
1907 db.CurrentEncounter = nil
1908end
1909
1910function WeakAuras.CreateEncounterTable(encounter_id)
1911 local _, _, _, _, _, _, _, ZoneMapID = GetInstanceInfo()
1912 WeakAuras.CurrentEncounter = {
1913 id = encounter_id,
1914 zone_id = ZoneMapID,
1915 boss_guids = {},
1916 }
1917 timer:ScheduleTimer(WeakAuras.StoreBossGUIDs, 2)
1918
1919 return WeakAuras.CurrentEncounter
1920end
1921
1922local encounterScriptsDeferred = {}
1923local function LoadEncounterInitScriptsImpl(id)
1924 if (WeakAuras.currentInstanceType ~= "raid") then
1925 return
1926 end
1927 if (id) then
1928 local data = db.displays[id]
1929 if (data and data.load.use_encounterid and not WeakAuras.IsEnvironmentInitialized(id) and data.actions.init and data.actions.init.do_custom) then
1930 WeakAuras.ActivateAuraEnvironment(id)
1931 WeakAuras.ActivateAuraEnvironment(nil)
1932 end
1933 encounterScriptsDeferred[id] = nil
1934 else
1935 for id, data in pairs(db.displays) do
1936 if (data.load.use_encounterid and not WeakAuras.IsEnvironmentInitialized(id) and data.actions.init and data.actions.init.do_custom) then
1937 WeakAuras.ActivateAuraEnvironment(id)
1938 WeakAuras.ActivateAuraEnvironment(nil)
1939 end
1940 end
1941 end
1942end
1943
1944function WeakAuras.LoadEncounterInitScripts(id)
1945 if not WeakAuras.IsLoginFinished() then
1946 if encounterScriptsDeferred[id] then
1947 return
1948 end
1949 loginQueue[#loginQueue + 1] = {LoadEncounterInitScriptsImpl, {id}}
1950 encounterScriptsDeferred[id] = true
1951 return
1952 end
1953 LoadEncounterInitScriptsImpl(id)
1954end
1955
1956function WeakAuras.UpdateCurrentInstanceType(instanceType)
1957 if (not IsInInstance()) then
1958 WeakAuras.currentInstanceType = "none"
1959 else
1960 WeakAuras.currentInstanceType = instanceType or select (2, GetInstanceInfo())
1961 end
1962end
1963
1964local pausedOptionsProcessing = false;
1965function WeakAuras.pauseOptionsProcessing(enable)
1966 pausedOptionsProcessing = enable;
1967end
1968
1969function WeakAuras.IsOptionsProcessingPaused()
1970 return pausedOptionsProcessing;
1971end
1972
1973function WeakAuras.GroupType()
1974 if (IsInRaid()) then
1975 return "raid";
1976 end
1977 if (IsInGroup()) then
1978 return "group";
1979 end
1980 return "solo";
1981end
1982
1983local function GetInstanceTypeAndSize()
1984 local size, difficulty
1985 local inInstance, Type = IsInInstance()
1986 local _, instanceType, difficultyIndex, _, _, _, _, ZoneMapID = GetInstanceInfo()
1987 if (inInstance) then
1988 size = Type
1989 local difficultyInfo = WeakAuras.difficulty_info[difficultyIndex]
1990 if difficultyInfo then
1991 size, difficulty = difficultyInfo.size, difficultyInfo.difficulty
1992 end
1993 return size, difficulty, instanceType, ZoneMapID
1994 end
1995 return "none", "none", nil, nil
1996end
1997
1998function WeakAuras.InstanceType()
1999 return GetInstanceTypeAndSize(), nil
2000end
2001
2002function WeakAuras.InstanceDifficulty()
2003 return select(2, GetInstanceTypeAndSize())
2004end
2005
2006local toLoad = {}
2007local toUnload = {};
2008local function scanForLoadsImpl(toCheck, event, arg1, ...)
2009 if (WeakAuras.IsOptionsProcessingPaused()) then
2010 return;
2011 end
2012
2013 toCheck = toCheck or loadEvents[event or "SCAN_ALL"]
2014
2015 -- PET_BATTLE_CLOSE fires twice at the end of a pet battle. IsInBattle evaluates to TRUE during the
2016 -- first firing, and FALSE during the second. I am not sure if this check is necessary, but the
2017 -- following IF statement limits the impact of the PET_BATTLE_CLOSE event to the second one.
2018 if (event == "PET_BATTLE_CLOSE" and C_PetBattles.IsInBattle()) then return end
2019
2020 if (event == "PLAYER_LEVEL_UP") then
2021 playerLevel = arg1;
2022 end
2023
2024 -- encounter id stuff, we are holding the current combat id to further load checks.
2025 -- there is three ways to unload: encounter_end / zone changed (hearthstone used) / reload or disconnect
2026 -- regen_enabled isn't good due to combat drop abilities such invisibility, vanish, fake death, etc.
2027 local encounter_id = WeakAuras.CurrentEncounter and WeakAuras.CurrentEncounter.id or 0
2028
2029 if (event == "ENCOUNTER_START") then
2030 encounter_id = tonumber (arg1)
2031 WeakAuras.CreateEncounterTable (encounter_id)
2032 elseif (event == "ENCOUNTER_END") then
2033 encounter_id = 0
2034 WeakAuras.DestroyEncounterTable()
2035 end
2036
2037 if toCheck == nil or next(toCheck) == nil then
2038 return
2039 end
2040
2041 local player, realm, spec, zone = UnitName("player"), GetRealmName(), WeakAuras.IsClassic() and 1 or GetSpecialization(), GetRealZoneText();
2042 local specId = not WeakAuras.IsClassic() and GetSpecializationInfo(spec)
2043 local zoneId = C_Map.GetBestMapForUnit("player")
2044 local zonegroupId = zoneId and C_Map.GetMapGroupID(zoneId)
2045 local _, race = UnitRace("player")
2046 local faction = UnitFactionGroup("player")
2047
2048 local role = WeakAuras.IsClassic() and "none" or select(5, GetSpecializationInfo(spec));
2049
2050 local _, class = UnitClass("player");
2051
2052 local incombat = UnitAffectingCombat("player") -- or UnitAffectingCombat("pet");
2053 local inencounter = encounter_id ~= 0;
2054 local inpetbattle, vehicle, vehicleUi = false, false, false
2055 if not WeakAuras.IsClassic() then
2056 inpetbattle = C_PetBattles.IsInBattle()
2057 vehicle = UnitInVehicle('player') or UnitOnTaxi('player')
2058 vehicleUi = UnitHasVehicleUI('player') or HasOverrideActionBar()
2059 end
2060
2061 local size, difficulty, instanceType, ZoneMapID = GetInstanceTypeAndSize()
2062 WeakAuras.UpdateCurrentInstanceType(instanceType)
2063
2064 if (WeakAuras.CurrentEncounter) then
2065 if (ZoneMapID ~= WeakAuras.CurrentEncounter.zone_id and not incombat) then
2066 encounter_id = 0
2067 WeakAuras.DestroyEncounterTable()
2068 end
2069 end
2070
2071 if (event == "ZONE_CHANGED_NEW_AREA") then
2072 WeakAuras.LoadEncounterInitScripts();
2073 end
2074
2075 local group = WeakAuras.GroupType()
2076
2077 local affixes, warmodeActive, effectiveLevel = 0, false, 0
2078 if not WeakAuras.IsClassic() then
2079 effectiveLevel = UnitEffectiveLevel("player")
2080 affixes = C_ChallengeMode.IsChallengeModeActive() and select(2, C_ChallengeMode.GetActiveKeystoneInfo())
2081 warmodeActive = C_PvP.IsWarModeDesired();
2082 end
2083
2084 local changed = 0;
2085 local shouldBeLoaded, couldBeLoaded;
2086 wipe(toLoad);
2087 wipe(toUnload);
2088
2089 for id in pairs(toCheck) do
2090 local data = WeakAuras.GetData(id)
2091 if (data and not data.controlledChildren) then
2092 local loadFunc = loadFuncs[id];
2093 local loadOpt = loadFuncsForOptions[id];
2094 if WeakAuras.IsClassic() then
2095 shouldBeLoaded = loadFunc and loadFunc("ScanForLoads_Auras", incombat, inencounter, group, player, realm, class, race, faction, playerLevel, zone, size);
2096 couldBeLoaded = loadOpt and loadOpt("ScanForLoads_Auras", incombat, inencounter, group, player, realm, class, race, faction, playerLevel, zone, size);
2097 else
2098 shouldBeLoaded = loadFunc and loadFunc("ScanForLoads_Auras", incombat, inencounter, warmodeActive, inpetbattle, vehicle, vehicleUi, group, player, realm, class, spec, specId, race, faction, playerLevel, effectiveLevel, zone, zoneId, zonegroupId, encounter_id, size, difficulty, role, affixes);
2099 couldBeLoaded = loadOpt and loadOpt("ScanForLoads_Auras", incombat, inencounter, warmodeActive, inpetbattle, vehicle, vehicleUi, group, player, realm, class, spec, specId, race, faction, playerLevel, effectiveLevel, zone, zoneId, zonegroupId, encounter_id, size, difficulty, role, affixes);
2100 end
2101
2102 if(shouldBeLoaded and not loaded[id]) then
2103 changed = changed + 1;
2104 toLoad[id] = true;
2105 end
2106
2107 if(loaded[id] and not shouldBeLoaded) then
2108 toUnload[id] = true;
2109 changed = changed + 1;
2110 end
2111 if(shouldBeLoaded) then
2112 loaded[id] = true;
2113 elseif(couldBeLoaded) then
2114 loaded[id] = false;
2115 else
2116 loaded[id] = nil;
2117 end
2118 end
2119 end
2120
2121 if(changed > 0 and not paused) then
2122 WeakAuras.LoadDisplays(toLoad, event, arg1, ...);
2123 WeakAuras.UnloadDisplays(toUnload, event, arg1, ...);
2124 WeakAuras.FinishLoadUnload();
2125 end
2126
2127 for id, data in pairs(db.displays) do
2128 if(data.controlledChildren) then
2129 if(#data.controlledChildren > 0) then
2130 local any_loaded;
2131 for index, childId in pairs(data.controlledChildren) do
2132 if(loaded[childId] ~= nil) then
2133 any_loaded = true;
2134 break;
2135 end
2136 end
2137 loaded[id] = any_loaded;
2138 else
2139 loaded[id] = true;
2140 end
2141 end
2142 end
2143
2144
2145 if (WeakAuras.afterScanForLoads) then -- Hook for Options
2146 WeakAuras.afterScanForLoads();
2147 end
2148 wipe(toLoad);
2149 wipe(toUnload)
2150end
2151
2152function WeakAuras.ScanForLoads(toCheck, event, arg1, ...)
2153 if not WeakAuras.IsLoginFinished() then
2154 return
2155 end
2156 scanForLoadsImpl(toCheck, event, arg1, ...)
2157end
2158
2159local loadFrame = CreateFrame("FRAME");
2160WeakAuras.loadFrame = loadFrame;
2161WeakAuras.frames["Display Load Handling"] = loadFrame;
2162
2163loadFrame:RegisterEvent("ENCOUNTER_START");
2164loadFrame:RegisterEvent("ENCOUNTER_END");
2165
2166if not WeakAuras.IsClassic() then
2167 loadFrame:RegisterEvent("PLAYER_TALENT_UPDATE");
2168 loadFrame:RegisterEvent("PLAYER_PVP_TALENT_UPDATE");
2169 loadFrame:RegisterEvent("PLAYER_DIFFICULTY_CHANGED");
2170 loadFrame:RegisterEvent("PET_BATTLE_OPENING_START");
2171 loadFrame:RegisterEvent("PET_BATTLE_CLOSE");
2172 loadFrame:RegisterEvent("VEHICLE_UPDATE");
2173 loadFrame:RegisterEvent("UPDATE_OVERRIDE_ACTIONBAR");
2174 loadFrame:RegisterEvent("CHALLENGE_MODE_COMPLETED")
2175 loadFrame:RegisterEvent("CHALLENGE_MODE_START")
2176else
2177 loadFrame:RegisterEvent("CHARACTER_POINTS_CHANGED")
2178end
2179loadFrame:RegisterEvent("GROUP_ROSTER_UPDATE");
2180loadFrame:RegisterEvent("ZONE_CHANGED");
2181loadFrame:RegisterEvent("ZONE_CHANGED_INDOORS");
2182loadFrame:RegisterEvent("ZONE_CHANGED_NEW_AREA");
2183loadFrame:RegisterEvent("PLAYER_LEVEL_UP");
2184loadFrame:RegisterEvent("PLAYER_REGEN_DISABLED");
2185loadFrame:RegisterEvent("PLAYER_REGEN_ENABLED");
2186loadFrame:RegisterEvent("PLAYER_ROLES_ASSIGNED");
2187loadFrame:RegisterEvent("SPELLS_CHANGED");
2188loadFrame:RegisterEvent("UNIT_INVENTORY_CHANGED")
2189loadFrame:RegisterEvent("PLAYER_EQUIPMENT_CHANGED")
2190
2191local unitLoadFrame = CreateFrame("FRAME");
2192WeakAuras.loadFrame = unitLoadFrame;
2193WeakAuras.frames["Display Load Handling 2"] = unitLoadFrame;
2194
2195unitLoadFrame:RegisterUnitEvent("UNIT_FLAGS", "player");
2196if not WeakAuras.IsClassic() then
2197 unitLoadFrame:RegisterUnitEvent("UNIT_ENTERED_VEHICLE", "player");
2198 unitLoadFrame:RegisterUnitEvent("UNIT_EXITED_VEHICLE", "player");
2199end
2200
2201function WeakAuras.RegisterLoadEvents()
2202 loadFrame:SetScript("OnEvent", function(frame, ...)
2203 WeakAuras.StartProfileSystem("load");
2204 WeakAuras.ScanForLoads(nil, ...)
2205 WeakAuras.StopProfileSystem("load");
2206 end);
2207
2208 unitLoadFrame:SetScript("OnEvent", function(frame, e, arg1, ...)
2209 WeakAuras.StartProfileSystem("load");
2210 if (arg1 == "player") then
2211 WeakAuras.ScanForLoads(nil, e, arg1, ...)
2212 end
2213 WeakAuras.StopProfileSystem("load");
2214 end);
2215end
2216
2217function WeakAuras.ReloadAll()
2218 WeakAuras.UnloadAll();
2219 scanForLoadsImpl();
2220end
2221
2222function WeakAuras.UnloadAll()
2223 -- Even though auras are collapsed, their finish animation can be running
2224 for id in pairs(loaded) do
2225 WeakAuras.CancelAnimation(WeakAuras.regions[id].region, true, true, true, true, true, true)
2226 if clones[id] then
2227 for cloneId, region in pairs(clones[id]) do
2228 WeakAuras.CancelAnimation(region, true, true, true, true, true, true)
2229 end
2230 end
2231 end
2232
2233 for _, v in pairs(triggerState) do
2234 for i = 1, v.numTriggers do
2235 if (v[i]) then
2236 wipe(v[i]);
2237 end
2238 end
2239 end
2240
2241 for _, aura in pairs(timers) do
2242 for _, trigger in pairs(aura) do
2243 for _, record in pairs(trigger) do
2244 if (record.handle) then
2245 timer:CancelTimer(record.handle);
2246 end
2247 end
2248 end
2249 end
2250 wipe(timers);
2251
2252 for id in pairs(conditionChecksTimers.recheckTime) do
2253 if (conditionChecksTimers.recheckHandle[id]) then
2254 for _, v in pairs(conditionChecksTimers.recheckHandle[id]) do
2255 timer:CancelTimer(v);
2256 end
2257 end
2258 end
2259 wipe(conditionChecksTimers.recheckTime);
2260 wipe(conditionChecksTimers.recheckHandle);
2261
2262 for _, triggerSystem in pairs(triggerSystems) do
2263 triggerSystem.UnloadAll();
2264 end
2265 wipe(loaded);
2266end
2267
2268function WeakAuras.LoadDisplays(toLoad, ...)
2269 for id in pairs(toLoad) do
2270 WeakAuras.RegisterForGlobalConditions(id);
2271 triggerState[id].triggers = {};
2272 triggerState[id].triggerCount = 0;
2273 triggerState[id].show = false;
2274 triggerState[id].activeTrigger = nil;
2275 triggerState[id].activatedConditions = {};
2276 end
2277 for _, triggerSystem in pairs(triggerSystems) do
2278 triggerSystem.LoadDisplays(toLoad, ...);
2279 end
2280end
2281
2282function WeakAuras.UnloadDisplays(toUnload, ...)
2283 for _, triggerSystem in pairs(triggerSystems) do
2284 triggerSystem.UnloadDisplays(toUnload, ...);
2285 end
2286
2287 for id in pairs(toUnload) do
2288 -- Even though auras are collapsed, their finish animation can be running
2289 WeakAuras.CancelAnimation(WeakAuras.regions[id].region, true, true, true, true, true, true)
2290 if clones[id] then
2291 for cloneId, region in pairs(clones[id]) do
2292 WeakAuras.CancelAnimation(region, true, true, true, true, true, true)
2293 end
2294 end
2295
2296 for i = 1, triggerState[id].numTriggers do
2297 if (triggerState[id][i]) then
2298 wipe(triggerState[id][i]);
2299 end
2300 end
2301 triggerState[id].show = nil;
2302 triggerState[id].activeTrigger = nil;
2303
2304 if (timers[id]) then
2305 for _, trigger in pairs(timers[id]) do
2306 for _, record in pairs(trigger) do
2307 if (record.handle) then
2308 timer:CancelTimer(record.handle);
2309 end
2310 end
2311 end
2312 timers[id] = nil;
2313 end
2314
2315 conditionChecksTimers.recheckTime[id] = nil;
2316 if (conditionChecksTimers.recheckHandle[id]) then
2317 for _, v in pairs(conditionChecksTimers.recheckHandle[id]) do
2318 timer:CancelTimer(v);
2319 end
2320 end
2321 conditionChecksTimers.recheckHandle[id] = nil;
2322 WeakAuras.UnregisterForGlobalConditions(id);
2323
2324 WeakAuras.regions[id].region:Collapse();
2325 WeakAuras.CollapseAllClones(id);
2326 end
2327end
2328
2329function WeakAuras.FinishLoadUnload()
2330 for _, triggerSystem in pairs(triggerSystems) do
2331 triggerSystem.FinishLoadUnload();
2332 end
2333end
2334
2335-- transient cache of uid => id
2336-- eventually, the database will be migrated to index by uid
2337-- and this mapping will become redundant
2338-- this cache is loaded lazily via pAdd()
2339local UIDtoID = {}
2340
2341function WeakAuras.GetDataByUID(uid)
2342 return WeakAuras.GetData(UIDtoID[uid])
2343end
2344
2345function WeakAuras.Delete(data)
2346 local id = data.id;
2347 if(data.parent) then
2348 local parentData = db.displays[data.parent];
2349 if(parentData and parentData.controlledChildren) then
2350 for index, childId in pairs(parentData.controlledChildren) do
2351 if(childId == id) then
2352 tremove(parentData.controlledChildren, index);
2353 end
2354 end
2355 if parentData.sortHybridTable then
2356 parentData.sortHybridTable[id] = nil
2357 end
2358 WeakAuras.ClearAuraEnvironment(data.parent);
2359 end
2360 end
2361
2362 UIDtoID[data.uid] = nil
2363 if(data.controlledChildren) then
2364 for index, childId in pairs(data.controlledChildren) do
2365 local childData = db.displays[childId];
2366 if(childData) then
2367 childData.parent = nil;
2368 WeakAuras.Add(childData);
2369 end
2370 end
2371 end
2372
2373
2374 regions[id].region:Collapse()
2375 WeakAuras.CollapseAllClones(id);
2376
2377 WeakAuras.CancelAnimation(WeakAuras.regions[id].region, true, true, true, true, true, true)
2378
2379 if clones[id] then
2380 for cloneId, region in pairs(clones[id]) do
2381 WeakAuras.CancelAnimation(region, true, true, true, true, true, true)
2382 end
2383 end
2384
2385 regions[id].region:SetScript("OnUpdate", nil);
2386 regions[id].region:SetScript("OnShow", nil);
2387 regions[id].region:SetScript("OnHide", nil);
2388 regions[id].region:Hide();
2389
2390 db.registered[id] = nil;
2391 if(WeakAuras.importDisplayButtons and WeakAuras.importDisplayButtons[id]) then
2392 local button = WeakAuras.importDisplayButtons[id];
2393 button.checkbox:SetChecked(false);
2394 if(button.updateChecked) then
2395 button.updateChecked();
2396 end
2397 end
2398
2399 for _, triggerSystem in pairs(triggerSystems) do
2400 triggerSystem.Delete(id);
2401 end
2402
2403 regions[id].region = nil;
2404 regions[id] = nil;
2405 loaded[id] = nil;
2406 loadFuncs[id] = nil;
2407 loadFuncsForOptions[id] = nil;
2408 for event, eventData in pairs(loadEvents) do
2409 eventData[id] = nil
2410 end
2411 checkConditions[id] = nil;
2412 conditionChecksTimers.recheckTime[id] = nil;
2413 if (conditionChecksTimers.recheckHandle[id]) then
2414 for cloneId, v in pairs(conditionChecksTimers.recheckHandle[id]) do
2415 timer:CancelTimer(v);
2416 end
2417 end
2418 conditionChecksTimers.recheckHandle[id] = nil;
2419
2420 db.displays[id] = nil;
2421
2422 WeakAuras.DeleteAuraEnvironment(id)
2423 triggerState[id] = nil;
2424
2425 if (WeakAuras.personalRessourceDisplayFrame) then
2426 WeakAuras.personalRessourceDisplayFrame:delete(id);
2427 end
2428
2429 if (WeakAuras.mouseFrame) then
2430 WeakAuras.mouseFrame:delete(id);
2431 end
2432
2433 WeakAuras.customActionsFunctions[id] = nil;
2434 WeakAuras.customConditionsFunctions[id] = nil;
2435
2436 for event, funcs in pairs(dynamicConditions) do
2437 funcs[id] = nil;
2438 end
2439
2440 WeakAuras.frameLevels[id] = nil;
2441
2442 WeakAuras.DeleteCollapsedData(id)
2443end
2444
2445function WeakAuras.Rename(data, newid)
2446 local oldid = data.id;
2447 if(data.parent) then
2448 local parentData = db.displays[data.parent];
2449 if(parentData.controlledChildren) then
2450 for index, childId in pairs(parentData.controlledChildren) do
2451 if(childId == data.id) then
2452 parentData.controlledChildren[index] = newid;
2453 end
2454 end
2455 if parentData.sortHybridTable and parentData.sortHybridTable[oldid] then
2456 parentData.sortHybridTable[newid] = true
2457 parentData.sortHybridTable[oldid] = nil
2458 end
2459 end
2460 local parentRegion = WeakAuras.GetRegion(data.parent)
2461 if parentRegion and parentRegion.ReloadControlledChildren then
2462 parentRegion:ReloadControlledChildren()
2463 end
2464 end
2465
2466 UIDtoID[data.uid] = newid
2467 regions[newid] = regions[oldid];
2468 regions[oldid] = nil;
2469 regions[newid].region.id = newid;
2470
2471 for _, triggerSystem in pairs(triggerSystems) do
2472 triggerSystem.Rename(oldid, newid);
2473 end
2474
2475 loaded[newid] = loaded[oldid];
2476 loaded[oldid] = nil;
2477 loadFuncs[newid] = loadFuncs[oldid];
2478 loadFuncs[oldid] = nil;
2479
2480 loadFuncsForOptions[newid] = loadFuncsForOptions[oldid]
2481 loadFuncsForOptions[oldid] = nil;
2482
2483 for event, eventData in pairs(loadEvents) do
2484 eventData[newid] = eventData[oldid]
2485 eventData[oldid] = nil
2486 end
2487
2488 checkConditions[newid] = checkConditions[oldid];
2489 checkConditions[oldid] = nil;
2490
2491 conditionChecksTimers.recheckTime[newid] = conditionChecksTimers.recheckTime[oldid];
2492 conditionChecksTimers.recheckTime[oldid] = nil;
2493
2494 conditionChecksTimers.recheckHandle[newid] = conditionChecksTimers.recheckHandle[oldid];
2495 conditionChecksTimers.recheckHandle[oldid] = nil;
2496
2497 timers[newid] = timers[oldid];
2498 timers[oldid] = nil;
2499
2500 triggerState[newid] = triggerState[oldid];
2501 triggerState[oldid] = nil;
2502
2503 WeakAuras.RenameAuraEnvironment(oldid, newid)
2504
2505 db.displays[newid] = db.displays[oldid];
2506 db.displays[oldid] = nil;
2507
2508 if(clones[oldid]) then
2509 clones[newid] = clones[oldid];
2510 clones[oldid] = nil;
2511 for cloneid, clone in pairs(clones[newid]) do
2512 clone.id = newid;
2513 end
2514 end
2515
2516 db.displays[newid].id = newid;
2517
2518 if(data.controlledChildren) then
2519 for index, childId in pairs(data.controlledChildren) do
2520 local childData = db.displays[childId];
2521 if(childData) then
2522 childData.parent = data.id;
2523 end
2524 end
2525 if regions[newid].ReloadControlledChildren then
2526 regions[newid]:ReloadControlledChildren()
2527 end
2528 end
2529
2530 for key, animation in pairs(animations) do
2531 if animation.name == oldid then
2532 animation.name = newid;
2533 end
2534 end
2535
2536 if (WeakAuras.personalRessourceDisplayFrame) then
2537 WeakAuras.personalRessourceDisplayFrame:rename(oldid, newid);
2538 end
2539
2540 if (WeakAuras.mouseFrame) then
2541 WeakAuras.mouseFrame:rename(oldid, newid);
2542 end
2543
2544 WeakAuras.customActionsFunctions[newid] = WeakAuras.customActionsFunctions[oldid];
2545 WeakAuras.customActionsFunctions[oldid] = nil;
2546
2547 WeakAuras.customConditionsFunctions[newid] = WeakAuras.customConditionsFunctions[oldid];
2548 WeakAuras.customConditionsFunctions[oldid] = nil;
2549
2550 for event, funcs in pairs(dynamicConditions) do
2551 funcs[newid] = funcs[oldid]
2552 funcs[oldid] = nil;
2553 end
2554
2555 WeakAuras.frameLevels[newid] = WeakAuras.frameLevels[oldid];
2556 WeakAuras.frameLevels[oldid] = nil;
2557
2558 WeakAuras.ProfileRenameAura(oldid, newid);
2559
2560 WeakAuras.RenameCollapsedData(oldid, newid)
2561end
2562
2563function WeakAuras.Convert(data, newType)
2564 local id = data.id;
2565 regions[id].region:SetScript("OnUpdate", nil);
2566 regions[id].region:Hide();
2567 WeakAuras.EndEvent(id, 0, true);
2568
2569 WeakAuras.FakeStatesFor(id, false)
2570
2571 regions[id].region = nil;
2572 regions[id] = nil;
2573
2574 data.regionType = newType;
2575 WeakAuras.Add(data);
2576 WeakAuras.ResetCollapsed(id)
2577
2578 WeakAuras.FakeStatesFor(id, true)
2579
2580 local parentRegion = WeakAuras.GetRegion(data.parent)
2581 if parentRegion and parentRegion.ReloadControlledChildren then
2582 parentRegion:ReloadControlledChildren()
2583 end
2584end
2585
2586function WeakAuras.DeepCopy(source, dest)
2587 local function recurse(source, dest)
2588 for i,v in pairs(source) do
2589 if(type(v) == "table") then
2590 dest[i] = type(dest[i]) == "table" and dest[i] or {};
2591 recurse(v, dest[i]);
2592 else
2593 dest[i] = v;
2594 end
2595 end
2596 end
2597 recurse(source, dest);
2598end
2599
2600function WeakAuras.RegisterAddon(addon, displayName, description, icon)
2601 if(addons[addon]) then
2602 addons[addon].displayName = displayName;
2603 addons[addon].description = description;
2604 addons[addon].icon = icon;
2605 addons[addon].displays = addons[addon].displays or {};
2606 else
2607 addons[addon] = {
2608 displayName = displayName,
2609 description = description,
2610 icon = icon,
2611 displays = {}
2612 };
2613 end
2614end
2615
2616function WeakAuras.RegisterDisplay(addon, data, force)
2617 tinsert(from_files, {addon, data, force});
2618end
2619
2620function WeakAuras.AddManyFromAddons(table)
2621 for _, addData in ipairs(table) do
2622 WeakAuras.AddFromAddon(addData[1], addData[2], addData[3]);
2623 end
2624end
2625
2626function WeakAuras.AddFromAddon(addon, data, force)
2627 local id = data.id;
2628 if(id and addons[addon]) then
2629 addons[addon].displays[id] = data;
2630 if(db.registered[id]) then
2631 -- This display was already registered
2632 -- It is unnecessary to add it again
2633 elseif(force and not db.registered[id] == false) then
2634 if(db.displays[id]) then
2635 -- ID collision
2636 collisions[id] = {addon, data};
2637 else
2638 db.registered[id] = addon;
2639 WeakAuras.Add(data);
2640 end
2641 end
2642 end
2643end
2644
2645function WeakAuras.CollisionResolved(addon, data, force)
2646 WeakAuras.AddFromAddon(addon, data, force);
2647end
2648
2649function WeakAuras.IsDefinedByAddon(id)
2650 return db.registered[id];
2651end
2652
2653function WeakAuras.ResolveCollisions(onFinished)
2654 local num = 0;
2655 for id, _ in pairs(collisions) do
2656 num = num + 1;
2657 end
2658
2659 if(num > 0) then
2660 local baseText;
2661 local buttonText;
2662 if(registeredFromAddons) then
2663 if(num == 1) then
2664 baseText = L["Resolve collisions dialog singular"];
2665 buttonText = L["Done"];
2666 else
2667 baseText = L["Resolve collisions dialog"];
2668 buttonText = L["Next"];
2669 end
2670 else
2671 if(num == 1) then
2672 baseText = L["Resolve collisions dialog startup singular"];
2673 buttonText = L["Done"];
2674 else
2675 baseText = L["Resolve collisions dialog startup"];
2676 buttonText = L["Next"];
2677 end
2678 end
2679
2680 local numResolved = 0;
2681 local currentId = next(collisions);
2682
2683 local function UpdateText(popup)
2684 popup.text:SetText(baseText..(numResolved or "error").."/"..(num or "error"));
2685 end
2686
2687 StaticPopupDialogs["WEAKAURAS_RESOLVE_COLLISIONS"] = {
2688 text = baseText,
2689 button1 = buttonText,
2690 OnAccept = function(self)
2691 -- Do the collision resolution
2692 local newId = self.editBox:GetText();
2693 if(WeakAuras.OptionsFrame and WeakAuras.OptionsFrame() and WeakAuras.displayButtons and WeakAuras.displayButtons[currentId]) then
2694 WeakAuras.displayButtons[currentId].callbacks.OnRenameAction(newId)
2695 else
2696 local data = WeakAuras.GetData(currentId);
2697 if(data) then
2698 WeakAuras.Rename(data, newId);
2699 else
2700 print("|cFF8800FFWeakAuras|r: Data not found");
2701 end
2702 end
2703
2704 WeakAuras.CollisionResolved(collisions[currentId][1], collisions[currentId][2], true);
2705 numResolved = numResolved + 1;
2706
2707 -- Get the next id to resolve
2708 currentId = next(collisions, currentId);
2709 if(currentId) then
2710 -- There is another conflict to resolve - hook OnHide to reshow the dialog as soon as it hides
2711 self:SetScript("OnHide", function(self)
2712 self:Show();
2713 UpdateText(self);
2714 self.editBox:SetText(currentId);
2715 self:SetScript("OnHide", nil);
2716 if not(next(collisions, currentId)) then
2717 self.button1:SetText(L["Done"]);
2718 end
2719 end);
2720 else
2721 self.editBox:SetScript("OnTextChanged", nil);
2722 wipe(collisions);
2723 if(onFinished) then
2724 onFinished();
2725 end
2726 end
2727 end,
2728 hasEditBox = true,
2729 hasWideEditBox = true,
2730 hideOnEscape = true,
2731 whileDead = true,
2732 showAlert = true,
2733 timeout = 0,
2734 preferredindex = STATICPOPUP_NUMDIALOGS
2735 };
2736
2737 local popup = StaticPopup_Show("WEAKAURAS_RESOLVE_COLLISIONS");
2738 popup.editBox:SetScript("OnTextChanged", function(self)
2739 local newid = self:GetText();
2740 if(collisions[newid] or db.displays[newid]) then
2741 popup.button1:Disable();
2742 else
2743 popup.button1:Enable();
2744 end
2745 end);
2746 popup.editBox:SetText(currentId);
2747 popup.text:SetJustifyH("left");
2748 popup.icon:SetTexture("Interface\\Addons\\WeakAuras\\Media\\Textures\\icon.blp");
2749 popup.icon:SetVertexColor(0.833, 0, 1);
2750
2751 UpdateText(popup);
2752 elseif(onFinished) then
2753 onFinished();
2754 end
2755end
2756
2757StaticPopupDialogs["WEAKAURAS_CONFIRM_REPAIR"] = {
2758 text = "",
2759 button1 = L["Repair"],
2760 button2 = L["Cancel"],
2761 OnAccept = function(self)
2762 WeakAuras.RepairDatabase()
2763 end,
2764 OnShow = function(self)
2765 if self.data.reason == "user" then
2766 self.text:SetText(L["Manual Repair Confirmation Dialog"]:format(WeakAuras.LastUpgrade()))
2767 else
2768 self.text:SetText(L["Automatic Repair Confirmation Dialog"]:format(WeakAuras.LastUpgrade()))
2769 end
2770 end,
2771 OnCancel = function(self)
2772 if self.data.reason ~= "user" then
2773 WeakAuras.Login()
2774 end
2775 end,
2776 whileDead = true,
2777 showAlert = true,
2778 timeout = 0,
2779 preferredindex = STATICPOPUP_NUMDIALOGS
2780}
2781
2782function WeakAuras.LastUpgrade()
2783 return db.lastUpgrade and date(nil, db.lastUpgrade) or "unknown"
2784end
2785
2786function WeakAuras.NeedToRepairDatabase()
2787 return db.dbVersion and db.dbVersion > WeakAuras.InternalVersion()
2788end
2789
2790function WeakAuras.RepairDatabase(loginAfter)
2791 local coro = coroutine.create(function()
2792 WeakAuras.SetImporting(true)
2793 -- set db version to current code version
2794 db.dbVersion = WeakAuras.InternalVersion()
2795 -- reinstall snapshots from history
2796 for id, data in pairs(db.displays) do
2797 local snapshot = WeakAuras.GetMigrationSnapshot(data.uid, true)
2798 if snapshot then
2799 db.displays[id] = snapshot
2800 coroutine.yield()
2801 end
2802 end
2803 WeakAuras.SetImporting(false)
2804 -- finally, login
2805 WeakAuras.Login()
2806 end)
2807 WeakAuras.dynFrame:AddAction("repair", coro)
2808end
2809
2810local function ModernizeAnimation(animation)
2811 if (type(animation) ~= "string") then
2812 return nil;
2813 end
2814 return animation:gsub("^%s*return%s*", "");
2815end
2816
2817local function ModernizeAnimations(animations)
2818 if (not animations) then
2819 return;
2820 end
2821 animations.alphaFunc = ModernizeAnimation(animations.alphaFunc);
2822 animations.translateFunc = ModernizeAnimation(animations.translateFunc);
2823 animations.scaleFunc = ModernizeAnimation(animations.scaleFunc);
2824 animations.rotateFunc = ModernizeAnimation(animations.rotateFunc);
2825 animations.colorFunc = ModernizeAnimation(animations.colorFunc);
2826end
2827
2828local modelMigration = CreateFrame("PlayerModel")
2829
2830-- Takes as input a table of display data and attempts to update it to be compatible with the current version
2831function WeakAuras.Modernize(data)
2832 if (not data.internalVersion) then
2833 data.internalVersion = 1;
2834 end
2835
2836 -- Version 2 was introduced April 2018 in Legion
2837 if (data.internalVersion < 2) then
2838 -- Add trigger count
2839 if not data.numTriggers then
2840 data.numTriggers = 1 + (data.additional_triggers and #data.additional_triggers or 0)
2841 end
2842
2843 local load = data.load;
2844
2845 if (not load.ingroup) then
2846 load.ingroup = {};
2847 if (load.use_ingroup == true) then
2848 load.ingroup.single = nil;
2849 load.ingroup.multi = {
2850 ["group"] = true,
2851 ["raid"] = true
2852 };
2853 load.use_ingroup = false;
2854 elseif (load.use_ingroup == false) then
2855 load.ingroup.single = "solo";
2856 load.ingroup.multi = {};
2857 load.use_ingroup = true;
2858 end
2859 end
2860
2861
2862 -- Convert load options into single/multi format
2863 for index, prototype in pairs(WeakAuras.load_prototype.args) do
2864 local protoname = prototype.name;
2865 if(prototype.type == "multiselect") then
2866 if(not load[protoname] or type(load[protoname]) ~= "table") then
2867 local value = load[protoname];
2868 load[protoname] = {};
2869 if(value) then
2870 load[protoname].single = value;
2871 end
2872 end
2873 load[protoname].multi = load[protoname].multi or {};
2874 elseif(load[protoname] and type(load[protoname]) == "table") then
2875 load[protoname] = nil;
2876 end
2877 end
2878
2879 -- upgrade from singleselecting talents to multi select, see ticket 52
2880 if (type(load.talent) == "number") then
2881 local talent = load.talent;
2882 load.talent = {};
2883 load.talent.single = talent;
2884 load.talent.multi = {}
2885 end
2886
2887
2888 --upgrade to support custom trigger combination logic
2889 if (data.disjunctive == true) then
2890 data.disjunctive = "any";
2891 end
2892 if(data.disjunctive == false) then
2893 data.disjunctive = "all";
2894 end
2895
2896 -- Change English-language class tokens to locale-agnostic versions
2897 local class_agnosticize = {
2898 ["Death Knight"] = "DEATHKNIGHT",
2899 ["Druid"] = "DRUID",
2900 ["Hunter"] = "HUNTER",
2901 ["Mage"] = "MAGE",
2902 ["Monk"] = "MONK",
2903 ["Paladin"] = "PALADIN",
2904 ["Priest"] = "PRIEST",
2905 ["Rogue"] = "ROGUE",
2906 ["Shaman"] = "SHAMAN",
2907 ["Warlock"] = "WARLOCK",
2908 ["Warrior"] = "WARRIOR"
2909 };
2910
2911 if(load.class.single) then
2912 load.class.single = class_agnosticize[load.class.single] or load.class.single;
2913 end
2914
2915 if(load.class.multi) then
2916 for i,v in pairs(load.class.multi) do
2917 if(class_agnosticize[i]) then
2918 load.class.multi[class_agnosticize[i]] = true;
2919 load.class.multi[i] = nil;
2920 end
2921 end
2922 end
2923
2924 -- Add dynamic text info to Progress Bars
2925 -- Also convert custom displayText to new displayText
2926 if(data.regionType == "aurabar") then
2927 data.displayTextLeft = data.displayTextLeft or (not data.auto and data.displayText) or "%n";
2928 data.displayTextRight = data.displayTextRight or "%p";
2929
2930 if (data.barInFront ~= nil) then
2931 data.borderInFront = not data.barInFront;
2932 data.backdropInFront = not data.barInFront;
2933 data.barInFront = nil;
2934 end
2935 end
2936
2937 if(data.regionType == "icon") then
2938 if (data.cooldownTextEnabled == nil) then
2939 data.cooldownTextEnabled = true;
2940 end
2941 if (data.displayStacks) then
2942 data.text1Enabled = true;
2943 data.text1 = data.displayStacks;
2944 data.displayStacks = nil;
2945 data.text1Color = data.textColor;
2946 data.textColor = nil;
2947 data.text1Point = data.stacksPoint;
2948 data.stacksPoint = nil;
2949 data.text1Containment = data.stacksContainment;
2950 data.stacksContainment = nil;
2951 data.text1Font = data.font;
2952 data.font = nil;
2953 data.text1FontSize = data.fontSize;
2954 data.fontSize = nil;
2955 data.text1FontFlags = data.fontFlags;
2956 data.fontFlags = nil;
2957
2958 data.text2Enabled = false;
2959 data.text2 = "%p";
2960 data.text2Color = {1, 1, 1, 1};
2961 data.text2Point = "CENTER";
2962 data.text2Containment = "INSIDE";
2963 data.text2Font = "Friz Quadrata TT";
2964 data.text2FontSize = 24;
2965 data.text2FontFlags = "OUTLINE";
2966 end
2967 end
2968
2969 -- Upgrade some old variables
2970 if data.regionType == "aurabar" then
2971 -- "border" changed to "borderEdge"
2972 if data.border and type(data.border) ~= "boolean" then
2973 data.borderEdge = data.border;
2974 data.border = data.borderEdge ~= "None";
2975 end
2976 -- Multiple text settings
2977 if data.textColor then
2978 if not data.timerColor then
2979 data.timerColor = {};
2980 data.timerColor[1] = data.textColor[1];
2981 data.timerColor[2] = data.textColor[2];
2982 data.timerColor[3] = data.textColor[3];
2983 data.timerColor[4] = data.textColor[4];
2984 end
2985 if not data.stacksColor then
2986 data.stacksColor = {};
2987 data.stacksColor[1] = data.textColor[1];
2988 data.stacksColor[2] = data.textColor[2];
2989 data.stacksColor[3] = data.textColor[3];
2990 data.stacksColor[4] = data.textColor[4];
2991 end
2992 end
2993 -- Multiple text settings
2994 if data.font then
2995 if not data.textFont then
2996 data.textFont = data.font;
2997 end
2998 if not data.timerFont then
2999 data.timerFont = data.font;
3000 end
3001 if not data.stacksFont then
3002 data.stacksFont = data.font;
3003 end
3004
3005 data.font = nil;
3006 end
3007 -- Multiple text settings
3008 if data.fontSize then
3009 if not data.textSize then
3010 data.textSize = data.fontSize;
3011 end
3012 if not data.timerSize then
3013 data.timerSize = data.fontSize;
3014 end
3015 if not data.stacksSize then
3016 data.stacksSize = data.fontSize;
3017 end
3018
3019 data.fontSize = nil;
3020 end
3021
3022 -- fontFlags (outline)
3023 if not data.fontFlags then
3024 data.fontFlags = "OUTLINE";
3025 end
3026 end
3027
3028 if data.regionType == "text" then
3029 if (type(data.outline) == "boolean") then
3030 data.outline = data.outline and "OUTLINE" or "None";
3031 end
3032 end
3033
3034 if data.regionType == "model" then
3035 if (data.api == nil) then
3036 data.api = false;
3037 end
3038 end
3039
3040 if (data.regionType == "progresstexture") then
3041 if (not data.version or data.version < 2) then
3042 if (data.orientation == "CLOCKWISE") then
3043 if (data.inverse) then
3044 data.startAngle, data.endAngle = 360 - data.endAngle, 360 - data.startAngle;
3045 data.orientation = (data.orientation == "CLOCKWISE") and "ANTICLOCKWISE" or "CLOCKWISE";
3046 end
3047 elseif (data.orientation == "ANTICLOCKWISE") then
3048 data.startAngle, data.endAngle = 360 - data.endAngle, 360 - data.startAngle;
3049 if (data.inverse) then
3050 data.orientation = (data.orientation == "CLOCKWISE") and "ANTICLOCKWISE" or "CLOCKWISE";
3051 end
3052 end
3053 data.version = 2;
3054 end
3055 end
3056
3057 if (not data.activeTriggerMode) then
3058 data.activeTriggerMode = 0;
3059 end
3060
3061 if (data.sort == "hybrid") then
3062 if (not data.hybridPosition) then
3063 data.hybridPosition = "hybridLast";
3064 end
3065 if (not data.hybridSortMode) then
3066 data.hybridSortMode = "descending";
3067 end
3068 end
3069
3070 if (data.conditions) then
3071 for conditionIndex, condition in ipairs(data.conditions) do
3072 if (not condition.check) then
3073 condition.check = {
3074 ["trigger"] = condition.trigger,
3075 ["variable"] = condition.condition,
3076 ["op"] = condition.op,
3077 ["value"] = condition.value
3078 };
3079 condition.trigger = nil;
3080 condition.condition = nil;
3081 condition.op = nil;
3082 condition.value = nil;
3083 end
3084 end
3085 end
3086 ModernizeAnimations(data.animation and data.animation.start);
3087 ModernizeAnimations(data.animation and data.animation.main);
3088 ModernizeAnimations(data.animation and data.animation.finish);
3089 end -- End of V1 => V2
3090
3091 -- Version 3 was introduced April 2018 in Legion
3092 if (data.internalVersion < 3) then
3093 if (data.parent) then
3094 local parentData = WeakAuras.GetData(data.parent);
3095 if(parentData and parentData.regionType == "dynamicgroup") then
3096 -- Version 3 allowed for offsets for dynamic groups, before that they were ignored
3097 -- Thus reset them in the V2 to V3 upgrade
3098 data.xOffset = 0;
3099 data.yOffset = 0;
3100 end
3101 end
3102 end
3103
3104 -- Version 4 was introduced July 2018 in BfA
3105 if (data.internalVersion < 4) then
3106 if (data.conditions) then
3107 for conditionIndex, condition in ipairs(data.conditions) do
3108 if (condition.check) then
3109 local triggernum = condition.check.trigger;
3110 if (triggernum) then
3111 local trigger;
3112 if (triggernum == 0) then
3113 trigger = data.trigger;
3114 elseif(data.additional_triggers and data.additional_triggers[triggernum]) then
3115 trigger = data.additional_triggers[triggernum].trigger;
3116 end
3117 if (trigger and trigger.event == "Cooldown Progress (Spell)") then
3118 if (condition.check.variable == "stacks") then
3119 condition.check.variable = "charges";
3120 end
3121 end
3122 end
3123 end
3124 end
3125 end
3126 end
3127
3128 -- Version 5 was introduced July 2018 in BFA
3129 if data.internalVersion < 5 then
3130 -- this is to fix hybrid sorting
3131 if data.sortHybridTable then
3132 if data.controlledChildren then
3133 local newSortTable = {}
3134 for index, isHybrid in pairs(data.sortHybridTable) do
3135 local childID = data.controlledChildren[index]
3136 if childID then
3137 newSortTable[childID] = isHybrid
3138 end
3139 end
3140 data.sortHybridTable = newSortTable
3141 end
3142 end
3143 end
3144
3145 -- Version 6 was introduced July 30, 2018 in BFA
3146 -- Changes were entirely within triggers, so no code runs here
3147
3148 -- Version 7 was introduced September 1, 2018 in BFA
3149 -- Triggers were cleaned up into a 1-indexed array
3150
3151 if data.internalVersion < 7 then
3152
3153 -- migrate trigger data
3154 data.triggers = data.additional_triggers or {}
3155 tinsert(data.triggers, 1, {
3156 trigger = data.trigger or {},
3157 untrigger = data.untrigger or {},
3158 })
3159 data.additional_triggers = nil
3160 data.trigger = nil
3161 data.untrigger = nil
3162 data.numTriggers = nil
3163 data.triggers.customTriggerLogic = data.customTriggerLogic
3164 data.customTriggerLogic = nil
3165 local activeTriggerMode = data.activeTriggerMode or WeakAuras.trigger_modes.first_active
3166 if activeTriggerMode ~= WeakAuras.trigger_modes.first_active then
3167 activeTriggerMode = activeTriggerMode + 1
3168 end
3169 data.triggers.activeTriggerMode = activeTriggerMode
3170 data.activeTriggerMode = nil
3171 data.triggers.disjunctive = data.disjunctive
3172 data.disjunctive = nil
3173 -- migrate condition trigger references
3174 local function recurseRepairChecks(checks)
3175 if not checks then return end
3176 for _, check in pairs(checks) do
3177 if check.trigger and check.trigger >= 0 then
3178 check.trigger = check.trigger + 1
3179 end
3180 recurseRepairChecks(check.checks)
3181 end
3182 end
3183 for _, condition in pairs(data.conditions) do
3184 if condition.check.trigger and condition.check.trigger >= 0 then
3185 condition.check.trigger = condition.check.trigger + 1
3186 end
3187 recurseRepairChecks(condition.check.checks)
3188 end
3189 end
3190
3191 -- Version 8 was introduced in September 2018
3192 -- Changes are in PreAdd
3193
3194 -- Version 9 was introduced in September 2018
3195 if data.internalVersion < 9 then
3196 local function repairCheck(check)
3197 if check and check.variable == "buffed" then
3198 local trigger = check.trigger and data.triggers[check.trigger] and data.triggers[check.trigger].trigger;
3199 if (trigger) then
3200 if(trigger.buffShowOn == "showOnActive") then
3201 check.variable = "show";
3202 elseif (trigger.buffShowOn == "showOnMissing") then
3203 check.variable = "show";
3204 check.value = check.value == 0 and 1 or 0;
3205 end
3206 end
3207 end
3208 end
3209
3210 local function recurseRepairChecks(checks)
3211 if not checks then return end
3212 for _, check in pairs(checks) do
3213 repairCheck(check);
3214 recurseRepairChecks(check.checks);
3215 end
3216 end
3217 for _, condition in pairs(data.conditions) do
3218 repairCheck(condition.check);
3219 recurseRepairChecks(condition.check.checks);
3220 end
3221 end
3222
3223 -- Version 10 is skipped, due to a bad migration script (see https://github.com/WeakAuras/WeakAuras2/pull/1091)
3224
3225 -- Version 11 was introduced in January 2019
3226 if data.internalVersion < 11 then
3227 if data.url and data.url ~= "" then
3228 local slug, version = data.url:match("wago.io/([^/]+)/([0-9]+)")
3229 if not slug and not version then
3230 version = 1
3231 end
3232 if version and tonumber(version) then
3233 data.version = tonumber(version)
3234 end
3235 end
3236 end
3237
3238 -- Version 12 was introduced February 2019 in BfA
3239 if (data.internalVersion < 12) then
3240 if data.cooldownTextEnabled ~= nil then
3241 data.cooldownTextDisabled = not data.cooldownTextEnabled
3242 data.cooldownTextEnabled = nil
3243 end
3244 end
3245
3246 -- Version 13 was introduced March 2019 in BFA
3247 if data.internalVersion < 13 then
3248 if data.regionType == "dynamicgroup" then
3249 local selfPoints = {
3250 default = "CENTER",
3251 RIGHT = function(data)
3252 if data.align == "LEFT" then
3253 return "TOPLEFT"
3254 elseif data.align == "RIGHT" then
3255 return "BOTTOMLEFT"
3256 else
3257 return "LEFT"
3258 end
3259 end,
3260 LEFT = function(data)
3261 if data.align == "LEFT" then
3262 return "TOPRIGHT"
3263 elseif data.align == "RIGHT" then
3264 return "BOTTOMRIGHT"
3265 else
3266 return "RIGHT"
3267 end
3268 end,
3269 UP = function(data)
3270 if data.align == "LEFT" then
3271 return "BOTTOMLEFT"
3272 elseif data.align == "RIGHT" then
3273 return "BOTTOMRIGHT"
3274 else
3275 return "BOTTOM"
3276 end
3277 end,
3278 DOWN = function(data)
3279 if data.align == "LEFT" then
3280 return "TOPLEFT"
3281 elseif data.align == "RIGHT" then
3282 return "TOPRIGHT"
3283 else
3284 return "TOP"
3285 end
3286 end,
3287 HORIZONTAL = function(data)
3288 if data.align == "LEFT" then
3289 return "TOP"
3290 elseif data.align == "RIGHT" then
3291 return "BOTTOM"
3292 else
3293 return "CENTER"
3294 end
3295 end,
3296 VERTICAL = function(data)
3297 if data.align == "LEFT" then
3298 return "LEFT"
3299 elseif data.align == "RIGHT" then
3300 return "RIGHT"
3301 else
3302 return "CENTER"
3303 end
3304 end,
3305 CIRCLE = "CENTER",
3306 COUNTERCIRCLE = "CENTER",
3307 }
3308 local selfPoint = selfPoints[data.grow or "DOWN"] or selfPoints.DOWN
3309 if type(selfPoint) == "function" then
3310 selfPoint = selfPoint(data)
3311 end
3312 data.selfPoint = selfPoint
3313 end
3314 end
3315
3316 -- Version 14 was introduced March 2019 in BFA
3317 if data.internalVersion < 14 then
3318 if data.triggers then
3319 for triggerId, triggerData in pairs(data.triggers) do
3320 if type(triggerData) == "table"
3321 and triggerData.trigger
3322 and triggerData.trigger.debuffClass
3323 and type(triggerData.trigger.debuffClass) == "string"
3324 and triggerData.trigger.debuffClass ~= ""
3325 then
3326 local idx = triggerData.trigger.debuffClass
3327 data.triggers[triggerId].trigger.debuffClass = { [idx] = true }
3328 end
3329 end
3330 end
3331 end
3332
3333 -- Version 15 was introduced April 2019 in BFA
3334 if data.internalVersion < 15 then
3335 if data.triggers then
3336 for triggerId, triggerData in ipairs(data.triggers) do
3337 if triggerData.trigger.type == "status" and triggerData.trigger.event == "Spell Known" then
3338 triggerData.trigger.use_exact_spellName = true
3339 end
3340 end
3341 end
3342 end
3343
3344 -- Version 16 was introduced May 2019 in BFA
3345 if data.internalVersion < 16 then
3346 -- first conversion: attempt to migrate texture paths to file ids
3347 if data.regionType == "texture" and type(data.texture) == "string" then
3348 local textureId = GetFileIDFromPath(data.texture:gsub("\\\\", "\\"))
3349 if textureId and textureId > 0 then
3350 data.texture = tostring(textureId)
3351 end
3352 end
3353 if data.regionType == "progresstexture" then
3354 if type(data.foregroundTexture) == "string" then
3355 local textureId = GetFileIDFromPath(data.foregroundTexture:gsub("\\\\", "\\"))
3356 if textureId and textureId > 0 then
3357 data.foregroundTexture = tostring(textureId)
3358 end
3359 end
3360 if type(data.backgroundTexture) == "string" then
3361 local textureId = GetFileIDFromPath(data.backgroundTexture:gsub("\\\\", "\\"))
3362 if textureId and textureId > 0 then
3363 data.backgroundTexture = tostring(textureId)
3364 end
3365 end
3366 end
3367 -- second conversion: migrate name/realm conditions to tristate
3368 if data.load.use_name == false then
3369 data.load.use_name = nil
3370 end
3371 if data.load.use_realm == false then
3372 data.load.use_realm = nil
3373 end
3374 end
3375
3376 -- Version 18 was a migration for stance/form trigger, but deleted later because of migration issue
3377
3378 -- Version 19 were introduced in July 2019 in BFA
3379 if data.internalVersion < 19 then
3380 if data.triggers then
3381 for triggerId, triggerData in ipairs(data.triggers) do
3382 if triggerData.trigger.type == "status" and triggerData.trigger.event == "Cast" and triggerData.trigger.unit == "multi" then
3383 triggerData.trigger.unit = "nameplate"
3384 end
3385 end
3386 end
3387 end
3388
3389 -- Version 20 was introduced July 2019 in BFA
3390 if data.internalVersion < 20 then
3391 if data.regionType == "icon" then
3392 local convertPoint = function(containment, point)
3393 if not point or point == "CENTER" then
3394 return "CENTER"
3395 elseif containment == "INSIDE" then
3396 return "INNER_" .. point
3397 elseif containment == "OUTSIDE" then
3398 return "OUTER_" .. point
3399 end
3400 end
3401
3402 local text1 = {
3403 ["type"] = "subtext",
3404 text_visible = data.text1Enabled ~= false,
3405 text_color = data.text1Color,
3406 text_text = data.text1,
3407 text_font = data.text1Font,
3408 text_fontSize = data.text1FontSize,
3409 text_fontType = data.text1FontFlags,
3410 text_selfPoint = "AUTO",
3411 text_anchorPoint = convertPoint(data.text1Containment, data.text1Point),
3412 anchorXOffset = 0,
3413 anchorYOffset = 0,
3414 text_shadowColor = { 0, 0, 0, 1},
3415 text_shadowXOffset = 0,
3416 text_shadowYOffset = 0,
3417 }
3418
3419 local usetext2 = data.text2Enabled
3420
3421 local text2 = {
3422 ["type"] = "subtext",
3423 text_visible = data.text2Enabled or false,
3424 text_color = data.text2Color,
3425 text_text = data.text2,
3426 text_font = data.text2Font,
3427 text_fontSize = data.text2FontSize,
3428 text_fontType = data.text2FontFlags,
3429 text_selfPoint = "AUTO",
3430 text_anchorPoint = convertPoint(data.text2Containment, data.text2Point),
3431 anchorXOffset = 0,
3432 anchorYOffset = 0,
3433 text_shadowColor = { 0, 0, 0, 1},
3434 text_shadowXOffset = 0,
3435 text_shadowYOffset = 0,
3436 }
3437
3438 data.text1Enabled = nil
3439 data.text1Color = nil
3440 data.text1 = nil
3441 data.text1Font = nil
3442 data.text1FontSize = nil
3443 data.text1FontFlags = nil
3444 data.text1Containment = nil
3445 data.text1Point = nil
3446
3447 data.text2Enabled = nil
3448 data.text2Color = nil
3449 data.text2 = nil
3450 data.text2Font = nil
3451 data.text2FontSize = nil
3452 data.text2FontFlags = nil
3453 data.text2Containment = nil
3454 data.text2Point = nil
3455
3456 local propertyRenames = {
3457 text1Color = "sub.1.text_color",
3458 text1FontSize = "sub.1.text_fontSize",
3459 text2Color = "sub.2.text_color",
3460 text2FontSize = "sub.2.text_fontSize"
3461 }
3462
3463 tinsert(data.subRegions, text1)
3464 if (usetext2) then
3465 tinsert(data.subRegions, text2)
3466 end
3467
3468 if (data.conditions) then
3469 for conditionIndex, condition in ipairs(data.conditions) do
3470 for changeIndex, change in ipairs(condition.changes) do
3471 if propertyRenames[change.property] then
3472 change.property = propertyRenames[change.property]
3473 end
3474 end
3475 end
3476 end
3477 end
3478 end
3479
3480 -- Version 20 was introduced May 2019 in BFA
3481 if data.internalVersion < 20 then
3482 if data.regionType == "aurabar" then
3483 local orientationToPostion = {
3484 HORIZONTAL_INVERSE = { "INNER_LEFT", "INNER_RIGHT" },
3485 HORIZONTAL = { "INNER_RIGHT", "INNER_LEFT" },
3486 VERTICAL_INVERSE = { "INNER_BOTTOM", "INNER_TOP" },
3487 VERTICAL = {"INNER_TOP", "INNER_BOTTOM"}
3488 }
3489
3490 local positions = orientationToPostion[data.orientation] or { "INNER_LEFT", "INNER_RIGHT" }
3491
3492 local text1 = {
3493 ["type"] = "subtext",
3494 text_visible = data.timer,
3495 text_color = data.timerColor,
3496 text_text = data.displayTextRight,
3497 text_font = data.timerFont,
3498 text_fontSize = data.timerSize,
3499 text_fontType = data.timerFlags,
3500 text_selfPoint = "AUTO",
3501 text_anchorPoint = positions[1],
3502 anchorXOffset = 0,
3503 anchorYOffset = 0,
3504 text_shadowColor = { 0, 0, 0, 1},
3505 text_shadowXOffset = 1,
3506 text_shadowYOffset = -1,
3507 rotateText = data.rotateText
3508 }
3509
3510 local text2 = {
3511 ["type"] = "subtext",
3512 text_visible = data.text,
3513 text_color = data.textColor,
3514 text_text = data.displayTextLeft,
3515 text_font = data.textFont,
3516 text_fontSize = data.textSize,
3517 text_fontType = data.textFlags,
3518 text_selfPoint = "AUTO",
3519 text_anchorPoint = positions[2],
3520 anchorXOffset = 0,
3521 anchorYOffset = 0,
3522 text_shadowColor = { 0, 0, 0, 1},
3523 text_shadowXOffset = 1,
3524 text_shadowYOffset = -1,
3525 rotateText = data.rotateText
3526 }
3527
3528 local text3 = {
3529 ["type"] = "subtext",
3530 text_visible = data.stacks,
3531 text_color = data.stacksColor,
3532 text_text = "%s",
3533 text_font = data.stacksFont,
3534 text_fontSize = data.stacksSize,
3535 text_fontType = data.stacksFlags,
3536 text_selfPoint = "AUTO",
3537 text_anchorPoint = "ICON_CENTER",
3538 anchorXOffset = 0,
3539 anchorYOffset = 0,
3540 text_shadowColor = { 0, 0, 0, 1},
3541 text_shadowXOffset = 1,
3542 text_shadowYOffset = -1,
3543 rotateText = data.rotateText
3544 }
3545
3546 data.timer = nil
3547 data.textColor = nil
3548 data.displayTextRight = nil
3549 data.textFont = nil
3550 data.textSize = nil
3551 data.textFlags = nil
3552 data.text = nil
3553 data.timerColor = nil
3554 data.displayTextLeft = nil
3555 data.timerFont = nil
3556 data.timerSize = nil
3557 data.timerFlags = nil
3558 data.stacks = nil
3559 data.stacksColor = nil
3560 data.stacksFont = nil
3561 data.stacksSize = nil
3562 data.stacksFlags = nil
3563 data.rotateText = nil
3564
3565 local propertyRenames = {
3566 timerColor = "sub.1.text_color",
3567 timerSize = "sub.1.text_fontSize",
3568 textColor = "sub.2.text_color",
3569 textSize = "sub.2.text_fontSize",
3570 stacksColor = "sub.3.text_color",
3571 stacksSize = "sub.3.text_fontSize",
3572 }
3573
3574 data.subRegions = data.subRegions or {}
3575 tinsert(data.subRegions, text1)
3576 tinsert(data.subRegions, text2)
3577 tinsert(data.subRegions, text3)
3578
3579 if (data.conditions) then
3580 for conditionIndex, condition in ipairs(data.conditions) do
3581 for changeIndex, change in ipairs(condition.changes) do
3582 if propertyRenames[change.property] then
3583 change.property = propertyRenames[change.property]
3584 end
3585 end
3586 end
3587 end
3588
3589 end
3590 end
3591
3592 if data.internalVersion < 21 then
3593 if data.regionType == "dynamicgroup" then
3594 data.border = data.background and data.background ~= "None"
3595 data.borderEdge = data.border
3596 data.borderBackdrop = data.background ~= "None" and data.background
3597 data.borderInset = data.backgroundInset
3598 data.background = nil
3599 data.backgroundInset = nil
3600 end
3601 end
3602
3603 if data.internalVersion < 22 then
3604 if data.regionType == "aurabar" then
3605 data.subRegions = data.subRegions or {}
3606
3607 local border = {
3608 ["type"] = "subborder",
3609 border_visible = data.border,
3610 border_color = data.borderColor,
3611 border_edge = data.borderEdge,
3612 border_offset = data.borderOffset,
3613 border_size = data.borderSize,
3614 border_anchor = "bar",
3615 }
3616
3617 data.border = nil
3618 data.borderColor = nil
3619 data.borderEdge = nil
3620 data.borderOffset = nil
3621 data.borderInset = nil
3622 data.borderSize = nil
3623 if data.borderInFront then
3624 tinsert(data.subRegions, border)
3625 else
3626 tinsert(data.subRegions, 1, border)
3627 end
3628
3629 local propertyRenames = {
3630 borderColor = "sub.".. #data.subRegions..".border_color",
3631 }
3632
3633 if (data.conditions) then
3634 for conditionIndex, condition in ipairs(data.conditions) do
3635 for changeIndex, change in ipairs(condition.changes) do
3636 if propertyRenames[change.property] then
3637 change.property = propertyRenames[change.property]
3638 end
3639 end
3640 end
3641 end
3642 end
3643 end
3644
3645 if data.internalVersion < 23 then
3646 if data.triggers then
3647 for triggerId, triggerData in ipairs(data.triggers) do
3648 local trigger = triggerData.trigger
3649 -- Stance/Form/Aura form field type changed from type="select" to type="multiselect"
3650 if trigger and trigger.type == "status" and trigger.event == "Stance/Form/Aura" then
3651 local value = trigger.form
3652 if type(value) ~= "table" then
3653 if trigger.use_form == false then
3654 if value then
3655 trigger.form = { multi = { [value] = true } }
3656 else
3657 trigger.form = { multi = { } }
3658 end
3659 elseif trigger.use_form then
3660 trigger.form = { single = value }
3661 end
3662 end
3663 end
3664 end
3665 end
3666 end
3667
3668 if data.internalVersion < 24 then
3669 if data.triggers then
3670 for triggerId, triggerData in ipairs(data.triggers) do
3671 local trigger = triggerData.trigger
3672 if trigger and trigger.type == "status" and trigger.event == "Weapon Enchant" then
3673 if trigger.use_inverse then
3674 trigger.showOn = "showOnMissing"
3675 else
3676 trigger.showOn = "showOnActive"
3677 end
3678 trigger.use_inverse = nil
3679 if not trigger.use_weapon then
3680 trigger.use_weapon = "true"
3681 trigger.weapon = "main"
3682 end
3683 end
3684 end
3685 end
3686 end
3687
3688 if data.internalVersion < 25 then
3689 if data.regionType == "icon" then
3690 data.subRegions = data.subRegions or {}
3691 -- Need to check if glow is needed
3692
3693 local prefix = "sub.".. #data.subRegions + 1 .. "."
3694 -- For Conditions
3695 local propertyRenames = {
3696 glow = prefix .. "glow",
3697 useGlowColor = prefix .. "useGlowColor",
3698 glowColor = prefix .. "glowColor",
3699 glowType = prefix .. "glowType",
3700 glowLines = prefix .. "glowLines",
3701 glowFrequency = prefix .. "glowFrequency",
3702 glowLength = prefix .. "glowLength",
3703 glowThickness = prefix .. "glowThickness",
3704 glowScale = prefix .. "glowScale",
3705 glowBorder = prefix .. "glowBorder",
3706 glowXOffset = prefix .. "glowXOffset",
3707 glowYOffset = prefix .. "glowYOffset",
3708 }
3709
3710 local needsGlow = data.glow
3711 if (not needsGlow and data.conditions) then
3712 for conditionIndex, condition in ipairs(data.conditions) do
3713 for changeIndex, change in ipairs(condition.changes) do
3714 if propertyRenames[change.property] then
3715 needsGlow = true
3716 break
3717 end
3718 end
3719 end
3720 end
3721
3722 if needsGlow then
3723 local glow = {
3724 ["type"] = "subglow",
3725 glow = data.glow,
3726 useGlowColor = data.useGlowColor,
3727 glowColor = data.glowColor,
3728 glowType = data.glowType,
3729 glowLines = data.glowLines,
3730 glowFrequency = data.glowFrequency,
3731 glowLength = data.glowLength,
3732 glowThickness = data.glowThickness,
3733 glowScale = data.glowScale,
3734 glowBorder = data.glowBorder,
3735 glowXOffset = data.glowXOffset,
3736 glowYOffset = data.glowYOffset,
3737 }
3738 tinsert(data.subRegions, glow)
3739 end
3740
3741 data.glow = nil
3742 data.useglowColor = nil
3743 data.useGlowColor = nil
3744 data.glowColor = nil
3745 data.glowType = nil
3746 data.glowLines = nil
3747 data.glowFrequency = nil
3748 data.glowLength = nil
3749 data.glowThickness = nil
3750 data.glowScale = nil
3751 data.glowBorder = nil
3752 data.glowXOffset = nil
3753 data.glowYOffset = nil
3754
3755 if (data.conditions) then
3756 for conditionIndex, condition in ipairs(data.conditions) do
3757 for changeIndex, change in ipairs(condition.changes) do
3758 if propertyRenames[change.property] then
3759 change.property = propertyRenames[change.property]
3760 end
3761 end
3762 end
3763 end
3764 end
3765 end
3766
3767 if data.internalVersion < 26 then
3768 if data.conditions then
3769 for conditionIndex, condition in ipairs(data.conditions) do
3770 for changeIndex, change in ipairs(condition.changes) do
3771 if change.property == "xOffset" or change.property == "yOffset" then
3772 change.value = (change.value or 0) - (data[change.property] or 0)
3773 change.property = change.property .. "Relative"
3774 end
3775 end
3776 end
3777 end
3778 end
3779
3780 for _, triggerSystem in pairs(triggerSystems) do
3781 triggerSystem.Modernize(data);
3782 end
3783
3784 data.internalVersion = max(data.internalVersion or 0, internalVersion);
3785end
3786
3787function WeakAuras.ValidateUniqueDataIds(silent)
3788 -- ensure that there are no duplicated uids anywhere in the database
3789 local seenUIDs = {}
3790 for _, data in pairs(db.displays) do
3791 if type(data.uid) == "string" then
3792 if seenUIDs[data.uid] then
3793 if not silent then
3794 prettyPrint("duplicate uid \""..data.uid.."\" detected in saved variables between \""..data.id.."\" and \""..seenUIDs[data.uid].id.."\".")
3795 end
3796 data.uid = WeakAuras.GenerateUniqueID()
3797 seenUIDs[data.uid] = data
3798 else
3799 seenUIDs[data.uid] = data
3800 end
3801 elseif data.uid ~= nil then
3802 if not silent then
3803 prettyPrint("invalid uid detected in saved variables for \""..data.id.."\"")
3804 end
3805 data.uid = WeakAuras.GenerateUniqueID()
3806 seenUIDs[data.uid] = data
3807 end
3808 end
3809 for uid, data in pairs(seenUIDs) do
3810 UIDtoID[uid] = data.id
3811 end
3812end
3813
3814function WeakAuras.SyncParentChildRelationships(silent)
3815 -- 1. Find all auras where data.parent ~= nil or data.controlledChildren ~= nil
3816 -- If an aura has both, then remove data.parent
3817 -- If an aura has data.parent which doesn't exist, then remove data.parent
3818 -- If an aura has data.parent which doesn't have data.controledChildren, then remove data.parent
3819 -- 2. For each aura with data.controlledChildren, iterate through the list of children and remove entries where:
3820 -- The child doesn't exist in the database
3821 -- The child ID is duplicated in data.controlledChildren (only the first will be kept)
3822 -- The child's data.parent points to a different parent
3823 -- Otherwise, mark the child as having a valid parent relationship
3824 -- 3. For each aura with data.parent, remove data.parent if it was not marked to have a valid relationship in 2.
3825 local parents = {}
3826 local children = {}
3827 local childHasParent = {}
3828 for id, data in pairs(db.displays) do
3829 if data.parent then
3830 if data.controlledChildren then
3831 if not silent then
3832 prettyPrint("detected corruption in saved variables: "..id.." is a group that thinks it's a parent.")
3833 end
3834 -- A display cannot have both children and a parent
3835 data.parent = nil
3836 parents[id] = data
3837 elseif not db.displays[data.parent] then
3838 if not(silent) then
3839 prettyPrint("detected corruption in saved variables: "..id.." has a nonexistent parent.")
3840 end
3841 data.parent = nil
3842 elseif not db.displays[data.parent].controlledChildren then
3843 if not silent then
3844 prettyPrint("detected corruption in saved variables: "..id.." thinks "..data.parent..
3845 " controls it, but "..data.parent.." is not a group.")
3846 end
3847 data.parent = nil
3848 else
3849 children[id] = data
3850 end
3851 elseif data.controlledChildren then
3852 parents[id] = data
3853 end
3854 end
3855
3856 for id, data in pairs(parents) do
3857 local groupChildren = {}
3858 local childrenToRemove = {}
3859 for index, childID in ipairs(data.controlledChildren) do
3860 local child = children[childID]
3861 if not child then
3862 if not silent then
3863 prettyPrint("detected corruption in saved variables: "..id.." thinks it controls "..childID.." which doesn't exist.")
3864 end
3865 childrenToRemove[index] = true
3866 elseif child.parent ~= id then
3867 if not silent then
3868 prettyPrint("detected corruption in saved variables: "..id.." thinks it controls "..childID.." which it does not.")
3869 end
3870 childrenToRemove[index] = true
3871 elseif groupChildren[childID] then
3872 if not silent then
3873 prettyPrint("detected corruption in saved variables: "..id.." has "..childID.." as a child in multiple positions.")
3874 end
3875 childrenToRemove[index] = true
3876 else
3877 groupChildren[childID] = index
3878 childHasParent[childID] = true
3879 end
3880 end
3881 if next(childrenToRemove) ~= nil then
3882 for i = #data.controlledChildren, 1, -1 do
3883 if childrenToRemove[i] then
3884 tremove(data.controlledChildren, i)
3885 end
3886 end
3887 end
3888 end
3889
3890 for id, data in pairs(children) do
3891 if not childHasParent[id] then
3892 if not silent then
3893 prettyPrint("detected corruption in saved variables: "..id.." should be controlled by "..data.parent.." but isn't.")
3894 end
3895 local parent = parents[data.parent]
3896 tinsert(parent.controlledChildren, id)
3897 end
3898 end
3899end
3900
3901function WeakAuras.AddMany(table, takeSnapshots)
3902 local idtable = {};
3903 for _, data in ipairs(table) do
3904 idtable[data.id] = data;
3905 end
3906 local loaded = {};
3907 local function load(id, depends)
3908 local data = idtable[id];
3909 if(data.parent) then
3910 if(idtable[data.parent]) then
3911 if(tContains(depends, data.parent)) then
3912 error("Circular dependency in WeakAuras.AddMany between "..table.concat(depends, ", "));
3913 else
3914 if not(loaded[data.parent]) then
3915 local dependsOut = {};
3916 for i,v in pairs(depends) do
3917 dependsOut[i] = v;
3918 end
3919 tinsert(dependsOut, data.parent);
3920 load(data.parent, dependsOut);
3921 end
3922 end
3923 else
3924 data.parent = nil;
3925 end
3926 end
3927 if not(loaded[id]) then
3928 WeakAuras.Add(data, takeSnapshots);
3929 coroutine.yield();
3930 loaded[id] = true;
3931 end
3932 end
3933 local groups = {}
3934 for id, data in pairs(idtable) do
3935 load(id, {});
3936 if data.regionType == "dynamicgroup" or data.regionType == "group" then
3937 groups[data] = true
3938 end
3939 end
3940 for data in pairs(groups) do
3941 if data.type == "dynamicgroup" then
3942 regions[data.id].region:ReloadControlledChildren()
3943 else
3944 WeakAuras.Add(data)
3945 end
3946 coroutine.yield();
3947 end
3948end
3949
3950local function customOptionIsValid(option)
3951 if not option.type then
3952 return false
3953 elseif WeakAuras.author_option_classes[option.type] == "simple" then
3954 if not option.key
3955 or not option.name
3956 or not option.default == nil then
3957 return false
3958 end
3959 elseif WeakAuras.author_option_classes[option.type] == "group" then
3960 if not option.key
3961 or not option.name
3962 or not option.default == nil
3963 or not option.subOptions then
3964 return false
3965 end
3966 end
3967 return true
3968end
3969
3970local function validateUserConfig(data, options, config)
3971 local authorOptionKeys, corruptOptions = {}, {}
3972 for index, option in ipairs(options) do
3973 if not customOptionIsValid(option) then
3974 prettyPrint(data.id .. " Custom Option #" .. index .. " in " .. data.id .. " has been detected as corrupt, and has been deleted.")
3975 corruptOptions[index] = true
3976 else
3977 local optionClass = WeakAuras.author_option_classes[option.type]
3978 if optionClass == "simple" then
3979 if not option.key then
3980 option.key = WeakAuras.GenerateUniqeID()
3981 end
3982 authorOptionKeys[option.key] = index
3983 if config[option.key] == nil then
3984 if type(option.default) ~= "table" then
3985 config[option.key] = option.default
3986 else
3987 config[option.key] = CopyTable(option.default)
3988 end
3989 end
3990 elseif optionClass == "group" then
3991 authorOptionKeys[option.key] = "group"
3992 local subOptions = option.subOptions
3993 if type(config[option.key]) ~= "table" then
3994 config[option.key] = {}
3995 end
3996 local subConfig = config[option.key]
3997 if option.groupType == "array" then
3998 for k, v in pairs(subConfig) do
3999 if type(k) ~= "number" or type(v) ~= "table" then
4000 -- if k was not a number, then this was a simple group before
4001 -- if v is not a table, then this was likely a color option
4002 wipe(subConfig) -- second iteration will fill table with defaults
4003 break
4004 end
4005 end
4006 if option.limitType == "fixed" then
4007 for i = #subConfig + 1, option.size do
4008 -- add missing entries
4009 subConfig[i] = {}
4010 end
4011 end
4012 if option.limitType ~= "none" then
4013 for i = option.size + 1, #subConfig do
4014 -- remove excess entries
4015 subConfig[i] = nil
4016 end
4017 end
4018 for _, toValidate in pairs(subConfig) do
4019 validateUserConfig(data, subOptions, toValidate)
4020 end
4021 else
4022 if type(next(subConfig)) ~= "string" then
4023 -- either there are no sub options, in which case this is a noop
4024 -- or this group was previously an array, in which case we need to wipe
4025 wipe(subConfig)
4026 end
4027 validateUserConfig(data, subOptions, subConfig)
4028 end
4029 end
4030 end
4031 end
4032 for i = #options, 1, -1 do
4033 if corruptOptions[i] then
4034 tremove(options, i)
4035 end
4036 end
4037 for key, value in pairs(config) do
4038 if not authorOptionKeys[key] then
4039 config[key] = nil
4040 elseif authorOptionKeys[key] ~= "group" then
4041 local option = options[authorOptionKeys[key]]
4042 if type(value) ~= type(option.default) then
4043 -- if type mismatch then we know that it can't be right
4044 if type(option.default) ~= "table" then
4045 config[key] = option.default
4046 else
4047 config[key] = CopyTable(option.default)
4048 end
4049 elseif option.type == "input" and option.useLength then
4050 config[key] = config[key]:sub(1, option.length)
4051 elseif option.type == "number" or option.type == "range" then
4052 if (option.max and option.max < value) or (option.min and option.min > value) then
4053 config[key] = option.default
4054 else
4055 if option.type == "number" and option.step then
4056 local min = option.min or 0
4057 config[key] = option.step * Round((value - min)/option.step) + min
4058 end
4059 end
4060 elseif option.type == "select" then
4061 if value < 1 or value > #option.values then
4062 config[key] = option.default
4063 end
4064 elseif option.type == "multiselect" then
4065 local multiselect = config[key]
4066 for i, v in ipairs(multiselect) do
4067 if option.default[i] ~= nil then
4068 if type(v) ~= "boolean" then
4069 multiselect[i] = option.default[i]
4070 end
4071 else
4072 multiselect[i] = nil
4073 end
4074 end
4075 for i, v in ipairs(option.default) do
4076 if type(multiselect[i]) ~= "boolean" then
4077 multiselect[i] = v
4078 end
4079 end
4080 elseif option.type == "color" then
4081 for i = 1, 4 do
4082 local c = config[key][i]
4083 if type(c) ~= "number" or c < 0 or c > 1 then
4084 config[key] = option.default
4085 break
4086 end
4087 end
4088 end
4089 end
4090 end
4091end
4092
4093local function removeSpellNames(data)
4094 local trigger
4095 for i = 1, #data.triggers do
4096 trigger = data.triggers[i].trigger
4097 if trigger and trigger.type == "aura" then
4098 if type(trigger.spellName) == "number" then
4099 trigger.realSpellName = GetSpellInfo(trigger.spellName) or trigger.realSpellName
4100 end
4101 if (trigger.spellId) then
4102 trigger.name = GetSpellInfo(trigger.spellId) or trigger.name;
4103 end
4104 if (trigger.spellIds) then
4105 for i = 1, 10 do
4106 if (trigger.spellIds[i]) then
4107 trigger.names = trigger.names or {};
4108 trigger.names[i] = GetSpellInfo(trigger.spellIds[i]) or trigger.names[i];
4109 end
4110 end
4111 end
4112 end
4113 end
4114end
4115
4116local oldDataStub = {
4117 -- note: this is the minimal data stub which prevents false positives in WeakAuras.diff upon reimporting an aura.
4118 -- pending a refactor of other code which adds unnecessary fields, it is possible to shrink it
4119 trigger = {
4120 type = "aura",
4121 names = {},
4122 event = "Health",
4123 subeventPrefix = "SPELL",
4124 subeventSuffix = "_CAST_START",
4125 spellIds = {},
4126 unit = "player",
4127 debuffType = "HELPFUL",
4128 },
4129 numTriggers = 1,
4130 untrigger = {},
4131 load = {
4132 size = {
4133 multi = {},
4134 },
4135 spec = {
4136 multi = {},
4137 },
4138 class = {
4139 multi = {},
4140 },
4141 },
4142 actions = {
4143 init = {},
4144 start = {},
4145 finish = {},
4146 },
4147 animation = {
4148 start = {
4149 type = "none",
4150 duration_type = "seconds",
4151 },
4152 main = {
4153 type = "none",
4154 duration_type = "seconds",
4155 },
4156 finish = {
4157 type = "none",
4158 duration_type = "seconds",
4159 },
4160 },
4161 conditions = {},
4162}
4163
4164local oldDataStub2 = {
4165 -- note: this is the minimal data stub which prevents false positives in WeakAuras.diff upon reimporting an aura.
4166 -- pending a refactor of other code which adds unnecessary fields, it is possible to shrink it
4167 triggers = {
4168 {
4169 trigger = {
4170 type = "aura",
4171 names = {},
4172 event = "Health",
4173 subeventPrefix = "SPELL",
4174 subeventSuffix = "_CAST_START",
4175 spellIds = {},
4176 unit = "player",
4177 debuffType = "HELPFUL",
4178 },
4179 untrigger = {},
4180 },
4181 },
4182 load = {
4183 size = {
4184 multi = {},
4185 },
4186 spec = {
4187 multi = {},
4188 },
4189 class = {
4190 multi = {},
4191 },
4192 },
4193 actions = {
4194 init = {},
4195 start = {},
4196 finish = {},
4197 },
4198 animation = {
4199 start = {
4200 type = "none",
4201 duration_type = "seconds",
4202 },
4203 main = {
4204 type = "none",
4205 duration_type = "seconds",
4206 },
4207 finish = {
4208 type = "none",
4209 duration_type = "seconds",
4210 },
4211 },
4212 conditions = {},
4213}
4214
4215function WeakAuras.PreAdd(data)
4216 -- Readd what Compress removed before version 8
4217 if (not data.internalVersion or data.internalVersion < 7) then
4218 WeakAuras.validate(data, oldDataStub)
4219 elseif (data.internalVersion < 8) then
4220 WeakAuras.validate(data, oldDataStub2)
4221 end
4222
4223 local default = data.regionType and WeakAuras.regionTypes[data.regionType] and WeakAuras.regionTypes[data.regionType].default
4224 if default then
4225 WeakAuras.validate(data, default)
4226 end
4227
4228 local regionValidate = data.regionType and WeakAuras.regionTypes[data.regionType] and WeakAuras.regionTypes[data.regionType].validate
4229 if regionValidate then
4230 regionValidate(data)
4231 end
4232
4233 if data.subRegions then
4234 local result = {}
4235 for index, subRegionData in ipairs(data.subRegions) do
4236 local subType = subRegionData.type
4237 if subType and WeakAuras.subRegionTypes[subType] then
4238 -- If it is not supported, then drop it
4239 if WeakAuras.subRegionTypes[subType].supports(data.regionType) then
4240 local default = WeakAuras.subRegionTypes[subType].default
4241 if type(default) == "function" then
4242 default = default(data.regionType)
4243 end
4244 if default then
4245 WeakAuras.validate(subRegionData, default)
4246 end
4247
4248 tinsert(result, subRegionData)
4249 end
4250 else
4251 -- Unknown sub type is because the user didn't restart their client
4252 -- so keep it
4253 tinsert(result, subRegionData)
4254 end
4255 end
4256 data.subRegions = result
4257 end
4258
4259 WeakAuras.Modernize(data);
4260 WeakAuras.validate(data, WeakAuras.data_stub);
4261 validateUserConfig(data, data.authorOptions, data.config)
4262 removeSpellNames(data)
4263 data.init_started = nil
4264 data.init_completed = nil
4265 data.expanded = nil
4266end
4267
4268local function pAdd(data, simpleChange)
4269 local id = data.id;
4270 if not(id) then
4271 error("Improper arguments to WeakAuras.Add - id not defined");
4272 return;
4273 end
4274
4275 data.uid = data.uid or WeakAuras.GenerateUniqueID()
4276 local otherID = UIDtoID[data.uid]
4277 if not otherID then
4278 UIDtoID[data.uid] = id
4279 elseif otherID ~= id then
4280 -- duplicate uid
4281 data.uid = WeakAuras.GenerateUniqueID()
4282 UIDtoID[data.uid] = id
4283 end
4284
4285 if simpleChange then
4286 db.displays[id] = data
4287 WeakAuras.SetRegion(data)
4288 WeakAuras.UpdatedTriggerState(id)
4289 else
4290 if (data.controlledChildren) then
4291 WeakAuras.ClearAuraEnvironment(id);
4292 if data.parent then
4293 WeakAuras.ClearAuraEnvironment(data.parent);
4294 end
4295 db.displays[id] = data;
4296 WeakAuras.SetRegion(data);
4297 else
4298 local visible
4299 if (WeakAuras.IsOptionsOpen()) then
4300 visible = WeakAuras.FakeStatesFor(id, false)
4301 else
4302 if (WeakAuras.regions[id] and WeakAuras.regions[id].region) then
4303 WeakAuras.regions[id].region:Collapse()
4304 else
4305 WeakAuras.CollapseAllClones(id)
4306 end
4307 end
4308
4309 WeakAuras.ClearAuraEnvironment(id);
4310 if data.parent then
4311 WeakAuras.ClearAuraEnvironment(data.parent);
4312 end
4313
4314 db.displays[id] = data;
4315
4316 if (not data.triggers.activeTriggerMode or data.triggers.activeTriggerMode > #data.triggers) then
4317 data.triggers.activeTriggerMode = WeakAuras.trigger_modes.first_active;
4318 end
4319
4320 for _, triggerSystem in pairs(triggerSystems) do
4321 triggerSystem.Add(data);
4322 end
4323
4324 local loadFuncStr, events = WeakAuras.ConstructFunction(load_prototype, data.load);
4325 for event, eventData in pairs(loadEvents) do
4326 eventData[id] = nil
4327 end
4328 for event in pairs(events) do
4329 loadEvents[event] = loadEvents[event] or {}
4330 loadEvents[event][id] = true
4331 end
4332 loadEvents["SCAN_ALL"] = loadEvents["SCAN_ALL"] or {}
4333 loadEvents["SCAN_ALL"][id] = true
4334
4335 local loadForOptionsFuncStr = WeakAuras.ConstructFunction(load_prototype, data.load, true);
4336 local loadFunc = WeakAuras.LoadFunction(loadFuncStr, id, "load");
4337 local loadForOptionsFunc = WeakAuras.LoadFunction(loadForOptionsFuncStr, id, "options load");
4338 local triggerLogicFunc;
4339 if data.triggers.disjunctive == "custom" then
4340 triggerLogicFunc = WeakAuras.LoadFunction("return "..(data.triggers.customTriggerLogic or ""), id, "trigger combination");
4341 end
4342 WeakAuras.LoadCustomActionFunctions(data);
4343 WeakAuras.LoadConditionPropertyFunctions(data);
4344 local checkConditionsFuncStr = WeakAuras.ConstructConditionFunction(data);
4345 local checkCondtionsFunc = checkConditionsFuncStr and WeakAuras.LoadFunction(checkConditionsFuncStr, id, "condition checks");
4346 debug(id.." - Load", 1);
4347 debug(loadFuncStr);
4348
4349 loadFuncs[id] = loadFunc;
4350 loadFuncsForOptions[id] = loadForOptionsFunc;
4351 checkConditions[id] = checkCondtionsFunc;
4352 clones[id] = clones[id] or {};
4353
4354 if (timers[id]) then
4355 for _, trigger in pairs(timers[id]) do
4356 for _, record in pairs(trigger) do
4357 if (record.handle) then
4358 timer:CancelTimer(record.handle);
4359 end
4360 end
4361 end
4362 timers[id] = nil;
4363 end
4364
4365 local region = WeakAuras.SetRegion(data);
4366
4367 triggerState[id] = {
4368 disjunctive = data.triggers.disjunctive or "all",
4369 numTriggers = #data.triggers,
4370 activeTriggerMode = data.triggers.activeTriggerMode or WeakAuras.trigger_modes.first_active,
4371 triggerLogicFunc = triggerLogicFunc,
4372 triggers = {},
4373 triggerCount = 0,
4374 activatedConditions = {},
4375 };
4376
4377 WeakAuras.LoadEncounterInitScripts(id);
4378
4379 if (WeakAuras.IsOptionsOpen()) then
4380 WeakAuras.FakeStatesFor(id, visible)
4381 end
4382
4383 if not(paused) then
4384 WeakAuras.ScanForLoads({[id] = true});
4385 end
4386 end
4387 end
4388end
4389
4390function WeakAuras.Add(data, takeSnapshot, simpleChange)
4391 if takeSnapshot then
4392 WeakAuras.SetMigrationSnapshot(data.uid, CopyTable(data))
4393 end
4394 WeakAuras.PreAdd(data)
4395 pAdd(data, simpleChange);
4396end
4397
4398function WeakAuras.SetRegion(data, cloneId)
4399 local regionType = data.regionType;
4400 if not(regionType) then
4401 error("Improper arguments to WeakAuras.SetRegion - regionType not defined");
4402 else
4403 if(not regionTypes[regionType]) then
4404 regionType = "fallback";
4405 print("Improper arguments to WeakAuras.CreateRegion - regionType \""..data.regionType.."\" is not supported");
4406 end
4407
4408 local id = data.id;
4409 if not(id) then
4410 error("Improper arguments to WeakAuras.SetRegion - id not defined");
4411 else
4412 local region;
4413 if(cloneId) then
4414 region = clones[id][cloneId];
4415 if (not region or region.regionType ~= data.regionType) then
4416 if (region) then
4417 clonePool[region.regionType] = clonePool[region.regionType] or {};
4418 tinsert(clonePool[region.regionType], region);
4419 region:Hide();
4420 end
4421 if(clonePool[data.regionType] and clonePool[data.regionType][1]) then
4422 clones[id][cloneId] = tremove(clonePool[data.regionType]);
4423 else
4424 local clone = regionTypes[data.regionType].create(frame, data);
4425 clone.regionType = data.regionType;
4426 clone:Hide();
4427 clones[id][cloneId] = clone;
4428 end
4429 region = clones[id][cloneId];
4430 end
4431 else
4432 if((not regions[id]) or (not regions[id].region) or regions[id].regionType ~= regionType) then
4433 region = regionTypes[regionType].create(frame, data);
4434 region.regionType = regionType;
4435 regions[id] = {
4436 regionType = regionType,
4437 region = region
4438 };
4439 if regionType ~= "dynamicgroup" and regionType ~= "group" then
4440 region.toShow = false
4441 region:Hide()
4442 else
4443 region.toShow = true
4444 end
4445 else
4446 region = regions[id].region;
4447 end
4448 end
4449 region.id = id;
4450 region.cloneId = cloneId or "";
4451 WeakAuras.validate(data, regionTypes[regionType].default);
4452
4453 local parent = frame;
4454 if(data.parent) then
4455 if(regions[data.parent]) then
4456 parent = regions[data.parent].region;
4457 else
4458 data.parent = nil;
4459 end
4460 end
4461 local loginFinished = WeakAuras.IsLoginFinished();
4462 local anim_cancelled = loginFinished and WeakAuras.CancelAnimation(region, true, true, true, true, true, true);
4463
4464 regionTypes[regionType].modify(parent, region, data);
4465 WeakAuras.regionPrototype.AddSetDurationInfo(region);
4466 WeakAuras.regionPrototype.AddExpandFunction(data, region, cloneId, parent, parent.regionType)
4467
4468
4469 data.animation = data.animation or {};
4470 data.animation.start = data.animation.start or {type = "none"};
4471 data.animation.main = data.animation.main or {type = "none"};
4472 data.animation.finish = data.animation.finish or {type = "none"};
4473 if(WeakAuras.CanHaveDuration(data)) then
4474 data.animation.start.duration_type = data.animation.start.duration_type or "seconds";
4475 data.animation.main.duration_type = data.animation.main.duration_type or "seconds";
4476 data.animation.finish.duration_type = data.animation.finish.duration_type or "seconds";
4477 else
4478 data.animation.start.duration_type = "seconds";
4479 data.animation.main.duration_type = "seconds";
4480 data.animation.finish.duration_type = "seconds";
4481 end
4482
4483 if(cloneId) then
4484 clonePool[regionType] = clonePool[regionType] or {};
4485 end
4486 if(anim_cancelled) then
4487 WeakAuras.Animate("display", data, "main", data.animation.main, region, false, nil, true, cloneId);
4488 end
4489 return region;
4490 end
4491 end
4492end
4493
4494function WeakAuras.EnsureClone(id, cloneId)
4495 clones[id] = clones[id] or {};
4496 if not(clones[id][cloneId]) then
4497 local data = WeakAuras.GetData(id);
4498 WeakAuras.SetRegion(data, cloneId);
4499 clones[id][cloneId].justCreated = true;
4500 end
4501 return clones[id][cloneId];
4502end
4503
4504function WeakAuras.GetRegion(id, cloneId)
4505 if(cloneId and cloneId ~= "") then
4506 return WeakAuras.EnsureClone(id, cloneId);
4507 end
4508 return WeakAuras.regions[id] and WeakAuras.regions[id].region;
4509end
4510
4511function WeakAuras.CollapseAllClones(id, triggernum)
4512 if(clones[id]) then
4513 for i,v in pairs(clones[id]) do
4514 v:Collapse();
4515 end
4516 end
4517end
4518
4519function WeakAuras.SetAllStatesHidden(id, triggernum)
4520 local triggerState = WeakAuras.GetTriggerStateForTrigger(id, triggernum);
4521 local changed = false
4522 for id, state in pairs(triggerState) do
4523 changed = changed or state.show
4524 state.show = false;
4525 state.changed = true;
4526 end
4527 return changed
4528end
4529
4530function WeakAuras.SetAllStatesHiddenExcept(id, triggernum, list)
4531 local triggerState = WeakAuras.GetTriggerStateForTrigger(id, triggernum);
4532 for cloneId, state in pairs(triggerState) do
4533 if (not (list[cloneId])) then
4534 state.show = false;
4535 state.changed = true;
4536 end
4537 end
4538end
4539
4540function WeakAuras.ReleaseClone(id, cloneId, regionType)
4541 if (not clones[id]) then
4542 return;
4543 end
4544 local region = clones[id][cloneId];
4545 clones[id][cloneId] = nil;
4546 clonePool[regionType][#clonePool[regionType] + 1] = region;
4547end
4548
4549function WeakAuras.HandleChatAction(message_type, message, message_dest, message_channel, r, g, b, region, customFunc)
4550 if (message:find('%%')) then
4551 message = WeakAuras.ReplacePlaceHolders(message, region, customFunc);
4552 end
4553 if(message_type == "PRINT") then
4554 DEFAULT_CHAT_FRAME:AddMessage(message, r or 1, g or 1, b or 1);
4555 elseif(message_type == "COMBAT") then
4556 if(CombatText_AddMessage) then
4557 CombatText_AddMessage(message, COMBAT_TEXT_SCROLL_FUNCTION, r or 1, g or 1, b or 1);
4558 end
4559 elseif(message_type == "WHISPER") then
4560 if(message_dest) then
4561 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
4562 pcall(function() SendChatMessage(message, "WHISPER", nil, UnitName("target")) end);
4563 else
4564 pcall(function() SendChatMessage(message, "WHISPER", nil, message_dest) end);
4565 end
4566 end
4567 elseif(message_type == "CHANNEL") then
4568 local channel = message_channel and tonumber(message_channel);
4569 if(GetChannelName(channel)) then
4570 pcall(function() SendChatMessage(message, "CHANNEL", nil, channel) end);
4571 end
4572 elseif(message_type == "SMARTRAID") then
4573 local isInstanceGroup = IsInGroup(LE_PARTY_CATEGORY_INSTANCE)
4574 if UnitInBattleground("player") then
4575 pcall(function() SendChatMessage(message, "INSTANCE_CHAT") end)
4576 elseif UnitInRaid("player") then
4577 pcall(function() SendChatMessage(message, "RAID") end)
4578 elseif UnitInParty("player") then
4579 if isInstanceGroup then
4580 pcall(function() SendChatMessage(message, "INSTANCE_CHAT") end)
4581 else
4582 pcall(function() SendChatMessage(message, "PARTY") end)
4583 end
4584 else
4585 if WeakAuras.IsClassic() or IsInInstance() then
4586 pcall(function() SendChatMessage(message, "SAY") end)
4587 end
4588 end
4589 elseif(message_type == "SAY" or message_type == "YELL") then
4590 if WeakAuras.IsClassic() or IsInInstance() then
4591 pcall(function() SendChatMessage(message, message_type, nil, nil) end)
4592 end
4593 else
4594 pcall(function() SendChatMessage(message, message_type, nil, nil) end);
4595 end
4596end
4597
4598function WeakAuras.PerformActions(data, type, region)
4599 if (paused or WeakAuras.IsOptionsOpen()) then
4600 return;
4601 end;
4602 local actions;
4603 if(type == "start") then
4604 actions = data.actions.start;
4605 elseif(type == "finish") then
4606 actions = data.actions.finish;
4607 else
4608 return;
4609 end
4610
4611 if(actions.do_message and actions.message_type and actions.message and not squelch_actions) then
4612 local customFunc = WeakAuras.customActionsFunctions[data.id][type .. "_message"];
4613 WeakAuras.HandleChatAction(actions.message_type, actions.message, actions.message_dest, actions.message_channel, actions.r, actions.g, actions.b, region, customFunc);
4614 end
4615
4616 if (actions.stop_sound) then
4617 if (region.SoundStop) then
4618 region:SoundStop();
4619 end
4620 end
4621
4622 if(actions.do_sound and actions.sound) then
4623 if (region.SoundPlay) then
4624 region:SoundPlay(actions);
4625 end
4626 end
4627
4628 if(actions.do_custom and actions.custom and not squelch_actions) then
4629 local func = WeakAuras.customActionsFunctions[data.id][type]
4630 if func then
4631 WeakAuras.ActivateAuraEnvironment(region.id, region.cloneId, region.state, region.states);
4632 xpcall(func, geterrorhandler());
4633 WeakAuras.ActivateAuraEnvironment(nil);
4634 end
4635 end
4636
4637 -- Apply start glow actions even if squelch_actions is true, but don't apply finish glow actions
4638 local squelch_glow = squelch_actions and (type == "finish");
4639 if(actions.do_glow and actions.glow_action and actions.glow_frame and not squelch_glow) then
4640 local glowStart, glowStop
4641 if actions.glow_type == "ACShine" then
4642 glowStart = LCG.AutoCastGlow_Start
4643 glowStop = LCG.AutoCastGlow_Stop
4644 elseif actions.glow_type == "Pixel" then
4645 glowStart = LCG.PixelGlow_Start
4646 glowStop = LCG.PixelGlow_Stop
4647 else
4648 glowStart = WeakAuras.ShowOverlayGlow
4649 glowStop = WeakAuras.HideOverlayGlow
4650 end
4651
4652 local glow_frame
4653 local original_glow_frame
4654 if(actions.glow_frame:sub(1, 10) == "WeakAuras:") then
4655 local frame_name = actions.glow_frame:sub(11);
4656 if(regions[frame_name]) then
4657 glow_frame = regions[frame_name].region;
4658 end
4659 else
4660 glow_frame = WeakAuras.GetSanitizedGlobal(actions.glow_frame);
4661 original_glow_frame = glow_frame
4662 end
4663
4664 if (glow_frame) then
4665 if (not glow_frame.__WAGlowFrame) then
4666 glow_frame.__WAGlowFrame = CreateFrame("Frame", nil, glow_frame);
4667 glow_frame.__WAGlowFrame:SetAllPoints(glow_frame);
4668 glow_frame.__WAGlowFrame:SetSize(glow_frame:GetSize());
4669 end
4670 glow_frame = glow_frame.__WAGlowFrame;
4671 end
4672
4673 if(glow_frame) then
4674 if(actions.glow_action == "show") then
4675 local color
4676 if actions.use_glow_color then
4677 color = actions.glow_color
4678 end
4679 glowStart(glow_frame, color);
4680 elseif(actions.glow_action == "hide") then
4681 glowStop(glow_frame);
4682 if original_glow_frame then
4683 glowStop(original_glow_frame);
4684 end
4685 end
4686 end
4687 end
4688end
4689
4690local function noopErrorHandler() end
4691
4692local updatingAnimations;
4693local last_update = GetTime();
4694function WeakAuras.UpdateAnimations()
4695 WeakAuras.StartProfileSystem("animations");
4696 local errorHandler = WeakAuras.IsOptionsOpen() and noopErrorHandler or geterrorhandler()
4697 for groupId, groupRegion in pairs(pending_controls) do
4698 pending_controls[groupId] = nil;
4699 groupRegion:DoPositionChildren();
4700 end
4701 local time = GetTime();
4702 local elapsed = time - last_update;
4703 last_update = time;
4704 local num = 0;
4705 for id, anim in pairs(animations) do
4706 WeakAuras.StartProfileAura(anim.name);
4707 num = num + 1;
4708 local finished = false;
4709 if(anim.duration_type == "seconds") then
4710 if anim.duration > 0 then
4711 anim.progress = anim.progress + (elapsed / anim.duration);
4712 else
4713 anim.progress = anim.progress + (elapsed / 1);
4714 end
4715 if(anim.progress >= 1) then
4716 anim.progress = 1;
4717 finished = true;
4718 end
4719 elseif(anim.duration_type == "relative") then
4720 local state = anim.region.state;
4721 if (not state
4722 or (state.progressType == "timed" and state.duration < 0.01)
4723 or (state.progressType == "static" and state.value < 0.01)) then
4724 anim.progress = 0;
4725 if(anim.type == "start" or anim.type == "finish") then
4726 finished = true;
4727 end
4728 else
4729 local relativeProgress = 0;
4730 if(state.progressType == "static") then
4731 relativeProgress = state.value / state.total;
4732 elseif (state.progressType == "timed") then
4733 relativeProgress = 1 - ((state.expirationTime - time) / state.duration);
4734 end
4735 relativeProgress = state.inverse and (1 - relativeProgress) or relativeProgress;
4736 anim.progress = relativeProgress / anim.duration
4737 local iteration = math.floor(anim.progress);
4738 --anim.progress = anim.progress - iteration;
4739 if not(anim.iteration) then
4740 anim.iteration = iteration;
4741 elseif(anim.iteration ~= iteration) then
4742 anim.iteration = nil;
4743 finished = true;
4744 end
4745 end
4746 else
4747 anim.progress = 1;
4748 end
4749 local progress = anim.inverse and (1 - anim.progress) or anim.progress;
4750 WeakAuras.ActivateAuraEnvironment(anim.name, anim.cloneId, anim.region.state, anim.region.states);
4751 if(anim.translateFunc) then
4752 if (anim.region.SetOffsetAnim) then
4753 local ok, x, y = xpcall(anim.translateFunc, errorHandler, progress, 0, 0, anim.dX, anim.dY);
4754 anim.region:SetOffsetAnim(x, y);
4755 else
4756 anim.region:ClearAllPoints();
4757 local ok, x, y = xpcall(anim.translateFunc, errorHandler, progress, anim.startX, anim.startY, anim.dX, anim.dY);
4758 if (ok) then
4759 anim.region:SetPoint(anim.selfPoint, anim.anchor, anim.anchorPoint, x, y);
4760 end
4761 end
4762 end
4763 if(anim.alphaFunc) then
4764 local ok, alpha = xpcall(anim.alphaFunc, errorHandler, progress, anim.startAlpha, anim.dAlpha);
4765 if (ok) then
4766 if (anim.region.SetAnimAlpha) then
4767 anim.region:SetAnimAlpha(alpha);
4768 else
4769 anim.region:SetAlpha(alpha);
4770 end
4771 end
4772 end
4773 if(anim.scaleFunc) then
4774 local ok, scaleX, scaleY = xpcall(anim.scaleFunc, errorHandler, progress, 1, 1, anim.scaleX, anim.scaleY);
4775 if (ok) then
4776 if(anim.region.Scale) then
4777 anim.region:Scale(scaleX, scaleY);
4778 else
4779 anim.region:SetWidth(anim.startWidth * scaleX);
4780 anim.region:SetHeight(anim.startHeight * scaleY);
4781 end
4782 end
4783 end
4784 if(anim.rotateFunc and anim.region.Rotate) then
4785 local ok, rotate = xpcall(anim.rotateFunc, errorHandler, progress, anim.startRotation, anim.rotate);
4786 if (ok) then
4787 anim.region:Rotate(rotate);
4788 end
4789 end
4790 if(anim.colorFunc and anim.region.ColorAnim) then
4791 local startR, startG, startB, startA = anim.region:GetColor();
4792 startR, startG, startB, startA = startR or 1, startG or 1, startB or 1, startA or 1;
4793 local ok, r, g, b, a = xpcall(anim.colorFunc, errorHandler, progress, startR, startG, startB, startA, anim.colorR, anim.colorG, anim.colorB, anim.colorA);
4794 if (ok) then
4795 anim.region:ColorAnim(r, g, b, a);
4796 end
4797 end
4798 WeakAuras.ActivateAuraEnvironment(nil);
4799 if(finished) then
4800 if not(anim.loop) then
4801 if (anim.region.SetOffsetAnim) then
4802 anim.region:SetOffsetAnim(0, 0);
4803 else
4804 if(anim.startX) then
4805 anim.region:SetPoint(anim.selfPoint, anim.anchor, anim.anchorPoint, anim.startX, anim.startY);
4806 end
4807 end
4808 if (anim.region.SetAnimAlpha) then
4809 anim.region:SetAnimAlpha(nil);
4810 elseif(anim.startAlpha) then
4811 anim.region:SetAlpha(anim.startAlpha);
4812 end
4813 if(anim.startWidth) then
4814 if(anim.region.Scale) then
4815 anim.region:Scale(1, 1);
4816 else
4817 anim.region:SetWidth(anim.startWidth);
4818 anim.region:SetHeight(anim.startHeight);
4819 end
4820 end
4821 if(anim.startRotation) then
4822 if(anim.region.Rotate) then
4823 anim.region:Rotate(anim.startRotation);
4824 end
4825 end
4826 if(anim.region.ColorAnim) then
4827 anim.region:ColorAnim(nil);
4828 end
4829 animations[id] = nil;
4830 end
4831
4832 if(anim.loop) then
4833 WeakAuras.Animate(anim.namespace, anim.data, anim.type, anim.anim, anim.region, anim.inverse, anim.onFinished, anim.loop, anim.cloneId);
4834 elseif(anim.onFinished) then
4835 anim.onFinished();
4836 end
4837 end
4838 WeakAuras.StopProfileAura(anim.name);
4839 end
4840 -- XXX: I tried to have animations only update if there are actually animation data to animate upon.
4841 -- This caused all start animations to break, and I couldn't figure out why.
4842 -- May revisit at a later time.
4843 --[[
4844 if(num == 0) then
4845 WeakAuras.debug("Animation stopped", 3);
4846 frame:SetScript("OnUpdate", nil);
4847 updatingAnimations = nil;
4848 updatingAnimations = nil;
4849 end
4850 ]]--
4851
4852 WeakAuras.StopProfileSystem("animations");
4853end
4854
4855function WeakAuras.RegisterGroupForPositioning(id, region)
4856 pending_controls[id] = region
4857 updatingAnimations = true
4858 frame:SetScript("OnUpdate", WeakAuras.UpdateAnimations)
4859end
4860
4861function WeakAuras.Animate(namespace, data, type, anim, region, inverse, onFinished, loop, cloneId)
4862 local id = data.id;
4863 local key = tostring(region);
4864 local valid;
4865 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
4866 valid = true;
4867 elseif(anim and anim.type == "preset" and anim.preset and anim_presets[anim.preset]) then
4868 anim = anim_presets[anim.preset];
4869 valid = true;
4870 end
4871 if(valid) then
4872 local progress, duration, selfPoint, anchor, anchorPoint, startX, startY, startAlpha, startWidth, startHeight, startRotation;
4873 local translateFunc, alphaFunc, scaleFunc, rotateFunc, colorFunc;
4874 if(animations[key]) then
4875 if(animations[key].type == type and not loop) then
4876 return "no replace";
4877 end
4878 anim.x = anim.x or 0;
4879 anim.y = anim.y or 0;
4880 selfPoint, anchor, anchorPoint, startX, startY = animations[key].selfPoint, animations[key].anchor, animations[key].anchorPoint, animations[key].startX, animations[key].startY;
4881 anim.alpha = anim.alpha or 0;
4882 startAlpha = animations[key].startAlpha;
4883 anim.scalex = anim.scalex or 1;
4884 anim.scaley = anim.scaley or 1;
4885 startWidth, startHeight = animations[key].startWidth, animations[key].startHeight;
4886 anim.rotate = anim.rotate or 0;
4887 startRotation = animations[key].startRotation;
4888 anim.colorR = anim.colorR or 1;
4889 anim.colorG = anim.colorG or 1;
4890 anim.colorB = anim.colorB or 1;
4891 anim.colorA = anim.colorA or 1;
4892 else
4893 anim.x = anim.x or 0;
4894 anim.y = anim.y or 0;
4895 if not region.SetOffsetAnim then
4896 selfPoint, anchor, anchorPoint, startX, startY = region:GetPoint(1);
4897 end
4898 anim.alpha = anim.alpha or 0;
4899 startAlpha = region:GetAlpha();
4900 anim.scalex = anim.scalex or 1;
4901 anim.scaley = anim.scaley or 1;
4902 startWidth, startHeight = region:GetWidth(), region:GetHeight();
4903 anim.rotate = anim.rotate or 0;
4904 startRotation = region.GetRotation and region:GetRotation() or 0;
4905 anim.colorR = anim.colorR or 1;
4906 anim.colorG = anim.colorG or 1;
4907 anim.colorB = anim.colorB or 1;
4908 anim.colorA = anim.colorA or 1;
4909 end
4910
4911 if(anim.use_translate) then
4912 if not(anim.translateType == "custom" and anim.translateFunc) then
4913 anim.translateType = anim.translateType or "straightTranslate";
4914 anim.translateFunc = anim_function_strings[anim.translateType]
4915 end
4916 if (anim.translateFunc) then
4917 translateFunc = WeakAuras.LoadFunction("return " .. anim.translateFunc, id, "translate animation");
4918 else
4919 if (region.SetOffsetAnim) then
4920 region:SetOffsetAnim(0, 0);
4921 else
4922 region:SetPoint(selfPoint, anchor, anchorPoint, startX, startY);
4923 end
4924 end
4925 else
4926 if (region.SetOffsetAnim) then
4927 region:SetOffsetAnim(0, 0);
4928 else
4929 region:SetPoint(selfPoint, anchor, anchorPoint, startX, startY);
4930 end
4931 end
4932 if(anim.use_alpha) then
4933 if not(anim.alphaType == "custom" and anim.alphaFunc) then
4934 anim.alphaType = anim.alphaType or "straight";
4935 anim.alphaFunc = anim_function_strings[anim.alphaType]
4936 end
4937 if (anim.alphaFunc) then
4938 alphaFunc = WeakAuras.LoadFunction("return " .. anim.alphaFunc, id, "alpha animation");
4939 else
4940 if (region.SetAnimAlpha) then
4941 region:SetAnimAlpha(nil);
4942 else
4943 region:SetAlpha(startAlpha);
4944 end
4945 end
4946 else
4947 if (region.SetAnimAlpha) then
4948 region:SetAnimAlpha(nil);
4949 else
4950 region:SetAlpha(startAlpha);
4951 end
4952 end
4953 if(anim.use_scale) then
4954 if not(anim.scaleType == "custom" and anim.scaleFunc) then
4955 anim.scaleType = anim.scaleType or "straightScale";
4956 anim.scaleFunc = anim_function_strings[anim.scaleType]
4957 end
4958 if (anim.scaleFunc) then
4959 scaleFunc = WeakAuras.LoadFunction("return " .. anim.scaleFunc, id, "scale animation");
4960 else
4961 region:Scale(1, 1);
4962 end
4963 elseif(region.Scale) then
4964 region:Scale(1, 1);
4965 end
4966 if(anim.use_rotate) then
4967 if not(anim.rotateType == "custom" and anim.rotateFunc) then
4968 anim.rotateType = anim.rotateType or "straight";
4969 anim.rotateFunc = anim_function_strings[anim.rotateType]
4970 end
4971 if (anim.rotateFunc) then
4972 rotateFunc = WeakAuras.LoadFunction("return " .. anim.rotateFunc, id, "rotate animation");
4973 else
4974 region:Rotate(startRotation);
4975 end
4976 elseif(region.Rotate) then
4977 region:Rotate(startRotation);
4978 end
4979 if(anim.use_color) then
4980 if not(anim.colorType == "custom" and anim.colorFunc) then
4981 anim.colorType = anim.colorType or "straightColor";
4982 anim.colorFunc = anim_function_strings[anim.colorType]
4983 end
4984 if (anim.colorFunc) then
4985 colorFunc = WeakAuras.LoadFunction("return " .. anim.colorFunc, id, "color animation");
4986 else
4987 region:ColorAnim(nil);
4988 end
4989 elseif(region.ColorAnim) then
4990 region:ColorAnim(nil);
4991 end
4992
4993 duration = WeakAuras.ParseNumber(anim.duration) or 0;
4994 progress = 0;
4995 if(namespace == "display" and type == "main" and not onFinished and not anim.duration_type == "relative") then
4996 local data = WeakAuras.GetData(id);
4997 if(data and data.parent) then
4998 local parentRegion = regions[data.parent].region;
4999 if(parentRegion and parentRegion.controlledRegions) then
5000 for index, regionData in pairs(parentRegion.controlledRegions) do
5001 local childRegion = regionData.region;
5002 local childKey = regionData.key;
5003 if(childKey and childKey ~= tostring(region) and animations[childKey] and animations[childKey].type == "main" and duration == animations[childKey].duration) then
5004 progress = animations[childKey].progress;
5005 break;
5006 end
5007 end
5008 end
5009 end
5010 end
5011
5012 local animation = animations[key] or {}
5013 animations[key] = animation
5014
5015 animation.progress = progress
5016 animation.startX = startX
5017 animation.startY = startY
5018 animation.startAlpha = startAlpha
5019 animation.startWidth = startWidth
5020 animation.startHeight = startHeight
5021 animation.startRotation = startRotation
5022 animation.dX = (anim.use_translate and anim.x)
5023 animation.dY = (anim.use_translate and anim.y)
5024 animation.dAlpha = (anim.use_alpha and (anim.alpha - startAlpha))
5025 animation.scaleX = (anim.use_scale and anim.scalex)
5026 animation.scaleY = (anim.use_scale and anim.scaley)
5027 animation.rotate = anim.rotate
5028 animation.colorR = (anim.use_color and anim.colorR)
5029 animation.colorG = (anim.use_color and anim.colorG)
5030 animation.colorB = (anim.use_color and anim.colorB)
5031 animation.colorA = (anim.use_color and anim.colorA)
5032 animation.translateFunc = translateFunc
5033 animation.alphaFunc = alphaFunc
5034 animation.scaleFunc = scaleFunc
5035 animation.rotateFunc = rotateFunc
5036 animation.colorFunc = colorFunc
5037 animation.region = region
5038 animation.selfPoint = selfPoint
5039 animation.anchor = anchor
5040 animation.anchorPoint = anchorPoint
5041 animation.duration = duration
5042 animation.duration_type = anim.duration_type or "seconds"
5043 animation.inverse = inverse
5044 animation.type = type
5045 animation.loop = loop
5046 animation.onFinished = onFinished
5047 animation.name = id
5048 animation.cloneId = cloneId or ""
5049 animation.namespace = namespace;
5050 animation.data = data;
5051 animation.anim = anim;
5052
5053 if not(updatingAnimations) then
5054 frame:SetScript("OnUpdate", WeakAuras.UpdateAnimations);
5055 updatingAnimations = true;
5056 end
5057 return true;
5058 else
5059 if(animations[key]) then
5060 if(animations[key].type ~= type or loop) then
5061 WeakAuras.CancelAnimation(region, true, true, true, true, true);
5062 end
5063 end
5064 return false;
5065 end
5066end
5067
5068function WeakAuras.IsAnimating(region)
5069 local key = tostring(region);
5070 local anim = animations[key];
5071 if(anim) then
5072 return anim.type;
5073 else
5074 return nil;
5075 end
5076end
5077
5078function WeakAuras.CancelAnimation(region, resetPos, resetAlpha, resetScale, resetRotation, resetColor, doOnFinished)
5079 local key = tostring(region);
5080 local anim = animations[key];
5081
5082 if(anim) then
5083 if(resetPos) then
5084 if (anim.region.SetOffsetAnim) then
5085 anim.region:SetOffsetAnim(0, 0);
5086 else
5087 anim.region:ClearAllPoints();
5088 anim.region:SetPoint(anim.selfPoint, anim.anchor, anim.anchorPoint, anim.startX, anim.startY);
5089 end
5090 end
5091 if(resetAlpha) then
5092 if (anim.region.SetAnimAlpha) then
5093 anim.region:SetAnimAlpha(nil);
5094 else
5095 anim.region:SetAlpha(anim.startAlpha);
5096 end
5097 end
5098 if(resetScale) then
5099 if(anim.region.Scale) then
5100 anim.region:Scale(1, 1);
5101 else
5102 anim.region:SetWidth(anim.startWidth);
5103 anim.region:SetHeight(anim.startHeight);
5104 end
5105 end
5106 if(resetRotation and anim.region.Rotate) then
5107 anim.region:Rotate(anim.startRotation);
5108 end
5109 if(resetColor and anim.region.ColorAnim) then
5110 anim.region:ColorAnim(nil);
5111 end
5112
5113 animations[key] = nil;
5114 if(doOnFinished and anim.onFinished) then
5115 anim.onFinished();
5116 end
5117 return true;
5118 else
5119 return false;
5120 end
5121end
5122
5123function WeakAuras.GetData(id)
5124 return id and db.displays[id];
5125end
5126
5127function WeakAuras.GetTriggerSystem(data, triggernum)
5128 local triggerType = data.triggers[triggernum] and data.triggers[triggernum].trigger.type
5129 return triggerType and triggerTypes[triggerType]
5130end
5131
5132local function wrapTriggerSystemFunction(functionName, mode)
5133 local func;
5134 func = function(data, triggernum, ...)
5135 if (not triggernum) then
5136 return func(data, data.triggers.activeTriggerMode or -1, ...);
5137 elseif (triggernum < 0) then
5138 local result;
5139 if (mode == "or") then
5140 result = false;
5141 for i = 1, #data.triggers do
5142 result = result or func(data, i);
5143 end
5144 elseif (mode == "and") then
5145 result = true;
5146 for i = 1, #data.triggers do
5147 result = result and func(data, i);
5148 end
5149 elseif (mode == "table") then
5150 result = {};
5151 for i = 1, #data.triggers do
5152 local tmp = func(data, i);
5153 if (tmp) then
5154 for k, v in pairs(tmp) do
5155 result[k] = v;
5156 end
5157 end
5158 end
5159 elseif (mode == "call") then
5160 for i = 1, #data.triggers do
5161 func(data, i, ...);
5162 end
5163 elseif (mode == "firstValue") then
5164 result = nil;
5165 for i = 1, #data.triggers do
5166 local tmp = func(data, i);
5167 if (tmp) then
5168 result = tmp;
5169 break;
5170 end
5171 end
5172 elseif (mode == "nameAndIcon") then
5173 for i = 1, #data.triggers do
5174 local tmp1, tmp2 = func(data, i);
5175 if (tmp1) then
5176 return tmp1, tmp2;
5177 end
5178 end
5179 end
5180 return result;
5181 else -- triggernum >= 1
5182 local triggerSystem = WeakAuras.GetTriggerSystem(data, triggernum);
5183 if (not triggerSystem) then
5184 return false
5185 end
5186 return triggerSystem[functionName](data, triggernum, ...);
5187 end
5188 end
5189 return func;
5190end
5191
5192WeakAuras.CanHaveDuration = wrapTriggerSystemFunction("CanHaveDuration", "firstValue");
5193WeakAuras.CanHaveAuto = wrapTriggerSystemFunction("CanHaveAuto", "or");
5194WeakAuras.CanHaveClones = wrapTriggerSystemFunction("CanHaveClones", "or");
5195WeakAuras.CanHaveTooltip = wrapTriggerSystemFunction("CanHaveTooltip", "or");
5196WeakAuras.GetNameAndIcon = wrapTriggerSystemFunction("GetNameAndIcon", "nameAndIcon");
5197WeakAuras.GetTriggerDescription = wrapTriggerSystemFunction("GetTriggerDescription", "call");
5198
5199local wrappedGetOverlayInfo = wrapTriggerSystemFunction("GetOverlayInfo", "table");
5200
5201WeakAuras.GetAdditionalProperties = function(data, triggernum, ...)
5202 local additionalProperties = ""
5203 for i = 1, #data.triggers do
5204 local triggerSystem = WeakAuras.GetTriggerSystem(data, i);
5205 if (triggerSystem) then
5206 local add = triggerSystem.GetAdditionalProperties(data, i)
5207 if (add and add ~= "") then
5208 if additionalProperties ~= "" then
5209 additionalProperties = additionalProperties .. "\n"
5210 end
5211 additionalProperties = additionalProperties .. add;
5212 end
5213 end
5214 end
5215
5216 if additionalProperties ~= "" then
5217 additionalProperties = "\n\n"
5218 .. L["Additional Trigger Replacements"] .. "\n"
5219 .. additionalProperties .. "\n\n"
5220 .. L["The trigger number is optional, and uses the trigger providing dynamic information if not specified."]
5221 end
5222 return additionalProperties
5223end
5224
5225function WeakAuras.GetOverlayInfo(data, triggernum)
5226 local overlayInfo;
5227 if (data.controlledChildren) then
5228 overlayInfo = {};
5229 for index, childId in pairs(data.controlledChildren) do
5230 local childData = WeakAuras.GetData(childId);
5231 local tmp = wrappedGetOverlayInfo(childData, triggernum);
5232 if (tmp) then
5233 for k, v in pairs(tmp) do
5234 overlayInfo[k] = v;
5235 end
5236 end
5237 end
5238 else
5239 overlayInfo = wrappedGetOverlayInfo(data, triggernum);
5240 end
5241 return overlayInfo;
5242end
5243
5244function WeakAuras.GetTriggerConditions(data)
5245 local conditions = {};
5246 for i = 1, #data.triggers do
5247 local triggerSystem = WeakAuras.GetTriggerSystem(data, i);
5248 if (triggerSystem) then
5249 conditions[i] = triggerSystem.GetTriggerConditions(data, i);
5250 conditions[i] = conditions[i] or {};
5251 conditions[i].show = {
5252 display = L["Active"],
5253 type = "bool",
5254 test = function(state, needle)
5255 return (state and state.id and triggerState[state.id].triggers[i] or false) == (needle == 1);
5256 end
5257 }
5258 end
5259 end
5260 return conditions;
5261end
5262
5263function WeakAuras.CreateFallbackState(id, triggernum)
5264 fallbacksStates[id] = fallbacksStates[id] or {};
5265 fallbacksStates[id][triggernum] = fallbacksStates[id][triggernum] or {};
5266
5267 local states = fallbacksStates[id][triggernum];
5268 states[""] = states[""] or {};
5269 local state = states[""];
5270
5271 local data = db.displays[id];
5272 local triggerSystem = WeakAuras.GetTriggerSystem(data, triggernum);
5273 if (triggerSystem) then
5274 triggerSystem.CreateFallbackState(data, triggernum, state)
5275 state.trigger = data.triggers[triggernum].trigger
5276 state.triggernum = triggernum
5277 else
5278 state.show = true;
5279 state.changed = true;
5280 state.progressType = "timed";
5281 state.duration = 0;
5282 state.expirationTime = math.huge;
5283 end
5284
5285 state.id = id
5286
5287 return states;
5288end
5289
5290function WeakAuras.CanShowNameInfo(data)
5291 if(data.regionType == "aurabar" or data.regionType == "icon" or data.regionType == "text") then
5292 return true;
5293 else
5294 return false;
5295 end
5296end
5297
5298function WeakAuras.CanShowStackInfo(data)
5299 if(data.regionType == "aurabar" or data.regionType == "icon" or data.regionType == "text") then
5300 return true;
5301 else
5302 return false;
5303 end
5304end
5305
5306function WeakAuras.CorrectSpellName(input)
5307 local inputId = tonumber(input);
5308 if(inputId) then
5309 local name = GetSpellInfo(inputId);
5310 if(name) then
5311 return inputId;
5312 else
5313 return nil;
5314 end
5315 elseif WeakAuras.IsClassic() and input then
5316 local name, _, _, _, _, _, spellId = GetSpellInfo(input)
5317 if spellId then
5318 return spellId
5319 end
5320 elseif(input) then
5321 local link;
5322 if(input:sub(1,1) == "\124") then
5323 link = input;
5324 else
5325 link = GetSpellLink(input);
5326 end
5327 if(link) and link ~= "" then
5328 local itemId = link:match("spell:(%d+)");
5329 return tonumber(itemId);
5330 elseif not WeakAuras.IsClassic() then
5331 for tier = 1, MAX_TALENT_TIERS do
5332 for column = 1, NUM_TALENT_COLUMNS do
5333 local _, _, _, _, _, spellId = GetTalentInfo(tier, column, 1)
5334 local name = GetSpellInfo(spellId);
5335 if name == input then
5336 return spellId;
5337 end
5338 end
5339 end
5340 end
5341 end
5342end
5343
5344function WeakAuras.CorrectItemName(input)
5345 local inputId = tonumber(input);
5346 if(inputId) then
5347 return inputId;
5348 elseif(input) then
5349 local _, link = GetItemInfo(input);
5350 if(link) then
5351 local itemId = link:match("item:(%d+)");
5352 return tonumber(itemId);
5353 else
5354 return nil;
5355 end
5356 end
5357end
5358
5359local currentTooltipRegion;
5360local currentTooltipOwner;
5361function WeakAuras.UpdateMouseoverTooltip(region)
5362 if(region == currentTooltipRegion) then
5363 WeakAuras.ShowMouseoverTooltip(currentTooltipRegion, currentTooltipOwner);
5364 end
5365end
5366
5367function WeakAuras.ShowMouseoverTooltip(region, owner)
5368 currentTooltipRegion = region;
5369 currentTooltipOwner = owner;
5370
5371 GameTooltip:SetOwner(owner, "ANCHOR_NONE");
5372 GameTooltip:SetPoint("LEFT", owner, "RIGHT");
5373 GameTooltip:ClearLines();
5374
5375 local triggerType;
5376 if (region.state) then
5377 triggerType = region.state.trigger.type;
5378 end
5379
5380 local triggerSystem = triggerType and triggerTypes[triggerType];
5381 if (not triggerSystem) then
5382 GameTooltip:Hide();
5383 return;
5384 end
5385
5386 if (triggerSystem.SetToolTip(region.state.trigger, region.state)) then
5387 GameTooltip:Show();
5388 else
5389 GameTooltip:Hide();
5390 end
5391end
5392
5393function WeakAuras.HideTooltip()
5394 currentTooltipRegion = nil;
5395 currentTooltipOwner = nil;
5396 GameTooltip:Hide();
5397end
5398
5399do
5400 local hiddenTooltip;
5401 function WeakAuras.GetHiddenTooltip()
5402 if not(hiddenTooltip) then
5403 hiddenTooltip = CreateFrame("GameTooltip", "WeakAurasTooltip", nil, "GameTooltipTemplate");
5404 hiddenTooltip:SetOwner(WorldFrame, "ANCHOR_NONE");
5405 hiddenTooltip:AddFontStrings(
5406 hiddenTooltip:CreateFontString("$parentTextLeft1", nil, "GameTooltipText"),
5407 hiddenTooltip:CreateFontString("$parentTextRight1", nil, "GameTooltipText")
5408 );
5409 end
5410 return hiddenTooltip;
5411 end
5412end
5413
5414function WeakAuras.GetAuraTooltipInfo(unit, index, filter)
5415 local tooltip = WeakAuras.GetHiddenTooltip();
5416 tooltip:ClearLines();
5417 tooltip:SetUnitAura(unit, index, filter);
5418 local tooltipTextLine = select(5, tooltip:GetRegions())
5419
5420 local tooltipText = tooltipTextLine and tooltipTextLine:GetObjectType() == "FontString" and tooltipTextLine:GetText() or "";
5421 local debuffType = "none";
5422 local found = false;
5423 local tooltipSize = {};
5424 if(tooltipText) then
5425 for t in tooltipText:gmatch("(%d[%d%.,]*)") do
5426 if (LARGE_NUMBER_SEPERATOR == ",") then
5427 t = t:gsub(",", "");
5428 else
5429 t = t:gsub("%.", "");
5430 t = t:gsub(",", ".");
5431 end
5432 tinsert(tooltipSize, tonumber(t));
5433 end
5434 end
5435
5436 if (#tooltipSize) then
5437 return tooltipText, debuffType, unpack(tooltipSize);
5438 else
5439 return tooltipText, debuffType, 0;
5440 end
5441end
5442
5443local FrameTimes = {};
5444function WeakAuras.ProfileFrames(all)
5445 UpdateAddOnCPUUsage();
5446 for name, frame in pairs(WeakAuras.frames) do
5447 local FrameTime = GetFrameCPUUsage(frame);
5448 FrameTimes[name] = FrameTimes[name] or 0;
5449 if(all or FrameTime > FrameTimes[name]) then
5450 print("|cFFFF0000"..name.."|r -", FrameTime, "-", FrameTime - FrameTimes[name]);
5451 end
5452 FrameTimes[name] = FrameTime;
5453 end
5454end
5455
5456local DisplayTimes = {};
5457function WeakAuras.ProfileDisplays(all)
5458 UpdateAddOnCPUUsage();
5459 for id, regionData in pairs(WeakAuras.regions) do
5460 local DisplayTime = GetFrameCPUUsage(regionData.region, true);
5461 DisplayTimes[id] = DisplayTimes[id] or 0;
5462 if(all or DisplayTime > DisplayTimes[id]) then
5463 print("|cFFFF0000"..id.."|r -", DisplayTime, "-", DisplayTime - DisplayTimes[id]);
5464 end
5465 DisplayTimes[id] = DisplayTime;
5466 end
5467end
5468
5469function WeakAuras.RegisterTutorial(name, displayName, description, icon, steps, order)
5470 tutorials[name] = {
5471 name = name,
5472 displayName = displayName,
5473 description = description,
5474 icon = icon,
5475 steps = steps,
5476 order = order
5477 };
5478end
5479
5480function WeakAuras.ValueFromPath(data, path)
5481 if not data then
5482 return nil
5483 end
5484 if(#path == 1) then
5485 return data[path[1]];
5486 else
5487 local reducedPath = {};
5488 for i=2,#path do
5489 reducedPath[i-1] = path[i];
5490 end
5491 return WeakAuras.ValueFromPath(data[path[1]], reducedPath);
5492 end
5493end
5494
5495function WeakAuras.ValueToPath(data, path, value)
5496 if not data then
5497 return
5498 end
5499 if(#path == 1) then
5500 data[path[1]] = value;
5501 else
5502 local reducedPath = {};
5503 for i=2,#path do
5504 reducedPath[i-1] = path[i];
5505 end
5506 WeakAuras.ValueToPath(data[path[1]], reducedPath, value);
5507 end
5508end
5509
5510function WeakAuras.FixGroupChildrenOrderForGroup(data)
5511 local frameLevel = 5;
5512 for i=1, #data.controlledChildren do
5513 WeakAuras.SetFrameLevel(data.controlledChildren[i], frameLevel);
5514 frameLevel = frameLevel + 4;
5515 end
5516end
5517
5518WeakAuras.frameLevels = {};
5519function WeakAuras.SetFrameLevel(id, frameLevel)
5520 if (WeakAuras.frameLevels[id] == frameLevel) then
5521 return;
5522 end
5523 if (WeakAuras.regions[id] and WeakAuras.regions[id].region) then
5524 WeakAuras.ApplyFrameLevel(WeakAuras.regions[id].region, frameLevel)
5525 end
5526 if (clones[id]) then
5527 for i,v in pairs(clones[id]) do
5528 WeakAuras.ApplyFrameLevel(v, frameLevel)
5529 end
5530 end
5531 WeakAuras.frameLevels[id] = frameLevel;
5532end
5533
5534function WeakAuras.GetFrameLevelFor(id)
5535 return WeakAuras.frameLevels[id] or 5;
5536end
5537
5538function WeakAuras.ApplyFrameLevel(region, frameLevel)
5539 frameLevel = frameLevel or WeakAuras.GetFrameLevelFor(region.id)
5540 region:SetFrameLevel(frameLevel)
5541 if region.subRegions then
5542 for index, subRegion in pairs(region.subRegions) do
5543 subRegion:SetFrameLevel(frameLevel + index + 1)
5544 end
5545 end
5546end
5547
5548function WeakAuras.EnsureString(input)
5549 if (input == nil) then
5550 return "";
5551 end
5552 return tostring(input);
5553end
5554
5555-- Handle coroutines
5556local dynFrame = {};
5557do
5558 -- Internal data
5559 dynFrame.frame = CreateFrame("frame");
5560 dynFrame.update = {};
5561 dynFrame.size = 0;
5562
5563 -- Add an action to be resumed via OnUpdate
5564 function dynFrame.AddAction(self, name, func)
5565 if not name then
5566 name = string.format("NIL", dynFrame.size+1);
5567 end
5568
5569 if not dynFrame.update[name] then
5570 dynFrame.update[name] = func;
5571 dynFrame.size = dynFrame.size + 1
5572 dynFrame.frame:Show();
5573 end
5574 end
5575
5576 -- Remove an action from OnUpdate
5577 function dynFrame.RemoveAction(self, name)
5578 if dynFrame.update[name] then
5579 dynFrame.update[name] = nil;
5580 dynFrame.size = dynFrame.size - 1
5581 if dynFrame.size == 0 then
5582 dynFrame.frame:Hide();
5583 end
5584 end
5585 end
5586
5587 -- Setup frame
5588 dynFrame.frame:Hide();
5589 dynFrame.frame:SetScript("OnUpdate", function(self, elapsed)
5590 -- Start timing
5591 local start = debugprofilestop();
5592 local hasData = true;
5593
5594 -- Resume as often as possible (Limit to 16ms per frame -> 60 FPS)
5595 while (debugprofilestop() - start < 16 and hasData) do
5596 -- Stop loop without data
5597 hasData = false;
5598
5599 -- Resume all coroutines
5600 for name, func in pairs(dynFrame.update) do
5601 -- Loop has data
5602 hasData = true;
5603
5604 -- Resume or remove
5605 if coroutine.status(func) ~= "dead" then
5606 local ok, msg = coroutine.resume(func)
5607 if not ok then
5608 geterrorhandler()(msg .. '\n' .. debugstack(func))
5609 end
5610 else
5611 dynFrame:RemoveAction(name);
5612 end
5613 end
5614 end
5615 end);
5616end
5617
5618WeakAuras.dynFrame = dynFrame;
5619
5620function WeakAuras.SetDynamicIconCache(name, spellId, icon)
5621 db.dynamicIconCache[name] = db.dynamicIconCache[name] or {};
5622 db.dynamicIconCache[name][spellId] = icon;
5623end
5624
5625function WeakAuras.GetDynamicIconCache(name)
5626 if (db.dynamicIconCache[name]) then
5627 local fallback = nil;
5628 for spellId, icon in pairs(db.dynamicIconCache[name]) do
5629 fallback = icon;
5630 if (type(spellId) == "number" and IsSpellKnown(spellId)) then -- TODO save this information?
5631 return db.dynamicIconCache[name][spellId];
5632 end
5633 end
5634 return fallback;
5635 end
5636
5637 if WeakAuras.spellCache then
5638 return WeakAuras.spellCache.GetIcon(name);
5639 end
5640 return nil;
5641end
5642
5643function WeakAuras.RegisterTriggerSystem(types, triggerSystem)
5644 for _, v in ipairs(types) do
5645 triggerTypes[v] = triggerSystem;
5646 end
5647 tinsert(triggerSystems, triggerSystem);
5648end
5649
5650function WeakAuras.RegisterTriggerSystemOptions(types, func)
5651 for _, v in ipairs(types) do
5652 triggerTypesOptions[v] = func;
5653 end
5654end
5655
5656function WeakAuras.GetTriggerStateForTrigger(id, triggernum)
5657 if (triggernum == -1) then
5658 return WeakAuras.GetGlobalConditionState();
5659 end
5660 triggerState[id][triggernum] = triggerState[id][triggernum] or {}
5661 return triggerState[id][triggernum];
5662end
5663
5664
5665do
5666 local visibleFakeStates = {}
5667
5668 local UpdateFakeTimesHandle
5669
5670 local function UpdateFakeTimers()
5671 local t = GetTime()
5672 for id, triggers in pairs(triggerState) do
5673 local changed = false
5674 for triggernum, triggerData in ipairs(triggers) do
5675 for id, state in pairs(triggerData) do
5676 if state.progressType == "timed" and state.expirationTime and state.expirationTime < t and state.duration and state.duration > 0 then
5677 state.expirationTime = state.expirationTime + state.duration
5678 state.changed = true
5679 changed = true
5680 end
5681 end
5682 end
5683 if changed then
5684 WeakAuras.UpdatedTriggerState(id)
5685 end
5686 end
5687 end
5688
5689 function WeakAuras.SetFakeStates()
5690 if UpdateFakeTimesHandle then
5691 return
5692 end
5693
5694 for id, states in pairs(triggerState) do
5695 local changed
5696 for triggernum in ipairs(states) do
5697 changed = WeakAuras.SetAllStatesHidden(id, triggernum) or changed
5698 end
5699 if changed then
5700 WeakAuras.UpdatedTriggerState(id)
5701 end
5702 end
5703 UpdateFakeTimesHandle = timer:ScheduleRepeatingTimer(UpdateFakeTimers, 1)
5704 end
5705
5706 function WeakAuras.ClearFakeStates()
5707 timer:CancelTimer(UpdateFakeTimesHandle)
5708 for id in pairs(triggerState) do
5709 WeakAuras.FakeStatesFor(id, false)
5710 end
5711 end
5712
5713 local function ApplyFakeAlpha(region)
5714 if (region.GetRegionAlpha and region:GetRegionAlpha() < 0.5) then
5715 region:SetAlpha(0.5);
5716 end
5717 end
5718
5719 local function RestoreAlpha(region)
5720 if (region.GetRegionAlpha) then
5721 region:SetAlpha(region:GetRegionAlpha());
5722 end
5723 end
5724
5725 function WeakAuras.FakeStatesFor(id, visible)
5726 if visibleFakeStates[id] == visible then
5727 return visibleFakeStates[id]
5728 end
5729 if visible then
5730 visibleFakeStates[id] = true
5731 WeakAuras.UpdateFakeStatesFor(id)
5732 else
5733 visibleFakeStates[id] = false
5734 if triggerState[id] then
5735 local changed = false
5736 for triggernum, state in ipairs(triggerState[id]) do
5737 changed = WeakAuras.SetAllStatesHidden(id, triggernum) or changed
5738 end
5739 if changed then
5740 WeakAuras.UpdatedTriggerState(id)
5741 end
5742 end
5743 if WeakAuras.regions[id] and WeakAuras.regions[id].region then
5744 RestoreAlpha(WeakAuras.regions[id].region)
5745 end
5746 end
5747 return not visibleFakeStates[id]
5748 end
5749
5750 function WeakAuras.UpdateFakeStatesFor(id)
5751 if (WeakAuras.IsOptionsOpen() and visibleFakeStates[id]) then
5752 local data = WeakAuras.GetData(id)
5753 if (data) then
5754 for triggernum, trigger in ipairs(data.triggers) do
5755 WeakAuras.SetAllStatesHidden(id, triggernum)
5756 local triggerSystem = WeakAuras.GetTriggerSystem(data, triggernum)
5757 if triggerSystem and triggerSystem.CreateFakeStates then
5758 triggerSystem.CreateFakeStates(id, triggernum)
5759 end
5760 end
5761 WeakAuras.UpdatedTriggerState(id)
5762 if WeakAuras.GetMoverSizerId() == id then
5763 WeakAuras.SetMoverSizer(id)
5764 end
5765
5766 if WeakAuras.regions[id] and WeakAuras.regions[id].region then
5767 ApplyFakeAlpha(WeakAuras.regions[id].region)
5768 end
5769 end
5770 end
5771 end
5772end
5773
5774local function startStopTimers(id, cloneId, triggernum, state)
5775 if (state.show) then
5776 if (state.autoHide and state.duration and state.duration > 0) then -- autohide, update timer
5777 timers[id] = timers[id] or {};
5778 timers[id][triggernum] = timers[id][triggernum] or {};
5779 timers[id][triggernum][cloneId] = timers[id][triggernum][cloneId] or {};
5780 local record = timers[id][triggernum][cloneId];
5781 if (state.expirationTime == nil) then
5782 state.expirationTime = GetTime() + state.duration;
5783 end
5784 if (record.expirationTime ~= state.expirationTime or record.state ~= state) then
5785 if (record.handle ~= nil) then
5786 timer:CancelTimer(record.handle);
5787 end
5788
5789 record.handle = timer:ScheduleTimerFixed(
5790 function()
5791 if (state.show ~= false and state.show ~= nil) then
5792 state.show = false;
5793 state.changed = true;
5794 WeakAuras.UpdatedTriggerState(id);
5795 end
5796 end,
5797 state.expirationTime - GetTime());
5798 record.expirationTime = state.expirationTime;
5799 record.state = state
5800 end
5801 else -- no auto hide, delete timer
5802 if (timers[id] and timers[id][triggernum] and timers[id][triggernum][cloneId]) then
5803 local record = timers[id][triggernum][cloneId];
5804 if (record.handle) then
5805 timer:CancelTimer(record.handle);
5806 end
5807 record.handle = nil;
5808 record.expirationTime = nil;
5809 record.state = nil
5810 end
5811 end
5812 else -- not shown
5813 if(timers[id] and timers[id][triggernum] and timers[id][triggernum][cloneId]) then
5814 local record = timers[id][triggernum][cloneId];
5815 if (record.handle) then
5816 timer:CancelTimer(record.handle);
5817 end
5818 record.handle = nil;
5819 record.expirationTime = nil;
5820 record.state = nil
5821 end
5822 end
5823end
5824
5825local function ApplyStateToRegion(id, cloneId, region, parent)
5826 region:Update();
5827
5828 region.subRegionEvents:Notify("Update")
5829
5830 WeakAuras.UpdateMouseoverTooltip(region);
5831 region:Expand();
5832 if parent and parent.ActivateChild then
5833 parent:ActivateChild(id, cloneId)
5834 end
5835end
5836
5837-- Fallbacks if the states are empty
5838local emptyState = {};
5839emptyState[""] = {};
5840
5841local function applyToTriggerStateTriggers(stateShown, id, triggernum)
5842 if (stateShown and not triggerState[id].triggers[triggernum]) then
5843 triggerState[id].triggers[triggernum] = true;
5844 triggerState[id].triggerCount = triggerState[id].triggerCount + 1;
5845 return true;
5846 elseif (not stateShown and triggerState[id].triggers[triggernum]) then
5847 triggerState[id].triggers[triggernum] = false;
5848 triggerState[id].triggerCount = triggerState[id].triggerCount - 1;
5849 return true;
5850 end
5851 return false;
5852end
5853
5854local function evaluateTriggerStateTriggers(id)
5855 local result = false;
5856 WeakAuras.ActivateAuraEnvironment(id);
5857
5858 if WeakAuras.IsOptionsOpen() then
5859 -- While the options are open ignore the combination function
5860 return triggerState[id].triggerCount > 0
5861 end
5862
5863 if (triggerState[id].disjunctive == "any" and triggerState[id].triggerCount > 0) then
5864 result = true;
5865 elseif(triggerState[id].disjunctive == "all" and triggerState[id].triggerCount == triggerState[id].numTriggers) then
5866 result = true;
5867 else
5868 if (triggerState[id].disjunctive == "custom" and triggerState[id].triggerLogicFunc) then
5869 local ok, returnValue = xpcall(triggerState[id].triggerLogicFunc, geterrorhandler(), triggerState[id].triggers);
5870 result = ok and returnValue;
5871 end
5872 end
5873
5874 WeakAuras.ActivateAuraEnvironment(nil);
5875 return result;
5876end
5877
5878local function ApplyStatesToRegions(id, activeTrigger, states)
5879 -- Show new clones
5880 local data = WeakAuras.GetData(id)
5881 local parent
5882 if data and data.parent then
5883 parent = WeakAuras.GetRegion(data.parent)
5884 end
5885 if parent and parent.Suspend then
5886 parent:Suspend()
5887 end
5888 for cloneId, state in pairs(states) do
5889 if (state.show) then
5890 local region = WeakAuras.GetRegion(id, cloneId);
5891 local applyChanges = not region.toShow or state.changed or region.state ~= state
5892 region.state = state
5893 region.states = region.states or {}
5894 local needsTimerTick = false
5895 for triggernum = -1, triggerState[id].numTriggers do
5896 local triggerState
5897 if triggernum == activeTrigger then
5898 triggerState = state
5899 else
5900 local triggerStates = WeakAuras.GetTriggerStateForTrigger(id, triggernum)
5901 triggerState = triggerStates[cloneId] or triggerStates[""] or {}
5902 end
5903 applyChanges = applyChanges or region.states[triggernum] ~= triggerState or (triggerState and triggerState.changed)
5904 region.states[triggernum] = triggerState
5905 needsTimerTick = needsTimerTick or (triggerState and triggerState.show and triggerState.progressType == "timed")
5906 end
5907
5908 region:SetTriggerProvidesTimer(needsTimerTick)
5909
5910 if (applyChanges) then
5911 ApplyStateToRegion(id, cloneId, region, parent);
5912 if (checkConditions[id]) then
5913 checkConditions[id](region, not state.show);
5914 end
5915 end
5916 end
5917 end
5918 if parent and parent.Resume then
5919 parent:Resume()
5920 end
5921end
5922
5923function WeakAuras.UpdatedTriggerState(id)
5924 if (not triggerState[id]) then
5925 return;
5926 end
5927
5928 local changed = false;
5929 for triggernum = 1, triggerState[id].numTriggers do
5930 triggerState[id][triggernum] = triggerState[id][triggernum] or {};
5931
5932 local anyStateShown = false;
5933
5934 for cloneId, state in pairs(triggerState[id][triggernum]) do
5935 state.trigger = db.displays[id].triggers[triggernum] and db.displays[id].triggers[triggernum].trigger;
5936 state.triggernum = triggernum;
5937 state.id = id;
5938
5939 if (state.changed) then
5940 startStopTimers(id, cloneId, triggernum, state);
5941 end
5942 anyStateShown = anyStateShown or state.show;
5943 end
5944 -- Update triggerState.triggers
5945 changed = applyToTriggerStateTriggers(anyStateShown, id, triggernum) or changed;
5946 end
5947
5948 -- Figure out whether we should be shown or not
5949 local show = triggerState[id].show;
5950
5951
5952 if (changed or show == nil) then
5953 show = evaluateTriggerStateTriggers(id);
5954 end
5955
5956 -- Figure out which subtrigger is active, and if it changed
5957 local newActiveTrigger = triggerState[id].activeTriggerMode;
5958 if (newActiveTrigger == WeakAuras.trigger_modes.first_active) then
5959 -- Mode is first active trigger, so find a active trigger
5960 for i = 1, triggerState[id].numTriggers do
5961 if (triggerState[id].triggers[i]) then
5962 newActiveTrigger = i;
5963 break;
5964 end
5965 end
5966 end
5967
5968 local oldShow = triggerState[id].show;
5969 triggerState[id].activeTrigger = newActiveTrigger;
5970 triggerState[id].show = show;
5971
5972 local activeTriggerState = WeakAuras.GetTriggerStateForTrigger(id, newActiveTrigger);
5973 if (not next(activeTriggerState)) then
5974 if (show) then
5975 activeTriggerState = WeakAuras.CreateFallbackState(id, newActiveTrigger)
5976 else
5977 activeTriggerState = emptyState;
5978 end
5979 elseif (show) then
5980 local needsFallback = true;
5981 for cloneId, state in pairs(activeTriggerState) do
5982 if (state.show) then
5983 needsFallback = false;
5984 break;
5985 end
5986 end
5987 if (needsFallback) then
5988 activeTriggerState = WeakAuras.CreateFallbackState(id, newActiveTrigger);
5989 end
5990 end
5991
5992 local region;
5993 -- Now apply
5994 if (show and not oldShow) then -- Hide => Show
5995 ApplyStatesToRegions(id, newActiveTrigger, activeTriggerState);
5996 elseif (not show and oldShow) then -- Show => Hide
5997 for cloneId, clone in pairs(clones[id]) do
5998 clone:Collapse()
5999 end
6000 WeakAuras.regions[id].region:Collapse()
6001 elseif (show and oldShow) then -- Already shown, update regions
6002 -- Hide old clones
6003 for cloneId, clone in pairs(clones[id]) do
6004 if (not activeTriggerState[cloneId] or not activeTriggerState[cloneId].show) then
6005 clone:Collapse()
6006 end
6007 end
6008 if (not activeTriggerState[""] or not activeTriggerState[""].show) then
6009 WeakAuras.regions[id].region:Collapse()
6010 end
6011 -- Show new states
6012 ApplyStatesToRegions(id, newActiveTrigger, activeTriggerState);
6013 end
6014
6015 for triggernum = 1, triggerState[id].numTriggers do
6016 triggerState[id][triggernum] = triggerState[id][triggernum] or {};
6017 for cloneId, state in pairs(triggerState[id][triggernum]) do
6018 if (not state.show) then
6019 triggerState[id][triggernum][cloneId] = nil;
6020 end
6021 state.changed = false;
6022 end
6023 end
6024end
6025
6026function WeakAuras.RunCustomTextFunc(region, customFunc)
6027 if not customFunc then
6028 return nil
6029 end
6030 local state = region.state
6031 WeakAuras.ActivateAuraEnvironment(region.id, region.cloneId, region.state, region.states);
6032 local progress = WeakAuras.dynamic_texts.p.func(state, region)
6033 local dur = WeakAuras.dynamic_texts.t.func(state, region)
6034 local name = WeakAuras.dynamic_texts.n.func(state, region)
6035 local icon = WeakAuras.dynamic_texts.i.func(state, region)
6036 local stacks = WeakAuras.dynamic_texts.s.func(state, region)
6037
6038 local expirationTime
6039 local duration
6040 if state then
6041 if state.progressType == "timed" then
6042 expirationTime = state.expirationTime
6043 duration = state.duration
6044 else
6045 expirationTime = state.total
6046 duration = state.value
6047 end
6048 end
6049
6050 local custom = {select(2, xpcall(customFunc, geterrorhandler(), expirationTime or math.huge, duration or 0, progress, dur, name, icon, stacks))}
6051 WeakAuras.ActivateAuraEnvironment(nil)
6052 return custom
6053end
6054
6055local function ReplaceValuePlaceHolders(textStr, region, customFunc, state)
6056 local regionValues = region.values;
6057 local value;
6058 if string.sub(textStr, 1, 1) == "c" then
6059 local custom
6060 if customFunc then
6061 custom = WeakAuras.RunCustomTextFunc(region, customFunc)
6062 else
6063 custom = region.values.custom
6064 end
6065
6066 local index = tonumber(textStr:match("^c(%d+)$") or 1)
6067 if custom then
6068 value = WeakAuras.EnsureString(custom[index])
6069 end
6070 else
6071 local variable = WeakAuras.dynamic_texts[textStr];
6072 if (not variable) then
6073 return nil;
6074 end
6075 value = variable.func(state, region)
6076 end
6077 return value or "";
6078end
6079
6080-- States:
6081-- 0 Normal state, text is just appened to result. Can transition to percent start state 1 via %
6082-- 1 Percent start state, entered via %. Can transition to via { to braced, via % to normal, AZaz09 to percent rest state
6083-- 2 Percent rest state, stay in it via AZaz09, transition to normal on anything else
6084-- 3 Braced state, } transitions to normal, everything else stay in braced state
6085local function nextState(char, state)
6086 if state == 0 then -- Normal State
6087 if char == 37 then -- % sign
6088 return 1 -- Enter Percent state
6089 end
6090 return 0
6091 elseif state == 1 then -- Percent Start State
6092 if char == 37 then -- % sign
6093 return 0 -- Return to normal state
6094 elseif char == 123 then -- { sign
6095 return 3 -- Enter Braced state
6096 elseif (char >= 48 and char <= 57) or (char >= 65 and char <= 90) or (char >= 97 and char <= 122) or char == 46 then
6097 -- 0-9a-zA-Z or dot character
6098 return 2 -- Enter Percent rest state
6099 end
6100 return 0 -- % followed by non alpha-numeric. Back to normal state
6101 elseif state == 2 then
6102 if (char >= 48 and char <= 57) or (char >= 65 and char <= 90) or (char >= 97 and char <= 122) or char == 46 then
6103 return 2 -- Continue in same state
6104 end
6105 if char == 37 then
6106 return 1 -- End of %, but also start of new %
6107 end
6108 return 0 -- Back to normal
6109 elseif state == 3 then
6110 if char == 125 then -- } closing brace
6111 return 0 -- Back to normal
6112 end
6113 return 3
6114 end
6115 -- Shouldn't happen
6116 return state
6117end
6118
6119local function ContainsPlaceHolders(textStr, symbolFunc)
6120 if not textStr then
6121 return false
6122 end
6123
6124 if textStr:find("\\n") then
6125 return true
6126 end
6127
6128 local endPos = textStr:len();
6129 local state = 0
6130 local currentPos = 1
6131 local start = 1
6132 while currentPos <= endPos do
6133 local char = string.byte(textStr, currentPos);
6134 local nextState = nextState(char, state)
6135
6136 if state == 1 then -- Last char was a %
6137 if char == 123 then
6138 start = currentPos + 1
6139 else
6140 start = currentPos
6141 end
6142 elseif state == 2 or state == 3 then
6143 if nextState == 0 or nextState == 1 then
6144 local symbol = string.sub(textStr, start, currentPos - 1)
6145 if symbolFunc(symbol) then
6146 return true
6147 end
6148 end
6149 end
6150
6151 state = nextState
6152 currentPos = currentPos + 1
6153 end
6154
6155 if state == 2 then
6156 local symbol = string.sub(textStr, start, currentPos - 1)
6157 if symbolFunc(symbol) then
6158 return true
6159 end
6160 end
6161
6162 return false
6163end
6164
6165function WeakAuras.ContainsCustomPlaceHolder(textStr)
6166 return ContainsPlaceHolders(textStr, function(symbol)
6167 return string.match(symbol, "^c%d*$")
6168 end)
6169end
6170
6171function WeakAuras.ContainsPlaceHolders(textStr, toCheck)
6172 return ContainsPlaceHolders(textStr, function(symbol)
6173 if symbol:len() == 1 and toCheck:find(symbol, 1, true) then
6174 return true
6175 end
6176
6177 local _, last = symbol:find("^%d+%.")
6178 if not last then
6179 return false
6180 end
6181
6182 symbol = symbol:sub(last + 1)
6183 if symbol:len() == 1 and toCheck:find(symbol, 1, true) then
6184 return true
6185 end
6186 end)
6187end
6188
6189function WeakAuras.ContainsAnyPlaceHolders(textStr)
6190 return ContainsPlaceHolders(textStr, function(symbol) return true end)
6191end
6192
6193local function ValueForSymbol(symbol, region, customFunc, regionState, regionStates)
6194 local triggerNum, sym = string.match(symbol, "(.+)%.(.+)")
6195 triggerNum = triggerNum and tonumber(triggerNum)
6196 if triggerNum and sym then
6197 if regionStates and regionStates[triggerNum] then
6198 if regionStates[triggerNum][sym] then
6199 return regionStates[triggerNum].show and tostring(regionStates[triggerNum][sym]) or ""
6200 else
6201 local value = regionStates[triggerNum].show and ReplaceValuePlaceHolders(sym, region, customFunc, regionStates[triggerNum]);
6202 return value or ""
6203 end
6204 end
6205 return ""
6206 elseif regionState and regionState[symbol] then
6207 return regionState.show and tostring(regionState[symbol]) or ""
6208 else
6209 local value = regionState and regionState.show and ReplaceValuePlaceHolders(symbol, region, customFunc, regionState);
6210 return value or ""
6211 end
6212end
6213
6214function WeakAuras.ReplacePlaceHolders(textStr, region, customFunc)
6215 local regionValues = region.values;
6216 local regionState = region.state;
6217 local regionStates = region.states;
6218 if (not regionState and not regionValues) then
6219 return;
6220 end
6221 local endPos = textStr:len();
6222 if (endPos < 2) then
6223 textStr = textStr:gsub("\\n", "\n");
6224 return textStr;
6225 end
6226
6227 if (endPos == 2) then
6228 if string.byte(textStr, 1) == 37 then
6229 local value = ReplaceValuePlaceHolders(string.sub(textStr, 2), region, customFunc, regionState);
6230 if (value) then
6231 textStr = tostring(value);
6232 end
6233 end
6234 textStr = textStr:gsub("\\n", "\n");
6235 return textStr;
6236 end
6237
6238 local result = ""
6239 local currentPos = 1 -- Position of the "cursor"
6240 local state = 0
6241 local start = 1 -- Start of whatever "word" we are currently considering, doesn't include % or {} symbols
6242
6243 while currentPos <= endPos do
6244 local char = string.byte(textStr, currentPos);
6245 if state == 0 then -- Normal State
6246 if char == 37 then -- % sign
6247 if currentPos > start then
6248 result = result .. string.sub(textStr, start, currentPos - 1)
6249 end
6250 end
6251 elseif state == 1 then -- Percent Start State
6252 if char == 37 then
6253 start = currentPos
6254 elseif char == 123 then
6255 start = currentPos + 1
6256 elseif (char >= 48 and char <= 57) or (char >= 65 and char <= 90) or (char >= 97 and char <= 122) or char == 46 then
6257 -- 0-9a-zA-Z or dot character
6258 start = currentPos
6259 else
6260 start = currentPos
6261 end
6262 elseif state == 2 then -- Percent Rest State
6263 if (char >= 48 and char <= 57) or (char >= 65 and char <= 90) or (char >= 97 and char <= 122) or char == 46 then
6264 -- 0-9a-zA-Z or dot character
6265 else -- End of variable
6266 local symbol = string.sub(textStr, start, currentPos - 1)
6267 result = result .. ValueForSymbol(symbol, region, customFunc, regionState, regionStates)
6268
6269 if char == 37 then
6270 else
6271 start = currentPos
6272 end
6273 end
6274 elseif state == 3 then
6275 if char == 125 then -- } closing brace
6276 local symbol = string.sub(textStr, start, currentPos - 1)
6277 result = result .. ValueForSymbol(symbol, region, customFunc, regionState, regionStates)
6278 start = currentPos + 1
6279 end
6280 end
6281 state = nextState(char, state)
6282 currentPos = currentPos + 1
6283 end
6284
6285 if state == 0 and currentPos > start then
6286 result = result .. string.sub(textStr, start, currentPos - 1)
6287 elseif state == 2 and currentPos > start then
6288 local symbol = string.sub(textStr, start, currentPos - 1)
6289 result = result .. ValueForSymbol(symbol, region, customFunc, regionState, regionStates)
6290 elseif state == 1 then
6291 result = result .. "%"
6292 end
6293
6294 textStr = result:gsub("\\n", "\n");
6295 return textStr;
6296end
6297
6298function WeakAuras.IsTriggerActive(id)
6299 local active = triggerState[id];
6300 return active and active.show;
6301end
6302
6303-- Attach to Cursor/Frames code
6304-- Very simple function to convert a hsv angle to a color with
6305-- value hardcoded to 1 and saturation hardcoded to 0.75
6306local function colorWheel(angle)
6307 local hh = angle / 60;
6308 local i = floor(hh);
6309 local ff = hh - i;
6310 local p = 0;
6311 local q = 0.75 * (1.0 - ff);
6312 local t = 0.75 * ff;
6313 if (i == 0) then
6314 return 0.75, t, p;
6315 elseif (i == 1) then
6316 return q, 0.75, p;
6317 elseif (i == 2) then
6318 return p, 0.75, t;
6319 elseif (i == 3) then
6320 return p, q, 0.75;
6321 elseif (i == 4) then
6322 return t, p, 0.75;
6323 else
6324 return 0.75, p, q;
6325 end
6326end
6327
6328local function xPositionNextToOptions()
6329 local xOffset;
6330 local optionsFrame = WeakAuras.OptionsFrame();
6331 local centerX = (optionsFrame:GetLeft() + optionsFrame:GetRight()) / 2;
6332 if (centerX > GetScreenWidth() / 2) then
6333 if (optionsFrame:GetLeft() > 400) then
6334 xOffset = optionsFrame:GetLeft() - 200;
6335 else
6336 xOffset = optionsFrame:GetLeft() / 2;
6337 end
6338 else
6339 if (GetScreenWidth() - optionsFrame:GetRight() > 400 ) then
6340 xOffset = optionsFrame:GetRight() + 200;
6341 else
6342 xOffset = (GetScreenWidth() + optionsFrame:GetRight()) / 2;
6343 end
6344 end
6345 return xOffset;
6346end
6347
6348local mouseFrame;
6349local function ensureMouseFrame()
6350 if (mouseFrame) then
6351 return;
6352 end
6353 mouseFrame = CreateFrame("FRAME", "WeakAurasAttachToMouseFrame", UIParent);
6354 mouseFrame.attachedVisibleFrames = {};
6355 mouseFrame:SetWidth(1);
6356 mouseFrame:SetHeight(1);
6357
6358 local moverFrame = CreateFrame("FRAME", "WeakAurasMousePointerFrame", mouseFrame);
6359 mouseFrame.moverFrame = moverFrame;
6360 moverFrame:SetPoint("TOPLEFT", mouseFrame, "CENTER");
6361 moverFrame:SetWidth(32);
6362 moverFrame:SetHeight(32);
6363 moverFrame:SetFrameStrata("FULLSCREEN"); -- above settings dialog
6364
6365 moverFrame:EnableMouse(true)
6366 moverFrame:SetScript("OnMouseDown", function()
6367 mouseFrame:SetMovable(true);
6368 mouseFrame:StartMoving()
6369 end);
6370 moverFrame:SetScript("OnMouseUp", function()
6371 mouseFrame:StopMovingOrSizing();
6372 mouseFrame:SetMovable(false);
6373 local xOffset = mouseFrame:GetRight() - GetScreenWidth();
6374 local yOffset = mouseFrame:GetTop() - GetScreenHeight();
6375 db.mousePointerFrame = db.mousePointerFrame or {};
6376 db.mousePointerFrame.xOffset = xOffset;
6377 db.mousePointerFrame.yOffset = yOffset;
6378 end);
6379 moverFrame.colorWheelAnimation = function()
6380 local angle = ((GetTime() - moverFrame.startTime) % 5) / 5 * 360;
6381 moverFrame.texture:SetVertexColor(colorWheel(angle));
6382 end;
6383 local texture = moverFrame:CreateTexture(nil, "BACKGROUND");
6384 moverFrame.texture = texture;
6385 texture:SetAllPoints(moverFrame);
6386 texture:SetTexture("Interface\\Cursor\\Point");
6387
6388 local label = moverFrame:CreateFontString(nil, "BACKGROUND", "GameFontHighlightSmall")
6389 label:SetJustifyH("LEFT")
6390 label:SetJustifyV("TOP")
6391 label:SetPoint("TOPLEFT", moverFrame, "BOTTOMLEFT");
6392 label:SetText("WeakAuras Anchor");
6393
6394 moverFrame:Hide();
6395
6396 mouseFrame.OptionsOpened = function()
6397 if (db.mousePointerFrame) then
6398 -- Restore from settings
6399 mouseFrame:ClearAllPoints();
6400 mouseFrame:SetPoint("TOPRIGHT", UIParent, "TOPRIGHT", db.mousePointerFrame.xOffset, db.mousePointerFrame.yOffset);
6401 else
6402 -- Fnd a suitable position
6403 local optionsFrame = WeakAuras.OptionsFrame();
6404 local yOffset = (optionsFrame:GetTop() + optionsFrame:GetBottom()) / 2;
6405 local xOffset = xPositionNextToOptions();
6406 -- We use the top right, because the main frame usees the top right as the reference too
6407 mouseFrame:ClearAllPoints();
6408 mouseFrame:SetPoint("TOPRIGHT", UIParent, "TOPRIGHT", xOffset - GetScreenWidth(), yOffset - GetScreenHeight());
6409 end
6410 -- Change the color of the mouse cursor
6411 moverFrame.startTime = GetTime();
6412 moverFrame:SetScript("OnUpdate", moverFrame.colorWheelAnimation);
6413 mouseFrame:SetScript("OnUpdate", nil);
6414 end
6415
6416 mouseFrame.moveWithMouse = function()
6417 local scale = 1 / UIParent:GetEffectiveScale();
6418 local x, y = GetCursorPosition();
6419 mouseFrame:SetPoint("CENTER", UIParent, "BOTTOMLEFT", x * scale, y * scale);
6420 end
6421
6422 mouseFrame.OptionsClosed = function()
6423 moverFrame:Hide();
6424 mouseFrame:ClearAllPoints();
6425 mouseFrame:SetScript("OnUpdate", mouseFrame.moveWithMouse);
6426 moverFrame:SetScript("OnUpdate", nil);
6427 wipe(mouseFrame.attachedVisibleFrames);
6428 end
6429
6430 mouseFrame.expand = function(self, id)
6431 local data = WeakAuras.GetData(id);
6432 if (data.anchorFrameType == "MOUSE") then
6433 self.attachedVisibleFrames[id] = true;
6434 self:updateVisible();
6435 end
6436 end
6437
6438 mouseFrame.collapse = function(self, id)
6439 self.attachedVisibleFrames[id] = nil;
6440 self:updateVisible();
6441 end
6442
6443 mouseFrame.rename = function(self, oldid, newid)
6444 self.attachedVisibleFrames[newid] = self.attachedVisibleFrames[oldid];
6445 self.attachedVisibleFrames[oldid] = nil;
6446 self:updateVisible();
6447 end
6448
6449 mouseFrame.delete = function(self, id)
6450 self.attachedVisibleFrames[id] = nil;
6451 self:updateVisible();
6452 end
6453
6454 mouseFrame.anchorFrame = function(self, id, anchorFrameType)
6455 if (anchorFrameType == "MOUSE") then
6456 self.attachedVisibleFrames[id] = true;
6457 else
6458 self.attachedVisibleFrames[id] = nil;
6459 end
6460 self:updateVisible();
6461 end
6462
6463 mouseFrame.updateVisible = function(self)
6464 if (not WeakAuras.IsOptionsOpen()) then
6465 return;
6466 end
6467
6468 if (next(self.attachedVisibleFrames)) then
6469 mouseFrame.moverFrame:Show();
6470 else
6471 mouseFrame.moverFrame:Hide();
6472 end
6473 end
6474
6475 if (WeakAuras.IsOptionsOpen()) then
6476 mouseFrame:OptionsOpened();
6477 else
6478 mouseFrame:OptionsClosed();
6479 end
6480
6481 WeakAuras.mouseFrame = mouseFrame;
6482end
6483
6484local personalRessourceDisplayFrame;
6485local function ensurePRDFrame()
6486 if (personalRessourceDisplayFrame) then
6487 return;
6488 end
6489 personalRessourceDisplayFrame = CreateFrame("FRAME", "WeakAurasAttachToPRD", UIParent);
6490 personalRessourceDisplayFrame:Hide();
6491 personalRessourceDisplayFrame.attachedVisibleFrames = {};
6492 WeakAuras.personalRessourceDisplayFrame = personalRessourceDisplayFrame;
6493
6494 local moverFrame = CreateFrame("FRAME", "WeakAurasPRDMoverFrame", personalRessourceDisplayFrame);
6495 personalRessourceDisplayFrame.moverFrame = moverFrame;
6496 moverFrame:SetPoint("TOPLEFT", personalRessourceDisplayFrame, "TOPLEFT", -2, 2);
6497 moverFrame:SetPoint("BOTTOMRIGHT", personalRessourceDisplayFrame, "BOTTOMRIGHT", 2, -2);
6498 moverFrame:SetFrameStrata("FULLSCREEN"); -- above settings dialog
6499
6500 moverFrame:EnableMouse(true)
6501 moverFrame:SetScript("OnMouseDown", function()
6502 personalRessourceDisplayFrame:SetMovable(true);
6503 personalRessourceDisplayFrame:StartMoving()
6504 end);
6505 moverFrame:SetScript("OnMouseUp", function()
6506 personalRessourceDisplayFrame:StopMovingOrSizing();
6507 personalRessourceDisplayFrame:SetMovable(false);
6508 local xOffset = personalRessourceDisplayFrame:GetRight();
6509 local yOffset = personalRessourceDisplayFrame:GetTop();
6510
6511 db.personalRessourceDisplayFrame = db.personalRessourceDisplayFrame or {};
6512 local scale = UIParent:GetEffectiveScale() / personalRessourceDisplayFrame:GetEffectiveScale();
6513 db.personalRessourceDisplayFrame.xOffset = xOffset / scale - GetScreenWidth();
6514 db.personalRessourceDisplayFrame.yOffset = yOffset / scale - GetScreenHeight();
6515 end);
6516 moverFrame:Hide();
6517
6518 local texture = moverFrame:CreateTexture(nil, "BACKGROUND");
6519 personalRessourceDisplayFrame.texture = texture;
6520 texture:SetAllPoints(moverFrame);
6521 texture:SetTexture("Interface\\AddOns\\WeakAuras\\Media\\Textures\\PRDFrame");
6522
6523 local label = moverFrame:CreateFontString(nil, "BACKGROUND", "GameFontHighlight")
6524 label:SetPoint("CENTER", moverFrame, "CENTER");
6525 label:SetText("WeakAuras Anchor");
6526
6527 personalRessourceDisplayFrame:RegisterEvent('NAME_PLATE_UNIT_ADDED');
6528 personalRessourceDisplayFrame:RegisterEvent('NAME_PLATE_UNIT_REMOVED');
6529
6530 personalRessourceDisplayFrame.Attach = function(self, frame, frameTL, frameBR)
6531 self:SetParent(frame);
6532 self:ClearAllPoints();
6533 self:SetPoint("TOPLEFT", frameTL, "TOPLEFT");
6534 self:SetPoint("BOTTOMRIGHT", frameBR, "BOTTOMRIGHT");
6535 self:Show()
6536 end
6537
6538 personalRessourceDisplayFrame.Detach = function(self, frame)
6539 self:ClearAllPoints();
6540 self:Hide()
6541 self:SetParent(UIParent)
6542 end
6543
6544 personalRessourceDisplayFrame.OptionsOpened = function()
6545 personalRessourceDisplayFrame:Detach();
6546 personalRessourceDisplayFrame:SetScript("OnEvent", nil);
6547 personalRessourceDisplayFrame:ClearAllPoints();
6548 personalRessourceDisplayFrame:Show()
6549 local xOffset, yOffset;
6550 if (db.personalRessourceDisplayFrame) then
6551 xOffset = db.personalRessourceDisplayFrame.xOffset;
6552 yOffset = db.personalRessourceDisplayFrame.yOffset;
6553 end
6554
6555 -- Calculate size of self nameplate
6556 local prdWidth;
6557 local prdHeight;
6558
6559 if (KuiNameplatesCore and KuiNameplatesCore.profile) then
6560 prdWidth = KuiNameplatesCore.profile.frame_width_personal;
6561 prdHeight = KuiNameplatesCore.profile.frame_height_personal;
6562 if (KuiNameplatesCore.profile.ignore_uiscale) then
6563 local _, screenWidth = GetPhysicalScreenSize();
6564 local uiScale = 1;
6565 if (screenWidth) then
6566 uiScale = 768 / screenWidth;
6567 end
6568 personalRessourceDisplayFrame:SetScale(uiScale / UIParent:GetEffectiveScale());
6569 else
6570 personalRessourceDisplayFrame:SetScale(1);
6571 end
6572 personalRessourceDisplayFrame.texture:SetTexture("Interface\\AddOns\\WeakAuras\\Media\\Textures\\PRDFrameKui");
6573 else
6574 local namePlateVerticalScale = tonumber(GetCVar("NamePlateVerticalScale"));
6575 local zeroBasedScale = namePlateVerticalScale - 1.0;
6576 local clampedZeroBasedScale = Saturate(zeroBasedScale);
6577 local horizontalScale = tonumber(GetCVar("NamePlateHorizontalScale"));
6578 local baseNamePlateWidth = NamePlateDriverFrame.baseNamePlateWidth;
6579 prdWidth = baseNamePlateWidth * horizontalScale * Lerp(1.1, 1.0, clampedZeroBasedScale) - 24;
6580 prdHeight = 4 * namePlateVerticalScale * Lerp(1.2, 1.0, clampedZeroBasedScale) * 2 + 1;
6581 personalRessourceDisplayFrame:SetScale(1 / UIParent:GetEffectiveScale());
6582 personalRessourceDisplayFrame.texture:SetTexture("Interface\\AddOns\\WeakAuras\\Media\\Textures\\PRDFrame");
6583 end
6584
6585 local scale = UIParent:GetEffectiveScale() / personalRessourceDisplayFrame:GetEffectiveScale();
6586 if (not xOffset or not yOffset) then
6587 local optionsFrame = WeakAuras.OptionsFrame();
6588 yOffset = optionsFrame:GetBottom() + prdHeight / scale - GetScreenHeight();
6589 xOffset = xPositionNextToOptions() + prdWidth / 2 / scale - GetScreenWidth();
6590 end
6591
6592 xOffset = xOffset * scale;
6593 yOffset = yOffset * scale;
6594
6595 personalRessourceDisplayFrame:SetPoint("TOPRIGHT", UIParent, "TOPRIGHT", xOffset, yOffset);
6596 personalRessourceDisplayFrame:SetPoint("BOTTOMLEFT", UIParent, "TOPRIGHT", xOffset - prdWidth, yOffset - prdHeight);
6597 end
6598
6599 personalRessourceDisplayFrame.OptionsClosed = function()
6600 personalRessourceDisplayFrame:SetScale(1);
6601 local frame = C_NamePlate.GetNamePlateForUnit("player");
6602 if (frame) then
6603 if (frame.kui and frame.kui.bg and frame.kui:IsShown()) then
6604 personalRessourceDisplayFrame:Attach(frame.kui, frame.kui.bg, frame.kui.bg);
6605 elseif (ElvUIPlayerNamePlateAnchor) then
6606 personalRessourceDisplayFrame:Attach(ElvUIPlayerNamePlateAnchor, ElvUIPlayerNamePlateAnchor, ElvUIPlayerNamePlateAnchor);
6607 else
6608 personalRessourceDisplayFrame:Attach(frame, frame.UnitFrame.healthBar, NamePlateDriverFrame.classNamePlatePowerBar);
6609 end
6610 else
6611 personalRessourceDisplayFrame:Detach();
6612 personalRessourceDisplayFrame:Hide();
6613 end
6614
6615 personalRessourceDisplayFrame:SetScript("OnEvent", personalRessourceDisplayFrame.eventHandler);
6616 personalRessourceDisplayFrame.texture:Hide();
6617 personalRessourceDisplayFrame.moverFrame:Hide();
6618 wipe(personalRessourceDisplayFrame.attachedVisibleFrames);
6619 end
6620
6621 personalRessourceDisplayFrame.eventHandler = function(self, event, nameplate)
6622 WeakAuras.StartProfileSystem("prd");
6623 if (event == "NAME_PLATE_UNIT_ADDED") then
6624 if (UnitIsUnit(nameplate, "player")) then
6625 local frame = C_NamePlate.GetNamePlateForUnit("player");
6626 if (frame) then
6627 if (frame.kui and frame.kui.bg and frame.kui:IsShown()) then
6628 personalRessourceDisplayFrame:Attach(frame.kui, KuiNameplatesPlayerAnchor, KuiNameplatesPlayerAnchor);
6629 elseif (ElvUIPlayerNamePlateAnchor) then
6630 personalRessourceDisplayFrame:Attach(ElvUIPlayerNamePlateAnchor, ElvUIPlayerNamePlateAnchor, ElvUIPlayerNamePlateAnchor);
6631 else
6632 personalRessourceDisplayFrame:Attach(frame, frame.UnitFrame.healthBar, NamePlateDriverFrame.classNamePlatePowerBar);
6633 end
6634 personalRessourceDisplayFrame:Show();
6635 db.personalRessourceDisplayFrame = db.personalRessourceDisplayFrame or {};
6636 else
6637 personalRessourceDisplayFrame:Detach();
6638 personalRessourceDisplayFrame:Hide();
6639 end
6640 end
6641 elseif (event == "NAME_PLATE_UNIT_REMOVED") then
6642 if (UnitIsUnit(nameplate, "player")) then
6643 personalRessourceDisplayFrame:Detach();
6644 personalRessourceDisplayFrame:Hide();
6645 end
6646 end
6647 WeakAuras.StopProfileSystem("prd");
6648 end
6649
6650 personalRessourceDisplayFrame.expand = function(self, id)
6651 local data = WeakAuras.GetData(id);
6652 if (data.anchorFrameType == "PRD") then
6653 self.attachedVisibleFrames[id] = true;
6654 self:updateVisible();
6655 end
6656 end
6657
6658 personalRessourceDisplayFrame.collapse = function(self, id)
6659 self.attachedVisibleFrames[id] = nil;
6660 self:updateVisible();
6661 end
6662
6663 personalRessourceDisplayFrame.rename = function(self, oldid, newid)
6664 self.attachedVisibleFrames[newid] = self.attachedVisibleFrames[oldid];
6665 self.attachedVisibleFrames[oldid] = nil;
6666 self:updateVisible();
6667 end
6668
6669 personalRessourceDisplayFrame.delete = function(self, id)
6670 self.attachedVisibleFrames[id] = nil;
6671 self:updateVisible();
6672 end
6673
6674 personalRessourceDisplayFrame.anchorFrame = function(self, id, anchorFrameType)
6675 if (anchorFrameType == "PRD") then
6676 self.attachedVisibleFrames[id] = true;
6677 else
6678 self.attachedVisibleFrames[id] = nil;
6679 end
6680 self:updateVisible();
6681 end
6682
6683 personalRessourceDisplayFrame.updateVisible = function(self)
6684 if (not WeakAuras.IsOptionsOpen()) then
6685 return;
6686 end
6687
6688 if (next(self.attachedVisibleFrames)) then
6689 personalRessourceDisplayFrame.texture:Show();
6690 personalRessourceDisplayFrame.moverFrame:Show();
6691 personalRessourceDisplayFrame:Show();
6692 else
6693 personalRessourceDisplayFrame.texture:Hide();
6694 personalRessourceDisplayFrame.moverFrame:Hide();
6695 personalRessourceDisplayFrame:Hide();
6696 end
6697 end
6698
6699 if (WeakAuras.IsOptionsOpen()) then
6700 personalRessourceDisplayFrame.OptionsOpened();
6701 else
6702 personalRessourceDisplayFrame.OptionsClosed();
6703 end
6704end
6705
6706local postPonedAnchors = {};
6707local anchorTimer
6708
6709local function tryAnchorAgain()
6710 local delayed = postPonedAnchors;
6711 postPonedAnchors = {};
6712 anchorTimer = nil;
6713
6714 for id, _ in pairs(delayed) do
6715 local data = WeakAuras.GetData(id);
6716 local region = WeakAuras.GetRegion(id);
6717 if (data and region) then
6718 local parent = frame;
6719 if (data.parent and regions[data.parent]) then
6720 parent = regions[data.parent].region;
6721 end
6722 WeakAuras.AnchorFrame(data, region, parent);
6723 end
6724 end
6725end
6726
6727local function postponeAnchor(id)
6728 postPonedAnchors[id] = true;
6729 if (not anchorTimer) then
6730 anchorTimer = timer:ScheduleTimer(tryAnchorAgain, 1);
6731 end
6732end
6733
6734local HiddenFrames = CreateFrame("FRAME", "WeakAurasHiddenFrames")
6735HiddenFrames:Hide()
6736
6737local function GetAnchorFrame(region, anchorFrameType, parent, anchorFrameFrame)
6738 local id = region.id
6739 if not id then return end
6740 if (personalRessourceDisplayFrame) then
6741 personalRessourceDisplayFrame:anchorFrame(id, anchorFrameType);
6742 end
6743
6744 if (mouseFrame) then
6745 mouseFrame:anchorFrame(id, anchorFrameType);
6746 end
6747
6748 if (anchorFrameType == "SCREEN") then
6749 return parent;
6750 end
6751
6752 if (anchorFrameType == "PRD") then
6753 ensurePRDFrame();
6754 personalRessourceDisplayFrame:anchorFrame(id, anchorFrameType);
6755 return personalRessourceDisplayFrame;
6756 end
6757
6758 if (anchorFrameType == "MOUSE") then
6759 ensureMouseFrame();
6760 mouseFrame:anchorFrame(id, anchorFrameType);
6761 return mouseFrame;
6762 end
6763
6764 if (anchorFrameType == "SELECTFRAME" and anchorFrameFrame) then
6765 if(anchorFrameFrame:sub(1, 10) == "WeakAuras:") then
6766 local frame_name = anchorFrameFrame:sub(11);
6767 if (frame_name == id) then
6768 return parent;
6769 end
6770 if(regions[frame_name]) then
6771 return regions[frame_name].region;
6772 end
6773 postponeAnchor(id);
6774 else
6775 if (WeakAuras.GetSanitizedGlobal(anchorFrameFrame)) then
6776 return WeakAuras.GetSanitizedGlobal(anchorFrameFrame);
6777 end
6778 postponeAnchor(id);
6779 return parent;
6780 end
6781 end
6782
6783 if (anchorFrameType == "CUSTOM" and region.customAnchorFunc) then
6784 WeakAuras.StartProfileSystem("custom region anchor")
6785 WeakAuras.StartProfileAura(region.id)
6786 WeakAuras.ActivateAuraEnvironment(region.id, region.cloneId, region.state)
6787 local ok, frame = xpcall(region.customAnchorFunc, geterrorhandler())
6788 WeakAuras.ActivateAuraEnvironment()
6789 WeakAuras.StopProfileSystem("custom region anchor")
6790 WeakAuras.StopProfileAura(region.id)
6791 if ok and frame then
6792 return frame
6793 elseif WeakAuras.IsOptionsOpen() then
6794 return parent
6795 else
6796 return HiddenFrames
6797 end
6798 end
6799 -- Fallback
6800 return parent;
6801end
6802
6803local anchorFrameDeferred = {}
6804
6805function WeakAuras.AnchorFrame(data, region, parent)
6806 if data.anchorFrameType == "CUSTOM"
6807 and (data.regionType == "group" or data.regionType == "dynamicgroup")
6808 and not WeakAuras.IsLoginFinished()
6809 and not anchorFrameDeferred[data.id]
6810 then
6811 loginQueue[#loginQueue + 1] = {WeakAuras.AnchorFrame, {data, region, parent}}
6812 anchorFrameDeferred[data.id] = true
6813 else
6814 local anchorParent = GetAnchorFrame(region, data.anchorFrameType, parent, data.anchorFrameFrame);
6815 if not anchorParent then return end
6816 if (data.anchorFrameParent or data.anchorFrameParent == nil
6817 or data.anchorFrameType == "SCREEN" or data.anchorFrameType == "MOUSE") then
6818 local errorhandler = function(text)
6819 geterrorhandler()(L["'ERROR: Anchoring %s': \n"]:format(data.id) .. text)
6820 end
6821 xpcall(region.SetParent, errorhandler, region, anchorParent);
6822 else
6823 region:SetParent(frame);
6824 end
6825
6826 region:SetAnchor(data.selfPoint, anchorParent, data.anchorPoint);
6827
6828 if(data.frameStrata == 1) then
6829 region:SetFrameStrata(region:GetParent():GetFrameStrata());
6830 else
6831 region:SetFrameStrata(WeakAuras.frame_strata_types[data.frameStrata]);
6832 end
6833 anchorFrameDeferred[data.id] = nil
6834 end
6835end
6836
6837function WeakAuras.FindUnusedId(prefix)
6838 prefix = prefix or "New"
6839 local num = 2;
6840 local id = prefix
6841 while(db.displays[id]) do
6842 id = prefix .. " " .. num;
6843 num = num + 1;
6844 end
6845 return id
6846end
6847
6848function WeakAuras.SetModel(frame, model_path, model_fileId, isUnit, isDisplayInfo)
6849 if isDisplayInfo then
6850 pcall(frame.SetDisplayInfo, frame, tonumber(model_fileId))
6851 elseif isUnit then
6852 pcall(frame.SetUnit, frame, model_fileId)
6853 else
6854 pcall(frame.SetModel, frame, tonumber(model_fileId))
6855 end
6856end
6857
6858function WeakAuras.IsCLEUSubevent(subevent)
6859 if WeakAuras.subevent_prefix_types[subevent] then
6860 return true
6861 else
6862 for prefix in pairs(WeakAuras.subevent_prefix_types) do
6863 if subevent:match(prefix) then
6864 local suffix = subevent:sub(#prefix + 1)
6865 if WeakAuras.subevent_suffix_types[suffix] then
6866 return true
6867 end
6868 end
6869 end
6870 end
6871 return false
6872end
6873
6874-- SafeToNumber converts a string to number, but only if it fits into a unsigned 32bit integer
6875-- The C api often takes only 32bit values, and complains if passed a value outside
6876function WeakAuras.SafeToNumber(input)
6877 local nr = tonumber(input)
6878 return nr and (nr < 2147483648 and nr > -2147483649) and nr or nil
6879end
6880
6881local textSymbols = {
6882 ["{rt1}"] = "|TInterface\\TargetingFrame\\UI-RaidTargetingIcon_1:0|t",
6883 ["{rt2}"] = "|TInterface\\TargetingFrame\\UI-RaidTargetingIcon_2:0|t ",
6884 ["{rt3}"] = "|TInterface\\TargetingFrame\\UI-RaidTargetingIcon_3:0|t ",
6885 ["{rt4}"] = "|TInterface\\TargetingFrame\\UI-RaidTargetingIcon_4:0|t ",
6886 ["{rt5}"] = "|TInterface\\TargetingFrame\\UI-RaidTargetingIcon_5:0|t ",
6887 ["{rt6}"] = "|TInterface\\TargetingFrame\\UI-RaidTargetingIcon_6:0|t ",
6888 ["{rt7}"] = "|TInterface\\TargetingFrame\\UI-RaidTargetingIcon_7:0|t ",
6889 ["{rt8}"] = "|TInterface\\TargetingFrame\\UI-RaidTargetingIcon_8:0|t "
6890}
6891
6892function WeakAuras.ReplaceRaidMarkerSymbols(txt)
6893 local start = 1
6894
6895 while true do
6896 local firstChar = txt:find("{", start, true)
6897 if not firstChar then
6898 return txt
6899 end
6900 local lastChar = txt:find("}", firstChar, true)
6901 if not lastChar then
6902 return txt
6903 end
6904 local replace = textSymbols[txt:sub(firstChar, lastChar)]
6905 if replace then
6906 txt = txt:sub(1, firstChar - 1) .. replace .. txt:sub(lastChar + 1)
6907 start = firstChar + #replace
6908 else
6909 start = lastChar
6910 end
6911 end
6912end
6913
6914function WeakAuras.ReplaceLocalizedRaidMarkers(txt)
6915 local start = 1
6916
6917 while true do
6918 local firstChar = txt:find("{", start, true)
6919 if not firstChar then
6920 return txt
6921 end
6922 local lastChar = txt:find("}", firstChar, true)
6923 if not lastChar then
6924 return txt
6925 end
6926
6927 local symbol = strlower(txt:sub(firstChar + 1, lastChar - 1))
6928 if ICON_TAG_LIST[symbol] then
6929 local replace = "rt" .. ICON_TAG_LIST[symbol]
6930 if replace then
6931 txt = txt:sub(1, firstChar) .. replace .. txt:sub(lastChar)
6932 start = firstChar + #replace
6933 else
6934 start = lastChar
6935 end
6936 else
6937 start = lastChar
6938 end
6939 end
6940end
6941
6942local trackableUnits = {}
6943trackableUnits["player"] = true
6944trackableUnits["target"] = true
6945trackableUnits["focus"] = true
6946trackableUnits["pet"] = true
6947trackableUnits["vehicle"] = true
6948
6949for i = 1, 5 do
6950 trackableUnits["arena" .. i] = true
6951 trackableUnits["arenapet" .. i] = true
6952end
6953
6954for i = 1, 4 do
6955 trackableUnits["boss" .. i] = true
6956 trackableUnits["party" .. i] = true
6957 trackableUnits["partypet" .. i] = true
6958end
6959
6960for i = 1, 40 do
6961 trackableUnits["raid" .. i] = true
6962 trackableUnits["raidpet" .. i] = true
6963 trackableUnits["nameplate" .. i] = true
6964end
6965
6966
6967function WeakAuras.UntrackableUnit(unit)
6968 return not trackableUnits[unit]
6969end