--[[ BuffTrigger.lua This file contains the "aura" trigger for buffs and debuffs. It registers the BuffTrigger table for the trigger type "aura" and has the following API: Add(data) Adds an aura, setting up internal data structures for all buff triggers. LoadDisplays(id) Loads the aura ids, enabling all buff triggers in the aura. UnloadDisplays(id) Unloads the aura ids, disabling all buff triggers in the aura. UnloadAll() Unloads all auras, disabling all buff triggers. ScanAll() Updates all triggers by checking all triggers. Delete(id) Removes all data for aura id. Rename(oldid, newid) Updates all data for aura oldid to use newid. Modernize(data) Updates all buff triggers in data. ##################################################### # Helper functions mainly for the WeakAuras Options # ##################################################### CanHaveDuration(data, triggernum) Returns whether the trigger can have a duration. GetOverlayInfo(data, triggernum) Returns a table containing all overlays. Currently there aren't any CanHaveAuto(data, triggernum) Returns whether the icon can be automatically selected. CanHaveClones(data, triggernum) Returns whether the trigger can have clones. CanHaveTooltip(data, triggernum) Returns the type of tooltip to show for the trigger. GetNameAndIcon(data, triggernum) Returns the name and icon to show in the options. GetAdditionalProperties(data, triggernum) Returns the tooltip text for additional properties. GetTriggerConditions(data, triggernum) Returns the potential conditions for a trigger ]]-- if not WeakAuras.IsCorrectVersion() then return end -- Lua APIs local tinsert, wipe = table.insert, wipe local pairs, next, type = pairs, next, type local BUFF_MAX_DISPLAY = 255 -- Do tell when you find the real value. local UnitGroupRolesAssigned = function() return "DAMAGER" end local WeakAuras = WeakAuras; local L = WeakAuras.L; local BuffTrigger = {}; local timer = WeakAuras.timer; local function_strings = WeakAuras.function_strings; local auras = WeakAuras.auras; local specificBosses = WeakAuras.specificBosses; local specificUnits = WeakAuras.specificUnits; local loaded_auras = WeakAuras.loaded_auras; WeakAuras.me = GetUnitName("player", true) WeakAuras.myGUID = nil local aura_cache = {}; do aura_cache.max = 0; aura_cache.watched = {}; aura_cache.players = {}; aura_cache.TANK = 0; aura_cache.HEALER = 0; aura_cache.DAMAGER = 0; aura_cache.playerRole = "NONE"; --- Tests if aura_cache data is consistent with trigger settings, eg. OwnOnly, RemainingTime, StackCount. -- Extra check needed because aura_cache can potentially contain data of two different triggers with different settings! -- @param acEntry -- @param data -- @return boolean local function TestNonUniformSettings(acEntry, data) if(data.remFunc) then if not(data.remFunc(acEntry.expirationTime - GetTime())) then return false end end -- Test OwnOnly if ( data.ownOnly == true and WeakAuras.myGUID ~= acEntry.casterGUID or data.ownOnly == false and WeakAuras.myGUID == acEntry.casterGUID ) then return false; end -- Test StackCount if (data.count and not data.count(acEntry.count)) then return false; end -- Success return true; end function aura_cache.ForceUpdate() if not(WeakAuras.IsPaused()) then WeakAuras.ScanAurasGroup() end end function aura_cache.Watch(self, id, triggernum) self.watched[id] = self.watched[id] or {}; self.watched[id][triggernum] = self.watched[id][triggernum] or {}; self.watched[id][triggernum].players = self.watched[id][triggernum].players or {}; self:ForceUpdate() end function aura_cache.Rename(self, oldid, newid) self.watched[newid] = self.watched[oldid]; self.watched[oldid] = nil; end function aura_cache.Unwatch(self, id, triggernum) self.watched[id][triggernum] = nil; end function aura_cache.GetMaxNumber(self) return self.max; end function aura_cache.GetNumber(self, id, triggernum, data) local num = 0; for guid, _ in pairs(self.players) do -- Need to check if cached data conforms to trigger if(self.watched[id][triggernum].players[guid] and TestNonUniformSettings(self.watched[id][triggernum].players[guid], data)) then num = num + 1; end end return num; end function aura_cache.GetDynamicInfo(self, id, triggernum, data) local bestDuration, bestExpirationTime, bestName, bestIcon, bestCount, bestSpellId, bestUnitCaster = 0, math.huge, "", "", 0, 0, ""; if(self.watched[id] and self.watched[id][triggernum]) then for guid, durationInfo in pairs(self.watched[id][triggernum].players) do -- Need to check if cached data conforms to trigger if(durationInfo.expirationTime < bestExpirationTime and TestNonUniformSettings(durationInfo, data)) then bestDuration = durationInfo.duration; bestExpirationTime = durationInfo.expirationTime; bestName = durationInfo.name; bestIcon = durationInfo.icon; bestCount = durationInfo.count; bestSpellId = durationInfo.spellId; bestUnitCaster = durationInfo.unitCaster; end end end return bestDuration, bestExpirationTime, bestName, bestIcon, bestCount, bestSpellId, bestUnitCaster; end function aura_cache.GetPlayerDynamicInfo(self, id, triggernum, guid, data) local bestDuration, bestExpirationTime, bestName, bestIcon, bestCount, bestSpellId, bestUnitCaster = 0, math.huge, "", "", 0, 0, ""; if(self.watched[id] and self.watched[id][triggernum]) then local durationInfo = self.watched[id][triggernum].players[guid] if(durationInfo) then -- Need to check if cached data conforms to trigger if(durationInfo.expirationTime < bestExpirationTime and TestNonUniformSettings(durationInfo, data)) then bestDuration = durationInfo.duration; bestExpirationTime = durationInfo.expirationTime; bestName = durationInfo.name; bestIcon = durationInfo.icon; bestCount = durationInfo.count; bestSpellId = durationInfo.spellId; bestUnitCaster = durationInfo.unitCaster; end end end return bestDuration, bestExpirationTime, bestName, bestIcon, bestCount, bestSpellId, bestUnitCaster; end function aura_cache.GetAffected(self, id, triggernum, data) local affected = {}; if(self.watched[id] and self.watched[id][triggernum]) then for guid, acEntry in pairs(self.watched[id][triggernum].players) do -- Need to check if cached data conforms to trigger if (TestNonUniformSettings(acEntry, data)) then affected[self.players[guid]] = true; end end end return affected; end function aura_cache.GetUnaffected(self, id, triggernum, data) local affected = self:GetAffected(id, triggernum, data); local ret = {}; for guid, name in pairs(self.players) do if not(affected[name]) then ret[name] = true; end end return ret; end function aura_cache.AssertAura(self, id, triggernum, guid, duration, expirationTime, name, icon, count, casterGUID, spellId, unitCaster) -- Don't watch aura on non watched players if not self.players[guid] then return end if not(self.watched[id][triggernum].players[guid]) then self.watched[id][triggernum].players[guid] = { duration = duration, expirationTime = expirationTime, name = name, icon = icon, count = count, unitCaster = unitCaster, spellId = spellId, casterGUID = casterGUID }; else local auradata = self.watched[id][triggernum].players[guid]; if(expirationTime ~= auradata.expirationTime) then auradata.duration = duration; auradata.expirationTime = expirationTime; auradata.name = name; auradata.icon = icon; auradata.count = count; auradata.unitCaster = unitCaster; auradata.spellId = spellId; auradata.casterGUID = casterGUID; end end end function aura_cache.DeassertAura(self, id, triggernum, guid) if(self.watched[id] and self.watched[id][triggernum] and self.watched[id][triggernum].players[guid]) then self.watched[id][triggernum].players[guid] = nil; end end function aura_cache.AssertMember(self, guid, name) if not(self.players[guid]) then self.max = self.max + 1; end self.players[guid] = name; end function aura_cache.DeassertMember(self, guid) if(self.players[guid]) then self.players[guid] = nil; for id, v in pairs(self.watched) do for triggernum, _ in pairs(v) do self:DeassertAura(id, triggernum, guid); end end self.max = self.max - 1; end end function aura_cache.AssertMemberList(self, guids) local toDelete = {}; for guid, _ in pairs(self.players) do if not(guids[guid]) then toDelete[guid] = true; end end for guid, _ in pairs(toDelete) do self:DeassertMember(guid); end for guid, name in pairs(guids) do self:AssertMember(guid, name); end self:ForceUpdate(); end end WeakAuras.aura_cache = aura_cache; function WeakAuras.SetAuraVisibility(id, triggernum, cloneId, buffShowOn, unitExists, active, unit, duration, expirationTime, name, icon, count, index, spellId, unitCaster) local triggerState = WeakAuras.GetTriggerStateForTrigger(id, triggernum); local show; if (not UnitExists(unit)) then show = unitExists; elseif (buffShowOn == "showAlways") then show = true; elseif(buffShowOn == "showOnMissing") then show = not active; else show = active; end cloneId = cloneId or ""; if (not show and not triggerState[cloneId]) then return false; end triggerState[cloneId] = triggerState[cloneId] or {}; local state = triggerState[cloneId]; if (state.show ~= show) then state.show = show; state.changed = true; end if (state.show) then if (state.active ~= active) then state.active = active; state.changed = true; end if (state.index ~= index) then state.index = index; state.changed = true; end if (state.spellId ~= spellId) then state.spellId = spellId; state.changed = true; end if (state.progressType ~= "timed") then state.progressType = "timed"; state.changed = true; end if (state.expirationTime ~= expirationTime) then state.expirationTime = expirationTime; state.changed = true; end if (state.duration ~= duration) then state.duration = duration; state.changed = true; end local autoHide = false; if (state.autoHide ~= autoHide) then state.autoHide = autoHide; state.changed = true; end if (state.name ~= name) then state.name = name; state.changed = true; end if (state.icon ~= icon) then state.icon = icon; state.changed = true; end if (state.stacks ~= count) then state.stacks = count; state.changed = true; end unitCaster = unitCaster and UnitName(unitCaster); if (state.unitCaster ~= unitCaster) then state.unitCaster = unitCaster; state.changed = true; end if (state.GUID ~= UnitGUID(unit)) then state.GUID = UnitGUID(unit); state.changed = true; end end if (state.changed) then return true; end return false; end --- Calls GetSpellInfo on trigger data to return aura name and icon. -- @param trigger -- @return name and icon local function GetNameAndIconFromTrigger(trigger) if (trigger.fullscan) then if (trigger.spellId) then local name, _, icon = GetSpellInfo(trigger.spellId); return name, icon; end if (trigger.name) then return trigger.name, WeakAuras.GetDynamicIconCache(trigger.name); end else if (trigger.spellIds and trigger.spellIds[1]) then local name, _, icon = GetSpellInfo(trigger.spellIds[1]); return name, icon; end if (trigger.names and trigger.names[1]) then return trigger.names[1], WeakAuras.GetDynamicIconCache(trigger.names[1]); end end end local aura_scan_cache = {}; local aura_lists = {}; function WeakAuras.ScanAuras(unit) local time = GetTime(); -- Reset scan cache for this unit aura_scan_cache[unit] = aura_scan_cache[unit] or {}; for i,v in pairs(aura_scan_cache[unit]) do v.up_to_date = 0; end -- Make unit available outside local old_unit = WeakAuras.CurrentUnit; WeakAuras.CurrentUnit = unit; local fixedUnit = unit == "party0" and "player" or unit local uGUID = UnitGUID(fixedUnit) or fixedUnit; -- Link corresponding display (and aura cache) local aura_object; wipe(aura_lists); if(unit:sub(1, 4) == "raid") then if(aura_cache.players[uGUID]) then aura_lists[1] = loaded_auras["group"]; aura_object = aura_cache; end elseif(unit:sub(1, 5) == "party") then aura_lists[1] = loaded_auras["group"]; aura_object = aura_cache; elseif(specificBosses[unit]) then aura_lists[1] = loaded_auras["boss"]; elseif(unit:sub(1,5) == "arena") then aura_lists[1] = loaded_auras["arena"]; else if(unit == "player" and loaded_auras["group"]) then WeakAuras.ScanAuras("party0"); end aura_lists[1] = loaded_auras[unit]; end -- Add group auras for specific units -- XXX: why? if(specificUnits[unit] and not aura_object) then tinsert(aura_lists, loaded_auras["group"]); end -- Locals local cloneIdList; local groupcloneToUpdate; -- Units GUID unit = fixedUnit; -- Iterate over all displays (list of display lists) for _, aura_list in pairs(aura_lists) do -- Locals local name, icon, count, duration, expirationTime, unitCaster, isStealable, spellId = true; local tooltip, debuffClass, tooltipSize; local remaining, checkPassed; -- Iterate over all displays (display lists) for id,triggers in pairs(aura_list) do WeakAuras.StartProfileAura(id); -- Iterate over all triggers local updateTriggerState = false; for triggernum, data in pairs(triggers) do if(not data.specificUnit or UnitIsUnit(data.unit, unit)) then -- Filters local filter = data.debuffType..(data.ownOnly and "|PLAYER" or ""); local active = false; -- Full aura scan works differently if(data.fullscan) then -- Make sure scan cache exists aura_scan_cache[unit][filter] = aura_scan_cache[unit][filter] or {up_to_date = 0}; -- Reset clone list if cloneIdList then wipe(cloneIdList); end if(data.autoclone and not cloneIdList) then cloneIdList = {}; end -- Iterate over all units auras local index = 0; name = true; while(name) do -- Get nexted! index = index + 1; -- Update scan cache if(aura_scan_cache[unit][filter].up_to_date < index) then -- Query aura data name, _, icon, count, debuffClass, duration, expirationTime, unitCaster, isStealable, _, spellId = UnitAura(unit, index, filter); if (debuffClass == nil) then debuffClass = "none"; elseif (debuffClass == "") then debuffClass = "enrage" else debuffClass = string.lower(debuffClass); end local tooltipSize1, tooltipSize2, tooltipSize3; tooltip, _, tooltipSize1, tooltipSize2, tooltipSize3 = WeakAuras.GetAuraTooltipInfo(unit, index, filter); tooltipSize = {tooltipSize1, tooltipSize2, tooltipSize3} aura_scan_cache[unit][filter][index] = aura_scan_cache[unit][filter][index] or {}; -- Save aura data to cache local current_aura = aura_scan_cache[unit][filter][index]; current_aura.name = name; current_aura.icon = icon; current_aura.count = count; current_aura.duration = duration; current_aura.expirationTime = expirationTime; current_aura.isStealable = isStealable; current_aura.spellId = spellId; current_aura.tooltip = tooltip; current_aura.debuffClass = debuffClass; current_aura.tooltipSize = tooltipSize; current_aura.unitCaster = unitCaster; -- Updated aura_scan_cache[unit][filter].up_to_date = index; -- Use cached data instead else -- Fetch cached aura data local current_aura = aura_scan_cache[unit][filter][index]; name = current_aura.name; icon = current_aura.icon; count = current_aura.count; duration = current_aura.duration; expirationTime = current_aura.expirationTime; isStealable = current_aura.isStealable; spellId = current_aura.spellId; tooltip = current_aura.tooltip; debuffClass = current_aura.debuffClass; tooltipSize = current_aura.tooltipSize; if unitCaster ~= nil then unitCaster = current_aura.unitCaster else unitCaster = "Unknown" end end local casGUID = unitCaster and UnitGUID(unitCaster); -- Aura conforms to trigger options? if(data.subcount) then local index = data.subcountCount or 1; count = tooltipSize[index]; end if(name and ((not data.count) or count and data.count(count)) and (data.ownOnly ~= false or not UnitIsUnit("player", unitCaster or "")) and data.scanFunc(name, tooltip, isStealable, spellId, debuffClass)) then remaining = expirationTime - time; checkPassed = true; if(data.remFunc) then if not(data.remFunc(remaining)) then checkPassed = false; end -- Schedule remaining time, re-scan later if(remaining > data.rem) then WeakAuras.ScheduleAuraScan(unit, time + (remaining - data.rem)); end end if checkPassed then -- Show display and handle clones WeakAuras.SetDynamicIconCache(name, spellId, icon); if(data.autoclone) then local cloneId = name .. spellId .."-"..(casGUID or "unknown"); if (WeakAuras.SetAuraVisibility(id, triggernum, cloneId, data.buffShowOn, data.unitExists, true, unit, duration, expirationTime, name, icon, count, index, spellId, unitCaster)) then updateTriggerState = true; end active = true; cloneIdList[cloneId] = true; -- Simply show display (show) else if (WeakAuras.SetAuraVisibility(id, triggernum, nil, data.buffShowOn, data.unitExists, true, unit, duration, expirationTime, name, icon, count, index, spellId, unitCaster)) then updateTriggerState = true; end active = true; break; end end end end -- Update display visibility and clones visibility (hide) if not(active) then local nameFromTrigger, iconFromTrigger; nameFromTrigger, iconFromTrigger = GetNameAndIconFromTrigger(data); if (WeakAuras.SetAuraVisibility(id, triggernum, nil, data.buffShowOn, data.unitExists, nil, unit, 0, math.huge, nameFromTrigger, iconFromTrigger)) then updateTriggerState = true; end end if(data.autoclone) then WeakAuras.SetAllStatesHiddenExcept(id, triggernum, cloneIdList); updateTriggerState = true; end -- Not using full aura scan else -- Reset clone list if groupcloneToUpdate then wipe(groupcloneToUpdate); end if(aura_object and data.groupclone and not data.specificUnit and not groupcloneToUpdate) then groupcloneToUpdate = {}; end -- Check all selected auras (for one trigger) local bestDuration, bestExpirationTime, bestName, bestIcon, bestCount, bestCasGUID, bestSpellId, bestUnitCaster; for index, checkname in pairs(data.names) do -- Fetch aura data local detected for i = 1, BUFF_MAX_DISPLAY do name, _, icon, count, _, duration, expirationTime, unitCaster, isStealable, _, spellId = UnitAura(unit, i, filter); if not name then break end if name == checkname then detected = true break end end if (detected) then WeakAuras.SetDynamicIconCache(name, spellId, icon); end checkPassed = false; -- Aura conforms to trigger options? if(name and ((not data.count) or data.count(count)) and (data.ownOnly ~= false or not UnitIsUnit("player", unitCaster or ""))) then remaining = expirationTime - time; checkPassed = true; if(data.remFunc) then if not(data.remFunc(remaining)) then checkPassed = false; end -- Schedule remaining time, re-scan later if(remaining > data.rem) then WeakAuras.ScheduleAuraScan(unit, time + (remaining - data.rem)); end end end local casGUID = unitCaster and UnitGUID(unitCaster); -- Aura conforms to trigger if(checkPassed) then active = true; if (not bestExpirationTime or expirationTime > bestExpirationTime) then bestDuration = duration; bestExpirationTime = expirationTime; bestName = name; bestIcon = icon; bestCount = count; bestCasGUID = casGUID; bestSpellId = spellId; bestUnitCaster = unitCaster; end end end local satisfies_ignoreSelf = not data.ignoreSelf or not UnitIsUnit(unit, "player") if not satisfies_ignoreSelf then active = false end -- Update aura cache (and clones) if (active) then if(aura_object and not data.specificUnit) then aura_object:AssertAura(id, triggernum, uGUID, bestDuration, bestExpirationTime, bestName, bestIcon, bestCount, bestCasGUID, bestSpellId, bestUnitCaster); if(data.groupclone) then groupcloneToUpdate[uGUID] = GetUnitName(unit, true); end else if (WeakAuras.SetAuraVisibility(id, triggernum, nil, data.buffShowOn, data.unitExists, true, unit, bestDuration, bestExpirationTime, bestName, bestIcon, bestCount, nil, bestSpellId, bestUnitCaster)) then updateTriggerState = true; end end else if(aura_object and not data.specificUnit) then -- Update aura cache (and clones) aura_object:DeassertAura(id, triggernum, uGUID); if(data.groupclone) then groupcloneToUpdate[uGUID] = GetUnitName(unit, true); end else local nameFromTrigger, iconFromTrigger; nameFromTrigger, iconFromTrigger = GetNameAndIconFromTrigger(data); if (WeakAuras.SetAuraVisibility(id, triggernum, nil, data.buffShowOn, data.unitExists, nil, unit, 0, math.huge, nameFromTrigger, iconFromTrigger)) then updateTriggerState = true; end end end -- Processing a unit=group related unit if(aura_object and not data.specificUnit) then -- unit=group require valid count function if(data.group_count) then -- Query count from aura cache local aura_count = aura_object:GetNumber(id, triggernum, data) local max = aura_object:GetMaxNumber(); if (data.ignoreSelf) then max = max - 1; end local satisfies_count = data.group_count(aura_count, max); if(data.hideAlone and not IsInGroup()) then satisfies_count = false; end -- Satisfying count condition if(satisfies_count) then -- Update clones (show) if(data.groupclone) then for guid, playerName in pairs(aura_cache.players) do local duration, expirationTime, name, icon, count, spellId, unitCaster = aura_object:GetPlayerDynamicInfo(id, triggernum, guid, data); if(name ~= "") then if (WeakAuras.SetAuraVisibility(id, triggernum, playerName, data.buffShowOn, data.unitExists, true, unit, duration, expirationTime, playerName, icon, count, nil, spellId, unitCaster)) then updateTriggerState = true; end else if (WeakAuras.SetAuraVisibility(id, triggernum, playerName, data.buffShowOn, data.unitExists, nil, unit, duration, expirationTime, playerName, icon, count, nil, spellId, unitCaster)) then updateTriggerState = true; end end end -- Update display information else -- Get display related information local duration, expirationTime, name, icon, count, spellId, unitCaster = aura_object:GetDynamicInfo(id, triggernum, data); -- Process affected players if(data.name_info == "players") then local affected = aura_object:GetAffected(id, triggernum, data); local num = 0; name = ""; for affected_name, _ in pairs(affected) do local space = affected_name:find(" "); name = name..(space and affected_name:sub(1, space - 1).."*" or affected_name)..", "; num = num + 1; end if(num == 0) then name = WeakAuras.L["None"]; else name = name:sub(1, -3); end -- Process unaffected players elseif(data.name_info == "nonplayers") then local unaffected = aura_object:GetUnaffected(id, triggernum, data); local num = 0; name = ""; for unaffected_name, _ in pairs(unaffected) do local space = unaffected_name:find(" "); name = name..(space and unaffected_name:sub(1, space - 1).."*" or unaffected_name)..", "; num = num + 1; end if(num == 0) then name = WeakAuras.L["None"]; else name = name:sub(1, -3); end end -- Process stacks/aura count if(data.stack_info == "count") then count = aura_count; end -- Update display visibility (show) if (WeakAuras.SetAuraVisibility(id, triggernum, nil, data.buffShowOn, data.unitExists, true, unit, duration, expirationTime, name, icon, count, nil, spellId, unitCaster)) then updateTriggerState = true; end end -- Not satisfying count else -- Update clones if(data.groupclone) then WeakAuras.SetAllStatesHidden(id, triggernum); updateTriggerState = true; -- Update display visibility (hide) else local nameFromTrigger, iconFromTrigger; nameFromTrigger, iconFromTrigger = GetNameAndIconFromTrigger(data); if (WeakAuras.SetAuraVisibility(id, triggernum, nil, data.buffShowOn, data.unitExists, nil, unit, 0, math.huge, nameFromTrigger, iconFromTrigger)) then updateTriggerState = true; end end end end end end end end if (updateTriggerState) then WeakAuras.UpdatedTriggerState(id); end WeakAuras.StopProfileAura(id); end end -- Update current unit once again WeakAuras.CurrentUnit = old_unit; end function WeakAuras.ScanAurasGroup() if IsInRaid() then for i=1, GetNumGroupMembers() do WeakAuras.ScanAuras(WeakAuras.raidUnits[i]) end elseif IsInGroup() then for i=1, GetNumSubgroupMembers() do WeakAuras.ScanAuras(WeakAuras.partyUnits[i]) end WeakAuras.ScanAuras("player") else WeakAuras.ScanAuras("player") end end local function GroupRosterUpdate(event) WeakAuras.StartProfileSystem("bufftrigger"); aura_cache.TANK = 0; aura_cache.HEALER = 0; aura_cache.DAMAGER = 0; aura_cache.playerRole = "NONE"; local recheck = false; local groupMembers,playerName,uid,guid, role = {}; if IsInRaid() then for i=1, GetNumGroupMembers() do uid = WeakAuras.raidUnits[i]; role = UnitGroupRolesAssigned(uid) if aura_cache[role] then aura_cache[role] = aura_cache[role] + 1; end playerName = GetUnitName(uid,true); if playerName then playerName = playerName:gsub("-", " - "); else recheck = true; end if (playerName == UNKNOWNOBJECT) then recheck = true; end guid = UnitGUID(uid); if (guid) then groupMembers[guid] = playerName; end end role = UnitGroupRolesAssigned("player") aura_cache.playerRole = role; elseif IsInGroup() then for i=1, GetNumSubgroupMembers() do uid = WeakAuras.partyUnits[i]; role = UnitGroupRolesAssigned(uid) if (aura_cache[role]) then aura_cache[role] = aura_cache[role] + 1; end guid = UnitGUID(uid); local playerName = GetUnitName(uid,true); if (playerName == UNKNOWNOBJECT) then recheck = true; end if (guid) then groupMembers[guid] = playerName; end end role = UnitGroupRolesAssigned("player") if (aura_cache[role]) then aura_cache[role] = aura_cache[role] + 1; aura_cache.playerRole = role; end end if (not WeakAuras.myGUID) then WeakAuras.myGUID = UnitGUID("player") end groupMembers[WeakAuras.myGUID] = WeakAuras.me; aura_cache:AssertMemberList(groupMembers); if (recheck) then timer:ScheduleTimer(GroupRosterUpdate, 0.5); end WeakAuras.StopProfileSystem("bufftrigger"); end local groupFrame = CreateFrame("FRAME"); WeakAuras.frames["Group Makeup Handler"] = groupFrame; groupFrame:RegisterEvent("PARTY_MEMBERS_CHANGED"); groupFrame:RegisterEvent("RAID_ROSTER_UPDATE"); groupFrame:RegisterEvent("PLAYER_ENTERING_WORLD"); groupFrame:SetScript("OnEvent", function(self, event) GroupRosterUpdate(); end); do local pendingTracks = {}; local UIDsfromGUID = {}; local GUIDfromUID = {}; function WeakAuras.ReleaseUID(UID) if(GUIDfromUID[UID]) then if(UIDsfromGUID[GUIDfromUID[UID]] and UIDsfromGUID[GUIDfromUID[UID]][UID]) then UIDsfromGUID[GUIDfromUID[UID]][UID] = nil; else -- If this code is reached, it means there was some kind of coordination error between the two lists -- This shouldn't ever happen, but it is recoverable -- Search through the whole UIDsfromGUID table and remove all instances of UID for GUID,UIDs in pairs(UIDsfromGUID) do for iUID,v in pairs(UIDs) do if(iUID == UID or iUID == UID) then UIDs[iUID] = nil; end end end end end GUIDfromUID[UID] = nil; end function WeakAuras.SetUID(GUID, UID) WeakAuras.ReleaseUID(UID); if not(UIDsfromGUID[GUID]) then UIDsfromGUID[GUID] = {}; end UIDsfromGUID[GUID][UID] = true; GUIDfromUID[UID] = GUID; end function WeakAuras.GetUID(GUID) if not(UIDsfromGUID[GUID]) then return nil; end -- iterate through key/value pairs from the table of UIDs that are registered for this GUID, until a *confirmed* match is found -- confirming is necessary in case UIDs are not always released correctly (which may actually be close to impossible) for returnUID,v in pairs(UIDsfromGUID[GUID]) do -- check the validity of this entry if(UnitGUID(returnUID) == GUID) then return returnUID; else WeakAuras.ReleaseUID(returnUID); end end return nil; end --- Updates region data to see if states changed. -- @param id -- @param data -- @param triggernum -- @param GUID -- @return boolean local function updateRegion(id, data, triggernum, GUID) local auradata = data.GUIDs[GUID]; local triggerState = WeakAuras.GetTriggerStateForTrigger(id, triggernum); triggerState[GUID] = triggerState[GUID] or {}; local state = triggerState[GUID]; if (state.progressType ~= "timed") then state.progressType = "timed"; state.changed = true; end if(auradata and auradata.unitName) then if (state.show ~= true) then state.show = true; state.changed = true; end if (state.expirationTime ~= auradata.expirationTime) then state.expirationTime = auradata.expirationTime; state.changed = true; end if (state.duration ~= auradata.duration) then state.duration = auradata.duration; state.changed = true; end if (state.autoHide ~= true) then state.autoHide = true; state.changed = true; end if (state.name ~= auradata.unitName) then state.name = auradata.unitName; state.changed = true; end local icon = auradata.icon or WeakAuras.GetDynamicIconCache(auradata.name) or "Interface\\Icons\\INV_Misc_QuestionMark"; if (state.icon ~= icon) then state.icon = icon; state.changed = true; end if (state.stacks ~= auradata.count) then state.stacks = auradata.count; state.changed = true; end if (state.unitCaster ~= auradata.unitCaster) then state.unitCaster = auradata.unitCaster; state.changed = true; end if (state.GUID ~= GUID) then state.GUID = GUID; state.changed = true; end else if (state.show ~= false) then state.show = false; state.changed = true; end end if (state.changed) then return true; end return false; end local function updateSpell(spellName, unit, destGUID) if (not loaded_auras[spellName]) then return end; for id, triggers in pairs(loaded_auras[spellName]) do WeakAuras.StartProfileAura(id); local updateTriggerState = false; for triggernum, data in pairs(triggers) do local filter = data.debuffType..(data.ownOnly and "|PLAYER" or ""); local detected local name, icon, count, duration, expirationTime, unitCaster, spellId, _ for i = 1, BUFF_MAX_DISPLAY do name, _, icon, count, _, duration, expirationTime, unitCaster, _, _, spellId = UnitAura(unit, i, filter); if not name then break end if name == spellName then detected = true break end end if(detected and (data.spellId == nil or data.spellId == spellId)) then data.GUIDs = data.GUIDs or {}; data.GUIDs[destGUID] = data.GUIDs[destGUID] or {}; data.GUIDs[destGUID].name = spellName; data.GUIDs[destGUID].unitName = GetUnitName(unit, true); data.GUIDs[destGUID].duration = duration; data.GUIDs[destGUID].expirationTime = expirationTime; data.GUIDs[destGUID].icon = icon; data.GUIDs[destGUID].count = count; data.GUIDs[destGUID].unitCaster = unitCaster and UnitName(unitCaster); data.GUIDs[destGUID].spellId = spellId; updateTriggerState = updateRegion(id, data, triggernum, destGUID) or updateTriggerState; end end if (updateTriggerState) then WeakAuras.UpdatedTriggerState(id); end WeakAuras.StopProfileAura(id); end end local function combatLog(_, event, _, sourceName, _, destGUID, destName, _, spellId, spellName, _, auraType, amount) if(loaded_auras[spellName]) then if(message == "SPELL_AURA_APPLIED" or message == "SPELL_AURA_REFRESH" or message == "SPELL_AURA_APPLIED_DOSE" or message == "SPELL_AURA_REMOVED_DOSE") then local unit = WeakAuras.GetUID(destGUID); if(unit) then updateSpell(spellName, unit, destGUID); else for id, triggers in pairs(loaded_auras[spellName]) do WeakAuras.StartProfileAura(id); local updateTriggerState = false; for triggernum, data in pairs(triggers) do if((not data.ownOnly) or UnitIsUnit(sourceName or "", "player")) then pendingTracks[destGUID] = pendingTracks[destGUID] or {}; pendingTracks[destGUID][spellName] = true; data.GUIDs = data.GUIDs or {}; data.GUIDs[destGUID] = data.GUIDs[destGUID] or {}; data.GUIDs[destGUID].name = spellName; data.GUIDs[destGUID].unitName = destName; local icon = spellId and select(3, GetSpellInfo(spellId)); if (message == "SPELL_AURA_APPLIED_DOSE" or message == "SPELL_AURA_REMOVED_DOSE") then -- Shouldn't affect duration/expirationTime nor icon data.GUIDs[destGUID].duration = data.GUIDs[destGUID].duration or 0; data.GUIDs[destGUID].expirationTime = data.GUIDs[destGUID].expirationTime or math.huge; data.GUIDs[destGUID].icon = data.GUIDs[destGUID].icon or icon; else data.GUIDs[destGUID].duration = 0; data.GUIDs[destGUID].expirationTime = math.huge; data.GUIDs[destGUID].icon = icon; end data.GUIDs[destGUID].count = amount or 0; data.GUIDs[destGUID].spellId = spellId; data.GUIDs[destGUID].unitCaster = sourceName and UnitName(sourceName); updateTriggerState = updateRegion(id, data, triggernum, destGUID) or updateTriggerState; end end if (updateTriggerState) then WeakAuras.UpdatedTriggerState(id); end WeakAuras.StopProfileAura(id); end end elseif(message == "SPELL_AURA_REMOVED") then for id, triggers in pairs(loaded_auras[spellName]) do local updateTriggerState = false; for triggernum, data in pairs(triggers) do if((not data.ownOnly) or UnitIsUnit(sourceName or "", "player")) then -- WeakAuras.debug("Removed "..spellName.." from "..destGUID.." ("..(data.GUIDs and data.GUIDs[destGUID] and data.GUIDs[destGUID].unitName or "error")..") - "..(data.ownOnly and "own only" or "not own only")..", "..sourceName, 3); data.GUIDs = data.GUIDs or {}; data.GUIDs[destGUID] = nil; updateTriggerState = updateRegion(id, data, triggernum, destGUID) or updateTriggerState; end end if (updateTriggerState) then WeakAuras.UpdatedTriggerState(id); end end end end end local function uidTrack(unit) local GUID = UnitGUID(unit); if(GUID) then WeakAuras.SetUID(GUID, unit); if(pendingTracks[GUID]) then for spellName,_ in pairs(pendingTracks[GUID]) do updateSpell(spellName, unit, GUID); pendingTracks[GUID][spellName] = nil; end end else WeakAuras.ReleaseUID(unit); end unit = unit.."target"; GUID = UnitGUID(unit); if(GUID) then WeakAuras.SetUID(GUID, unit); if(pendingTracks[GUID]) then for spellName,_ in pairs(pendingTracks[GUID]) do updateSpell(spellName, unit, GUID); pendingTracks[GUID][spellName] = nil; end end else WeakAuras.ReleaseUID(unit); end end local function checkExists() WeakAuras.StartProfileSystem("bufftrigger - multi"); for unit, auras in pairs(loaded_auras) do if not(WeakAuras.unit_types[unit]) then for id, triggers in pairs(auras) do WeakAuras.StartProfileAura(id); local updateTriggerState = false; for triggernum, data in pairs(triggers) do if(data.GUIDs) then for GUID, GUIDData in pairs(data.GUIDs) do if(GUIDData.expirationTime and GUIDData.expirationTime + 2 < GetTime()) then data.GUIDs[GUID] = nil; updateTriggerState = updateRegion(id, data, triggernum, GUID) or updateTriggerState; end end end end if (updateTriggerState) then WeakAuras.UpdatedTriggerState(id); end WeakAuras.StopProfileAura(id); end end end WeakAuras.StopProfileSystem("bufftrigger - multi"); end local function handleEvent(frame, event, ...) WeakAuras.StartProfileSystem("bufftrigger - multi"); if(event == "COMBAT_LOG_EVENT_UNFILTERED") then combatLog(...); elseif(event == "UNIT_TARGET") then uidTrack(...); elseif(event == "PLAYER_FOCUS_CHANGED") then uidTrack("focus"); elseif(event == "NAME_PLATE_UNIT_ADDED") then uidTrack(...); elseif(event == "NAME_PLATE_UNIT_REMOVED") then local unit = ... WeakAuras.ReleaseUID(unit); unit = unit.."target"; WeakAuras.ReleaseUID(unit); elseif(event == "UNIT_AURA") then -- Note: Using UNIT_AURA in addition to COMBAT_LOG_EVENT_UNFILTERED, -- because the combat log event does not contain duration information local uid = ...; if not uid then return end local guid = UnitGUID(uid); for spellName, auras in pairs(loaded_auras) do if not(WeakAuras.unit_types[spellName]) then for id, triggers in pairs(auras) do WeakAuras.StartProfileAura(id); local updateTriggerState = false; for triggernum, data in pairs(triggers) do local filter = data.debuffType..(data.ownOnly and "|PLAYER" or ""); local detected local name, icon, count, duration, expirationTime, unitCaster, _ for i = 1, BUFF_MAX_DISPLAY do name, _, icon, count, _, duration, expirationTime, unitCaster = UnitAura(uid, i, filter); if not name then break end if name == spellName then detected = true break end end if(detected) then data.GUIDs = data.GUIDs or {}; data.GUIDs[guid] = data.GUIDs[guid] or {}; data.GUIDs[guid].name = spellName; data.GUIDs[guid].unitName = GetUnitName(uid, true); data.GUIDs[guid].duration = duration; data.GUIDs[guid].expirationTime = expirationTime; data.GUIDs[guid].icon = icon; data.GUIDs[guid].count = count; data.GUIDs[guid].unitCaster = unitCaster and UnitName(unitCaster); updateTriggerState = updateRegion(id, data, triggernum, guid) or updateTriggerState; end end if (updateTriggerState) then WeakAuras.UpdatedTriggerState(id); end WeakAuras.StopProfileAura(id); end end end end WeakAuras.StopProfileSystem("bufftrigger - multi"); end local combatAuraFrame; function WeakAuras.InitMultiAura() if not(combatAuraFrame) then combatAuraFrame = CreateFrame("frame"); combatAuraFrame:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED"); combatAuraFrame:RegisterEvent("UNIT_TARGET"); combatAuraFrame:RegisterEvent("UNIT_AURA"); combatAuraFrame:RegisterEvent("PLAYER_FOCUS_CHANGED"); combatAuraFrame:RegisterEvent("NAME_PLATE_UNIT_ADDED"); combatAuraFrame:RegisterEvent("NAME_PLATE_UNIT_REMOVED"); combatAuraFrame:RegisterEvent("PLAYER_LEAVING_WORLD"); combatAuraFrame:SetScript("OnEvent", handleEvent); WeakAuras.frames["Multi-target Aura Trigger Handler"] = combatAuraFrame; timer:ScheduleRepeatingTimer(checkExists, 10) end end end do local scheduled_scans = {}; function WeakAuras.ScheduleAuraScan(unit, fireTime) scheduled_scans[unit] = scheduled_scans[unit] or {}; if not(scheduled_scans[unit][fireTime]) then WeakAuras.debug("Scheduled aura scan for "..unit.." at "..fireTime); local doScan = function() WeakAuras.debug("Performing aura scan for "..unit.." at "..fireTime.." ("..GetTime()..")"); scheduled_scans[unit][fireTime] = nil; WeakAuras.ScanAuras(unit); end scheduled_scans[unit][fireTime] = timer:ScheduleTimerFixed(doScan, fireTime - GetTime() + 0.1); end end end --- Adds auras to the loaded_auras table -- @param id -- @param triggernum -- @param data local function LoadAura(id, triggernum, data) local unit; if(data.specificUnit) then if(data.unit:lower():sub(1,4) == "boss") then specificBosses[data.unit] = true; unit = "boss"; elseif(data.unit:lower():sub(1,5) == "arena") then unit = "arena"; else specificUnits[data.unit] = true; unit = "group"; end elseif(data.unit == "multi") then unit = data.name else unit = data.unit; end if(unit) then loaded_auras[unit] = loaded_auras[unit] or {}; loaded_auras[unit][id] = loaded_auras[unit][id] or {}; loaded_auras[unit][id][triggernum] = data; end end function BuffTrigger.ScanAll() local unitIdstoScan = {}; local groupScan = false; for unit, auras in pairs(loaded_auras) do if(unit == "group") then groupScan = true; elseif(WeakAuras.unit_types[unit]) then unitIdstoScan[unit] = true; end end if (groupScan) then WeakAuras.ScanAurasGroup(); end for unit, _ in pairs(unitIdstoScan) do WeakAuras.ScanAuras(unit); end end local aura_scan_cooldowns = {}; local checkingScanCooldowns; local scanCooldownFrame = CreateFrame("frame"); WeakAuras.frames["Aura Scan Cooldown"] = scanCooldownFrame; local checkScanCooldownsFunc = function() WeakAuras.StartProfileSystem("bufftrigger") for unit,_ in pairs(aura_scan_cooldowns) do aura_scan_cooldowns[unit] = nil; WeakAuras.ScanAuras(unit); end checkingScanCooldowns = nil; scanCooldownFrame:SetScript("OnUpdate", nil); WeakAuras.StopProfileSystem("bufftrigger") end local frame = CreateFrame("FRAME"); WeakAuras.frames["WeakAuras Buff Frame"] = frame; frame:RegisterEvent("PLAYER_ENTERING_WORLD"); frame:RegisterEvent("PLAYER_FOCUS_CHANGED"); frame:RegisterEvent("PLAYER_TARGET_CHANGED"); frame:RegisterEvent("INSTANCE_ENCOUNTER_ENGAGE_UNIT"); frame:RegisterEvent("UNIT_AURA"); frame:RegisterEvent("UNIT_PET") frame:SetScript("OnEvent", function (frame, event, arg1, arg2, ...) if (WeakAuras.IsPaused()) then return end; WeakAuras.StartProfileSystem("bufftrigger"); if (event == "PLAYER_ENTERING_WORLD") then BuffTrigger.ScanAll(); elseif(event == "PLAYER_TARGET_CHANGED") then WeakAuras.ScanAuras("target"); elseif(event == "PLAYER_FOCUS_CHANGED") then WeakAuras.ScanAuras("focus"); elseif(event == "UNIT_PET" and arg1 == "player") then WeakAuras.ScanAuras("pet"); elseif(event == "INSTANCE_ENCOUNTER_ENGAGE_UNIT") then for unit,_ in pairs(specificBosses) do WeakAuras.ScanAuras(unit); end elseif(event == "UNIT_AURA") then if not arg1 then return end if( loaded_auras[arg1] or ( loaded_auras["group"] and ( arg1:sub(1, 4) == "raid" or arg1:sub(1, 5) == "party" or arg1 == "player" ) ) or ( loaded_auras["boss"] and arg1:sub(1,4) == "boss" ) or ( loaded_auras["arena"] and arg1:sub(1,5) == "arena" ) ) then -- This throttles aura scans to only happen at most once per frame per unit if not(aura_scan_cooldowns[arg1]) then aura_scan_cooldowns[arg1] = true; if not(checkingScanCooldowns) then checkingScanCooldowns = true; scanCooldownFrame:SetScript("OnUpdate", checkScanCooldownsFunc); end end end end WeakAuras.StopProfileSystem("bufftrigger"); end); function BuffTrigger.UnloadAll() wipe(loaded_auras); end function BuffTrigger.LoadDisplays(toLoad) for id in pairs(toLoad) do if(auras[id]) then for triggernum, data in pairs(auras[id]) do if(auras[id] and auras[id][triggernum]) then LoadAura(id, triggernum, data); end end end end end function BuffTrigger.UnloadDisplays(toUnload) for id in pairs(toUnload) do for unitname, auras in pairs(loaded_auras) do auras[id] = nil; end end end function BuffTrigger.FinishLoadUnload() BuffTrigger.ScanAll(); end --- Removes all data for an aura id -- @param id function BuffTrigger.Delete(id) auras[id] = nil; for i,v in pairs(loaded_auras) do v[id] = nil; end end --- Updates all data for aura oldid to use newid -- @param oldid -- @param newid function BuffTrigger.Rename(oldid, newid) auras[newid] = auras[oldid]; auras[oldid] = nil; aura_cache:Rename(oldid, newid); for i,v in pairs(loaded_auras) do v[newid] = v[oldid]; v[newid] = nil; end end --- Adds an aura, setting up internal data structures for all buff triggers. -- @param data function BuffTrigger.Add(data) local id = data.id; auras[id] = nil; for triggernum, triggerData in ipairs(data.triggers) do local trigger, untrigger = triggerData.trigger, triggerData.untrigger local triggerType; if(type(trigger) == "table") then triggerType = trigger.type; if(triggerType == "aura") then trigger.names = trigger.names or {}; trigger.spellIds = trigger.spellIds or {} trigger.unit = trigger.unit or "player"; trigger.debuffType = trigger.debuffType or "HELPFUL"; local countFunc, countFuncStr; if(trigger.useCount) then countFuncStr = function_strings.count:format(trigger.countOperator or ">=", tonumber(trigger.count) or 0); countFunc = WeakAuras.LoadFunction(countFuncStr); end local remFunc, remFuncStr; if(trigger.useRem) then remFuncStr = function_strings.count:format(trigger.remOperator or ">=", tonumber(trigger.rem) or 0); remFunc = WeakAuras.LoadFunction(remFuncStr); end local group_countFunc, group_countFuncStr; if(trigger.unit == "group") then local count, countType = WeakAuras.ParseNumber(trigger.group_count); if(trigger.group_countOperator and count and countType) then if(countType == "whole") then group_countFuncStr = function_strings.count:format(trigger.group_countOperator, count); else group_countFuncStr = function_strings.count_fraction:format(trigger.group_countOperator, count); end else group_countFuncStr = function_strings.count:format(">", 0); end group_countFunc = WeakAuras.LoadFunction(group_countFuncStr); WeakAuras.aura_cache:Watch(id, triggernum); end local scanFunc; if(trigger.fullscan) then scanFunc = function(name, tooltip, isStealable, spellId, debuffClass) if ( ( (not trigger.use_name) or ( trigger.name and trigger.name ~= "" and ( trigger.name_operator == "==" and name == trigger.name or trigger.name_operator == "find('%s')" and name:find(trigger.name) or trigger.name_operator == "match('%s')" and name:match(trigger.name) ) ) ) and ( (not trigger.use_tooltip) or ( trigger.tooltip and trigger.tooltip ~= "" and ( trigger.tooltip_operator == "==" and tooltip == trigger.tooltip or trigger.tooltip_operator == "find('%s')" and tooltip:find(trigger.tooltip) or trigger.tooltip_operator == "match('%s')" and tooltip:match(trigger.tooltip) ) ) ) and ((not trigger.use_stealable) or isStealable) and ((not trigger.use_spellId) or spellId == tonumber(trigger.spellId)) and ((not trigger.use_debuffClass) or debuffClass == trigger.debuffClass) ) then return true; else return false; end end -- end scanFunc end if(trigger.unit == "multi") then WeakAuras.InitMultiAura(); end if (trigger.buffShowOn == nil) then trigger.buffShowOn = "showOnActive"; end local buffShowOn = "showOnActive"; local unitExists = true; if (not(trigger.unit ~= "group" and trigger.autoclone) and trigger.unit ~= "multi" and trigger.unit ~= "group" and trigger.unit ~= "player") then unitExists = trigger.unitExists; end if (trigger.type == "aura" and not(trigger.unit ~= "group" and trigger.autoclone) and trigger.unit ~= "multi" and not(trigger.unit == "group" and not trigger.groupclone)) then buffShowOn = trigger.buffShowOn; end auras[id] = auras[id] or {}; auras[id][triggernum] = { count = countFunc, remFunc = remFunc, rem = tonumber(trigger.rem) or 0, group_count = group_countFunc, fullscan = trigger.fullscan, autoclone = trigger.autoclone, groupclone = trigger.groupclone, subcount = trigger.subcount, subcountCount = trigger.subcountCount, scanFunc = scanFunc, debuffType = trigger.debuffType, names = trigger.names, spellIds = trigger.spellIds, name = trigger.name, spellId = trigger.spellId, unit = trigger.unit == "member" and trigger.specificUnit or trigger.unit, specificUnit = trigger.unit == "member", useCount = trigger.useCount, ownOnly = trigger.ownOnly, buffShowOn = buffShowOn, unitExists = unitExists, numAdditionalTriggers = max(#data.triggers - 1, 0), hideAlone = trigger.hideAlone, stack_info = trigger.stack_info, name_info = trigger.name_info, ignoreSelf = trigger.ignoreSelf }; end end end end --- Updates old data to the new format. -- @param data function BuffTrigger.Modernize(data) for triggernum, triggerData in ipairs(data.triggers) do local trigger = triggerData.trigger; if (data.internalVersion < 2) then if (trigger and trigger.type == "aura") then if (trigger.showOn == nil or trigger.showOn == "showOnCooldown" or trigger.showOn == "showOnReady" or trigger.showOn == "showAlways") then trigger.showOn = trigger.inverse and "showOnMissing" or "showOnActive"; trigger.inverse = nil; end end end if data.internalVersion < 6 then if trigger and trigger.type == "aura" then if trigger.showOn == "showOnMissing" then trigger.buffShowOn = "showOnMissing" elseif trigger.showOn == "showActiveOrMissing" then trigger.buffShowOn = "showAlways" else trigger.buffShowOn = "showOnActive" end trigger.showOn = nil end end end end --- Returns whether the first trigger could be shown without any affected group members. -- @param data -- @param triggernum -- @return boolean local function CanGroupShowWithZero(data, triggernum) local trigger = data.triggers[triggernum].trigger local group_countFunc, group_countFuncStr; if(trigger.unit == "group") then local count, countType = WeakAuras.ParseNumber(trigger.group_count); if(trigger.group_countOperator and count and countType) then if(countType == "whole") then group_countFuncStr = function_strings.count:format(trigger.group_countOperator, count); else group_countFuncStr = function_strings.count_fraction:format(trigger.group_countOperator, count); end else group_countFuncStr = function_strings.count:format(">", 0); end group_countFunc = WeakAuras.LoadFunction(group_countFuncStr); return group_countFunc(0, 1); else return false; end end --- Returns whether the trigger can have a duration. -- @param data -- @param triggernum function BuffTrigger.CanHaveDuration(data, triggernum) local trigger = data.triggers[triggernum].trigger if (trigger.type == "aura" and not(trigger.unit ~= "group" and trigger.autoclone) and trigger.unit ~= "multi" and not(trigger.unit == "group" and not trigger.groupclone)) then if (trigger.buffShowOn ~= "showOnMissing") then return "timed"; else return false; end end return "timed"; end --- Returns a table containing the names of all overlays -- @param data -- @param triggernum function BuffTrigger.GetOverlayInfo(data, triggernum) return {}; end --- Returns whether the icon can be automatically selected. -- @param data -- @param triggernum -- @return boolean function BuffTrigger.CanHaveAuto(data, triggernum) return true; end --- Returns whether the trigger can have clones. -- @param data -- @param triggernum -- @return function BuffTrigger.CanHaveClones(data, triggernum) local trigger = data.triggers[triggernum].trigger return (trigger.fullscan and trigger.autoclone) or (trigger.unit == "group" and trigger.groupclone) or (trigger.unit == "multi"); end ---Returns the type of tooltip to show for the trigger. -- @param data -- @param triggernum -- @return string function BuffTrigger.CanHaveTooltip(data, triggernum) local trigger = data.triggers[triggernum].trigger if(trigger.unit == "group" and trigger.name_info ~= "aura" and not trigger.groupclone) then return "playerlist"; elseif(trigger.fullscan and trigger.unit ~= "group") then return "auraindex"; else return "aura"; end end function BuffTrigger.SetToolTip(trigger, state) local data = auras[state.id][state.triggernum]; if(trigger.unit == "group" and trigger.name_info ~= "aura" and not trigger.groupclone) then local name = ""; local playerList; if(trigger.name_info == "players") then playerList = WeakAuras.aura_cache:GetAffected(state.id, state.triggernum, data); name = L["Affected"]..":"; elseif(trigger.name_info == "nonplayers") then playerList = WeakAuras.aura_cache:GetUnaffected(state.id, state.triggernum, data); name = L["Missing"]..":"; else playerList = {}; end local numPlayers = 0; for playerName, _ in pairs(playerList) do numPlayers = numPlayers + 1; end if(numPlayers > 0) then GameTooltip:AddLine(name); local numRaid = IsInRaid() and GetNumGroupMembers() or 0; local groupMembers,playersString = {}; if(numRaid > 0) then local playerName, _, subgroup for i = 1,numRaid do -- Battleground-name, given by GetRaidRosterInfo (name-server) to GetUnitName(...) (name - server) transition playerName, _, subgroup = GetRaidRosterInfo(i); if(playerName) then playerName = playerName:gsub("-", " - ") if (playerList[playerName]) then groupMembers[subgroup] = groupMembers[subgroup] or {}; groupMembers[subgroup][playerName] = true end end end for subgroup, players in pairs(groupMembers) do playersString = L["Group %s"]:format(subgroup)..": "; local _,space,class,classColor; for playerName, _ in pairs(players) do space = playerName:find(" "); _, class = UnitClass((space and playerName:sub(1, space - 1) or playerName)); classColor = WeakAuras.class_color_types[class]; playersString = playersString..(classColor or "")..(space and playerName:sub(1, space - 1).."*" or playerName)..(classColor and "|r" or "")..(next(players, playerName) and ", " or ""); end GameTooltip:AddLine(playersString); end else local num = 0; playersString = ""; local _,space,class,classColor; for playerName, _ in pairs(playerList) do space = playerName:find(" "); _, class = UnitClass((space and playerName:sub(1, space - 1) or playerName)); classColor = WeakAuras.class_color_types[class]; playersString = playersString..(classColor or "")..(space and playerName:sub(1, space - 1).."*" or playerName)..(classColor and "|r" or "")..(next(playerList, playerName) and (", "..(num % 5 == 4 and "\n" or "")) or ""); num = num + 1; end GameTooltip:AddLine(playersString); end else GameTooltip:AddLine(name.." "..L["None"]); end return true elseif(trigger.fullscan and trigger.unit ~= "group" and state.index) then local unit = trigger.unit == "member" and trigger.specificUnit or trigger.unit; if(trigger.debuffType == "HELPFUL") then GameTooltip:SetUnitBuff(unit, state.index); elseif(trigger.debuffType == "HARMFUL") then GameTooltip:SetUnitDebuff(unit, state.index); end return true else if (state.spellId) then GameTooltip:SetSpellByID(state.spellId); return true end end return false end --- Returns the name and icon to show in the options. -- @param data -- @param triggernum -- @return name and icon function BuffTrigger.GetNameAndIcon(data, triggernum) local _, name, icon local trigger = data.triggers[triggernum].trigger if (trigger.fullscan) then if (trigger.spellId) then name, _, icon = GetSpellInfo(trigger.spellId); else name = trigger.name; icon = WeakAuras.spellCache.GetIcon(trigger.name); end else if (trigger.spellIds and trigger.spellIds[1]) then name, _, icon = GetSpellInfo(trigger.spellIds[1]) elseif(not (trigger.buffShowOn == "showOnMissing" or CanGroupShowWithZero(data, triggernum)) and trigger.names) then -- Try to get an icon from the icon cache for index, checkname in pairs(trigger.names) do local iconFromSpellCache = WeakAuras.spellCache.GetIcon(checkname); if(iconFromSpellCache) then name, icon = checkname, iconFromSpellCache; break; end end end end return name, icon; end --- Returns the tooltip text for additional properties. -- @param data -- @param triggernum -- @return string of additional properties function BuffTrigger.GetAdditionalProperties(data, triggernum) local ret = "|cFFFF0000%".. triggernum .. ".spellId|r -" .. L["Spell ID"] .. "\n"; ret = ret .. "|cFFFF0000%".. triggernum .. ".unitCaster|r -" .. L["Caster"] .. "\n"; return ret; end function BuffTrigger.GetTriggerConditions(data, triggernum) local result = {}; local trigger = data.triggers[triggernum].trigger result["unitCaster"] = { display = L["Caster"], type = "string", } result["expirationTime"] = { display = L["Remaining Duration"], type = "timer", } result["duration"] = { display = L["Total Duration"], type = "number", } result["stacks"] = { display = L["Stacks"], type = "number" } result["name"] = { display = L["Name"], type = "string" } if (trigger.type == "aura" and not(trigger.unit ~= "group" and trigger.fullscan and trigger.autoclone) and trigger.unit ~= "multi" and not(trigger.unit == "group" and not trigger.groupclone)) then if (trigger.buffShowOn == "showAlways") then result["buffed"] = { display = L["Buffed/Debuffed"], type = "bool", test = function(state, needle) return state and state.show and ((state.active and true or false) == (needle == 1)); end } end end return result; end function BuffTrigger.CreateFallbackState(data, triggernum, state) state.show = true; state.changed = true; state.progressType = "timed"; state.duration = 0; state.expirationTime = math.huge; local name, icon = GetNameAndIconFromTrigger(data, triggernum) state.name = name state.icon = icon end function BuffTrigger.GetName(triggerType) if (triggerType == "aura") then return L["Legacy Aura"]; end end function BuffTrigger.GetTriggerDescription(data, triggernum, namestable) local trigger = data.triggers[triggernum].trigger; if(trigger.fullscan) then tinsert(namestable, {L["Aura:"], L["Full Scan"]}); else for index, name in pairs(trigger.names) do local left = " "; if(index == 1) then if(#trigger.names > 0) then if(#trigger.names > 1) then left = L["Auras:"]; else left = L["Aura:"]; end end end local icon = WeakAuras.spellCache.GetIcon(name) or "Interface\\Icons\\INV_Misc_QuestionMark"; tinsert(namestable, {left, name, icon}); end end end function BuffTrigger.CreateFakeStates(id, triggernum) local allStates = WeakAuras.GetTriggerStateForTrigger(id, triggernum); local data = WeakAuras.GetData(id) local state = {} BuffTrigger.CreateFallbackState(data, triggernum, state) state.expirationTime = GetTime() + 60 state.duration = 65 state.progressType = "timed" allStates[""] = state if BuffTrigger.CanHaveClones(data, triggernum) then for i = 1, 2 do local state = {} BuffTrigger.CreateFallbackState(data, triggernum, state) state.expirationTime = GetTime() + 60 + i * 20 state.duration = 100 state.progressType = "timed" allStates[i] = state end end end WeakAuras.RegisterTriggerSystem({"aura"}, BuffTrigger);