You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

3992 lines
130 KiB

--[=[ BuffTrigger2.lua
This file contains the "aura2" trigger for buffs and debuffs. It has replaced the older Bufftrigger 1, which is now gone.
It registers the BuffTrigger table for the trigger type "aura2" 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.
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
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.IsLibsOK() then return end
--- @type string, Private
local AddonName, Private = ...
-- Lua APIs
local tinsert, wipe = table.insert, wipe
local pairs, next, type = pairs, next, type
local UnitAura = UnitAura
local LCD
if WeakAuras.IsClassicEra() then
LCD = LibStub("LibClassicDurations")
LCD:Register("WeakAuras")
UnitAura = LCD.UnitAuraWithBuffs
end
local newAPI = WeakAuras.IsRetail()
local WeakAuras = WeakAuras
local L = WeakAuras.L
local timer = WeakAuras.timer
local BuffTrigger = {}
local triggerInfos = {}
local watched_trigger_events = Private.watched_trigger_events
local UnitGroupRolesAssigned = WeakAuras.IsWrathOrRetail() and UnitGroupRolesAssigned or function() return "DAMAGER" end
-- keyed on unit, debuffType, spellname, with a scan object value
-- scan object: id, triggernum, scanFunc
local scanFuncName = {}
local scanFuncSpellId = {}
local scanFuncGeneral = {}
-- same as above but for group triggers
local scanFuncNameGroup = {}
local scanFuncSpellIdGroup = {}
local scanFuncGeneralGroup = {}
-- Contains all scanFuncs that should be check if the existence of a unit changed
local unitExistScanFunc = {}
-- Which units exist
local existingUnits = {}
-- Contains all scanFuncs that fetch the role + roleIcon
local groupRoleScanFunc = {}
-- Loaded ScanFuncs per unit type
local groupScanFuncs = {}
--Active ScanFuncs per actual unit id
local activeGroupScanFuncs = {}
local raidMarkScanFuncs = {}
-- Multi Target tracking
local scanFuncNameMulti = {}
local scanFuncSpellIdMulti = {}
local cleanupTimerMulti = {}
-- Auras that matched, unit, index
local matchData = {}
local matchDataUpToDate = {}
local matchDataMulti = {}
-- Auras that matched, keyed on id, triggernum, kept in sync with matchData
local matchDataByTrigger = {}
local matchDataChanged = {}
local nameplateExists = {}
local unitVisible = {}
local function UnitExistsFixed(unit)
if #unit > 9 and unit:sub(1, 9) == "nameplate" then
return nameplateExists[unit]
end
return UnitExists(unit) or UnitGUID(unit)
end
local function UnitIsVisibleFixed(unit)
if unitVisible[unit] == nil then
unitVisible[unit] = UnitIsVisible(unit)
end
return unitVisible[unit]
end
local function UnitInSubgroupOrPlayer(unit, includePets)
if includePets == nil then
return UnitInSubgroup(unit) or UnitIsUnit("player", unit)
elseif includePets == "PlayersAndPets" then
return UnitInSubgroup(WeakAuras.petUnitToUnit[unit] or unit) or UnitIsUnit("player", unit) or UnitIsUnit("pet", unit)
elseif includePets == "PetsOnly" then
return UnitInSubgroup(WeakAuras.petUnitToUnit[unit]) or UnitIsUnit("pet", unit)
end
end
local function GetOrCreateSubTable(base, next, ...)
if not next then
return base
end
base[next] = base[next] or {}
return GetOrCreateSubTable(base[next], ...)
end
local function GetSubTable(base, next, ...)
if not base then
return nil
end
if not next then
return base
end
return GetSubTable(base[next], ...)
end
local function IsGroupTrigger(trigger)
return trigger.unit == "group" or trigger.unit == "party" or trigger.unit == "raid"
or trigger.unit == "boss" or trigger.unit == "nameplate" or trigger.unit == "arena" or trigger.unit == "multi"
end
local function IsSingleMissing(trigger)
return not IsGroupTrigger(trigger) and trigger.matchesShowOn == "showOnMissing"
end
local function CanHaveMatchCheck(trigger)
if IsGroupTrigger(trigger) then
return true
end
if trigger.matchesShowOn == "showOnMissing" then
return false
end
if trigger.matchesShowOn == "showOnActive" or trigger.matchesShowOn == "showOnMatches" or not trigger.matchesShowOn then
return true
end
-- Always: If clones are shown
return trigger.showClones
end
local function HasMatchCount(trigger)
if IsGroupTrigger(trigger) then
return trigger.useMatch_count
else
return trigger.matchesShowOn == "showOnMatches"
end
end
local function ReferenceMatchData(id, triggernum, unit, filter, index)
local match = matchData[unit][filter][index]
local base = GetOrCreateSubTable(matchDataByTrigger, id, triggernum, unit)
base[index] = match
match.auras[id] = match.auras[id] or {}
match.auras[id][triggernum] = true
end
local function ScanMatchData(time, triggerInfo, unit, filter)
if matchData[unit] and matchData[unit][filter] then
for index, match in pairs(matchData[unit][filter]) do
if (not triggerInfo.auranames and not triggerInfo.auraspellids)
or (triggerInfo.auranames and tContains(triggerInfo.auranames, match.name))
or (triggerInfo.auraspellids and tContains(triggerInfo.auraspellids, match.spellId)) then
if triggerInfo.fetchTooltip then
matchData[unit][filter][index]:UpdateTooltip(time)
end
if not triggerInfo.scanFunc or triggerInfo.scanFunc(time, matchData[unit][filter][index]) then
local id = triggerInfo.id
local triggernum = triggerInfo.triggernum
ReferenceMatchData(id, triggernum, unit, filter, index)
matchDataChanged[id] = matchDataChanged[id] or {}
matchDataChanged[id][triggernum] = true
end
end
end
end
end
local function ReferenceMatchDataMulti(matchData, id, triggernum, destGUID)
local needToInsert = false
matchData.auras[id] = matchData.auras[id] or {}
needToInsert = not matchData.auras[id][triggernum]
matchData.auras[id][triggernum] = true
if needToInsert then
local matchDataByTriggerBase = GetOrCreateSubTable(matchDataByTrigger, id, triggernum, destGUID)
tinsert(matchDataByTriggerBase, matchData)
end
matchDataChanged[id] = matchDataChanged[id] or {}
matchDataChanged[id][triggernum] = true
end
local function MatchesTriggerInfoMulti(triggerInfo, sourceGUID)
if triggerInfo.ownOnly then
return sourceGUID == UnitGUID("player") or sourceGUID == UnitGUID("pet")
elseif triggerInfo.ownOnly == false then
return sourceGUID ~= UnitGUID("player") and sourceGUID ~= UnitGUID("pet")
else
return true
end
end
local function UpdateToolTipDataInMatchData(matchData, time)
if matchData.tooltipUpdated == time then
return
end
local changed = false
if matchData.unit and matchData.auraInstanceID then
local tooltip, _, tooltip1, tooltip2, tooltip3, tooltip4 = WeakAuras.GetAuraInstanceTooltipInfo(matchData.unit, matchData.auraInstanceID, matchData.filter)
changed = matchData.tooltip ~= tooltip or matchData.tooltip1 ~= tooltip1
or matchData.tooltip2 ~= tooltip2 or matchData.tooltip3 ~= tooltip3 or matchData.tooltip4 ~= tooltip4
matchData.tooltip, matchData.tooltip1, matchData.tooltip2, matchData.tooltip3, matchData.tooltip4 = tooltip, tooltip1, tooltip2, tooltip3, tooltip4
elseif matchData.unit and matchData.index and matchData.filter then
local tooltip, _, tooltip1, tooltip2, tooltip3, tooltip4 = WeakAuras.GetAuraTooltipInfo(matchData.unit, matchData.index, matchData.filter)
changed = matchData.tooltip ~= tooltip or matchData.tooltip1 ~= tooltip1
or matchData.tooltip2 ~= tooltip2 or matchData.tooltip3 ~= tooltip3 or matchData.tooltip4 ~= tooltip4
matchData.tooltip, matchData.tooltip1, matchData.tooltip2, matchData.tooltip3, matchData.tooltip4 = tooltip, tooltip1, tooltip2, tooltip3, tooltip4
end
matchData.tooltipUpdated = time
return changed
end
--- Compares two arrays (shallow)
---@param t1 any[]?
---@param t2 any[]?
---@return boolean
local function ArrayCompare(t1, t2)
if t1 == nil then
return t2 == nil
end
if t2 == nil then
return false
end
if #t1 ~= #t2 then
return false
end
for i = 1, #t1 do
if t1[i] ~= t2[i] then
return false
end
end
return true
end
local function UpdateMatchData(time, matchDataChanged, unit, index, auraInstanceID, filter, name, icon, stacks, debuffClass, duration, expirationTime, unitCaster, isStealable, isBossDebuff, isCastByPlayer, spellId, modRate, points)
if not matchData[unit] then
matchData[unit] = {}
end
if not matchData[unit][filter] then
matchData[unit][filter] = {}
end
local key = index or auraInstanceID
local debuffClassIcon = WeakAuras.EJIcons[debuffClass]
if not matchData[unit][filter][key] then
matchData[unit][filter][key] = {
name = name,
icon = icon,
stacks = stacks,
debuffClass = debuffClass,
debuffClassIcon = debuffClassIcon,
duration = duration,
expirationTime = expirationTime,
modRate = modRate,
unitCaster = unitCaster,
casterName = unitCaster and GetUnitName(unitCaster, false) or "",
spellId = spellId,
unit = unit,
unitName = GetUnitName(unit, false) or "",
isStealable = isStealable,
isBossDebuff = isBossDebuff,
isCastByPlayer = isCastByPlayer,
time = time,
lastChanged = time,
filter = filter,
index = index,
points = points,
auraInstanceID = auraInstanceID,
UpdateTooltip = UpdateToolTipDataInMatchData,
auras = {}
}
return true
end
local data = matchData[unit][filter][key]
local changed = false
if data.name ~= name then
data.name = name
changed = true
end
if data.icon ~= icon then
data.icon = icon
changed = true
end
if data.stacks ~= stacks then
data.stacks = stacks
changed = true
end
if data.debuffClass ~= debuffClass then
data.debuffClass = debuffClass
changed = true
end
if data.debuffClassIcon ~= debuffClassIcon then
data.debuffClassIcon = debuffClassIcon
changed = true
end
if data.duration ~= duration then
data.duration = duration
changed = true
end
if data.expirationTime ~= expirationTime then
data.expirationTime = expirationTime
changed = true
end
if data.modRate ~= modRate then
data.modRate = modRate
changed = true
end
if data.unitCaster ~= unitCaster then
data.unitCaster = unitCaster
changed = true
end
local casterName = unitCaster and GetUnitName(unitCaster, false) or ""
if data.casterName ~= casterName then
data.casterName = casterName
changed = true
end
if data.spellId ~= spellId then
data.spellId = spellId
changed = true
end
if data.isStealable ~= isStealable then
data.isStealable = isStealable
changed = true
end
if data.isBossDebuff ~= isBossDebuff then
data.isBossDebuff = isBossDebuff
changed = true
end
if data.isCastByPlayer ~= isCastByPlayer then
data.isCastByPlayer = isCastByPlayer
changed = true
end
local unitName = GetUnitName(unit, false) or ""
if data.unitName ~= unitName then
data.unitName = unitName
changed = true
end
if data.tooltipUpdated and data.tooltipUpdated < time then
changed = data:UpdateTooltip(time) or changed
end
if not ArrayCompare(points, data.points) then
data.points = points
changed = true
end
if changed then
data.lastChanged = time
end
if changed then
-- Tell old auras that used this match data
for id, triggerData in pairs(data.auras) do
for triggernum in pairs(triggerData) do
if matchDataByTrigger[id] and matchDataByTrigger[id][triggernum] and matchDataByTrigger[id][triggernum][unit] and matchDataByTrigger[id][triggernum][unit][key] then
matchDataByTrigger[id][triggernum][unit][key] = nil
matchDataChanged[id] = matchDataChanged[id] or {}
matchDataChanged[id][triggernum] = true
end
end
end
wipe(data.auras)
end
data.index = index
data.auraInstanceID = auraInstanceID
data.time = time
data.unit = unit
return changed or data.lastChanged == time
end
local function calculateNextCheck(triggerInfoRemaining, auraDataRemaining, auraDataExpirationTime, modRate, nextCheck)
if auraDataRemaining > 0 and auraDataRemaining >= triggerInfoRemaining then
if not nextCheck then
return auraDataExpirationTime - triggerInfoRemaining * modRate
else
return min(auraDataExpirationTime - triggerInfoRemaining * modRate, nextCheck)
end
end
return nextCheck
end
local function FindBestMatchData(time, id, triggernum, triggerInfo, matchedUnits)
-- Find best match
local bestMatch = nil
local matchCount = 0
local unitCount = 0
local stackCount = 0
local nextCheck
if not matchDataByTrigger[id] or not matchDataByTrigger[id][triggernum] then
return nil, 0, 0
end
for unit, unitData in pairs(matchDataByTrigger[id][triggernum]) do
local unitCounted = false
for index, auraData in pairs(unitData) do
local remCheck = true
if triggerInfo.remainingFunc and auraData.expirationTime then
local modRate = auraData.modRate or 1
local remaining = (auraData.expirationTime - time) / modRate
remCheck = triggerInfo.remainingFunc(remaining)
nextCheck = calculateNextCheck(triggerInfo.remainingCheck, remaining, auraData.expirationTime, modRate, nextCheck)
end
if remCheck then
matchCount = matchCount + 1
stackCount = stackCount + (auraData.stacks or 0)
matchedUnits[unit] = true
if not unitCounted then
unitCount = unitCount + 1
unitCounted = true
end
if not bestMatch or triggerInfo.compareFunc(bestMatch, auraData) then
bestMatch = auraData
end
end
end
end
return bestMatch, matchCount, unitCount, stackCount, nextCheck
end
local function FindBestMatchDataForUnit(time, id, triggernum, triggerInfo, unit)
-- Find best match
local bestMatch = nil
local matchCount = 0
local stackCount = 0
local nextCheck
if not matchDataByTrigger[id] or not matchDataByTrigger[id][triggernum] or not matchDataByTrigger[id][triggernum][unit] then
return nil, 0
end
for index, auraData in pairs(matchDataByTrigger[id][triggernum][unit]) do
local remCheck = true
if triggerInfo.remainingFunc and auraData.expirationTime then
local modRate = auraData.modRate or 1
local remaining = (auraData.expirationTime - time) / modRate
remCheck = triggerInfo.remainingFunc(remaining)
nextCheck = calculateNextCheck(triggerInfo.remainingCheck, remaining, auraData.expirationTime, modRate, nextCheck)
end
if remCheck then
matchCount = matchCount + 1
stackCount = stackCount + (auraData.stacks or 0)
if not bestMatch or triggerInfo.compareFunc(bestMatch, auraData) then
bestMatch = auraData
end
end
end
return bestMatch, matchCount, stackCount, nextCheck
end
local roleIcons = {
DAMAGER = CreateTextureMarkup([=[Interface\LFGFrame\UI-LFG-ICON-ROLES]=], 256, 256, 0, 0, GetTexCoordsForRole("DAMAGER")),
HEALER = CreateTextureMarkup([=[Interface\LFGFrame\UI-LFG-ICON-ROLES]=], 256, 256, 0, 0, GetTexCoordsForRole("HEALER")),
TANK = CreateTextureMarkup([=[Interface\LFGFrame\UI-LFG-ICON-ROLES]=], 256, 256, 0, 0, GetTexCoordsForRole("TANK"))
}
local function UpdateStateWithMatch(time, bestMatch, triggerStates, cloneId, matchCount, unitCount, maxUnitCount, matchCountPerUnit, totalStacks, affected, unaffected, role, raidMark)
local debuffClassIcon = WeakAuras.EJIcons[bestMatch.debuffClass]
if not triggerStates[cloneId] then
triggerStates[cloneId] = {
show = true,
changed = true,
name = bestMatch.name,
icon = bestMatch.icon,
stacks = bestMatch.stacks,
debuffClass = bestMatch.debuffClass,
debuffClassIcon = debuffClassIcon,
progressType = "timed",
duration = bestMatch.duration,
expirationTime = bestMatch.expirationTime,
modRate = bestMatch.modRate,
unitCaster = bestMatch.unitCaster,
casterName = bestMatch.casterName,
spellId = bestMatch.spellId,
index = bestMatch.index,
auraInstanceID = bestMatch.auraInstanceID,
filter = bestMatch.filter,
unit = bestMatch.unit,
unitName = bestMatch.unitName,
GUID = bestMatch.unit and UnitGUID(bestMatch.unit) or bestMatch.GUID,
role = role,
roleIcon = role and roleIcons[role],
raidMark = raidMark,
matchCount = matchCount,
unitCount = unitCount,
maxUnitCount = maxUnitCount,
matchCountPerUnit = matchCountPerUnit,
tooltip = bestMatch.tooltip,
tooltip1 = bestMatch.tooltip1,
tooltip2 = bestMatch.tooltip2,
tooltip3 = bestMatch.tooltip3,
tooltip4 = bestMatch.tooltip4,
points = bestMatch.points,
affected = affected,
unaffected = unaffected,
totalStacks = totalStacks,
initialTime = time,
refreshTime = time,
active = true,
time = time,
}
return true
else
local state = triggerStates[cloneId]
local changed = false
state.time = time
if state.unit ~= bestMatch.unit then
state.unit = bestMatch.unit
changed = true
end
local GUID = bestMatch.unit and UnitGUID(bestMatch.unit) or bestMatch.GUID
if state.GUID ~= GUID then
state.GUID = GUID
changed = true
end
if state.role ~= role then
state.role = role
state.roleIcon = roleIcons[role]
changed = true
end
if state.raidMark ~= raidMark then
state.raidMark = raidMark
changed = true
end
if state.unitName ~= bestMatch.unitName then
state.unitName = bestMatch.unitName
changed = true
end
if state.show ~= true then
state.show = true
changed = true
end
if state.name ~= bestMatch.name then
state.name = bestMatch.name
changed = true
end
if state.icon ~= bestMatch.icon then
state.icon = bestMatch.icon
changed = true
end
if state.stacks ~= bestMatch.stacks then
if state.stacks and bestMatch.stacks then
if state.stacks < bestMatch.stacks then
state.stackGainTime = time
state.stackLostTime = nil
else
state.stackGainTime = nil
state.stackLostTime = time
end
end
state.stacks = bestMatch.stacks
changed = true
end
if state.debuffClass ~= bestMatch.debuffClass then
state.debuffClass = bestMatch.debuffClass
changed = true
end
if state.debuffClassIcon ~= debuffClassIcon then
state.debuffClassIcon = debuffClassIcon
changed = true
end
if state.duration ~= bestMatch.duration then
state.duration = bestMatch.duration
changed = true
end
if not state.initialTime then
-- Only set initialTime if it wasn't set before
state.initialTime = time
changed = true
end
if state.expirationTime ~= bestMatch.expirationTime then
-- A bit fuzzy checking
if state.expirationTime and bestMatch.expirationTime and bestMatch.expirationTime - state.expirationTime > 0.2 then
state.refreshTime = time
end
state.expirationTime = bestMatch.expirationTime
changed = true
end
if state.modRate ~= bestMatch.modRate then
state.modRate = bestMatch.modRate
changed = true
end
if state.progressType ~= "timed" then
state.progressType = "timed"
changed = true
end
if state.unitCaster ~= bestMatch.unitCaster then
state.unitCaster = bestMatch.unitCaster
state.casterName = bestMatch.casterName
changed = true
end
if state.spellId ~= bestMatch.spellId then
state.spellId = bestMatch.spellId
changed = true
end
if state.index ~= bestMatch.index then
state.index = bestMatch.index
changed = true
end
if state.auraInstanceID ~= bestMatch.auraInstanceID then
state.auraInstanceID = bestMatch.auraInstanceID
changed = true
end
if state.filter ~= bestMatch.filter then
state.filter = bestMatch.filter
changed = true
end
if state.tooltip ~= bestMatch.tooltip then
state.tooltip = bestMatch.tooltip
changed = true
end
if state.tooltip1 ~= bestMatch.tooltip1 then
state.tooltip1 = bestMatch.tooltip1
changed = true
end
if state.tooltip2 ~= bestMatch.tooltip2 then
state.tooltip2 = bestMatch.tooltip2
changed = true
end
if state.tooltip3 ~= bestMatch.tooltip3 then
state.tooltip3 = bestMatch.tooltip3
changed = true
end
if state.tooltip4 ~= bestMatch.tooltip4 then
state.tooltip4 = bestMatch.tooltip4
changed = true
end
if not ArrayCompare(state.points, bestMatch.points) then
state.points = bestMatch.points
changed = true
end
if state.matchCount ~= matchCount then
state.matchCount = matchCount
changed = true
end
if state.unitCount ~= unitCount then
state.unitCount = unitCount
changed = true
end
if state.maxUnitCount ~= maxUnitCount then
state.maxUnitCount = maxUnitCount
changed = true
end
if state.matchCountPerUnit ~= matchCountPerUnit then
state.matchCountPerUnit = matchCountPerUnit
changed = true
end
if state.affected ~= affected then
state.affected = affected
changed = true
end
if state.unaffected ~= unaffected then
state.unaffected = unaffected
changed = true
end
if state.active ~= true then
state.active = true
changed = true
end
if state.totalStacks ~= totalStacks then
state.totalStacks = totalStacks
changed = true
end
if changed then
state.changed = true
return true
end
end
end
local function UpdateStateWithNoMatch(time, triggerStates, triggerInfo, cloneId, unit, matchCount, unitCount, maxUnitCount, matchCountPerUnit, totalStacks, affected, unaffected, role, raidMark)
local fallbackName, fallbackIcon = BuffTrigger.GetNameAndIconSimple(WeakAuras.GetData(triggerInfo.id), triggerInfo.triggernum)
if not triggerStates[cloneId] then
triggerStates[cloneId] = {
show = true,
changed = true,
progressType = 'timed',
duration = 0,
expirationTime = math.huge,
modRate = 1,
matchCount = matchCount,
unitCount = unitCount,
maxUnitCount = maxUnitCount,
matchCountPerUnit = matchCountPerUnit,
active = false,
time = time,
affected = affected,
unaffected = unaffected,
unit = unit,
role = role,
raidMark = raidMark,
roleIcon = role and roleIcons[role],
unitName = unit and GetUnitName(unit, false) or "",
destName = "",
name = fallbackName,
icon = fallbackIcon,
totalStacks = totalStacks
}
return true
else
local state = triggerStates[cloneId]
state.time = time
local changed = false
if state.show ~= true then
state.show = true
changed = true
end
if state.name ~= fallbackName then
state.name = fallbackName
changed = true
end
if state.icon ~= fallbackIcon then
state.icon = fallbackIcon
changed = true
end
if state.stacks then
state.stacks = nil
changed = true
end
if state.duration then
state.duration = nil
changed = true
end
if state.initialTime then
state.initialTime = nil
changed = true
end
if state.refreshTime then
state.refreshTime = nil
changed = true
end
if state.expirationTime ~= math.huge then
state.expirationTime = math.huge
changed = true
end
if state.progressType then
state.progressType = nil
changed = true
end
if state.modRate ~= 1 then
state.modRate = 1
changed = true
end
if state.unit ~= unit then
state.unit = unit
changed = true
end
local GUID = unit and UnitGUID(unit)
if state.GUID ~= GUID then
state.GUID = GUID
changed = true
end
if state.role ~= role then
state.role = role
state.roleIcon = roleIcons[role]
changed = true
end
if state.raidMark ~= raidMark then
state.raidMark = raidMark
changed = true
end
local unitName = unit and GetUnitName(unit, false) or ""
if state.unitName ~= unitName then
state.unitName = unitName
changed = true
end
if state.unitCaster then
state.unitCaster = nil
changed = true
end
if state.casterName ~= "" then
state.casterName = ""
changed = true
end
if state.spellId then
state.spellId = nil
changed = true
end
if state.index then
state.index = nil
changed = true
end
if state.auraInstanceID then
state.auraInstanceID = nil
changed = true
end
if state.tooltip or state.tooltip1 or state.tooltip2 or state.tooltip3 or state.tooltip4 then
state.tooltip, state.tooltip1, state.tooltip2, state.tooltip3, state.tooltip4 = nil, nil, nil, nil, nil
changed = true
end
if state.points then
state.points = nil
changed = true
end
if state.matchCount ~= matchCount then
state.matchCount = matchCount
changed = true
end
if state.unitCount ~= unitCount then
state.unitCount = unitCount
changed = true
end
if state.maxUnitCount ~= maxUnitCount then
state.maxUnitCount = maxUnitCount
changed = true
end
if state.matchCountPerUnit ~= matchCountPerUnit then
state.matchCountPerUnit = matchCountPerUnit
changed = true
end
if state.active then
state.active = false
changed = true
end
if state.affected ~= affected then
state.affected = affected
changed = true
end
if state.unaffected ~= unaffected then
state.unaffected = unaffected
changed = true
end
if state.totalStacks ~= totalStacks then
state.totalStacks = totalStacks
changed = true
end
if changed then
state.changed = true
return true
end
end
end
local function RemoveState(triggerStates, cloneId)
local state = triggerStates[cloneId]
if state then
if state.show then
state.show = false
state.changed = true
return true
end
end
end
local function GetAllUnits(unit, allUnits, includePets)
if unit == "group" then
if allUnits then
local i = 1
local raid = true
local pets = true
return function()
if raid then
if i <= 40 then
local ret
if includePets == "PlayersAndPets" then
ret = pets and WeakAuras.raidpetUnits[i] or WeakAuras.raidUnits[i]
pets = not pets
if pets then
i = i + 1
end
elseif includePets == "PetsOnly" then
ret = WeakAuras.raidpetUnits[i]
i = i + 1
else -- raid
ret = WeakAuras.raidUnits[i]
i = i + 1
end
return ret
end
if includePets and pets then
pets = not pets
return "pet"
end
raid = false
i = 1
return "player"
end
if i <= 4 then
local ret
if includePets == "PlayersAndPets" then
ret = pets and WeakAuras.partypetUnits[i] or WeakAuras.partyUnits[i]
pets = not pets
if pets then
i = i + 1
end
elseif includePets == "PetsOnly" then
ret = WeakAuras.partypetUnits[i]
i = i + 1
else -- group
ret = WeakAuras.partyUnits[i]
i = i + 1
end
return ret
end
i = 1
raid = true
end
end
-- allunits == false
if IsInRaid() then
local i = 1
local max = GetNumGroupMembers()
local pets = true
return function()
if i <= max then
local ret
if includePets == "PlayersAndPets" then
ret = pets and WeakAuras.raidpetUnits[i] or WeakAuras.raidUnits[i]
pets = not pets
if pets then
i = i + 1
end
elseif includePets == "PetsOnly" then
ret = WeakAuras.raidpetUnits[i]
i = i + 1
else -- raid
ret = WeakAuras.raidUnits[i]
i = i + 1
end
return ret
end
i = 1
end
else
local i = 0
local max = GetNumSubgroupMembers()
local pets = true
return function()
if i == 0 then
if includePets == "PlayersAndPets" then
local ret = pets and "pet" or "player"
pets = not pets
if pets then
i = i + 1
end
return ret
elseif includePets == "PetsOnly" then
i = 1
return "pet"
else -- group
i = 1
return "player"
end
else
if i <= max then
local ret
if includePets == "PlayersAndPets" then
ret = pets and WeakAuras.partypetUnits[i] or WeakAuras.partyUnits[i]
pets = not pets
if pets then
i = i + 1
end
elseif includePets == "PetsOnly" then
ret = WeakAuras.partypetUnits[i]
i = i + 1
else -- group
ret = WeakAuras.partyUnits[i]
i = i + 1
end
return ret
end
end
i = 0
end
end
elseif unit == "boss" or unit == "arena" or unit == "nameplate" then
local i = 1
local max
if unit == "boss" then
max = 10
elseif unit == "arena" then
max = 5
elseif unit == "nameplate" then
max = 40
else
return function() end
end
return function()
local ret = unit .. i
while not allUnits and not UnitExistsFixed(ret) do
i = i + 1
if i > max then
i = 1
return nil
end
ret = unit .. i
end
i = i + 1
if i > max then
i = 1
return nil
end
return ret
end
else
local toggle = false
return function()
toggle = not toggle
if toggle then
return unit
end
end
end
end
local function MaxUnitCount(triggerInfo)
if triggerInfo.groupTrigger then
return triggerInfo.maxUnitCount
else
return UnitExistsFixed(triggerInfo.unit) and 1 or 0
end
end
local function TriggerInfoApplies(triggerInfo, unit)
local controllingUnit = unit
if WeakAuras.UnitIsPet(unit) then
controllingUnit = WeakAuras.petUnitToUnit[unit]
end
if triggerInfo.ignoreSelf and UnitIsUnit("player", controllingUnit) then
return false
end
if triggerInfo.ignoreDead and UnitIsDeadOrGhost(unit) then
return false
end
if triggerInfo.ignoreDisconnected and not UnitIsConnected(unit) then
return false
end
if triggerInfo.ignoreInvisible and not UnitIsVisibleFixed(unit) then
return false
end
if triggerInfo.groupRole and not triggerInfo.groupRole[UnitGroupRolesAssigned(controllingUnit) or ""] then
return false
end
if triggerInfo.raidRole and not triggerInfo.raidRole[WeakAuras.UnitRaidRole(controllingUnit) or ""] then
return false
end
if triggerInfo.specId then
local spec = Private.LibSpecWrapper.SpecForUnit(controllingUnit)
if not triggerInfo.specId[spec] then
return false
end
end
if triggerInfo.arenaSpec and unit:sub(1, 5) == "arena" then
-- GetArenaOpponentSpec doesn't use unit ids!
local i = tonumber(unit:sub(6))
if not triggerInfo.arenaSpec[GetArenaOpponentSpec(i)] then
return false
end
end
if triggerInfo.hostility and WeakAuras.GetPlayerReaction(unit) ~= triggerInfo.hostility then
return false
end
if triggerInfo.unit == "group" then
local isPet = WeakAuras.UnitIsPet(unit)
if triggerInfo.includePets == "PetsOnly" and not isPet then
return false
elseif triggerInfo.includePets == nil and isPet then -- exclude pets
return false
end
end
if triggerInfo.unit == "group" and triggerInfo.groupSubType == "party" then
if IsInRaid() then
-- Filter our player/party# while in raid and keep only raid units that are correct
if not Private.multiUnitUnits.raid[unit] or not UnitInSubgroupOrPlayer(unit, triggerInfo.includePets) then
return false
end
else
if not UnitInSubgroupOrPlayer(unit, triggerInfo.includePets) then
return false
end
end
end
-- Filter our player/party# while in raid
if (triggerInfo.unit == "group" and triggerInfo.groupSubType == "group" and IsInRaid() and not Private.multiUnitUnits.raid[unit]) then
return false
end
if triggerInfo.unit == "group" and triggerInfo.groupSubType == "raid" and not Private.multiUnitUnits.raid[unit] then
return false
end
if triggerInfo.class and not triggerInfo.class[select(2, UnitClass(controllingUnit))] then
return false
end
if triggerInfo.npcId and select(6, strsplit('-', UnitGUID(unit) or '')) ~= triggerInfo.npcId then
return false
end
if triggerInfo.nameChecker and not triggerInfo.nameChecker:Check(WeakAuras.UnitNameWithRealm(unit)) then
return false
end
return true
end
local function FormatAffectedUnaffected(triggerInfo, matchedUnits)
local affected = ""
local unaffected = ""
for unit in GetAllUnits(triggerInfo.unit, nil, triggerInfo.includePets) do
if activeGroupScanFuncs[unit] and activeGroupScanFuncs[unit][triggerInfo] then
if matchedUnits[unit] then
affected = affected .. (GetUnitName(unit, false) or unit) .. ", "
else
unaffected = unaffected .. (GetUnitName(unit, false) or unit) .. ", "
end
end
end
unaffected = unaffected == "" and L["None"] or unaffected:sub(1, -3)
affected = affected == "" and L["None"] or affected:sub(1, -3)
return affected, unaffected
end
local recheckTriggerInfo
local function SatisfiesGroupMatchCount(triggerInfo, unitCount, maxUnitCount, matchCount)
if triggerInfo.groupCountFunc and not triggerInfo.groupCountFunc(unitCount, maxUnitCount) then
return false
end
if triggerInfo.matchCountFunc and not triggerInfo.matchCountFunc(matchCount) then
return false
end
return true
end
local function SatisfiesMatchCountPerUnit(triggerInfo, countPerUnit)
return not triggerInfo.matchPerUnitCountFunc or triggerInfo.matchPerUnitCountFunc(countPerUnit)
end
local function bestUnit(triggerInfo, bestMatch)
if bestMatch then
return bestMatch.unit
elseif not triggerInfo.groupTrigger and triggerInfo.unit then
return triggerInfo.unit
end
end
local function roleForTriggerInfo(triggerInfo, unit)
if triggerInfo.fetchRole then
return UnitGroupRolesAssigned(unit)
end
end
local function markForTriggerInfo(triggerInfo, unit)
if triggerInfo.fetchRaidMark then
local rt = GetRaidTargetIndex(unit)
if rt then
return "{rt" .. GetRaidTargetIndex(unit) .. "}"
end
end
end
local function SortMatchDataByUnitIndex(a, b)
if a.unit and b.unit and a.unit ~= b.unit then
return a.unit < b.unit
end
if a.index and b.index and a.index ~= b.index then
return a.index < b.index
end
return a.expirationTime < b.expirationTime
end
local function UpdateTriggerState(time, id, triggernum)
local triggerStates = WeakAuras.GetTriggerStateForTrigger(id, triggernum)
local triggerInfo = triggerInfos[id][triggernum]
local updated
local nextCheck
local matchCount = 0
---@type number?
local totalStacks = 0
local unitCount = 0
local auraDatas = {}
local maxUnitCount = MaxUnitCount(triggerInfo)
local matchedUnits = {}
local matchCountPerUnit = {}
if triggerInfo.combineMode == "showOne" then
local bestMatch
bestMatch, matchCount, unitCount, totalStacks, nextCheck = FindBestMatchData(time, id, triggernum, triggerInfo, matchedUnits)
local cloneId = ""
local useMatch = true
if triggerInfo.unitExists ~= nil and not existingUnits[triggerInfo.unit] then
useMatch = triggerInfo.unitExists
else
useMatch = SatisfiesGroupMatchCount(triggerInfo, unitCount, maxUnitCount, matchCount)
end
if useMatch then
local affected, unaffected
if triggerInfo.useAffected then
affected, unaffected = FormatAffectedUnaffected(triggerInfo, matchedUnits)
end
local unit = bestUnit(triggerInfo, bestMatch)
local role = roleForTriggerInfo(triggerInfo, unit)
local mark = markForTriggerInfo(triggerInfo, unit)
if bestMatch then
updated = UpdateStateWithMatch(time, bestMatch, triggerStates, cloneId, matchCount, unitCount, maxUnitCount, matchCount, totalStacks, affected, unaffected, role, mark)
else
updated = UpdateStateWithNoMatch(time, triggerStates, triggerInfo, cloneId, unit, 0, 0, maxUnitCount, 0, 0, affected, unaffected, role, mark)
end
else
updated = RemoveState(triggerStates, cloneId)
end
elseif triggerInfo.combineMode == "showClones" then
if matchDataByTrigger[id] and matchDataByTrigger[id][triggernum] then
for unit, unitData in pairs(matchDataByTrigger[id][triggernum]) do
local unitCounted = false
for index, auraData in pairs(unitData) do
local remCheck = true
if triggerInfo.remainingFunc and auraData.expirationTime then
local modRate = auraData.modRate or 1
local remaining = (auraData.expirationTime - time) / modRate
remCheck = triggerInfo.remainingFunc(remaining)
nextCheck = calculateNextCheck(triggerInfo.remainingCheck, remaining, auraData.expirationTime, modRate, nextCheck)
end
if remCheck then
tinsert(auraDatas, auraData)
matchCount = matchCount + 1
totalStacks = totalStacks + (auraData.stacks or 0)
matchedUnits[unit] = true
matchCountPerUnit[unit] = (matchCountPerUnit[unit] or 0) + 1
if not unitCounted then
unitCount = unitCount + 1
unitCounted = true
end
end
end
end
end
local useMatches = true
if triggerInfo.unitExists ~= nil and not existingUnits[triggerInfo.unit] then
useMatches = triggerInfo.unitExists
else
useMatches = SatisfiesGroupMatchCount(triggerInfo, unitCount, maxUnitCount, matchCount)
end
local cloneIds = {}
if useMatches then
table.sort(auraDatas, SortMatchDataByUnitIndex)
local affected, unaffected
if triggerInfo.useAffected then
affected, unaffected = FormatAffectedUnaffected(triggerInfo, matchedUnits)
end
local usedCloneIds = {};
for index, auraData in ipairs(auraDatas) do
local cloneId = (auraData.GUID or auraData.unit or "unknown") .. " " .. auraData.spellId
if usedCloneIds[cloneId] then
usedCloneIds[cloneId] = usedCloneIds[cloneId] + 1
cloneId = cloneId .. usedCloneIds[cloneId]
else
usedCloneIds[cloneId] = 1
end
local role = roleForTriggerInfo(triggerInfo, auraData.unit)
local mark = markForTriggerInfo(triggerInfo, auraData.unit)
updated = UpdateStateWithMatch(time, auraData, triggerStates, cloneId, matchCount, unitCount, maxUnitCount,
matchCountPerUnit[auraData.unit], totalStacks, affected, unaffected, role, mark) or updated
cloneIds[cloneId] = true
end
if matchCount == 0 then
local unit = bestUnit(triggerInfo, nil)
local role = roleForTriggerInfo(triggerInfo, unit)
local mark = markForTriggerInfo(triggerInfo, unit)
updated = UpdateStateWithNoMatch(time, triggerStates, triggerInfo, "", nil, 0, 0, maxUnitCount, 0, totalStacks,
affected, unaffected, role, mark) or updated
cloneIds[""] = true
end
end
for cloneId, state in pairs(triggerStates) do
if not cloneIds[cloneId] then
updated = RemoveState(triggerStates, cloneId) or updated
end
end
elseif triggerInfo.combineMode == "showPerUnit" then
local matches = {}
if matchDataByTrigger[id] and matchDataByTrigger[id][triggernum] then
for unit, unitData in pairs(matchDataByTrigger[id][triggernum]) do
local bestMatch, countPerUnit, stacks, nextCheckForMatch = FindBestMatchDataForUnit(time, id, triggernum, triggerInfo, unit)
if SatisfiesMatchCountPerUnit(triggerInfo, countPerUnit) then
matchCount = matchCount + countPerUnit
totalStacks = totalStacks + (stacks or 0)
if bestMatch then
unitCount = unitCount + 1
matchedUnits[unit] = true
end
if not nextCheck then
nextCheck = nextCheckForMatch
elseif nextCheckForMatch then
nextCheck = min(nextCheck, nextCheckForMatch)
end
matches[unit] = bestMatch
matchCountPerUnit[unit] = countPerUnit
end
end
end
local useMatches = SatisfiesGroupMatchCount(triggerInfo, unitCount, maxUnitCount, matchCount)
local cloneIds = {}
if useMatches then
local affected, unaffected
if triggerInfo.useAffected then
affected, unaffected = FormatAffectedUnaffected(triggerInfo, matchedUnits)
end
if triggerInfo.perUnitMode == "affected" then
for unit, bestMatch in pairs(matches) do
if bestMatch then
local role = roleForTriggerInfo(triggerInfo, unit)
local mark = markForTriggerInfo(triggerInfo, unit)
updated = UpdateStateWithMatch(time, bestMatch, triggerStates, unit, matchCount, unitCount, maxUnitCount,
matchCountPerUnit[unit], totalStacks, affected, unaffected, role, mark)
or updated
cloneIds[unit] = true
end
end
else
-- state per unaffected unit
for unit in GetAllUnits(triggerInfo.unit, nil, triggerInfo.includePets) do
if activeGroupScanFuncs[unit] and activeGroupScanFuncs[unit][triggerInfo] then
local bestMatch = matches[unit]
local role = roleForTriggerInfo(triggerInfo, unit)
local mark = markForTriggerInfo(triggerInfo, unit)
if bestMatch then
if triggerInfo.perUnitMode == "all" then
updated = UpdateStateWithMatch(time, bestMatch, triggerStates, unit, matchCount, unitCount, maxUnitCount,
matchCountPerUnit[unit], totalStacks, affected, unaffected, role, mark)
or updated
cloneIds[unit] = true
end
else
updated = UpdateStateWithNoMatch(time, triggerStates, triggerInfo, unit, unit, matchCount,
unitCount, maxUnitCount, matchCountPerUnit[unit], totalStacks,
affected, unaffected, role)
or updated
cloneIds[unit] = true
end
end
end
end
end
for cloneId, state in pairs(triggerStates) do
if not cloneIds[cloneId] then
updated = RemoveState(triggerStates, cloneId) or updated
end
end
end
if nextCheck then
if triggerInfo.nextScheduledCheck ~= nextCheck then
if triggerInfo.nextScheduledCheckHandle then
timer:CancelTimer(triggerInfo.nextScheduledCheckHandle)
end
triggerInfo.nextScheduledCheckHandle = timer:ScheduleTimerFixed(recheckTriggerInfo, nextCheck - time, triggerInfo)
triggerInfo.nextScheduledCheck = nextCheck
end
elseif triggerInfo.nextScheduledCheckHandle then
timer:CancelTimer(triggerInfo.nextScheduledCheckHandle)
triggerInfo.nextScheduledCheckHandle = nil
triggerInfo.nextScheduledCheck = nil
end
-- if the trigger has updated then check to see if it is flagged for WatchedTrigger and send to queue if it is
if updated then
if watched_trigger_events[id] and watched_trigger_events[id][triggernum] then
Private.AddToWatchedTriggerDelay(id, triggernum)
end
end
return updated
end
recheckTriggerInfo = function(triggerInfo)
matchDataChanged[triggerInfo.id] = matchDataChanged[triggerInfo.id] or {}
matchDataChanged[triggerInfo.id][triggerInfo.triggernum] = true
triggerInfo.nextScheduledCheckHandle = nil
triggerInfo.nextScheduledCheck = nil
end
local PrepareMatchData
do
local _time, _unit, _filter
local function HandleAura(aura)
if (not aura or not aura.name) then
return
end
local debuffClass = aura.dispelName
if debuffClass == nil then
debuffClass = "none"
elseif debuffClass == "" then
debuffClass = "enrage"
else
debuffClass = string.lower(debuffClass)
end
UpdateMatchData(_time, matchDataChanged, _unit, nil, aura.auraInstanceID, _filter, aura.name, aura.icon, aura.applications, debuffClass, aura.duration, aura.expirationTime, aura.sourceUnit, aura.isStealable, aura.isBossAura, aura.isFromPlayerOrPlayerPet, aura.spellId, aura.timeMod, aura.points)
end
PrepareMatchData = function(unit, filter)
if not matchDataUpToDate[unit] or not matchDataUpToDate[unit][filter] then
if newAPI then
_time = GetTime()
_unit = unit
_filter = filter
AuraUtil.ForEachAura(unit, filter, nil, HandleAura, true)
else
local time = GetTime()
local index = 1
while true do
local name, icon, stacks, debuffClass, duration, expirationTime, unitCaster, isStealable, _, spellId, _, isBossDebuff, isCastByPlayer, _, modRate = UnitAura(unit, index, filter)
if not name then
break
end
if debuffClass == nil then
debuffClass = "none"
elseif debuffClass == "" then
debuffClass = "enrage"
else
debuffClass = string.lower(debuffClass)
end
UpdateMatchData(time, matchDataChanged, unit, index, nil, filter, name, icon, stacks, debuffClass, duration, expirationTime, unitCaster, isStealable, isBossDebuff, isCastByPlayer, spellId, modRate, nil)
index = index + 1
end
end
matchDataUpToDate[unit] = matchDataUpToDate[unit] or {}
matchDataUpToDate[unit][filter] = true
end
end
end
local function CleanUpOutdatedMatchData(removeIndex, unit, filter)
-- Figure out if any matchData is outdated
if newAPI then
-- clean everything, as ScanUnitWithFilter is only used with index = 1 to wipe all data with newAPI
if matchData[unit] and matchData[unit][filter] then
for auraInstanceID, data in pairs(matchData[unit][filter]) do
for id, triggerData in pairs(data.auras) do
for triggernum in pairs(triggerData) do
matchDataByTrigger[id][triggernum][unit][auraInstanceID] = nil
matchDataChanged[id] = matchDataChanged[id] or {}
matchDataChanged[id][triggernum] = true
end
end
matchData[unit][filter][auraInstanceID] = nil
end
end
else
if matchData[unit] and matchData[unit][filter] then
for index = removeIndex, #matchData[unit][filter] do
local data = matchData[unit][filter][index]
if (data and data.index >= removeIndex) or not UnitExistsFixed(unit) then
matchData[unit][filter][index] = nil
for id, triggerData in pairs(data.auras) do
for triggernum in pairs(triggerData) do
matchDataByTrigger[id][triggernum][unit][index] = nil
matchDataChanged[id] = matchDataChanged[id] or {}
matchDataChanged[id][triggernum] = true
end
end
end
end
end
end
end
local function CleanUpMatchDataForUnit(unit, filter)
-- Figure out if any matchData is outdated
if matchData[unit] and matchData[unit][filter] then
for index, data in pairs(matchData[unit][filter]) do
matchData[unit][filter][index] = nil
for id, triggerData in pairs(data.auras) do
for triggernum in pairs(triggerData) do
if matchDataByTrigger[id] and matchDataByTrigger[id][triggernum]
and matchDataByTrigger[id][triggernum][unit]
and matchDataByTrigger[id][triggernum][unit][index]
then
matchDataByTrigger[id][triggernum][unit][index] = nil
matchDataChanged[id] = matchDataChanged[id] or {}
matchDataChanged[id][triggernum] = true
end
end
end
end
end
end
local function DeactivateScanFuncs(toDeactivate)
for unit, triggerInfosPerUnit in pairs(toDeactivate) do
for triggerInfo in pairs(triggerInfosPerUnit) do
local id = triggerInfo.id
local triggernum = triggerInfo.triggernum
local matches = GetSubTable(matchDataByTrigger, id, triggernum, unit)
if (matches) then
for index, match in pairs(matches) do
match.auras[id][triggernum] = nil
end
matchDataByTrigger[id][triggernum][unit] = nil
matchDataChanged[id] = matchDataChanged[id] or {}
matchDataChanged[id][triggernum] = true
end
end
end
end
local function CheckScanFuncs(scanFuncs, unit, filter, key)
if scanFuncs then
for triggerInfo in pairs(scanFuncs) do
if triggerInfo.fetchTooltip then
matchData[unit][filter][key]:UpdateTooltip(GetTime())
end
if not triggerInfo.scanFunc or triggerInfo.scanFunc(time, matchData[unit][filter][key]) then
local id = triggerInfo.id
local triggernum = triggerInfo.triggernum
ReferenceMatchData(id, triggernum, unit, filter, key)
matchDataChanged[id] = matchDataChanged[id] or {}
matchDataChanged[id][triggernum] = true
end
end
end
end
local ScanUnitWithFilter
do
local _matchDataChanged, _time, _unit, _filter, _scanFuncNameGroup, _scanFuncSpellIdGroup, _scanFuncGeneralGroup, _scanFuncName, _scanFuncSpellId, _scanFuncGeneral
local function HandleAura(aura)
if (not aura or not aura.name) then
return
end
local debuffClass = aura.dispelName
if debuffClass == nil then
debuffClass = "none"
elseif debuffClass == "" then
debuffClass = "enrage"
else
debuffClass = string.lower(debuffClass)
end
local name, spellId, auraInstanceID = aura.name, aura.spellId, aura.auraInstanceID
local updatedMatchData = UpdateMatchData(_time, _matchDataChanged, _unit, nil, auraInstanceID, _filter, name, aura.icon, aura.applications, debuffClass, aura.duration, aura.expirationTime, aura.sourceUnit, aura.isStealable, aura.isBossAura, aura.isFromPlayerOrPlayerPet, spellId, aura.timeMod, aura.points)
if updatedMatchData then -- Aura data changed, check against triggerInfos
CheckScanFuncs(_scanFuncName and _scanFuncName[name], _unit, _filter, auraInstanceID)
CheckScanFuncs(_scanFuncNameGroup and _scanFuncNameGroup[name], _unit, _filter, auraInstanceID)
CheckScanFuncs(_scanFuncSpellId and _scanFuncSpellId[spellId], _unit, _filter, auraInstanceID)
CheckScanFuncs(_scanFuncSpellIdGroup and _scanFuncSpellIdGroup[spellId], _unit, _filter, auraInstanceID)
CheckScanFuncs(_scanFuncGeneral, _unit, _filter, auraInstanceID)
CheckScanFuncs(_scanFuncGeneralGroup, _unit, _filter, auraInstanceID)
end
end
ScanUnitWithFilter = function(matchDataChanged, time, unit, filter, unitAuraUpdateInfo,
scanFuncNameGroup, scanFuncSpellIdGroup, scanFuncGeneralGroup,
scanFuncName, scanFuncSpellId, scanFuncGeneral)
if not scanFuncName and not scanFuncSpellId and not scanFuncGeneral and not scanFuncNameGroup and not scanFuncSpellIdGroup and not scanFuncGeneralGroup then
if matchDataUpToDate[unit] then
matchDataUpToDate[unit][filter] = nil
end
CleanUpOutdatedMatchData(1, unit, filter)
return
end
if UnitExistsFixed(unit) then
if newAPI then
-- copy parameters passed to ScanUnitWithFilter in parent's scope for HandleAura
_matchDataChanged, _time, _unit, _filter, _scanFuncNameGroup, _scanFuncSpellIdGroup, _scanFuncGeneralGroup, _scanFuncName, _scanFuncSpellId, _scanFuncGeneral = matchDataChanged, time, unit, filter, scanFuncNameGroup, scanFuncSpellIdGroup, scanFuncGeneralGroup, scanFuncName, scanFuncSpellId, scanFuncGeneral
if unitAuraUpdateInfo then
-- incremental
if unitAuraUpdateInfo.addedAuras ~= nil then
for _, aura in ipairs(unitAuraUpdateInfo.addedAuras) do
if (aura.isHelpful and filter == "HELPFUL") or (aura.isHarmful and filter == "HARMFUL") then
HandleAura(aura)
end
end
end
if unitAuraUpdateInfo.updatedAuraInstanceIDs ~= nil then
for _, auraInstanceID in ipairs(unitAuraUpdateInfo.updatedAuraInstanceIDs) do
local aura = C_UnitAuras.GetAuraDataByAuraInstanceID(unit, auraInstanceID)
if aura and ((aura.isHelpful and filter == "HELPFUL") or (aura.isHarmful and filter == "HARMFUL")) then
HandleAura(aura)
end
end
end
if unitAuraUpdateInfo.removedAuraInstanceIDs ~= nil then
for _, auraInstanceID in ipairs(unitAuraUpdateInfo.removedAuraInstanceIDs) do
if matchData[unit] and matchData[unit][filter] then
local data = matchData[unit][filter][auraInstanceID]
if data then
matchData[unit][filter][auraInstanceID] = nil
for id, triggerData in pairs(data.auras) do
for triggernum in pairs(triggerData) do
matchDataByTrigger[id][triggernum][unit][auraInstanceID] = nil
matchDataChanged[id] = matchDataChanged[id] or {}
matchDataChanged[id][triggernum] = true
end
end
end
end
end
end
else
-- full
-- clean first
CleanUpOutdatedMatchData(nil, unit, filter)
AuraUtil.ForEachAura(unit, filter, nil, HandleAura, true)
end
else
local index = 1
while true do
local name, icon, stacks, debuffClass, duration, expirationTime, unitCaster, isStealable, _, spellId, _, isBossDebuff, isCastByPlayer, _, modRate = UnitAura(unit, index, filter)
if not name then
break
end
if debuffClass == nil then
debuffClass = "none"
elseif debuffClass == "" then
debuffClass = "enrage"
else
debuffClass = string.lower(debuffClass)
end
local updatedMatchData = UpdateMatchData(time, matchDataChanged, unit, index, nil, filter, name, icon, stacks, debuffClass, duration, expirationTime, unitCaster, isStealable, isBossDebuff, isCastByPlayer, spellId, modRate, nil)
if updatedMatchData then -- Aura data changed, check against triggerInfos
CheckScanFuncs(scanFuncName and scanFuncName[name], unit, filter, index)
CheckScanFuncs(scanFuncNameGroup and scanFuncNameGroup[name], unit, filter, index)
CheckScanFuncs(scanFuncSpellId and scanFuncSpellId[spellId], unit, filter, index)
CheckScanFuncs(scanFuncSpellIdGroup and scanFuncSpellIdGroup[spellId], unit, filter, index)
CheckScanFuncs(scanFuncGeneral, unit, filter, index)
CheckScanFuncs(scanFuncGeneralGroup, unit, filter, index)
end
index = index + 1
end
CleanUpOutdatedMatchData(index, unit, filter)
end
end
matchDataUpToDate[unit] = matchDataUpToDate[unit] or {}
matchDataUpToDate[unit][filter] = true
end
end
local function UpdateStates(matchDataChanged, time)
for id, auraData in pairs(matchDataChanged) do
Private.StartProfileAura(id)
local updated = false
for triggernum in pairs(auraData) do
updated = UpdateTriggerState(time, id, triggernum) or updated
end
if updated then
Private.UpdatedTriggerState(id)
end
Private.StopProfileAura(id)
end
end
local function ScanGroupRoleScanFunc(matchDataChanged)
for id, idData in pairs(groupRoleScanFunc) do
matchDataChanged[id] = matchDataChanged[id] or {}
for _, triggerInfo in ipairs(idData) do
matchDataChanged[id][triggerInfo.triggernum] = true
end
end
end
local function ScanRaidMarkScanFunc(matchDataChanged)
for id, idData in pairs(raidMarkScanFuncs) do
matchDataChanged[id] = matchDataChanged[id] or {}
for _, triggerInfo in ipairs(idData) do
matchDataChanged[id][triggerInfo.triggernum] = true
end
end
end
local function ScanGroupUnit(time, matchDataChanged, unitType, unit, unitAuraUpdateInfo)
local unitExists = UnitExistsFixed(unit)
if existingUnits[unit] ~= unitExists then
existingUnits[unit] = unitExists
if unitExistScanFunc[unit] then
for id, idData in pairs(unitExistScanFunc[unit]) do
matchDataChanged[id] = matchDataChanged[id] or {}
for _, triggerInfo in ipairs(idData) do
matchDataChanged[id][triggerInfo.triggernum] = true
end
end
end
end
scanFuncName[unit] = scanFuncName[unit] or {}
scanFuncSpellId[unit] = scanFuncSpellId[unit] or {}
scanFuncGeneral[unit] = scanFuncGeneral[unit] or {}
if unitType then
scanFuncNameGroup[unit] = scanFuncNameGroup[unit] or {}
scanFuncSpellIdGroup[unit] = scanFuncSpellIdGroup[unit] or {}
scanFuncGeneralGroup[unit] = scanFuncGeneralGroup[unit] or {}
ScanUnitWithFilter(matchDataChanged, time, unit, "HELPFUL", unitAuraUpdateInfo,
scanFuncNameGroup[unit]["HELPFUL"],
scanFuncSpellIdGroup[unit]["HELPFUL"],
scanFuncGeneralGroup[unit]["HELPFUL"],
scanFuncName[unit]["HELPFUL"],
scanFuncSpellId[unit]["HELPFUL"],
scanFuncGeneral[unit]["HELPFUL"])
ScanUnitWithFilter(matchDataChanged, time, unit, "HARMFUL", unitAuraUpdateInfo,
scanFuncNameGroup[unit]["HARMFUL"],
scanFuncSpellIdGroup[unit]["HARMFUL"],
scanFuncGeneralGroup[unit]["HARMFUL"],
scanFuncName[unit]["HARMFUL"],
scanFuncSpellId[unit]["HARMFUL"],
scanFuncGeneral[unit]["HARMFUL"])
else
ScanUnitWithFilter(matchDataChanged, time, unit, "HELPFUL", unitAuraUpdateInfo, nil, nil, nil,
scanFuncName[unit]["HELPFUL"],
scanFuncSpellId[unit]["HELPFUL"],
scanFuncGeneral[unit]["HELPFUL"])
ScanUnitWithFilter(matchDataChanged, time, unit, "HARMFUL", unitAuraUpdateInfo, nil, nil, nil,
scanFuncName[unit]["HARMFUL"],
scanFuncSpellId[unit]["HARMFUL"],
scanFuncGeneral[unit]["HARMFUL"])
end
end
local function ScanUnit(time, unit, unitAuraUpdateInfo)
if (Private.multiUnitUnits.raid[unit] and IsInRaid()) then
ScanGroupUnit(time, matchDataChanged, "group", unit, unitAuraUpdateInfo)
elseif (Private.multiUnitUnits.party[unit] and not IsInRaid()) then
ScanGroupUnit(time, matchDataChanged, "group", unit, unitAuraUpdateInfo)
elseif Private.multiUnitUnits.boss[unit] then
ScanGroupUnit(time, matchDataChanged, "boss", unit, unitAuraUpdateInfo)
elseif Private.multiUnitUnits.arena[unit] then
ScanGroupUnit(time, matchDataChanged, "arena", unit, unitAuraUpdateInfo)
elseif unit:sub(1, 9) == "nameplate" then
ScanGroupUnit(time, matchDataChanged, "nameplate", unit, unitAuraUpdateInfo)
else
ScanGroupUnit(time, matchDataChanged, nil, unit, unitAuraUpdateInfo)
end
end
local function AddScanFuncs(triggerInfo, filter, unit, scanFuncName, scanFuncSpellId, scanFuncGeneral)
if triggerInfo.auranames then
for _, name in ipairs(triggerInfo.auranames) do
if name ~= "" then
local base = unit and GetOrCreateSubTable(scanFuncName, unit, filter, name) or GetOrCreateSubTable(scanFuncName, filter, name)
base[triggerInfo] = true
end
end
end
if triggerInfo.auraspellids then
for _, spellId in ipairs(triggerInfo.auraspellids) do
local base = unit and GetOrCreateSubTable(scanFuncSpellId, unit, filter, spellId) or GetOrCreateSubTable(scanFuncSpellId, filter, spellId)
base[triggerInfo] = true
end
end
if not triggerInfo.auranames and not triggerInfo.auraspellids and scanFuncGeneral then
local base = GetOrCreateSubTable(scanFuncGeneral, unit, filter)
base[triggerInfo] = true
end
if unit then
PrepareMatchData(unit, filter)
ScanMatchData(GetTime(), triggerInfo, unit, filter)
end
end
local function RemoveScanFuncs(triggerInfo, filter, unit, scanFuncName, scanFuncSpellId, scanFuncGeneral)
if triggerInfo.auranames then
for _, name in ipairs(triggerInfo.auranames) do
if name ~= "" then
local base = unit and GetSubTable(scanFuncName, unit, filter, name) or GetSubTable(scanFuncName, filter, name)
if base then
base[triggerInfo] = nil
end
end
end
end
if triggerInfo.auraspellids then
for _, spellId in ipairs(triggerInfo.auraspellids) do
local base = unit and GetSubTable(scanFuncSpellId, unit, filter, spellId) or GetSubTable(scanFuncSpellId, filter, spellId)
if base then
base[triggerInfo] = nil
end
end
end
if not triggerInfo.auranames and not triggerInfo.auraspellids and scanFuncGeneral then
local base = GetSubTable(scanFuncGeneral, unit, filter)
if base then
base[triggerInfo] = nil
end
end
end
local function RecheckActive(triggerInfo, unit, unitsToRemoveScan)
local isSelf, role, inParty, class
local unitExists = UnitExistsFixed(unit)
if unitExists and TriggerInfoApplies(triggerInfo, unit) then
if (not activeGroupScanFuncs[unit] or not activeGroupScanFuncs[unit][triggerInfo]) then
triggerInfo.maxUnitCount = triggerInfo.maxUnitCount + 1
if triggerInfo.debuffType == "BOTH" then
AddScanFuncs(triggerInfo, "HELPFUL", unit, scanFuncNameGroup, scanFuncSpellIdGroup, scanFuncGeneralGroup)
AddScanFuncs(triggerInfo, "HARMFUL", unit, scanFuncNameGroup, scanFuncSpellIdGroup, scanFuncGeneralGroup)
else
AddScanFuncs(triggerInfo, triggerInfo.debuffType, unit, scanFuncNameGroup, scanFuncSpellIdGroup, scanFuncGeneralGroup)
end
activeGroupScanFuncs[unit] = activeGroupScanFuncs[unit] or {}
activeGroupScanFuncs[unit][triggerInfo] = true
matchDataChanged[triggerInfo.id] = matchDataChanged[triggerInfo.id] or {}
matchDataChanged[triggerInfo.id][triggerInfo.triggernum] = true
end
else
-- Either the unit doesn't exist or the TriggerInfo no longer applies
if activeGroupScanFuncs[unit] and activeGroupScanFuncs[unit][triggerInfo] then
triggerInfo.maxUnitCount = triggerInfo.maxUnitCount - 1
if triggerInfo.debuffType == "BOTH" then
RemoveScanFuncs(triggerInfo, "HELPFUL", unit, scanFuncNameGroup, scanFuncSpellIdGroup, scanFuncGeneralGroup)
RemoveScanFuncs(triggerInfo, "HARMFUL", unit, scanFuncNameGroup, scanFuncSpellIdGroup, scanFuncGeneralGroup)
else
RemoveScanFuncs(triggerInfo, triggerInfo.debuffType, unit, scanFuncNameGroup, scanFuncSpellIdGroup, scanFuncGeneralGroup)
end
if unitsToRemoveScan then
unitsToRemoveScan[unit] = unitsToRemoveScan[unit] or {}
unitsToRemoveScan[unit][triggerInfo] = true
end
activeGroupScanFuncs[unit][triggerInfo] = nil
matchDataChanged[triggerInfo.id] = matchDataChanged[triggerInfo.id] or {}
matchDataChanged[triggerInfo.id][triggerInfo.triggernum] = true
end
end
end
local function RecheckActiveForUnitType(unitType, unit, unitsToRemoveScan)
if groupScanFuncs[unitType] then
for i, triggerInfo in ipairs(groupScanFuncs[unitType]) do
RecheckActive(triggerInfo, unit, unitsToRemoveScan)
end
end
end
local Buff2Frame = CreateFrame("Frame")
Private.frames["WeakAuras Buff2 Frame"] = Buff2Frame
local function EventHandler(frame, event, arg1, arg2, ...)
Private.StartProfileSystem("bufftrigger2")
local deactivatedTriggerInfos = {}
local unitsToRemove = {}
local time = GetTime()
local targetUnit = Private.player_target_events[event]
if targetUnit then
ScanGroupUnit(time, matchDataChanged, nil, targetUnit)
if not UnitExistsFixed(targetUnit) then
tinsert(unitsToRemove, targetUnit)
end
elseif event == "UNIT_PET" then
local pet = WeakAuras.unitToPetUnit[arg1]
if pet then
ScanGroupUnit(time, matchDataChanged, "group", pet)
RecheckActiveForUnitType("group", pet, deactivatedTriggerInfos)
if not UnitExistsFixed(pet) then
tinsert(unitsToRemove, pet)
end
end
elseif event == "NAME_PLATE_UNIT_ADDED" then
nameplateExists[arg1] = true
RecheckActiveForUnitType("nameplate", arg1, deactivatedTriggerInfos)
elseif event == "NAME_PLATE_UNIT_REMOVED" then
nameplateExists[arg1] = false
RecheckActiveForUnitType("nameplate", arg1, deactivatedTriggerInfos)
tinsert(unitsToRemove, arg1)
elseif event == "UNIT_FACTION" then
if arg1:sub(1, 9) == "nameplate" then
RecheckActiveForUnitType("nameplate", arg1, deactivatedTriggerInfos)
end
elseif event == "ENCOUNTER_START" or event == "ENCOUNTER_END" or event == "INSTANCE_ENCOUNTER_ENGAGE_UNIT" then
for unit in GetAllUnits("boss", true) do
RecheckActiveForUnitType("boss", unit, deactivatedTriggerInfos)
if not UnitExistsFixed(unit) then
tinsert(unitsToRemove, unit)
else
if newAPI then
ScanUnit(time, unit)
end
end
end
elseif event =="ARENA_OPPONENT_UPDATE" then
for unit in GetAllUnits("arena", true) do
RecheckActiveForUnitType("arena", unit, deactivatedTriggerInfos)
if not UnitExistsFixed(unit) then
tinsert(unitsToRemove, unit)
end
end
elseif event == "GROUP_ROSTER_UPDATE" then
unitVisible = {}
for unit in GetAllUnits("group", true, "PlayersAndPets") do
RecheckActiveForUnitType("group", unit, deactivatedTriggerInfos)
if not UnitExistsFixed(unit) then
tinsert(unitsToRemove, unit)
end
end
ScanGroupRoleScanFunc(matchDataChanged)
elseif event == "UNIT_FLAGS" or event == "UNIT_NAME_UPDATE" or event == "PLAYER_FLAGS_CHANGED"
or event == "PARTY_MEMBER_ENABLE" or event == "PARTY_MEMBER_DISABLE"
then
if event == "PARTY_MEMBER_ENABLE" then
unitVisible[arg1] = true
elseif event == "PARTY_MEMBER_DISABLE" then
unitVisible[arg1] = false
end
if Private.multiUnitUnits.group[arg1] then
RecheckActiveForUnitType("group", arg1, deactivatedTriggerInfos)
end
elseif event == "UNIT_ENTERED_VEHICLE" or event == "UNIT_EXITED_VEHICLE" then
if arg1 == "player" then
ScanGroupUnit(time, matchDataChanged, nil, "vehicle")
end
elseif event == "UNIT_AURA" then
if newAPI then
-- arg1: unit
-- arg2: unitAuraUpdateInfo
if arg2 == nil or arg2.isFullUpdate then
ScanUnit(time, arg1)
else
ScanUnit(time, arg1, arg2)
end
else
ScanUnit(time, arg1)
end
elseif event == "PLAYER_ENTERING_WORLD" then
for unit in pairs(matchData) do
ScanUnit(time, unit)
if not UnitExistsFixed(unit) then
tinsert(unitsToRemove, unit)
end
end
elseif event == "RAID_TARGET_UPDATE" then
ScanRaidMarkScanFunc(matchDataChanged)
end
DeactivateScanFuncs(deactivatedTriggerInfos)
for i, unit in ipairs(unitsToRemove) do
CleanUpMatchDataForUnit(unit, "HELPFUL")
CleanUpMatchDataForUnit(unit, "HARMFUL")
matchDataUpToDate[unit] = nil
end
Private.StopProfileSystem("bufftrigger2")
end
if WeakAuras.IsRetail() then
Private.LibSpecWrapper.Register(function(unit)
Private.StartProfileSystem("bufftrigger2")
local deactivatedTriggerInfos = {}
RecheckActiveForUnitType("group", unit, deactivatedTriggerInfos)
RecheckActiveForUnitType("group", WeakAuras.unitToPetUnit[unit], deactivatedTriggerInfos)
DeactivateScanFuncs(deactivatedTriggerInfos)
Private.StopProfileSystem("bufftrigger2")
end)
end
Buff2Frame:RegisterEvent("UNIT_AURA")
Buff2Frame:RegisterEvent("UNIT_FACTION")
Buff2Frame:RegisterEvent("UNIT_NAME_UPDATE")
Buff2Frame:RegisterEvent("UNIT_FLAGS")
Buff2Frame:RegisterEvent("PLAYER_FLAGS_CHANGED")
Buff2Frame:RegisterEvent("UNIT_PET")
Buff2Frame:RegisterEvent("RAID_TARGET_UPDATE")
if not WeakAuras.IsClassicEra() then
Buff2Frame:RegisterEvent("PLAYER_FOCUS_CHANGED")
if WeakAuras.IsWrathOrRetail() then
Buff2Frame:RegisterEvent("PLAYER_SOFT_ENEMY_CHANGED")
Buff2Frame:RegisterEvent("PLAYER_SOFT_FRIEND_CHANGED")
Buff2Frame:RegisterEvent("ARENA_OPPONENT_UPDATE")
end
Buff2Frame:RegisterEvent("UNIT_ENTERED_VEHICLE")
Buff2Frame:RegisterEvent("UNIT_EXITED_VEHICLE")
else
LCD.RegisterCallback("WeakAuras", "UNIT_BUFF", function(event, unit)
EventHandler(Buff2Frame, "UNIT_AURA", unit)
end)
end
Buff2Frame:RegisterEvent("PLAYER_TARGET_CHANGED")
Buff2Frame:RegisterEvent("ENCOUNTER_START")
Buff2Frame:RegisterEvent("ENCOUNTER_END")
Buff2Frame:RegisterEvent("INSTANCE_ENCOUNTER_ENGAGE_UNIT")
Buff2Frame:RegisterEvent("NAME_PLATE_UNIT_ADDED")
Buff2Frame:RegisterEvent("NAME_PLATE_UNIT_REMOVED")
Buff2Frame:RegisterEvent("GROUP_ROSTER_UPDATE")
Buff2Frame:RegisterEvent("PLAYER_ENTERING_WORLD")
Buff2Frame:RegisterEvent("PARTY_MEMBER_DISABLE")
Buff2Frame:RegisterEvent("PARTY_MEMBER_ENABLE")
Buff2Frame:SetScript("OnEvent", EventHandler)
Buff2Frame:SetScript("OnUpdate", function()
if WeakAuras.IsPaused() then
return
end
Private.StartProfileSystem("bufftrigger2")
if next(matchDataChanged) then
local time = GetTime()
UpdateStates(matchDataChanged, time)
wipe(matchDataChanged)
end
Private.StopProfileSystem("bufftrigger2")
end)
local function UnloadAura(scanFuncName, id)
for unit, unitData in pairs(scanFuncName) do
for debuffType, debuffData in pairs(unitData) do
for name, nameData in pairs(debuffData) do
for triggerInfo in pairs(nameData) do
if triggerInfo.id == id or not id then
if triggerInfo.nextScheduledCheckHandle then
timer:CancelTimer(triggerInfo.nextScheduledCheckHandle)
end
nameData[triggerInfo] = nil
end
end
if not next(nameData) then
debuffData[name] = nil
end
end
if not next(debuffData) then
unitData[debuffType] = nil
end
end
if not next(unitData) then
scanFuncName[unit] = nil
end
end
end
local function UnloadGeneral(scanFuncGeneral, id)
for unit, unitData in pairs(scanFuncGeneral) do
for debuffType, debuffData in pairs(unitData) do
for triggerInfo in pairs(debuffData) do
if triggerInfo.id == id or not id then
if triggerInfo.nextScheduledCheckHandle then
timer:CancelTimer(triggerInfo.nextScheduledCheckHandle)
end
debuffData[triggerInfo] = nil
end
end
if not next(debuffData) then
unitData[debuffType] = nil
end
end
if not next(unitData) then
scanFuncGeneral[unit] = nil
end
end
end
function BuffTrigger.UnloadAll()
UnloadAura(scanFuncName, nil)
UnloadAura(scanFuncSpellId, nil)
UnloadGeneral(scanFuncGeneral, nil)
for unit, unitData in pairs(matchData) do
for filter, filterData in pairs(unitData) do
for index, indexData in pairs(filterData) do
wipe(indexData.auras)
end
end
end
wipe(scanFuncName)
wipe(scanFuncSpellId)
wipe(scanFuncGeneral)
wipe(scanFuncNameGroup)
wipe(scanFuncSpellIdGroup)
wipe(scanFuncGeneralGroup)
wipe(scanFuncNameMulti)
wipe(scanFuncSpellIdMulti)
wipe(unitExistScanFunc)
wipe(groupRoleScanFunc)
wipe(groupScanFuncs)
wipe(raidMarkScanFuncs)
wipe(matchDataByTrigger)
wipe(matchDataMulti)
wipe(matchDataChanged)
wipe(activeGroupScanFuncs)
end
local function LoadAura(id, triggernum, triggerInfo)
if not triggerInfo.unit then
return
end
local time = GetTime();
local unitsToCheck = {}
if triggerInfo.unit == "multi" then
if triggerInfo.debuffType == "BOTH" then
AddScanFuncs(triggerInfo, "HELPFUL", nil, scanFuncNameMulti, scanFuncSpellIdMulti, nil)
AddScanFuncs(triggerInfo, "HARMFUL", nil, scanFuncNameMulti, scanFuncSpellIdMulti, nil)
else
AddScanFuncs(triggerInfo, triggerInfo.debuffType, nil, scanFuncNameMulti, scanFuncSpellIdMulti, nil)
end
elseif triggerInfo.groupTrigger then
triggerInfo.maxUnitCount = 0
for unit in GetAllUnits(triggerInfo.unit, nil, triggerInfo.includePets) do
RecheckActive(triggerInfo, unit, unitsToCheck)
end
else
if triggerInfo.debuffType == "BOTH" then
AddScanFuncs(triggerInfo, "HELPFUL", triggerInfo.unit, scanFuncName, scanFuncSpellId, scanFuncGeneral)
AddScanFuncs(triggerInfo, "HARMFUL", triggerInfo.unit, scanFuncName, scanFuncSpellId, scanFuncGeneral)
else
AddScanFuncs(triggerInfo, triggerInfo.debuffType, triggerInfo.unit, scanFuncName, scanFuncSpellId, scanFuncGeneral)
end
unitsToCheck[triggerInfo.unit] = true
end
if triggerInfo.unitExists ~= nil then
unitExistScanFunc[triggerInfo.unit] = unitExistScanFunc[triggerInfo.unit] or {}
unitExistScanFunc[triggerInfo.unit][id] = unitExistScanFunc[triggerInfo.unit][id] or {}
tinsert(unitExistScanFunc[triggerInfo.unit][id], triggerInfo)
if existingUnits[triggerInfo.unit] == nil then
existingUnits[triggerInfo.unit] = UnitExistsFixed(triggerInfo.unit)
end
end
if triggerInfo.fetchRole then
groupRoleScanFunc[id] = groupRoleScanFunc[id] or {}
tinsert(groupRoleScanFunc[id], triggerInfo)
end
if triggerInfo.fetchRaidMark then
raidMarkScanFuncs[id] = raidMarkScanFuncs[id] or {}
tinsert(raidMarkScanFuncs[id], triggerInfo)
end
if triggerInfo.groupTrigger then
groupScanFuncs[triggerInfo.unit] = groupScanFuncs[triggerInfo.unit] or {}
tinsert(groupScanFuncs[triggerInfo.unit], triggerInfo)
end
matchDataChanged[id] = matchDataChanged[id] or {}
matchDataChanged[id][triggernum] = true
end
function BuffTrigger.LoadDisplays(toLoad)
for id in pairs(toLoad) do
if triggerInfos[id] then
for triggernum, triggerInfo in pairs(triggerInfos[id]) do
LoadAura(id, triggernum, triggerInfo)
end
end
end
end
function BuffTrigger.UnloadDisplays(toUnload)
local updateGroupScanFuncs = false
for id in pairs(toUnload) do
UnloadAura(scanFuncName, id)
UnloadAura(scanFuncSpellId, id)
UnloadGeneral(scanFuncGeneral, id)
UnloadAura(scanFuncNameGroup, id)
UnloadAura(scanFuncSpellIdGroup, id)
UnloadGeneral(scanFuncGeneralGroup, id)
UnloadGeneral(scanFuncNameMulti, id)
UnloadGeneral(scanFuncSpellIdMulti, id)
for unit, unitData in pairs(unitExistScanFunc) do
unitData[id] = nil
end
groupRoleScanFunc[id] = nil
raidMarkScanFuncs[id] = nil
for unit, unitData in pairs(matchData) do
for filter, filterData in pairs(unitData) do
for index, indexData in pairs(filterData) do
indexData.auras[id] = nil
end
end
end
matchDataByTrigger[id] = nil
for guid, guidData in pairs(matchDataMulti) do
for key, data in pairs(matchDataMulti[guid]) do
for source, sourceData in pairs(data) do
sourceData.auras[id] = nil
end
end
end
matchDataChanged[id] = nil
end
for unitType, funcs in pairs(groupScanFuncs) do
for i = #funcs, 1, -1 do
if toUnload[funcs[i].id] then
tremove(funcs, i)
end
end
end
for unit, unitData in pairs(activeGroupScanFuncs) do
for triggerInfo in pairs(unitData) do
if toUnload[triggerInfo.id] then
unitData[triggerInfo] = nil
end
end
end
end
function BuffTrigger.FinishLoadUnload()
-- Nothing!
end
--- Removes all data for an aura id
--- @param id number
function BuffTrigger.Delete(id)
BuffTrigger.UnloadDisplays({[id] = true})
triggerInfos[id] = nil
end
--- Updates all data for aura oldid to use newid
--- @param oldid number
--- @param newid number
function BuffTrigger.Rename(oldid, newid)
triggerInfos[newid] = triggerInfos[oldid]
triggerInfos[oldid] = nil
if triggerInfos[newid] then
for triggernum, triggerData in pairs(triggerInfos[newid]) do
triggerData.id = newid
end
end
matchDataByTrigger[newid] = matchDataByTrigger[oldid]
matchDataByTrigger[oldid] = nil
for unit, unitData in pairs(matchData) do
for filter, filterData in pairs(unitData) do
for index, indexData in pairs(filterData) do
indexData.auras[newid] = indexData.auras[oldid]
indexData.auras[oldid] = nil
end
end
end
for unit, unitData in pairs(unitExistScanFunc) do
unitData[newid] = unitData[oldid]
unitData[oldid] = nil
end
groupRoleScanFunc[newid] = groupRoleScanFunc[oldid]
groupRoleScanFunc[oldid] = nil
raidMarkScanFuncs[newid] = raidMarkScanFuncs[oldid]
raidMarkScanFuncs[oldid] = nil
matchDataChanged[newid] = matchDataChanged[oldid]
matchDataChanged[oldid] = nil
end
local function createScanFunc(trigger)
local canHaveMatchCheck = CanHaveMatchCheck(trigger)
local isMulti = trigger.unit == "multi"
local useStacks = canHaveMatchCheck and not isMulti and trigger.useStacks
local use_stealable, use_isBossDebuff, use_castByPlayer
if canHaveMatchCheck and not isMulti then
use_stealable = trigger.use_stealable
use_isBossDebuff = trigger.use_isBossDebuff
use_castByPlayer = trigger.use_castByPlayer
end
local use_debuffClass = canHaveMatchCheck and not isMulti and trigger.use_debuffClass
local use_tooltip = canHaveMatchCheck and not isMulti and trigger.fetchTooltip and trigger.use_tooltip
local use_tooltipValue = canHaveMatchCheck and not isMulti and trigger.fetchTooltip and trigger.use_tooltipValue
local use_total = canHaveMatchCheck and not isMulti and trigger.useTotal and trigger.total
local use_ignore_name = canHaveMatchCheck and not isMulti and trigger.useIgnoreName and trigger.ignoreAuraNames
local use_ignore_spellId = canHaveMatchCheck and not isMulti and trigger.useIgnoreExactSpellId and trigger.ignoreAuraSpellids
if not useStacks and use_stealable == nil and use_isBossDebuff == nil and use_castByPlayer == nil
and not use_debuffClass and trigger.ownOnly == nil
and not use_tooltip and not use_tooltipValue and not trigger.useNamePattern and not use_total
and not use_ignore_name and not use_ignore_spellId then
return nil
end
local preamble = ""
local ret = [=[
return function(time, matchData)
]=]
if use_total then
local ret2 = [=[
if not(matchData.duration / matchData.modRate %s %s) then
return false
end
]=]
ret = ret .. ret2:format(trigger.totalOperator or ">=", tonumber(trigger.total) or 0)
end
if useStacks then
local ret2 = [=[
if not(matchData.stacks %s %s) then
return false
end
]=]
ret = ret .. ret2:format(trigger.stacksOperator or ">=", tonumber(trigger.stacks) or 0)
end
if use_stealable then
ret = ret .. [=[
if not matchData.isStealable then
return false
end
]=]
elseif use_stealable == false then
ret = ret .. [=[
if matchData.isStealable then
return false
end
]=]
end
if use_isBossDebuff then
ret = ret .. [=[
if not matchData.isBossDebuff then
return false
end
]=]
elseif use_isBossDebuff == false then
ret = ret .. [=[
if matchData.isBossDebuff then
return false
end
]=]
end
if use_castByPlayer then
ret = ret .. [=[
if not matchData.isCastByPlayer then
return false
end
]=]
elseif use_castByPlayer == false then
ret = ret .. [=[
if matchData.isCastByPlayer then
return false
end
]=]
end
if use_debuffClass then
local ret2 = [=[
local tDebuffClass = %s;
if not tDebuffClass[matchData.debuffClass] then
return false
end
]=]
ret = ret .. ret2:format(trigger.debuffClass and type(trigger.debuffClass) == "table" and Private.SerializeTable(trigger.debuffClass) or "{}")
end
if trigger.ownOnly then
ret = ret .. [=[
if matchData.unitCaster ~= 'player' and matchData.unitCaster ~= 'pet' and matchData.unitCaster ~= 'vehicle' then
return false
end
]=]
elseif trigger.ownOnly == false then
ret = ret .. [=[
if matchData.unitCaster == 'player' or matchData.unitCaster == 'pet' or matchData.unitCaster == 'vehicle' then
return false
end
]=]
end
if use_tooltip and trigger.tooltip_operator and trigger.tooltip then
if trigger.tooltip_operator == "==" then
local ret2 = [=[
if not matchData.tooltip or matchData.tooltip ~= %s then
return false
end
]=]
ret = ret .. ret2:format(Private.QuotedString(trigger.tooltip))
elseif trigger.tooltip_operator == "find('%s')" then
local ret2 = [=[
if not matchData.tooltip or not matchData.tooltip:find(%s, 1, true) then
return false
end
]=]
ret = ret .. ret2:format(Private.QuotedString(trigger.tooltip))
elseif trigger.tooltip_operator == "match('%s')" then
local ret2 = [=[
if not matchData.tooltip or not matchData.tooltip:match(%s) then
return false
end
]=]
ret = ret .. ret2:format(Private.QuotedString(trigger.tooltip))
end
end
if use_tooltipValue and trigger.tooltipValueNumber and trigger.tooltipValue_operator and trigger.tooltipValue then
local property = "tooltip" .. tonumber(trigger.tooltipValueNumber)
local ret2 = [=[
if not matchData.%s or not (matchData.%s %s %s) then
return false
end
]=]
ret = ret .. ret2:format(property, property, trigger.tooltipValue_operator, trigger.tooltipValue)
end
if trigger.useNamePattern and trigger.namePattern_operator and trigger.namePattern_name then
if trigger.namePattern_operator == "==" then
local ret2 = [=[
if not matchData.name == %s then
return false
end
]=]
ret = ret .. ret2:format(Private.QuotedString(trigger.namePattern_name))
elseif trigger.namePattern_operator == "find('%s')" then
local ret2 = [=[
if not matchData.name:find(%s, 1, true) then
return false
end
]=]
ret = ret .. ret2:format(Private.QuotedString(trigger.namePattern_name))
elseif trigger.namePattern_operator == "match('%s')" then
local ret2 = [=[
if not matchData.name:match(%s) then
return false
end
]=]
ret = ret .. ret2:format(Private.QuotedString(trigger.namePattern_name))
end
end
if use_ignore_name then
local names = {}
for index, spellName in ipairs(trigger.ignoreAuraNames) do
local spellId = WeakAuras.SafeToNumber(spellName)
local name = GetSpellInfo(spellId) or spellName
tinsert(names, name)
end
preamble = preamble .. "local ignoreNames = {\n"
for index, name in ipairs(names) do
preamble = preamble .. string.format(" [%q] = true,\n", name)
end
preamble = preamble .. "}\n"
ret = ret .. [=[
if ignoreNames[matchData.name] then
return false
end
]=]
end
if use_ignore_spellId then
preamble = preamble .. "local ignoreSpellId = {\n"
for index, spellId in ipairs(trigger.ignoreAuraSpellids) do
local spell = WeakAuras.SafeToNumber(spellId)
if spell then
preamble = preamble .. string.format(" [%s] = true,\n", spell)
end
end
preamble = preamble .. "}\n"
ret = ret .. [=[
if ignoreSpellId[matchData.spellId] then
return false
end
]=]
end
ret = ret .. [=[
return true
end
]=]
local func, err = loadstring(preamble .. ret)
if func then
return func()
end
end
local function highestExpirationTime(bestMatch, auraMatch)
if bestMatch.expirationTime and auraMatch.expirationTime then
return auraMatch.expirationTime > bestMatch.expirationTime
end
return true
end
local function lowestExpirationTime(bestMatch, auraMatch)
if bestMatch.expirationTime and auraMatch.expirationTime then
return auraMatch.expirationTime < bestMatch.expirationTime
end
return false
end
local function GreaterEqualOne(x)
return x >= 1
end
local function EqualZero(x)
return x == 0
end
local function InitProblems()
return {
untrackableSoftTarget = {
severity = "info",
message = L["A trigger in this aura is set up to track a soft target unit, but you don't have the CVars set up for this to work correctly. Consider either changing the unit tracked, or configuring the Soft Target CVars."],
flagged = false,
check = function(trigger)
return WeakAuras.IsUntrackableSoftTarget(trigger.unit)
end
}
}
end
local function CheckProblems(trigger, problems)
for _, problem in pairs(problems) do
if not problem.flagged and problem.check(trigger) then
problem.flagged = true
break
end
end
end
local function PublishProblems(problems, uid)
for key, problem in pairs(problems) do
if problem.flagged then
Private.AuraWarnings.UpdateWarning(uid, key, problem.severity, problem.message, problem.printOnConsole)
else
Private.AuraWarnings.UpdateWarning(uid, key)
end
end
end
--- Adds an aura, setting up internal data structures for all buff triggers.
--- @param data auraData
function BuffTrigger.Add(data)
local id = data.id
triggerInfos[id] = nil
local problems = InitProblems()
for triggernum, triggerData in ipairs(data.triggers) do
local trigger = triggerData.trigger
if trigger.type == "aura2" then
trigger.unit = trigger.unit or "player"
trigger.debuffType = trigger.debuffType or "HELPFUL"
local combineMode = "showOne"
local perUnitMode
CheckProblems(trigger, problems)
if not IsSingleMissing(trigger) and trigger.showClones then
if IsGroupTrigger(trigger) and trigger.combinePerUnit then
combineMode = "showPerUnit"
if trigger.unit == "multi" then
perUnitMode = "affected"
else
perUnitMode = trigger.perUnitMode or "affected"
end
else
combineMode = "showClones"
end
end
local scanFunc = createScanFunc(trigger)
local remFunc
if trigger.unit ~= "multi" and CanHaveMatchCheck(trigger) and trigger.useRem then
local remFuncStr = Private.function_strings.count:format(trigger.remOperator or ">=", tonumber(trigger.rem) or 0)
remFunc = Private.LoadFunction(remFuncStr)
end
local names
if trigger.useName and trigger.auranames then
names = {}
for index, spellName in ipairs(trigger.auranames) do
local spellId = WeakAuras.SafeToNumber(spellName)
names[index] = GetSpellInfo(spellId) or spellName
end
end
local showIfInvalidUnit
if trigger.unit ~= "player" and not IsGroupTrigger(trigger) then
showIfInvalidUnit = trigger.unitExists or false
end
local effectiveUseGroupCount = IsGroupTrigger(trigger) and trigger.useGroup_count
local groupCountFunc
if effectiveUseGroupCount then
local group_countFuncStr
local count, countType = Private.ParseNumber(trigger.group_count)
if trigger.group_countOperator and count and countType then
if countType == "whole" then
group_countFuncStr = Private.function_strings.count:format(trigger.group_countOperator, count)
else
group_countFuncStr = Private.function_strings.count_fraction:format(trigger.group_countOperator, count)
end
else
group_countFuncStr = Private.function_strings.count:format(">", 0)
end
groupCountFunc = Private.LoadFunction(group_countFuncStr)
end
local matchCountFunc
if HasMatchCount(trigger) and trigger.match_countOperator and trigger.match_count and tonumber(trigger.match_count) then
local count = tonumber(trigger.match_count)
local match_countFuncStr = Private.function_strings.count:format(trigger.match_countOperator, count)
matchCountFunc = Private.LoadFunction(match_countFuncStr)
elseif IsGroupTrigger(trigger) then
if trigger.showClones and not trigger.combinePerUnit then
matchCountFunc = GreaterEqualOne
end
elseif not IsGroupTrigger(trigger) then
if trigger.matchesShowOn == "showOnMissing" then
matchCountFunc = EqualZero
elseif trigger.matchesShowOn == "showOnActive" or not trigger.matchesShowOn then
matchCountFunc = GreaterEqualOne
end
end
local matchPerUnitCountFunc
if IsGroupTrigger(trigger) and combineMode == "showPerUnit" and perUnitMode ~= "unaffected" and trigger.useMatchPerUnit_count
and tonumber(trigger.matchPerUnit_count) and trigger.matchPerUnit_countOperator then
local count = tonumber(trigger.matchPerUnit_count)
local match_countFuncStr = Private.function_strings.count:format(trigger.matchPerUnit_countOperator, count)
matchPerUnitCountFunc = Private.LoadFunction(match_countFuncStr)
end
local groupTrigger = trigger.unit == "group" or trigger.unit == "raid" or trigger.unit == "party"
local effectiveIgnoreSelf = (groupTrigger or trigger.unit == "nameplate") and trigger.ignoreSelf
local effectiveGroupRole = WeakAuras.IsWrathOrRetail() and (groupTrigger and trigger.useGroupRole and trigger.group_role) or nil
local effectiveRaidRole = WeakAuras.IsClassicEraOrWrath() and (groupTrigger and trigger.useRaidRole and trigger.raid_role) or nil
local effectiveClass = groupTrigger and trigger.useClass and trigger.class
local effectiveSpecId = WeakAuras.IsRetail() and (groupTrigger and trigger.useActualSpec and trigger.actualSpec) or nil
local effectiveArenaSpec = WeakAuras.IsRetail() and (trigger.unit == "arena" and trigger.useArenaSpec and trigger.arena_spec) or nil
local effectiveHostility = trigger.unit == "nameplate" and trigger.useHostility and trigger.hostility
local effectiveIgnoreDead = groupTrigger and trigger.ignoreDead
local effectiveIgnoreDisconnected = groupTrigger and trigger.ignoreDisconnected
local effectiveIgnoreInvisible = groupTrigger and trigger.ignoreInvisible
local effectiveNameCheck = groupTrigger and trigger.useUnitName and trigger.unitName
local effectiveNpcId = trigger.unit == "nameplate" and trigger.useNpcId and trigger.npcId
if trigger.unit == "multi" then
BuffTrigger.InitMultiAura()
end
local auraspellids
if trigger.useExactSpellId and trigger.auraspellids then
auraspellids = {}
for _, spellIdString in ipairs(trigger.auraspellids) do
if spellIdString ~= "" then
local spellId = tonumber(spellIdString)
if spellId then
tinsert(auraspellids, spellId)
end
end
end
end
local unit
local groupSubType = "group"
if trigger.unit == "member" then
unit = trigger.specificUnit
elseif trigger.unit == "raid" then
unit = "group"
groupSubType = "raid"
elseif trigger.unit == "party" then
unit = "group"
groupSubType = "party"
else
unit = trigger.unit
end
local triggerInformation = {
auranames = names,
auraspellids = auraspellids,
unit = unit,
debuffType = trigger.debuffType,
ownOnly = trigger.ownOnly,
combineMode = combineMode,
perUnitMode = perUnitMode,
scanFunc = scanFunc,
remainingFunc = remFunc,
remainingCheck = trigger.unit ~= "multi" and CanHaveMatchCheck(trigger) and trigger.useRem and tonumber(trigger.rem) or 0,
id = id,
triggernum = triggernum,
compareFunc = trigger.combineMode == "showHighest" and highestExpirationTime or lowestExpirationTime,
unitExists = showIfInvalidUnit,
fetchTooltip = not IsSingleMissing(trigger) and trigger.unit ~= "multi" and trigger.fetchTooltip,
fetchRole = WeakAuras.IsRetail() and trigger.unit ~= "multi" and trigger.fetchRole,
fetchRaidMark = trigger.unit ~= "multi" and trigger.fetchRaidMark,
groupTrigger = IsGroupTrigger(trigger),
ignoreSelf = effectiveIgnoreSelf,
ignoreDead = effectiveIgnoreDead,
ignoreDisconnected = effectiveIgnoreDisconnected,
ignoreInvisible = effectiveIgnoreInvisible,
groupRole = effectiveGroupRole,
raidRole = effectiveRaidRole,
specId = effectiveSpecId,
arenaSpec = effectiveArenaSpec,
groupSubType = groupSubType,
groupCountFunc = groupCountFunc,
class = effectiveClass,
hostility = effectiveHostility,
matchCountFunc = matchCountFunc,
matchPerUnitCountFunc = matchPerUnitCountFunc,
useAffected = unit == "group" and trigger.useAffected,
isMulti = trigger.unit == "multi",
nameChecker = effectiveNameCheck and Private.ExecEnv.ParseNameCheck(trigger.unitName),
includePets = trigger.use_includePets and trigger.includePets or nil,
npcId = effectiveNpcId
}
triggerInfos[id] = triggerInfos[id] or {}
triggerInfos[id][triggernum] = triggerInformation
end
end
PublishProblems(problems, data.uid)
end
--- Returns whether the trigger can have a duration.
--- @param data table
--- @param triggernum number
function BuffTrigger.CanHaveDuration(data, triggernum)
return "timed"
end
--- Returns a table containing the names of all overlays
--- @param data table
--- @param triggernum number
function BuffTrigger.GetOverlayInfo(data, triggernum)
return {}
end
--- Returns whether the trigger can have clones.
--- @param data table
--- @param triggernum number
--- @return boolean
function BuffTrigger.CanHaveClones(data, triggernum)
local trigger = data.triggers[triggernum].trigger
if not IsSingleMissing(trigger) and trigger.showClones then
return true
end
return false
end
---Returns the type of tooltip to show for the trigger.
--- @param data table
--- @param triggernum number
--- @return string
function BuffTrigger.CanHaveTooltip(data, triggernum)
return "aura"
end
--- @return boolean
function BuffTrigger.SetToolTip(trigger, state)
if newAPI then
if not state.unit or not state.auraInstanceID then
return false
end
if state.filter == "HELPFUL" then
GameTooltip:SetUnitBuffByAuraInstanceID(state.unit, state.auraInstanceID, state.filter)
elseif state.filter == "HARMFUL" then
GameTooltip:SetUnitDebuffByAuraInstanceID(state.unit, state.auraInstanceID, state.filter)
end
else
if not state.unit or not state.index then
return false
end
if state.filter == "HELPFUL" then
GameTooltip:SetUnitBuff(state.unit, state.index)
elseif state.filter == "HARMFUL" then
GameTooltip:SetUnitDebuff(state.unit, state.index)
end
end
return true
end
function BuffTrigger.GetNameAndIconSimple(data, triggernum)
if not data then
return
end
local _, name, icon
local trigger = data.triggers[triggernum].trigger
if trigger.useName and trigger.auranames then
for index, spellName in ipairs(trigger.auranames) do
local spellId = WeakAuras.SafeToNumber(spellName)
if spellId then
name, _, icon = GetSpellInfo(spellName)
if name and icon then
return name, icon
end
elseif not tonumber(spellName) then
name, _, icon = GetSpellInfo(spellName)
if (name and icon) then
return name, icon
end
end
end
end
if trigger.useExactSpellId and trigger.auraspellids then
for index, spellIdString in ipairs(trigger.auraspellids) do
local spellId = spellIdString ~= "" and tonumber(spellIdString)
if spellId then
name, _, icon = GetSpellInfo(spellIdString)
if name and icon then
return name, icon
end
end
end
end
end
--- Returns the name and icon to show in the options.
--- @param data table
--- @param triggernum number
--- @return string|nil name, any icon
function BuffTrigger.GetNameAndIcon(data, triggernum)
local name, icon = BuffTrigger.GetNameAndIconSimple(data, triggernum)
if (not name or not icon and WeakAuras.spellCache) then
local trigger = data.triggers[triggernum].trigger
if trigger.useName and trigger.auranames then
for index, spellName in ipairs(trigger.auranames) do
icon = WeakAuras.spellCache.GetIcon(spellName)
if icon then
return spellName, icon
end
end
end
end
return name, icon
end
--- Returns the tooltip text for additional properties.
--- @param data table
--- @param triggernum number
--- @return string @additional properties
function BuffTrigger.GetAdditionalProperties(data, triggernum)
local trigger = data.triggers[triggernum].trigger
local ret = "|cFFFF0000%".. triggernum .. ".spellId|r - " .. L["Spell ID"] .. "\n"
ret = ret .. "|cFFFF0000%".. triggernum .. ".debuffClass|r - " .. L["Debuff Class"] .. "\n"
ret = ret .. "|cFFFF0000%".. triggernum .. ".debuffClassIcon|r - " .. L["Debuff Class Icon"] .. "\n"
ret = ret .. "|cFFFF0000%".. triggernum .. ".unitCaster|r - " .. L["Caster Unit"] .. "\n"
ret = ret .. "|cFFFF0000%".. triggernum .. ".casterName|r - " .. L["Caster Name"] .. "\n"
if trigger.unit ~= "multi" then
ret = ret .. "|cFFFF0000%".. triggernum .. ".unit|r - " .. L["Unit"] .. "\n"
end
ret = ret .. "|cFFFF0000%".. triggernum .. ".unitName|r - " .. L["Unit Name"] .. "\n"
ret = ret .. "|cFFFF0000%".. triggernum .. ".matchCount|r - " .. L["Match Count"] .. "\n"
ret = ret .. "|cFFFF0000%".. triggernum .. ".matchCountPerUnit|r - " .. L["Match Count per Unit"] .. "\n"
ret = ret .. "|cFFFF0000%".. triggernum .. ".unitCount|r - " .. L["Units Affected"] .. "\n"
ret = ret .. "|cFFFF0000%".. triggernum .. ".totalStacks|r - " .. L["Total stacks over all matches"] .. "\n"
if trigger.unit ~= "multi" then
ret = ret .. "|cFFFF0000%".. triggernum .. ".maxUnitCount|r - " .. L["Total Units"] .. "\n"
end
if not IsSingleMissing(trigger) and trigger.unit ~= "multi" and trigger.fetchTooltip then
ret = ret .. "|cFFFF0000%".. triggernum .. ".tooltip|r - " .. L["Tooltip"] .. "\n"
ret = ret .. "|cFFFF0000%".. triggernum .. ".tooltip1|r - " .. L["First Value of Tooltip Text"] .. "\n"
ret = ret .. "|cFFFF0000%".. triggernum .. ".tooltip2|r - " .. L["Second Value of Tooltip Text"] .. "\n"
ret = ret .. "|cFFFF0000%".. triggernum .. ".tooltip3|r - " .. L["Third Value of Tooltip Text"] .. "\n"
ret = ret .. "|cFFFF0000%".. triggernum .. ".tooltip4|r - " .. L["Fourth Value of Tooltip Text"] .. "\n"
end
if WeakAuras.IsRetail() and trigger.unit ~= "multi" and trigger.fetchRole then
ret = ret .. "|cFFFF0000%".. triggernum .. ".role|r - " .. L["Assigned Role"] .. "\n"
ret = ret .. "|cFFFF0000%".. triggernum .. ".roleIcon|r - " .. L["Assigned Role Icon"] .. "\n"
end
if trigger.unit ~= "multi" and trigger.fetchRaidMark then
ret = ret .. "|cFFFF0000%".. triggernum .. ".raidMark|r - " .. L["Raid Mark"] .. "\n"
end
if (trigger.unit == "group" or trigger.unit == "raid" or trigger.unit == "party") and trigger.useAffected then
ret = ret .. "|cFFFF0000%".. triggernum .. ".affected|r - " .. L["Names of affected Players"] .. "\n"
ret = ret .. "|cFFFF0000%".. triggernum .. ".unaffected|r - " .. L["Names of unaffected Players"] .. "\n"
end
return ret
end
function BuffTrigger.GetTriggerConditions(data, triggernum)
local trigger = data.triggers[triggernum].trigger
local result = {}
result["debuffClass"] = {
display = L["Debuff Type"],
type = "select",
values = Private.debuff_class_types
}
result["unitCaster"] = {
display = L["Caster"],
type = "string"
}
result["expirationTime"] = {
display = L["Remaining Duration"],
type = "timer",
useModRate = true
}
result["duration"] = {
display = L["Total Duration"],
type = "number",
useModRate = true
}
result["stacks"] = {
display = L["Stacks"],
type = "number"
}
result["name"] = {
display = L["Name"],
type = "string"
}
result["spellId"] = {
display = L["Spell Id"],
type = "number",
operator_types = "only_equal"
}
result["matchCount"] = {
display = L["Total Match Count"],
type = "number"
}
result["matchCountPerUnit"] = {
display = L["Match Count per Unit"],
type = "number"
}
result["unitCount"] = {
display = L["Affected Unit Count"],
type = "number"
}
result["totalStacks"] = {
display = L["Total Stacks"],
type = "number"
}
if trigger.unit ~= "multi" then
result["maxUnitCount"] = {
display = L["Total Unit Count"],
type = "number"
}
end
if not IsGroupTrigger(trigger) and trigger.matchesShowOn == "showAlways"
or IsGroupTrigger(trigger) and trigger.showClones and trigger.unit ~= "multi" and trigger.combinePerUnit
then
result["buffed"] = {
display = L["Aura(s) Found"],
type = "bool",
test = function(state, needle)
return state and state.show and ((state.active and true or false) == (needle == 1))
end
}
end
if not IsSingleMissing(trigger) and trigger.unit ~= "multi" and trigger.fetchTooltip then
result["tooltip1"] = {
display = L["Tooltip Value 1"],
type = "number"
}
result["tooltip2"] = {
display = L["Tooltip Value 2"],
type = "number"
}
result["tooltip3"] = {
display = L["Tooltip Value 3"],
type = "number"
}
result["tooltip4"] = {
display = L["Tooltip Value 4"],
type = "number"
}
end
if trigger.unit ~= "multi" then
result["stackGainTime"] = {
display = L["Since Stack Gain"],
type = "elapsedTimer"
}
result["stackLostTime"] = {
display = L["Since Stack Lost"],
type = "elapsedTimer"
}
result["initialTime"] = {
display = L["Since Apply"],
type = "elapsedTimer"
}
result["refreshTime"] = {
display = L["Since Apply/Refresh"],
type = "elapsedTimer"
}
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
state.modRate = 1
local name, icon = BuffTrigger.GetNameAndIconSimple(data, triggernum)
state.name = name
state.icon = icon
end
function BuffTrigger.GetName(triggerType)
if triggerType == "aura2" then
return L["Aura"]
end
end
function Private.CanConvertBuffTrigger2(trigger)
if trigger.type ~= "aura" then
return false
end
if trigger.unit == "multi" then
return true, L["Note: The available text replacements for multi triggers match the normal triggers now."]
end
if trigger.unit and trigger.hideAlone then
return false, L["Note: 'Hide Alone' is not available in the new aura tracking system. A load option can be used instead."]
end
if trigger.unit == "group" then
return true, L["Warning: Name info is now available via %affected, %unaffected. Number of affected group members via %unitCount. Some options behave differently now. This is not automatically adjusted."]
end
if trigger.fullscan then
if trigger.subcount then
return true, L["Warning: Tooltip values are now available via %tooltip1, %tooltip2, %tooltip3 instead of %s. This is not automatically adjusted."]
end
if trigger.use_name and trigger.use_spellId then
return false, L["Warning: Full Scan auras checking for both name and spell id can't be converted."]
end
end
return true
end
function Private.ConvertBuffTrigger2(trigger)
if not Private.CanConvertBuffTrigger2(trigger) then
return
end
trigger.type = "aura2"
if trigger.fullscan and trigger.autoclone then
trigger.combineMatches = "showClones"
else
trigger.combineMatches = "showLowest"
end
if trigger.fullscan and trigger.use_stealable then
trigger.use_stealable = true
else
trigger.use_stealable = nil
end
if trigger.fullscan and trigger.use_debuffClass and trigger.debuffClass then
else
trigger.use_debuffClass = false
end
if trigger.fullscan and trigger.use_tooltip then
trigger.fetchTooltip = true
else
trigger.use_tooltip = false
end
if trigger.fullscan and trigger.subcount then
trigger.fetchTooltip = true
end
if trigger.fullscan and trigger.use_name then
trigger.useNamePattern = true
trigger.namePattern_operator = trigger.name_operator
trigger.namePattern_name = trigger.name
end
if trigger.fullscan then
-- Use name from fullscan
if trigger.use_name then
if trigger.name_operator == "==" then
-- Convert to normal name check
trigger.useName = true
trigger.auranames = {}
trigger.auranames[1] = trigger.name
end
end
if trigger.use_spellId then
trigger.useExactSpellId = true
trigger.auraspellids = {}
trigger.auraspellids[1] = trigger.spellId
end
else
trigger.useName = true
end
if not trigger.fullscan and trigger.unit ~= "multi" then
trigger.auranames = {}
for i = 1, 9 do
trigger.auranames[i] = trigger.spellIds[i] and tostring(trigger.spellIds[i]) or trigger.names[i]
end
end
if trigger.unit == "multi" then
-- Closest to the old behavior
trigger.showClones = true
trigger.useName = true
trigger.auranames = {}
trigger.auranames[1] = tostring(trigger.spellId) or trigger.name
end
-- debuffType is exactly the same, no need to touch it
-- remaining is exactly the same for now
-- ownOnly is exactly the same
-- unitExists is exactly the same
if trigger.useCount then
trigger.useStacks = trigger.useCount
trigger.stacksOperator = trigger.countOperator
trigger.stacks = trigger.count
end
if trigger.fullscan and trigger.autoclone then
trigger.matchesShowOn = "showOnActive"
else
trigger.matchesShowOn = trigger.buffShowOn
end
if trigger.unit == "group" then
trigger.matchesShowOn = nil
trigger.showClones = trigger.groupclone
end
if trigger.unit == "group" and not trigger.groupclone then
if trigger.name_info == "players" or trigger.name_info == "nonplayers" then
trigger.useAffected = true
end
end
if trigger.unit == "group" and trigger.group_countOperator and trigger.group_count then
trigger.useGroup_count = true
else
trigger.useGroup_count = false
end
end
-- Multi Target trigger code
local multiAuraFrame
local pendingTracks = {}
local unitToGuid = {}
local guidToUnit = {}
local function ReleaseUID(unit)
local guid = unitToGuid[unit]
if guid then
guidToUnit[guid][unit] = nil
end
end
local function SetUID(guid, unit)
ReleaseUID(unit)
unitToGuid[unit] = guid
guidToUnit[guid] = guidToUnit[guid] or {}
guidToUnit[guid][unit] = true
end
local function GetUnit(guid)
if not guidToUnit[guid] then
return nil
end
for unit in pairs(guidToUnit[guid]) do
if UnitGUID(unit) == guid then
return unit
else
guidToUnit[guid][unit] = nil
end
end
end
local function TrackUid(unit)
local GUID = UnitGUID(unit)
if GUID then
SetUID(GUID, unit)
BuffTrigger.HandlePendingTracks(unit, GUID)
else
ReleaseUID(unit)
end
unit = unit.."target"
GUID = UnitGUID(unit)
if GUID then
SetUID(GUID, unit)
BuffTrigger.HandlePendingTracks(unit, GUID)
else
ReleaseUID(unit)
end
end
local function RemoveMatchDataMulti(base, destGUID, key, sourceGUID)
if base[key] and base[key][sourceGUID] then
for id, idData in pairs(base[key][sourceGUID].auras) do
for triggernum, triggerData in pairs(idData) do
tDeleteItem(matchDataByTrigger[id][triggernum][destGUID], base[key][sourceGUID])
if not next(matchDataByTrigger[id][triggernum][destGUID]) then
matchDataByTrigger[id][triggernum][destGUID] = nil
end
matchDataChanged[id] = matchDataChanged[id] or {}
matchDataChanged[id][triggernum] = true
end
end
base[key][sourceGUID] = nil
end
end
local function CleanUpMulti(guid)
cleanupTimerMulti[guid].handle = nil
cleanupTimerMulti[guid].nextTime = nil
local nextCheck
if matchDataMulti[guid] then
local time = GetTime()
for key, data in pairs(matchDataMulti[guid]) do
for source, sourceData in pairs(data) do
local removeAt
if sourceData.expirationTime and sourceData.expirationTime ~= math.huge then
removeAt = sourceData.expirationTime
else
removeAt = sourceData.time + 60
end
if removeAt <= time then
RemoveMatchDataMulti(matchDataMulti[guid], guid, key, source)
else
if not nextCheck then
nextCheck = removeAt
elseif (removeAt < nextCheck) then
nextCheck = removeAt
end
end
end
end
end
if nextCheck then
local timeUntilNext = nextCheck - GetTime()
if timeUntilNext > 0 then
cleanupTimerMulti[guid].handle = timer:ScheduleTimerFixed(CleanUpMulti, timeUntilNext, guid)
cleanupTimerMulti[guid].nextTime = nextCheck
end
end
end
local function ScheduleMultiCleanUp(guid, time)
cleanupTimerMulti[guid] = cleanupTimerMulti[guid] or {}
if not cleanupTimerMulti[guid].nextTime or time < cleanupTimerMulti[guid].nextTime then
if cleanupTimerMulti[guid].handle then
timer:CancelTimer(cleanupTimerMulti[guid].handle)
end
cleanupTimerMulti[guid].handle = timer:ScheduleTimerFixed(CleanUpMulti, time - GetTime(), guid)
cleanupTimerMulti[guid].nextTime = time
end
end
local function UpdateMatchDataMulti(time, base, key, event, sourceGUID, sourceName, destGUID, destName, spellId, spellName, amount)
local updated = false
local icon = spellId and select(3, GetSpellInfo(spellId))
ScheduleMultiCleanUp(destGUID, time + 60)
if not base[key] or not base[key][sourceGUID] then
updated = true
base[key] = base[key] or {}
base[key][sourceGUID] = {
name = spellName,
icon = icon,
duration = 0,
expirationTime = math.huge,
modRate = 1,
spellId = spellId,
GUID = destGUID,
sourceGUID = sourceGUID,
unitName = destName,
casterName = sourceName,
time = time,
auras = {}
}
else
base[key][sourceGUID] = base[key][sourceGUID] or {}
local match = base[key][sourceGUID]
match.time = time
if match.name ~= spellName then
match.name = spellName
updated = true
end
if match.unitName ~= destName then
match.unitName = destName
updated = true
end
local duration, expirationTime
if event == "SPELL_AURA_APPLIED_DOSE" or event == "SPELL_AURA_REMOVED_DOSE" then
-- Shouldn't affect duration/expirationTime nor icon
duration = match.duration or 0
expirationTime = match.expirationTime or math.huge
icon = match.icon or icon
else
duration = 0
expirationTime = math.huge
end
if match.duration ~= duration then
match.duration = duration
updated = true
end
if match.expirationTime ~= expirationTime then
match.expirationTime = expirationTime
updated = true
end
if match.icon ~= icon then
match.icon = icon
updated = true
end
if match.count ~= amount then
match.count = amount
updated = true
end
if match.spellId ~= spellId then
match.spellId = spellId
updated = true
end
if match.casterName ~= sourceName then
match.casterName = sourceName
updated = true
end
end
return updated
end
local function AugmentMatchDataMultiWith(matchData, unit, name, icon, stacks, debuffClass, duration, expirationTime, unitCaster, isStealable, _, spellId, _, _, _, _, modRate)
if expirationTime == 0 then
expirationTime = math.huge
else
ScheduleMultiCleanUp(matchData.GUID, expirationTime / (modRate or 1))
end
local changed = false
if matchData.name ~= name then
matchData.name = name
changed = true
end
if matchData.icon ~= icon then
matchData.icon = icon
changed = true
end
if matchData.stacks ~= stacks then
matchData.stacks = stacks
changed = true
end
if matchData.debuffClass ~= debuffClass then
matchData.debuffClass = debuffClass
changed = true
end
local debuffClassIcon = WeakAuras.EJIcons[debuffClass]
if matchData.debuffClassIcon ~= debuffClassIcon then
matchData.debuffClassIcon = debuffClassIcon
changed = true
end
if matchData.duration ~= duration then
matchData.duration = duration
changed = true
end
if matchData.expirationTime ~= expirationTime then
matchData.expirationTime = expirationTime
changed = true
end
if matchData.modRate ~= modRate then
matchData.modRate = modRate
changed = true
end
if matchData.unitCaster ~= unitCaster then
matchData.unitCaster = unitCaster
changed = true
end
local casterName = GetUnitName(unitCaster, false) or ""
if matchData.casterName ~= casterName then
matchData.casterName = casterName
changed = true
end
local unitName = GetUnitName(unit, false) or ""
if matchData.unitName ~= unitName then
matchData.unitName = unitName
changed = true
end
if matchData.spellId ~= spellId then
matchData.spellId = name
changed = true
end
return changed
end
local AugmentMatchDataMulti
do
local _matchData, _unit, _sourceGUID, _nameKey, _spellKey
local function HandleAura(aura)
if (not aura or not aura.name) then
return
end
local debuffClass = aura.dispelName
if debuffClass == nil then
debuffClass = "none"
elseif debuffClass == "" then
debuffClass = "enrage"
else
debuffClass = string.lower(debuffClass)
end
local auraSourceGuid = aura.sourceUnit and UnitGUID(aura.sourceUnit)
local name = aura.name
local spellId = aura.spellId
if (name == _nameKey or spellId == _spellKey) and _sourceGUID == auraSourceGuid then
local changed = AugmentMatchDataMultiWith(_matchData, _unit, name, aura.icon, aura.applications, debuffClass, aura.duration, aura.expirationTime, aura.sourceUnit, aura.isStealable, aura.isBossAura, aura.isFromPlayerOrPlayerPet, spellId, aura.timeMod)
return changed
end
end
AugmentMatchDataMulti = function(matchData, unit, filter, sourceGUID, nameKey, spellKey)
if newAPI then
_matchData, _unit, _sourceGUID, _nameKey, _spellKey = matchData, unit, sourceGUID, nameKey, spellKey
AuraUtil.ForEachAura(unit, filter, nil, HandleAura, true)
else
local index = 1
while true do
local name, icon, stacks, debuffClass, duration, expirationTime, unitCaster, isStealable, _, spellId, _, _, _, _, modRate = UnitAura(unit, index, filter)
if not name then
return false
end
if debuffClass == nil then
debuffClass = "none"
elseif debuffClass == "" then
debuffClass = "enrage"
else
debuffClass = string.lower(debuffClass)
end
local auraSourceGuid = unitCaster and UnitGUID(unitCaster)
if (name == nameKey or spellId == spellKey) and sourceGUID == auraSourceGuid then
local changed = AugmentMatchDataMultiWith(matchData, unit, name, icon, stacks, debuffClass, duration, expirationTime, unitCaster, isStealable, _, spellId, _, _, _, _, modRate)
return changed
end
index = index + 1
end
end
end
end
local function HandleCombatLog(scanFuncsName, scanFuncsSpellId, filter, event, sourceGUID, sourceName, destGUID, destName, spellId, spellName, amount)
local time = GetTime()
local unit = GetUnit(destGUID)
if scanFuncsName and scanFuncsName[spellName] or scanFuncsSpellId and scanFuncsSpellId[spellId] then
ScheduleMultiCleanUp(destGUID, time + 60)
matchDataMulti[destGUID] = matchDataMulti[destGUID] or {}
if scanFuncsSpellId and scanFuncsSpellId[spellId] then
local updatedSpellId = UpdateMatchDataMulti(time, matchDataMulti[destGUID], spellId, event, sourceGUID, sourceName, destGUID, destName, spellId, spellName, amount)
if unit then
updatedSpellId = AugmentMatchDataMulti(matchDataMulti[destGUID][spellId][sourceGUID], unit, filter, sourceGUID, nil, spellId) or updatedSpellId
else
pendingTracks[destGUID] = true
end
if updatedSpellId then
for triggerInfo in pairs(scanFuncsSpellId[spellId]) do
if MatchesTriggerInfoMulti(triggerInfo, sourceGUID) then
ReferenceMatchDataMulti(matchDataMulti[destGUID][spellId][sourceGUID], triggerInfo.id, triggerInfo.triggernum, destGUID)
end
end
end
end
if scanFuncsName and scanFuncsName[spellName] then
local updatedName = UpdateMatchDataMulti(time, matchDataMulti[destGUID], spellName, event, sourceGUID, sourceName, destGUID, destName, spellId, spellName, amount)
if unit then
updatedName = AugmentMatchDataMulti(matchDataMulti[destGUID][spellName][sourceGUID], unit, filter, sourceGUID, spellName, nil) or updatedName
else
pendingTracks[destGUID] = true
end
if updatedName then
for triggerInfo in pairs(scanFuncsName[spellName]) do
if MatchesTriggerInfoMulti(triggerInfo, sourceGUID) then
ReferenceMatchDataMulti(matchDataMulti[destGUID][spellName][sourceGUID], triggerInfo.id, triggerInfo.triggernum, destGUID)
end
end
end
end
end
end
local function HandleCombatLogRemove(scanFuncsName, scanFuncsSpellId, sourceGUID, destGUID, spellId, spellName)
if scanFuncsName and scanFuncsName[spellName] or scanFuncsSpellId and scanFuncsSpellId[spellId] then
if matchDataMulti[destGUID] then
RemoveMatchDataMulti(matchDataMulti[destGUID], destGUID, spellId, sourceGUID)
RemoveMatchDataMulti(matchDataMulti[destGUID], destGUID, spellName, sourceGUID)
end
end
end
local function CombatLog(_, event, _, sourceGUID, sourceName, _, _, destGUID, destName, _, _, spellId, spellName, _, auraType, amount)
if event == "SPELL_AURA_APPLIED" or event == "SPELL_AURA_REFRESH" or event == "SPELL_AURA_APPLIED_DOSE" or event == "SPELL_AURA_REMOVED_DOSE" then
if auraType == "BUFF" then
HandleCombatLog(scanFuncNameMulti["HELPFUL"], scanFuncSpellIdMulti["HELPFUL"], "HELPFUL", event, sourceGUID, sourceName, destGUID, destName, spellId, spellName, amount)
elseif auraType == "DEBUFF" then
HandleCombatLog(scanFuncNameMulti["HARMFUL"], scanFuncSpellIdMulti["HARMFUL"], "HARMFUL", event, sourceGUID, sourceName, destGUID, destName, spellId, spellName, amount)
end
elseif event == "SPELL_AURA_REMOVED" then
if auraType == "BUFF" then
HandleCombatLogRemove(scanFuncNameMulti["HELPFUL"], scanFuncSpellIdMulti["HELPFUL"], sourceGUID, destGUID, spellId, spellName)
elseif auraType == "DEBUFF" then
HandleCombatLogRemove(scanFuncNameMulti["HARMFUL"], scanFuncSpellIdMulti["HARMFUL"], sourceGUID, destGUID, spellId, spellName)
end
end
end
local CheckAurasMulti
do
local _base, _unit
local function HandleAura(aura)
if (not aura or not aura.name) then
return
end
local debuffClass = aura.dispelName
if debuffClass == nil then
debuffClass = "none"
elseif debuffClass == "" then
debuffClass = "enrage"
else
debuffClass = string.lower(debuffClass)
end
local auraCasterGUID = aura.sourceUnit and UnitGUID(aura.sourceUnit)
local name = aura.name
local spellId = aura.spellId
if _base[name] and _base[name][auraCasterGUID] then
local changed = AugmentMatchDataMultiWith(_base[name][auraCasterGUID], _unit, name, aura.icon, aura.applications, debuffClass, aura.duration, aura.expirationTime, aura.sourceUnit, aura.isStealable, aura.isBossAura, aura.isFromPlayerOrPlayerPet, spellId, aura.timeMod)
if changed then
for id, idData in pairs(_base[name][auraCasterGUID].auras) do
for triggernum in pairs(idData) do
matchDataChanged[id] = matchDataChanged[id] or {}
matchDataChanged[id][triggernum] = true
end
end
end
end
if _base[spellId] and _base[spellId][auraCasterGUID] then
local changed = AugmentMatchDataMultiWith(_base[spellId][auraCasterGUID], _unit, aura.name, aura.icon, aura.applications, debuffClass, aura.duration, aura.expirationTime, aura.sourceUnit, aura.isStealable, aura.isBossAura, aura.isFromPlayerOrPlayerPet, spellId, aura.timeMod)
if changed then
for id, idData in pairs(_base[spellId][auraCasterGUID].auras) do
for triggernum in pairs(idData) do
matchDataChanged[id] = matchDataChanged[id] or {}
matchDataChanged[id][triggernum] = true
end
end
end
end
end
CheckAurasMulti = function(base, unit, filter)
if newAPI then
_base = base
_unit = unit
AuraUtil.ForEachAura(unit, filter, nil, HandleAura, true)
else
local index = 1
while true do
local name, icon, stacks, debuffClass, duration, expirationTime, unitCaster, isStealable, _, spellId, _, _, _, _, modRate = UnitAura(unit, index, filter)
if not name then
return false
end
if debuffClass == nil then
debuffClass = "none"
elseif debuffClass == "" then
debuffClass = "enrage"
else
debuffClass = string.lower(debuffClass)
end
local auraCasterGUID = unitCaster and UnitGUID(unitCaster)
if base[name] and base[name][auraCasterGUID] then
local changed = AugmentMatchDataMultiWith(base[name][auraCasterGUID], unit, name, icon, stacks, debuffClass, duration, expirationTime, unitCaster, isStealable, _, spellId, _, _, _, _, modRate)
if changed then
for id, idData in pairs(base[name][auraCasterGUID].auras) do
for triggernum in pairs(idData) do
matchDataChanged[id] = matchDataChanged[id] or {}
matchDataChanged[id][triggernum] = true
end
end
end
end
if base[spellId] and base[spellId][auraCasterGUID] then
local changed = AugmentMatchDataMultiWith(base[spellId][auraCasterGUID], unit, name, icon, stacks, debuffClass, duration, expirationTime, unitCaster, isStealable, _, spellId, _, _, _, _, modRate)
if changed then
for id, idData in pairs(base[spellId][auraCasterGUID].auras) do
for triggernum in pairs(idData) do
matchDataChanged[id] = matchDataChanged[id] or {}
matchDataChanged[id][triggernum] = true
end
end
end
end
index = index + 1
end
end
end
end
function BuffTrigger.HandlePendingTracks(unit, GUID)
if pendingTracks[GUID] then
if matchDataMulti[GUID] then
CheckAurasMulti(matchDataMulti[GUID], unit, "HELPFUL")
CheckAurasMulti(matchDataMulti[GUID], unit, "HARMFUL")
end
end
end
function BuffTrigger.InitMultiAura()
if not multiAuraFrame then
multiAuraFrame = CreateFrame("Frame")
multiAuraFrame:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED")
multiAuraFrame:RegisterEvent("UNIT_TARGET")
multiAuraFrame:RegisterEvent("UNIT_AURA")
multiAuraFrame:RegisterEvent("PLAYER_TARGET_CHANGED")
if not WeakAuras.IsClassicEra() then
multiAuraFrame:RegisterEvent("PLAYER_SOFT_ENEMY_CHANGED")
multiAuraFrame:RegisterEvent("PLAYER_SOFT_FRIEND_CHANGED")
multiAuraFrame:RegisterEvent("PLAYER_FOCUS_CHANGED")
end
multiAuraFrame:RegisterEvent("NAME_PLATE_UNIT_ADDED")
multiAuraFrame:RegisterEvent("NAME_PLATE_UNIT_REMOVED")
multiAuraFrame:RegisterEvent("PLAYER_LEAVING_WORLD")
multiAuraFrame:SetScript("OnEvent", BuffTrigger.HandleMultiEvent)
Private.frames["Multi-target 2 Aura Trigger Handler"] = multiAuraFrame
end
end
function BuffTrigger.HandleMultiEvent(frame, event, ...)
Private.StartProfileSystem("bufftrigger2 - multi")
if event == "COMBAT_LOG_EVENT_UNFILTERED" then
CombatLog(CombatLogGetCurrentEventInfo())
elseif event == "UNIT_TARGET" then
TrackUid(...)
elseif Private.player_target_events[event] then
TrackUid(Private.player_target_events[event])
elseif event == "NAME_PLATE_UNIT_ADDED" then
TrackUid(...)
elseif event == "NAME_PLATE_UNIT_REMOVED" then
local unit = ...
ReleaseUID(unit)
unit = unit.."target"
ReleaseUID(unit)
elseif event == "UNIT_AURA" then
local unit = ...
local guid = UnitGUID(unit)
if matchDataMulti[guid] then
CheckAurasMulti(matchDataMulti[guid], unit, "HELPFUL")
CheckAurasMulti(matchDataMulti[guid], unit, "HARMFUL")
end
elseif event == "PLAYER_LEAVING_WORLD" then
-- Remove everything..
for GUID, GUIDData in pairs(matchDataMulti) do
for key in pairs(GUIDData) do
RemoveMatchDataMulti(GUIDData, GUID, key)
end
end
wipe(matchDataMulti)
end
Private.StopProfileSystem("bufftrigger2 - multi")
end
function BuffTrigger.GetTriggerDescription(data, triggernum, namestable)
local trigger = data.triggers[triggernum].trigger
if trigger.useName and trigger.auranames then
for index, name in pairs(trigger.auranames) do
if index > 10 then
tinsert(namestable, {" ", "[...]"})
break
end
local left = " "
if(index == 1) then
if(#trigger.auranames > 0) then
if(#trigger.auranames > 1) then
left = L["Auras:"]
else
left = L["Aura:"]
end
end
end
local icon
local spellId = WeakAuras.SafeToNumber(name)
if spellId then
icon = select(3, GetSpellInfo(spellId))
else
icon = WeakAuras.spellCache.GetIcon(name)
end
icon = icon or "Interface\\Icons\\INV_Misc_QuestionMark"
tinsert(namestable, {left, name, icon})
end
end
if trigger.useExactSpellId and trigger.auraspellids then
for index, spellId in pairs(trigger.auraspellids) do
if index > 10 then
tinsert(namestable, {" ", "[...]"})
break
end
local left = " "
if index == 1 then
if #trigger.auraspellids > 0 then
if #trigger.auraspellids > 1 then
left = L["Spell IDs:"]
else
left = L["Spell ID:"]
end
end
end
local icon = select(3, GetSpellInfo(spellId)) or "Interface\\Icons\\INV_Misc_QuestionMark"
tinsert(namestable, {left, spellId, 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"
state.stacks = 1
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"
state.stacks = 1
allStates[i] = state
end
end
end
WeakAuras.RegisterTriggerSystem({"aura2"}, BuffTrigger)