ElvUI/Modules/Nameplates/StyleFilter.lua

1388 lines
50 KiB
Lua
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

local E, L, V, P, G = unpack(select(2, ...)); --Import: Engine, Locales, PrivateDB, ProfileDB, GlobalDB
local mod = E:GetModule('NamePlates')
local LSM = E.Libs.LSM
local _G = _G
local ipairs, next, pairs, select = ipairs, next, pairs, select
local setmetatable, tostring, tonumber, type, unpack = setmetatable, tostring, tonumber, type, unpack
local strmatch, tinsert, tremove, sort, wipe = strmatch, tinsert, tremove, sort, wipe
local GetInstanceInfo = GetInstanceInfo
local GetLocale = GetLocale
local GetRaidTargetIndex = GetRaidTargetIndex
local GetSpecializationInfo = GetSpecializationInfo
local GetSpellCharges = GetSpellCharges
local GetSpellCooldown = GetSpellCooldown
local GetSpellInfo = GetSpellInfo
local GetTalentInfo = GetTalentInfo
local GetTime = GetTime
local IsResting = IsResting
local UnitPlayerControlled = UnitPlayerControlled
local UnitAffectingCombat = UnitAffectingCombat
local UnitCanAttack = UnitCanAttack
local UnitExists = UnitExists
local UnitHealth = UnitHealth
local UnitHealthMax = UnitHealthMax
local UnitInVehicle = UnitInVehicle
local UnitIsOwnerOrControllerOfUnit = UnitIsOwnerOrControllerOfUnit
local UnitIsPVP = UnitIsPVP
local UnitIsQuestBoss = UnitIsQuestBoss
local UnitIsTapDenied = UnitIsTapDenied
local UnitIsUnit = UnitIsUnit
local UnitLevel = UnitLevel
local UnitPower = UnitPower
local UnitPowerMax = UnitPowerMax
local UnitThreatSituation = UnitThreatSituation
local C_Timer_NewTimer = C_Timer.NewTimer
local C_SpecializationInfo_GetPvpTalentSlotInfo = C_SpecializationInfo.GetPvpTalentSlotInfo
local FallbackColor = {r=1, b=1, g=1}
mod.StyleFilterStackPattern = '([^\n]+)\n?(%d*)$'
mod.TriggerConditions = {
reactions = {'hated', 'hostile', 'unfriendly', 'neutral', 'friendly', 'honored', 'revered', 'exalted'},
raidTargets = {'star', 'circle', 'diamond', 'triangle', 'moon', 'square', 'cross', 'skull'},
tankThreat = {[0] = 3, 2, 1, 0},
frameTypes = {
FRIENDLY_PLAYER = 'friendlyPlayer',
FRIENDLY_NPC = 'friendlyNPC',
ENEMY_PLAYER = 'enemyPlayer',
ENEMY_NPC = 'enemyNPC',
PLAYER = 'player'
},
roles = {
TANK = 'tank',
HEALER = 'healer',
DAMAGER = 'damager'
},
keys = {
Modifier = IsModifierKeyDown,
Shift = IsShiftKeyDown,
Alt = IsAltKeyDown,
Control = IsControlKeyDown,
LeftShift = IsLeftShiftKeyDown,
LeftAlt = IsLeftAltKeyDown,
LeftControl = IsLeftControlKeyDown,
RightShift = IsRightShiftKeyDown,
RightAlt = IsRightAltKeyDown,
RightControl = IsRightControlKeyDown,
},
threat = {
[-3] = 'offTank',
[-2] = 'offTankBadTransition',
[-1] = 'offTankGoodTransition',
[0] = 'good',
[1] = 'badTransition',
[2] = 'goodTransition',
[3] = 'bad'
},
difficulties = {
-- dungeons
[1] = 'normal',
[2] = 'heroic',
[8] = 'mythic+',
[23] = 'mythic',
[24] = 'timewalking',
-- raids
[7] = 'lfr',
[17] = 'lfr',
[14] = 'normal',
[15] = 'heroic',
[16] = 'mythic',
[33] = 'timewalking',
[3] = 'legacy10normal',
[4] = 'legacy25normal',
[5] = 'legacy10heroic',
[6] = 'legacy25heroic',
}
}
do -- E.CreatureTypes; Do *not* change the value, only the key (['key'] = 'value').
local c, locale = {}, GetLocale()
if locale == 'frFR' then
c['Aberration'] = 'Aberration'
c['Bête'] = 'Beast'
c['Bestiole'] = 'Critter'
c['Démon'] = 'Demon'
c['Draconien'] = 'Dragonkin'
c['Élémentaire'] = 'Elemental'
c['Nuage de gaz'] = 'Gas Cloud'
c['Géant'] = 'Giant'
c['Humanoïde'] = 'Humanoid'
c['Machine'] = 'Mechanical'
c['Non spécifié'] = 'Not specified'
c['Totem'] = 'Totem'
c['Mort-vivant'] = 'Undead'
c['Mascotte sauvage'] = 'Wild Pet'
c['Familier pacifique'] = 'Non-combat Pet'
elseif locale == 'deDE' then
c['Anomalie'] = 'Aberration'
c['Wildtier'] = 'Beast'
c['Kleintier'] = 'Critter'
c['Dämon'] = 'Demon'
c['Drachkin'] = 'Dragonkin'
c['Elementar'] = 'Elemental'
c['Gaswolke'] = 'Gas Cloud'
c['Riese'] = 'Giant'
c['Humanoid'] = 'Humanoid'
c['Mechanisch'] = 'Mechanical'
c['Nicht spezifiziert'] = 'Not specified'
c['Totem'] = 'Totem'
c['Untoter'] = 'Undead'
c['Ungezähmtes Tier'] = 'Wild Pet'
c['Haustier'] = 'Non-combat Pet'
elseif locale == 'koKR' then
c['돌연변이'] = 'Aberration'
c['야수'] = 'Beast'
c['동물'] = 'Critter'
c['악마'] = 'Demon'
c['용족'] = 'Dragonkin'
c['정령'] = 'Elemental'
c['가스'] = 'Gas Cloud'
c['거인'] = 'Giant'
c['인간형'] = 'Humanoid'
c['기계'] = 'Mechanical'
c['기타'] = 'Not specified'
c['토템'] = 'Totem'
c['언데드'] = 'Undead'
c['야생 애완동물'] = 'Wild Pet'
c['애완동물'] = 'Non-combat Pet'
elseif locale == 'ruRU' then
c['Аберрация'] = 'Aberration'
c['Животное'] = 'Beast'
c['Существо'] = 'Critter'
c['Демон'] = 'Demon'
c['Дракон'] = 'Dragonkin'
c['Элементаль'] = 'Elemental'
c['Газовое облако'] = 'Gas Cloud'
c['Великан'] = 'Giant'
c['Гуманоид'] = 'Humanoid'
c['Механизм'] = 'Mechanical'
c['Не указано'] = 'Not specified'
c['Тотем'] = 'Totem'
c['Нежить'] = 'Undead'
c['дикий питомец'] = 'Wild Pet'
c['Спутник'] = 'Non-combat Pet'
elseif locale == 'zhCN' then
c['畸变'] = 'Aberration'
c['野兽'] = 'Beast'
c['小动物'] = 'Critter'
c['恶魔'] = 'Demon'
c['龙类'] = 'Dragonkin'
c['元素生物'] = 'Elemental'
c['气体云雾'] = 'Gas Cloud'
c['巨人'] = 'Giant'
c['人型生物'] = 'Humanoid'
c['机械'] = 'Mechanical'
c['未指定'] = 'Not specified'
c['图腾'] = 'Totem'
c['亡灵'] = 'Undead'
c['野生宠物'] = 'Wild Pet'
c['非战斗宠物'] = 'Non-combat Pet'
elseif locale == 'zhTW' then
c['畸變'] = 'Aberration'
c['野獸'] = 'Beast'
c['小動物'] = 'Critter'
c['惡魔'] = 'Demon'
c['龍類'] = 'Dragonkin'
c['元素生物'] = 'Elemental'
c['氣體雲'] = 'Gas Cloud'
c['巨人'] = 'Giant'
c['人型生物'] = 'Humanoid'
c['機械'] = 'Mechanical'
c['不明'] = 'Not specified'
c['圖騰'] = 'Totem'
c['不死族'] = 'Undead'
c['野生寵物'] = 'Wild Pet'
c['非戰鬥寵物'] = 'Non-combat Pet'
elseif locale == 'esES' then
c['Desviación'] = 'Aberration'
c['Bestia'] = 'Beast'
c['Alma'] = 'Critter'
c['Demonio'] = 'Demon'
c['Dragon'] = 'Dragonkin'
c['Elemental'] = 'Elemental'
c['Nube de Gas'] = 'Gas Cloud'
c['Gigante'] = 'Giant'
c['Humanoide'] = 'Humanoid'
c['Mecánico'] = 'Mechanical'
c['No especificado'] = 'Not specified'
c['Tótem'] = 'Totem'
c['No-muerto'] = 'Undead'
c['Mascota salvaje'] = 'Wild Pet'
c['Mascota no combatiente'] = 'Non-combat Pet'
elseif locale == 'esMX' then
c['Desviación'] = 'Aberration'
c['Bestia'] = 'Beast'
c['Alma'] = 'Critter'
c['Demonio'] = 'Demon'
c['Dragón'] = 'Dragonkin'
c['Elemental'] = 'Elemental'
c['Nube de Gas'] = 'Gas Cloud'
c['Gigante'] = 'Giant'
c['Humanoide'] = 'Humanoid'
c['Mecánico'] = 'Mechanical'
c['Sin especificar'] = 'Not specified'
c['Totém'] = 'Totem'
c['No-muerto'] = 'Undead'
c['Mascota salvaje'] = 'Wild Pet'
c['Mascota mansa'] = 'Non-combat Pet'
elseif locale == 'ptBR' then
c['Aberração'] = 'Aberration'
c['Fera'] = 'Beast'
c['Bicho'] = 'Critter'
c['Demônio'] = 'Demon'
c['Dracônico'] = 'Dragonkin'
c['Elemental'] = 'Elemental'
c['Gasoso'] = 'Gas Cloud'
c['Gigante'] = 'Giant'
c['Humanoide'] = 'Humanoid'
c['Mecânico'] = 'Mechanical'
c['Não especificado'] = 'Not specified'
c['Totem'] = 'Totem'
c['Renegado'] = 'Undead'
c['Mascote Selvagem'] = 'Wild Pet'
c['Mascote não-combatente'] = 'Non-combat Pet'
elseif locale == 'itIT' then
c['Aberrazione'] = 'Aberration'
c['Bestia'] = 'Beast'
c['Animale'] = 'Critter'
c['Demone'] = 'Demon'
c['Dragoide'] = 'Dragonkin'
c['Elementale'] = 'Elemental'
c['Nube di Gas'] = 'Gas Cloud'
c['Gigante'] = 'Giant'
c['Umanoide'] = 'Humanoid'
c['Meccanico'] = 'Mechanical'
c['Non Specificato'] = 'Not specified'
c['Totem'] = 'Totem'
c['Non Morto'] = 'Undead'
c['Mascotte selvatica'] = 'Wild Pet'
c['Animale Non combattente'] = 'Non-combat Pet'
else -- enUS
c['Aberration'] = 'Aberration'
c['Beast'] = 'Beast'
c['Critter'] = 'Critter'
c['Demon'] = 'Demon'
c['Dragonkin'] = 'Dragonkin'
c['Elemental'] = 'Elemental'
c['Gas Cloud'] = 'Gas Cloud'
c['Giant'] = 'Giant'
c['Humanoid'] = 'Humanoid'
c['Mechanical'] = 'Mechanical'
c['Not specified'] = 'Not specified'
c['Totem'] = 'Totem'
c['Undead'] = 'Undead'
c['Wild Pet'] = 'Wild Pet'
c['Non-combat Pet'] = 'Non-combat Pet'
end
E.CreatureTypes = c
end
function mod:StyleFilterTickerCallback(frame, button, timer)
if frame and frame:IsShown() then
mod:StyleFilterUpdate(frame, 'FAKE_AuraWaitTimer')
end
if button and button[timer] then
button[timer]:Cancel()
button[timer] = nil
end
end
function mod:StyleFilterTickerCreate(delay, frame, button, timer)
return C_Timer_NewTimer(delay, function() mod:StyleFilterTickerCallback(frame, button, timer) end)
end
function mod:StyleFilterAuraWait(frame, button, timer, timeLeft, mTimeLeft)
if button and not button[timer] then
local updateIn = timeLeft-mTimeLeft
if updateIn > 0 then -- also add a tenth of a second to updateIn to prevent the timer from firing on the same second
button[timer] = mod:StyleFilterTickerCreate(updateIn+0.1, frame, button, timer)
end
end
end
function mod:StyleFilterAuraCheck(frame, names, auras, mustHaveAll, missing, minTimeLeft, maxTimeLeft)
local total, count = 0, 0
for name, value in pairs(names) do
if value then -- only if they are turned on
total = total + 1 -- keep track of the names
if auras.createdIcons and auras.createdIcons > 0 then
for i = 1, auras.createdIcons do
local button = auras[i]
if button then
if button:IsShown() then
local spell, stacks, failed = strmatch(name, mod.StyleFilterStackPattern)
if stacks ~= '' then failed = not (button.stackCount and button.stackCount >= tonumber(stacks)) end
if not failed and ((button.name and button.name == spell) or (button.spellID and button.spellID == tonumber(spell))) then
local hasMinTime = minTimeLeft and minTimeLeft ~= 0
local hasMaxTime = maxTimeLeft and maxTimeLeft ~= 0
local timeLeft = (hasMinTime or hasMaxTime) and button.expiration and (button.expiration - GetTime())
local minTimeAllow = not hasMinTime or (timeLeft and timeLeft > minTimeLeft)
local maxTimeAllow = not hasMaxTime or (timeLeft and timeLeft < maxTimeLeft)
if timeLeft then -- if we use a min/max time setting; we must create a delay timer
if hasMinTime then mod:StyleFilterAuraWait(frame, button, 'hasMinTimer', timeLeft, minTimeLeft) end
if hasMaxTime then mod:StyleFilterAuraWait(frame, button, 'hasMaxTimer', timeLeft, maxTimeLeft) end
end
if minTimeAllow and maxTimeAllow then
count = count + 1 -- keep track of how many matches we have
end
end
else -- cancel stale timers
if button.hasMinTimer then button.hasMinTimer:Cancel() button.hasMinTimer = nil end
if button.hasMaxTimer then button.hasMaxTimer:Cancel() button.hasMaxTimer = nil end
end end end end end end
if total == 0 then
return nil -- If no auras are checked just pass nil, we dont need to run the filter here.
else
return ((mustHaveAll and not missing) and total == count) -- [x] Check for all [ ] Missing: total needs to match count
or ((not mustHaveAll and not missing) and count > 0) -- [ ] Check for all [ ] Missing: count needs to be greater than zero
or ((not mustHaveAll and missing) and count == 0) -- [ ] Check for all [x] Missing: count needs to be zero
or ((mustHaveAll and missing) and total ~= count) -- [x] Check for all [x] Missing: count must not match total
end
end
function mod:StyleFilterCooldownCheck(names, mustHaveAll)
local _, gcd = GetSpellCooldown(61304)
local total, count = 0, 0
for name, value in pairs(names) do
if GetSpellInfo(name) then -- check spell name valid, GetSpellCharges/GetSpellCooldown will return nil if not known by your class
if value == 'ONCD' or value == 'OFFCD' then -- only if they are turned on
total = total + 1 -- keep track of the names
local charges = GetSpellCharges(name)
local _, duration = GetSpellCooldown(name)
if (charges and charges == 0 and value == 'ONCD') -- charges exist and the current number of charges is 0 means that it is completely on cooldown.
or (charges and charges > 0 and value == 'OFFCD') -- charges exist and the current number of charges is greater than 0 means it is not on cooldown.
or (charges == nil and (duration > gcd and value == 'ONCD')) -- no charges exist and the duration of the cooldown is greater than the GCD spells current cooldown then it is on cooldown.
or (charges == nil and (duration <= gcd and value == 'OFFCD')) then -- no charges exist and the duration of the cooldown is at or below the current GCD cooldown spell then it is not on cooldown.
count = count + 1
-- print(((charges and charges == 0 and value == 'ONCD') and name..' (charge) passes because it is on cd') or ((charges and charges > 0 and value == 'OFFCD') and name..' (charge) passes because it is offcd') or ((charges == nil and (duration > gcd and value == 'ONCD')) and name..'passes because it is on cd.') or ((charges == nil and (duration <= gcd and value == 'OFFCD')) and name..' passes because it is off cd.'))
end end end end
if total == 0 then
return nil
else
return (mustHaveAll and total == count) or (not mustHaveAll and count > 0)
end
end
function mod:StyleFilterFinishedFlash(requested)
if not requested then self:Play() end
end
function mod:StyleFilterSetupFlash(FlashTexture)
local anim = FlashTexture:CreateAnimationGroup('Flash')
anim:SetScript('OnFinished', mod.StyleFilterFinishedFlash)
FlashTexture.anim = anim
local fadein = anim:CreateAnimation('ALPHA', 'FadeIn')
fadein:SetFromAlpha(0)
fadein:SetToAlpha(1)
fadein:SetOrder(2)
anim.fadein = fadein
local fadeout = anim:CreateAnimation('ALPHA', 'FadeOut')
fadeout:SetFromAlpha(1)
fadeout:SetToAlpha(0)
fadeout:SetOrder(1)
anim.fadeout = fadeout
return anim
end
function mod:StyleFilterUpdatePlate(frame, updateBase)
if updateBase then
mod:UpdatePlate(frame, true) -- enable elements back
end
if frame.frameType then
local db = mod:PlateDB(frame)
if db.health.enable then frame.Health:ForceUpdate() end
if db.power.enable then frame.Power:ForceUpdate() end
end
if mod.db.threat.enable and mod.db.threat.useThreatColor and not UnitIsTapDenied(frame.unit) then
frame.ThreatIndicator:ForceUpdate() -- this will account for the threat health color
end
mod:PlateFade(frame, mod.db.fadeIn and 1 or 0, 0, 1) -- fade those back in so it looks clean
end
function mod:StyleFilterBorderLock(backdrop, r, g, b, a)
if r then
backdrop.forcedBorderColors = {r,g,b,a}
backdrop:SetBackdropBorderColor(r,g,b,a)
else
backdrop.forcedBorderColors = nil
backdrop:SetBackdropBorderColor(unpack(E.media.bordercolor))
end
end
do
local empty = {}
function mod:StyleFilterChanges(frame)
return (frame and frame.StyleFilterChanges) or empty
end
end
function mod:StyleFilterSetChanges(frame, actions, HealthColor, PowerColor, Borders, HealthFlash, HealthTexture, Scale, Alpha, NameTag, PowerTag, HealthTag, TitleTag, LevelTag, Portrait, NameOnly, Visibility)
local c = frame.StyleFilterChanges
if not c then return end
local db = mod:PlateDB(frame)
if Visibility then
c.Visibility = true
mod:DisablePlate(frame) -- disable the plate elements
frame:ClearAllPoints() -- lets still move the frame out cause its clickable otherwise
frame:Point('TOP', E.UIParent, 'BOTTOM', 0, -500)
return -- We hide it. Lets not do other things (no point)
end
if HealthColor then
local hc = actions.color.healthColor
c.HealthColor = hc -- used by Health_UpdateColor
frame.Health:SetStatusBarColor(hc.r, hc.g, hc.b, hc.a)
frame.Cutaway.Health:SetVertexColor(hc.r * 1.5, hc.g * 1.5, hc.b * 1.5, hc.a)
end
if PowerColor then
local pc = actions.color.powerColor
c.PowerColor = true
frame.Power:SetStatusBarColor(pc.r, pc.g, pc.b, pc.a)
frame.Cutaway.Power:SetVertexColor(pc.r * 1.5, pc.g * 1.5, pc.b * 1.5, pc.a)
end
if Borders then
local bc = actions.color.borderColor
c.Borders = true
mod:StyleFilterBorderLock(frame.Health.backdrop, bc.r, bc.g, bc.b, bc.a)
if frame.Power.backdrop and (frame.frameType and db.power and db.power.enable) then
mod:StyleFilterBorderLock(frame.Power.backdrop, bc.r, bc.g, bc.b, bc.a)
end
end
if HealthFlash then
local fc = actions.flash.color
c.HealthFlash = true
if not HealthTexture then frame.HealthFlashTexture:SetTexture(LSM:Fetch('statusbar', mod.db.statusbar)) end
frame.HealthFlashTexture:SetVertexColor(fc.r, fc.g, fc.b)
local anim = frame.HealthFlashTexture.anim or mod:StyleFilterSetupFlash(frame.HealthFlashTexture)
anim.fadein:SetToAlpha(fc.a)
anim.fadeout:SetFromAlpha(fc.a)
frame.HealthFlashTexture:Show()
E:Flash(frame.HealthFlashTexture, actions.flash.speed * 0.1, true)
end
if HealthTexture then
local tx = LSM:Fetch('statusbar', actions.texture.texture)
c.HealthTexture = true
frame.Highlight.texture:SetTexture(tx)
frame.Health:SetStatusBarTexture(tx)
if HealthFlash then frame.HealthFlashTexture:SetTexture(tx) end
end
if Scale then
c.Scale = true
mod:ScalePlate(frame, actions.scale)
end
if Alpha then
c.Alpha = true
mod:PlateFade(frame, mod.db.fadeIn and 1 or 0, frame:GetAlpha(), actions.alpha / 100)
end
if Portrait then
c.Portrait = true
mod:Update_Portrait(frame)
frame.Portrait:ForceUpdate()
end
if NameOnly then
c.NameOnly = true
mod:DisablePlate(frame, true)
end
-- Keeps Tag changes after NameOnly
if NameTag then
c.NameTag = true
frame:Tag(frame.Name, actions.tags.name)
frame.Name:UpdateTag()
end
if PowerTag then
c.PowerTag = true
frame:Tag(frame.Power.Text, actions.tags.power)
frame.Power.Text:UpdateTag()
end
if HealthTag then
c.HealthTag = true
frame:Tag(frame.Health.Text, actions.tags.health)
frame.Health.Text:UpdateTag()
end
if TitleTag then
c.TitleTag = true
frame:Tag(frame.Title, actions.tags.title)
frame.Title:UpdateTag()
end
if LevelTag then
c.LevelTag = true
frame:Tag(frame.Level, actions.tags.level)
frame.Level:UpdateTag()
end
end
function mod:StyleFilterClearChanges(frame, updateBase, HealthColor, PowerColor, Borders, HealthFlash, HealthTexture, Scale, Alpha, NameTag, PowerTag, HealthTag, TitleTag, LevelTag, Portrait, NameOnly, Visibility)
local db = mod:PlateDB(frame)
if frame.StyleFilterChanges then
wipe(frame.StyleFilterChanges)
end
if Visibility then
mod:StyleFilterUpdatePlate(frame, updateBase)
frame:ClearAllPoints() -- pull the frame back in
frame:Point('CENTER')
end
if HealthColor then
local h = frame.Health
if h.r and h.g and h.b then
h:SetStatusBarColor(h.r, h.g, h.b)
frame.Cutaway.Health:SetVertexColor(h.r * 1.5, h.g * 1.5, h.b * 1.5, 1)
end
end
if PowerColor then
local pc = E.db.unitframe.colors.power[frame.Power.token] or _G.PowerBarColor[frame.Power.token] or FallbackColor
frame.Power:SetStatusBarColor(pc.r, pc.g, pc.b)
frame.Cutaway.Power:SetVertexColor(pc.r * 1.5, pc.g * 1.5, pc.b * 1.5, 1)
end
if Borders then
mod:StyleFilterBorderLock(frame.Health.backdrop)
if frame.Power.backdrop and (frame.frameType and db.power and db.power.enable) then
mod:StyleFilterBorderLock(frame.Power.backdrop)
end
end
if HealthFlash then
E:StopFlash(frame.HealthFlashTexture)
frame.HealthFlashTexture:Hide()
end
if HealthTexture then
local tx = LSM:Fetch('statusbar', mod.db.statusbar)
frame.Highlight.texture:SetTexture(tx)
frame.Health:SetStatusBarTexture(tx)
end
if Scale then
mod:ScalePlate(frame, frame.ThreatScale or 1)
end
if Alpha then
mod:PlateFade(frame, mod.db.fadeIn and 1 or 0, (frame.FadeObject and frame.FadeObject.endAlpha) or 0.5, 1)
end
if Portrait then
mod:Update_Portrait(frame)
frame.Portrait:ForceUpdate()
end
if NameOnly then
mod:StyleFilterUpdatePlate(frame, updateBase)
else -- Only update these if it wasn't NameOnly. Otherwise, it leads to `Update_Tags` which does the job.
if NameTag then frame:Tag(frame.Name, db.name.format) frame.Name:UpdateTag() end
if PowerTag then frame:Tag(frame.Power.Text, db.power.text.format) frame.Power.Text:UpdateTag() end
if HealthTag then frame:Tag(frame.Health.Text, db.health.text.format) frame.Health.Text:UpdateTag() end
if TitleTag then frame:Tag(frame.Title, db.title.format) frame.Title:UpdateTag() end
if LevelTag then frame:Tag(frame.Level, db.level.format) frame.Level:UpdateTag() end
end
end
function mod:StyleFilterThreatUpdate(frame, unit)
if mod:UnitExists(unit) then
local isTank, offTank, feedbackUnit = mod.ThreatIndicator_PreUpdate(frame.ThreatIndicator, unit, true)
if feedbackUnit and (feedbackUnit ~= unit) and mod:UnitExists(feedbackUnit) then
return isTank, offTank, UnitThreatSituation(feedbackUnit, unit)
else
return isTank, offTank, UnitThreatSituation(unit)
end
end
end
function mod:StyleFilterConditionCheck(frame, filter, trigger)
local passed -- skip StyleFilterPass when triggers are empty
-- Health
if trigger.healthThreshold then
local healthUnit = (trigger.healthUsePlayer and 'player') or frame.unit
local health, maxHealth = UnitHealth(healthUnit), UnitHealthMax(healthUnit)
local percHealth = (maxHealth and (maxHealth > 0) and health/maxHealth) or 0
local underHealthThreshold = trigger.underHealthThreshold and (trigger.underHealthThreshold ~= 0) and (trigger.underHealthThreshold > percHealth)
local overHealthThreshold = trigger.overHealthThreshold and (trigger.overHealthThreshold ~= 0) and (trigger.overHealthThreshold < percHealth)
if underHealthThreshold or overHealthThreshold then passed = true else return end
end
-- Power
if trigger.powerThreshold then
local powerUnit = (trigger.powerUsePlayer and 'player') or frame.unit
local power, maxPower = UnitPower(powerUnit, frame.PowerType), UnitPowerMax(powerUnit, frame.PowerType)
local percPower = (maxPower and (maxPower > 0) and power/maxPower) or 0
local underPowerThreshold = trigger.underPowerThreshold and (trigger.underPowerThreshold ~= 0) and (trigger.underPowerThreshold > percPower)
local overPowerThreshold = trigger.overPowerThreshold and (trigger.overPowerThreshold ~= 0) and (trigger.overPowerThreshold < percPower)
if underPowerThreshold or overPowerThreshold then passed = true else return end
end
-- Level
if trigger.level then
local myLevel = E.mylevel
local level = (frame.unit == 'player' and myLevel) or UnitLevel(frame.unit)
local curLevel = (trigger.curlevel and trigger.curlevel ~= 0 and (trigger.curlevel == level))
local minLevel = (trigger.minlevel and trigger.minlevel ~= 0 and (trigger.minlevel <= level))
local maxLevel = (trigger.maxlevel and trigger.maxlevel ~= 0 and (trigger.maxlevel >= level))
local matchMyLevel = trigger.mylevel and (level == myLevel)
if curLevel or minLevel or maxLevel or matchMyLevel then passed = true else return end
end
-- Resting
if trigger.isResting then
if IsResting() then passed = true else return end
end
-- Quest Boss
if trigger.questBoss then
if UnitIsQuestBoss(frame.unit) then passed = true else return end
end
-- Quest Unit
if trigger.isQuest or trigger.notQuest then
local quest = E.TagFunctions.GetQuestData(frame.unit)
if (trigger.isQuest and quest) or (trigger.notQuest and not quest) then passed = true else return end
end
-- Require Target
if trigger.requireTarget then
if UnitExists('target') then passed = true else return end
end
-- Player Combat
if trigger.inCombat or trigger.outOfCombat then
local inCombat = UnitAffectingCombat('player')
if (trigger.inCombat and inCombat) or (trigger.outOfCombat and not inCombat) then passed = true else return end
end
-- Unit Combat
if trigger.inCombatUnit or trigger.outOfCombatUnit then
local inCombat = UnitAffectingCombat(frame.unit)
if (trigger.inCombatUnit and inCombat) or (trigger.outOfCombatUnit and not inCombat) then passed = true else return end
end
-- Player Target
if trigger.isTarget or trigger.notTarget then
if (trigger.isTarget and frame.isTarget) or (trigger.notTarget and not frame.isTarget) then passed = true else return end
end
-- Unit Target
if trigger.targetMe or trigger.notTargetMe then
if (trigger.targetMe and frame.isTargetingMe) or (trigger.notTargetMe and not frame.isTargetingMe) then passed = true else return end
end
-- Unit Focus
if trigger.isFocus or trigger.notFocus then
if (trigger.isFocus and frame.isFocused) or (trigger.notFocus and not frame.isFocused) then passed = true else return end
end
-- Unit Pet
if trigger.isPet or trigger.isNotPet then
if (trigger.isPet and frame.isPet or trigger.isNotPet and not frame.isPet) then passed = true else return end
end
-- Unit Player Controlled
if trigger.isPlayerControlled or trigger.isNotPlayerControlled then
local playerControlled = UnitPlayerControlled(frame.unit) and not frame.isPlayer
if (trigger.isPlayerControlled and playerControlled or trigger.isNotPlayerControlled and not playerControlled) then passed = true else return end
end
-- Unit Owned By Player
if trigger.isOwnedByPlayer or trigger.isNotOwnedByPlayer then
local ownedByPlayer = UnitIsOwnerOrControllerOfUnit('player', frame.unit)
if (trigger.isOwnedByPlayer and ownedByPlayer or trigger.isNotOwnedByPlayer and not ownedByPlayer) then passed = true else return end
end
-- Unit PvP
if trigger.isPvP or trigger.isNotPvP then
local isPvP = UnitIsPVP(frame.unit)
if (trigger.isPvP and isPvP or trigger.isNotPvP and not isPvP) then passed = true else return end
end
-- Unit Tap Denied
if trigger.isTapDenied or trigger.isNotTapDenied then
local tapDenied = UnitIsTapDenied(frame.unit)
if (trigger.isTapDenied and tapDenied) or (trigger.isNotTapDenied and not tapDenied) then passed = true else return end
end
-- Player Vehicle
if trigger.inVehicle or trigger.outOfVehicle then
local inVehicle = UnitInVehicle('player')
if (trigger.inVehicle and inVehicle) or (trigger.outOfVehicle and not inVehicle) then passed = true else return end
end
-- Unit Vehicle
if trigger.inVehicleUnit or trigger.outOfVehicleUnit then
if (trigger.inVehicleUnit and frame.inVehicle) or (trigger.outOfVehicleUnit and not frame.inVehicle) then passed = true else return end
end
-- Player Can Attack
if trigger.playerCanAttack or trigger.playerCanNotAttack then
local canAttack = UnitCanAttack('player', frame.unit)
if (trigger.playerCanAttack and canAttack) or (trigger.playerCanNotAttack and not canAttack) then passed = true else return end
end
-- NPC Title
if trigger.hasTitleNPC or trigger.noTitleNPC then
local npcTitle = E.TagFunctions.GetTitleNPC(frame.unit)
if (trigger.hasTitleNPC and npcTitle) or (trigger.noTitleNPC and not npcTitle) then passed = true else return end
end
-- Classification
if trigger.classification.worldboss or trigger.classification.rareelite or trigger.classification.elite or trigger.classification.rare or trigger.classification.normal or trigger.classification.trivial or trigger.classification.minus then
if trigger.classification[frame.classification] then passed = true else return end
end
-- Group Role
if trigger.role.tank or trigger.role.healer or trigger.role.damager then
if trigger.role[mod.TriggerConditions.roles[E.myrole]] then passed = true else return end
end
-- Unit Type
if trigger.nameplateType and trigger.nameplateType.enable then
if trigger.nameplateType[mod.TriggerConditions.frameTypes[frame.frameType]] then passed = true else return end
end
-- Creature Type
if trigger.creatureType and trigger.creatureType.enable then
if trigger.creatureType[E.CreatureTypes[frame.creatureType]] then passed = true else return end
end
-- Key Modifier
if trigger.keyMod and trigger.keyMod.enable then
for key, value in pairs(trigger.keyMod) do
local isDown = mod.TriggerConditions.keys[key]
if value and isDown then
if isDown() then passed = true else return end
end
end
end
-- Reaction (or Reputation) Type
if trigger.reactionType and trigger.reactionType.enable then
if trigger.reactionType[mod.TriggerConditions.reactions[(trigger.reactionType.reputation and frame.repReaction) or frame.reaction]] then passed = true else return end
end
-- Threat
if trigger.threat and trigger.threat.enable then
if trigger.threat.good or trigger.threat.goodTransition or trigger.threat.badTransition or trigger.threat.bad or trigger.threat.offTank or trigger.threat.offTankGoodTransition or trigger.threat.offTankBadTransition then
local isTank, offTank, threat = mod:StyleFilterThreatUpdate(frame, frame.unit)
local checkOffTank = trigger.threat.offTank or trigger.threat.offTankGoodTransition or trigger.threat.offTankBadTransition
local status = (checkOffTank and offTank and threat and -threat) or (not checkOffTank and ((isTank and mod.TriggerConditions.tankThreat[threat]) or threat)) or nil
if trigger.threat[mod.TriggerConditions.threat[status]] then passed = true else return end
end
end
-- Raid Target
if trigger.raidTarget.star or trigger.raidTarget.circle or trigger.raidTarget.diamond or trigger.raidTarget.triangle or trigger.raidTarget.moon or trigger.raidTarget.square or trigger.raidTarget.cross or trigger.raidTarget.skull then
if trigger.raidTarget[mod.TriggerConditions.raidTargets[frame.RaidTargetIndex]] then passed = true else return end
end
-- Class and Specialization
if trigger.class and next(trigger.class) then
local Class = trigger.class[E.myclass]
if not Class or (Class.specs and next(Class.specs) and not Class.specs[E.myspec and GetSpecializationInfo(E.myspec)]) then
return
else
passed = true
end
end
do
local activeID = trigger.location.instanceIDEnabled
local activeType = trigger.instanceType.none or trigger.instanceType.scenario or trigger.instanceType.party or trigger.instanceType.raid or trigger.instanceType.arena or trigger.instanceType.pvp
local instanceName, instanceType, difficultyID, instanceID, _
-- Instance Type
if activeType or activeID then
instanceName, instanceType, difficultyID, _, _, _, _, instanceID = GetInstanceInfo()
end
if activeType then
if trigger.instanceType[instanceType] then
passed = true
-- Instance Difficulty
if instanceType == 'raid' or instanceType == 'party' then
local D = trigger.instanceDifficulty[(instanceType == 'party' and 'dungeon') or instanceType]
for _, value in pairs(D) do
if value and not D[mod.TriggerConditions.difficulties[difficultyID]] then return end
end
end
else return end
end
-- Location
if activeID or trigger.location.mapIDEnabled or trigger.location.zoneNamesEnabled or trigger.location.subZoneNamesEnabled then
if activeID and next(trigger.location.instanceIDs) then
if (instanceID and trigger.location.instanceIDs[tostring(instanceID)]) or trigger.location.instanceIDs[instanceName] then passed = true else return end
end
if trigger.location.mapIDEnabled and next(trigger.location.mapIDs) then
if (E.MapInfo.mapID and trigger.location.mapIDs[tostring(E.MapInfo.mapID)]) or trigger.location.mapIDs[E.MapInfo.name] then passed = true else return end
end
if trigger.location.zoneNamesEnabled and next(trigger.location.zoneNames) then
if trigger.location.zoneNames[E.MapInfo.realZoneText] then passed = true else return end
end
if trigger.location.subZoneNamesEnabled and next(trigger.location.subZoneNames) then
if trigger.location.subZoneNames[E.MapInfo.subZoneText] then passed = true else return end
end
end
end
-- Talents
if trigger.talent.enabled then
local pvpTalent = trigger.talent.type == 'pvp'
local selected, complete
for i = 1, (pvpTalent and 4) or 7 do
local Tier = 'tier'..i
local Talent = trigger.talent[Tier]
if trigger.talent[Tier..'enabled'] and Talent.column > 0 then
if pvpTalent then
-- column is actually the talentID for pvpTalents
local slotInfo = C_SpecializationInfo_GetPvpTalentSlotInfo(i)
selected = (slotInfo and slotInfo.selectedTalentID) == Talent.column
else
selected = select(4, GetTalentInfo(i, Talent.column, 1))
end
if (selected and not Talent.missing) or (Talent.missing and not selected) then
complete = true
if not trigger.talent.requireAll then
break -- break when not using requireAll because we matched one
end
elseif trigger.talent.requireAll then
complete = false -- fail because requireAll
break
end end end
if complete then passed = true else return end
end
-- Casting
if trigger.casting then
local b, c = frame.Castbar, trigger.casting
-- Spell
if c.spells and next(c.spells) then
for _, value in pairs(c.spells) do
if value then -- only run if at least one is selected
local castingSpell = (b.spellID and c.spells[tostring(b.spellID)]) or c.spells[b.spellName]
if (c.notSpell and not castingSpell) or (castingSpell and not c.notSpell) then passed = true else return end
break -- we can execute this once on the first enabled option then kill the loop
end
end
end
-- Status
if c.isCasting or c.isChanneling or c.notCasting or c.notChanneling then
if (c.isCasting and b.casting) or (c.isChanneling and b.channeling)
or (c.notCasting and not b.casting) or (c.notChanneling and not b.channeling) then passed = true else return end
end
-- Interruptible
if c.interruptible or c.notInterruptible then
if (b.casting or b.channeling) and ((c.interruptible and not b.notInterruptible)
or (c.notInterruptible and b.notInterruptible)) then passed = true else return end
end
end
-- Cooldown
if trigger.cooldowns and trigger.cooldowns.names and next(trigger.cooldowns.names) then
local cooldown = mod:StyleFilterCooldownCheck(trigger.cooldowns.names, trigger.cooldowns.mustHaveAll)
if cooldown ~= nil then -- ignore if none are set to ONCD or OFFCD
if cooldown then passed = true else return end
end
end
-- Buffs
if frame.Buffs and trigger.buffs then
-- Has Stealable
if trigger.buffs.hasStealable or trigger.buffs.hasNoStealable then
if (trigger.buffs.hasStealable and frame.Buffs.hasStealable) or (trigger.buffs.hasNoStealable and not frame.Buffs.hasStealable) then passed = true else return end
end
-- Names / Spell IDs
if trigger.buffs.names and next(trigger.buffs.names) then
local buff = mod:StyleFilterAuraCheck(frame, trigger.buffs.names, frame.Buffs, trigger.buffs.mustHaveAll, trigger.buffs.missing, trigger.buffs.minTimeLeft, trigger.buffs.maxTimeLeft)
if buff ~= nil then -- ignore if none are selected
if buff then passed = true else return end
end
end
end
-- Debuffs
if frame.Debuffs and trigger.debuffs and trigger.debuffs.names and next(trigger.debuffs.names) then
local debuff = mod:StyleFilterAuraCheck(frame, trigger.debuffs.names, frame.Debuffs, trigger.debuffs.mustHaveAll, trigger.debuffs.missing, trigger.debuffs.minTimeLeft, trigger.debuffs.maxTimeLeft)
if debuff ~= nil then -- ignore if none are selected
if debuff then passed = true else return end
end
end
-- Name or GUID
if trigger.names and next(trigger.names) then
for _, value in pairs(trigger.names) do
if value then -- only run if at least one is selected
local name = trigger.names[frame.unitName] or trigger.names[frame.npcID]
if (not trigger.negativeMatch and name) or (trigger.negativeMatch and not name) then passed = true else return end
break -- we can execute this once on the first enabled option then kill the loop
end
end
end
-- Plugin Callback
if mod.StyleFilterCustomChecks then
for _, customCheck in pairs(mod.StyleFilterCustomChecks) do
local custom = customCheck(frame, filter, trigger)
if custom ~= nil then -- ignore if nil return
if custom then passed = true else return end
end
end
end
-- Pass it along
if passed then
mod:StyleFilterPass(frame, filter.actions)
end
end
function mod:StyleFilterPass(frame, actions)
local db = mod:PlateDB(frame)
local healthBarEnabled = (frame.frameType and db.health.enable) or (mod.db.displayStyle ~= 'ALL') or (frame.isTarget and mod.db.alwaysShowTargetHealth)
local powerBarEnabled = frame.frameType and db.power and db.power.enable
local healthBarShown = healthBarEnabled and frame.Health:IsShown()
mod:StyleFilterSetChanges(frame, actions,
(healthBarShown and actions.color and actions.color.health), --HealthColor
(healthBarShown and powerBarEnabled and actions.color and actions.color.power), --PowerColor
(healthBarShown and actions.color and actions.color.border and frame.Health.backdrop), --Borders
(healthBarShown and actions.flash and actions.flash.enable and frame.HealthFlashTexture), --HealthFlash
(healthBarShown and actions.texture and actions.texture.enable), --HealthTexture
(healthBarShown and actions.scale and actions.scale ~= 1), --Scale
(actions.alpha and actions.alpha ~= -1), --Alpha
(actions.tags and actions.tags.name and actions.tags.name ~= ''), --NameTag
(actions.tags and actions.tags.power and actions.tags.power ~= ''), --PowerTag
(actions.tags and actions.tags.health and actions.tags.health ~= ''), --HealthTag
(actions.tags and actions.tags.title and actions.tags.title ~= ''), --TitleTag
(actions.tags and actions.tags.level and actions.tags.level ~= ''), --LevelTag
(actions.usePortrait), --Portrait
(actions.nameOnly), --NameOnly
(actions.hide) --Visibility
)
end
function mod:StyleFilterClear(frame, updateBase)
if frame == _G.ElvNP_Test then return end
local c = frame.StyleFilterChanges
if c and next(c) then
local shouldUpdate = c.NameOnly or c.Visibility
mod:StyleFilterClearChanges(frame, updateBase, c.HealthColor, c.PowerColor, c.Borders, c.HealthFlash, c.HealthTexture, c.Scale, c.Alpha, c.NameTag, c.PowerTag, c.HealthTag, c.TitleTag, c.LevelTag, c.Portrait, c.NameOnly, c.Visibility)
return shouldUpdate
end
end
function mod:StyleFilterSort(place)
if self[2] and place[2] then
return self[2] > place[2] -- Sort by priority: 1=first, 2=second, 3=third, etc
end
end
function mod:StyleFilterVehicleFunction(_, unit)
unit = unit or self.unit
self.inVehicle = UnitInVehicle(unit) or nil
end
mod.StyleFilterEventFunctions = { -- a prefunction to the injected ouf watch
PLAYER_TARGET_CHANGED = function(self)
self.isTarget = self.unit and UnitIsUnit(self.unit, 'target') or nil
end,
PLAYER_FOCUS_CHANGED = function(self)
self.isFocused = self.unit and UnitIsUnit(self.unit, 'focus') or nil
end,
RAID_TARGET_UPDATE = function(self)
self.RaidTargetIndex = self.unit and GetRaidTargetIndex(self.unit) or nil
end,
UNIT_TARGET = function(self, _, unit)
unit = unit or self.unit
self.isTargetingMe = UnitIsUnit(unit..'target', 'player') or nil
end,
UNIT_ENTERED_VEHICLE = mod.StyleFilterVehicleFunction,
UNIT_EXITED_VEHICLE = mod.StyleFilterVehicleFunction,
VEHICLE_UPDATE = mod.StyleFilterVehicleFunction
}
function mod:StyleFilterSetVariables(nameplate)
if nameplate == _G.ElvNP_Test then return end
for _, func in pairs(mod.StyleFilterEventFunctions) do
func(nameplate)
end
end
function mod:StyleFilterClearVariables(nameplate)
if nameplate == _G.ElvNP_Test then return end
nameplate.isTarget = nil
nameplate.isFocused = nil
nameplate.inVehicle = nil
nameplate.isTargetingMe = nil
nameplate.RaidTargetIndex = nil
nameplate.ThreatScale = nil
end
mod.StyleFilterTriggerList = {} -- configured filters enabled with sorted priority
mod.StyleFilterTriggerEvents = {} -- events required by the filter that we need to watch for
mod.StyleFilterPlateEvents = { -- events watched inside of ouf, which is called on the nameplate itself
NAME_PLATE_UNIT_ADDED = 1 -- rest is populated from StyleFilterDefaultEvents as needed
}
mod.StyleFilterDefaultEvents = { -- list of events style filter uses to populate plate events
-- this is a list of events already on the nameplate
'UNIT_AURA',
'UNIT_DISPLAYPOWER',
'UNIT_FACTION',
'UNIT_HEALTH',
'UNIT_MAXHEALTH',
'UNIT_NAME_UPDATE',
'UNIT_PET',
'UNIT_POWER_UPDATE',
-- list of events added during StyleFilterEvents
'MODIFIER_STATE_CHANGED',
'PLAYER_FOCUS_CHANGED',
'PLAYER_REGEN_DISABLED',
'PLAYER_REGEN_ENABLED',
'PLAYER_TARGET_CHANGED',
'PLAYER_UPDATE_RESTING',
'RAID_TARGET_UPDATE',
'QUEST_LOG_UPDATE',
'SPELL_UPDATE_COOLDOWN',
'UNIT_ENTERED_VEHICLE',
'UNIT_EXITED_VEHICLE',
'UNIT_FLAGS',
'UNIT_TARGET',
'UNIT_THREAT_LIST_UPDATE',
'UNIT_THREAT_SITUATION_UPDATE',
'VEHICLE_UPDATE'
}
function mod:StyleFilterWatchEvents()
for _, event in ipairs(mod.StyleFilterDefaultEvents) do
mod.StyleFilterPlateEvents[event] = mod.StyleFilterTriggerEvents[event] and true or nil
end
end
function mod:StyleFilterConfigure()
local events = mod.StyleFilterTriggerEvents
local list = mod.StyleFilterTriggerList
wipe(events)
wipe(list)
for filterName, filter in pairs(E.global.nameplate.filters) do
local t = filter.triggers
if t and E.db.nameplates and E.db.nameplates.filters then
if E.db.nameplates.filters[filterName] and E.db.nameplates.filters[filterName].triggers and E.db.nameplates.filters[filterName].triggers.enable then
tinsert(list, {filterName, t.priority or 1})
-- NOTE: 0 for fake events
events.FAKE_AuraWaitTimer = 0 -- for minTimeLeft and maxTimeLeft aura trigger
events.NAME_PLATE_UNIT_ADDED = 1
events.PLAYER_TARGET_CHANGED = 1
if t.casting then
if next(t.casting.spells) then
for _, value in pairs(t.casting.spells) do
if value then
events.FAKE_Casting = 0
break
end end end
if (t.casting.interruptible or t.casting.notInterruptible)
or (t.casting.isCasting or t.casting.isChanneling or t.casting.notCasting or t.casting.notChanneling) then
events.FAKE_Casting = 0
end
end
if t.isTapDenied or t.isNotTapDenied then events.UNIT_FLAGS = 1 end
if t.reactionType and t.reactionType.enable then events.UNIT_FACTION = 1 end
if t.keyMod and t.keyMod.enable then events.MODIFIER_STATE_CHANGED = 1 end
if t.targetMe or t.notTargetMe then events.UNIT_TARGET = 1 end
if t.isFocus or t.notFocus then events.PLAYER_FOCUS_CHANGED = 1 end
if t.isResting then events.PLAYER_UPDATE_RESTING = 1 end
if t.isPet then events.UNIT_PET = 1 end
if t.raidTarget and (t.raidTarget.star or t.raidTarget.circle or t.raidTarget.diamond or t.raidTarget.triangle or t.raidTarget.moon or t.raidTarget.square or t.raidTarget.cross or t.raidTarget.skull) then
events.RAID_TARGET_UPDATE = 1
end
if t.unitInVehicle then
events.UNIT_ENTERED_VEHICLE = 1
events.UNIT_EXITED_VEHICLE = 1
events.VEHICLE_UPDATE = 1
end
if t.healthThreshold then
events.UNIT_HEALTH = 1
events.UNIT_MAXHEALTH = 1
end
if t.powerThreshold then
events.UNIT_POWER_UPDATE = 1
events.UNIT_DISPLAYPOWER = 1
end
if t.threat and t.threat.enable then
events.UNIT_THREAT_SITUATION_UPDATE = 1
events.UNIT_THREAT_LIST_UPDATE = 1
end
if t.inCombat or t.outOfCombat or t.inCombatUnit or t.outOfCombatUnit then
events.PLAYER_REGEN_DISABLED = 1
events.PLAYER_REGEN_ENABLED = 1
events.UNIT_THREAT_LIST_UPDATE = 1
events.UNIT_FLAGS = 1
end
if t.location then
if (t.location.mapIDEnabled and next(t.location.mapIDs))
or (t.location.instanceIDEnabled and next(t.location.instanceIDs))
or (t.location.zoneNamesEnabled and next(t.location.zoneNames))
or (t.location.subZoneNamesEnabled and next(t.location.subZoneNames)) then
events.LOADING_SCREEN_DISABLED = 1
events.ZONE_CHANGED_NEW_AREA = 1
events.ZONE_CHANGED_INDOORS = 1
events.ZONE_CHANGED = 1
end
end
if t.isQuest or t.notQuest then
events.QUEST_LOG_UPDATE = 1
end
if t.hasTitleNPC or t.noTitleNPC then
events.UNIT_NAME_UPDATE = 1
end
if not events.UNIT_NAME_UPDATE and t.names and next(t.names) then
for _, value in pairs(t.names) do
if value then
events.UNIT_NAME_UPDATE = 1
break
end end end
if t.cooldowns and t.cooldowns.names and next(t.cooldowns.names) then
for _, value in pairs(t.cooldowns.names) do
if value == 'ONCD' or value == 'OFFCD' then
events.SPELL_UPDATE_COOLDOWN = 1
break
end end end
if t.buffs and (t.buffs.hasStealable or t.buffs.hasNoStealable) then
events.UNIT_AURA = 1
end
if not events.UNIT_AURA and t.buffs and t.buffs.names and next(t.buffs.names) then
for _, value in pairs(t.buffs.names) do
if value then
events.UNIT_AURA = 1
break
end end end
if not events.UNIT_AURA and t.debuffs and t.debuffs.names and next(t.debuffs.names) then
for _, value in pairs(t.debuffs.names) do
if value then
events.UNIT_AURA = 1
break
end end end
end end end
mod:StyleFilterWatchEvents()
if next(list) then
sort(list, mod.StyleFilterSort) -- sort by priority
end
end
function mod:StyleFilterUpdate(frame, event)
if frame == _G.ElvNP_Test then return end
if not frame.StyleFilterChanges or not mod.StyleFilterTriggerEvents[event] then return end
mod:StyleFilterClear(frame, true)
for filterNum in ipairs(mod.StyleFilterTriggerList) do
local filter = E.global.nameplate.filters[mod.StyleFilterTriggerList[filterNum][1]]
if filter then
mod:StyleFilterConditionCheck(frame, filter, filter.triggers)
end
end
end
do -- oUF style filter inject watch functions without actually registering any events
local update = function(frame, event, ...)
local eventFunc = mod.StyleFilterEventFunctions[event]
if eventFunc then eventFunc(frame, event, ...) end
mod:StyleFilterUpdate(frame, event)
end
local oUF_event_metatable = {
__call = function(funcs, frame, ...)
for _, func in next, funcs do
func(frame, ...)
end
end,
}
local oUF_fake_register = function(frame, event, remove)
local curev = frame[event]
if curev then
local kind = type(curev)
if kind == 'function' and curev ~= update then
frame[event] = setmetatable({curev, update}, oUF_event_metatable)
elseif kind == 'table' then
for index, infunc in next, curev do
if infunc == update then
if remove then
tremove(curev, index)
end
return
end end
tinsert(curev, update)
end
else
frame[event] = (not remove and update) or nil
end
end
local styleFilterIsWatching = function(frame, event)
local curev = frame[event]
if curev then
local kind = type(curev)
if kind == 'function' and curev == update then
return true
elseif kind == 'table' then
for _, infunc in next, curev do
if infunc == update then
return true
end end
end
end end
function mod:StyleFilterEventWatch(frame)
for _, event in ipairs(mod.StyleFilterDefaultEvents) do
local holdsEvent = styleFilterIsWatching(frame, event)
if mod.StyleFilterPlateEvents[event] then
if not holdsEvent then
oUF_fake_register(frame, event)
end
elseif holdsEvent then
oUF_fake_register(frame, event, true)
end end end
function mod:StyleFilterRegister(nameplate, event, unitless, func, objectEvent)
if objectEvent then
if not nameplate.objectEventFunc then
nameplate.objectEventFunc = function(_, evnt, ...) update(nameplate, evnt, ...) end
end
if not E:HasFunctionForObject(event, objectEvent, nameplate.objectEventFunc) then
E:RegisterEventForObject(event, objectEvent, nameplate.objectEventFunc)
end
elseif not nameplate:IsEventRegistered(event) then
nameplate:RegisterEvent(event, func or E.noop, unitless)
end
end
end
-- events we actually register on plates when they aren't added
function mod:StyleFilterEvents(nameplate)
if nameplate == _G.ElvNP_Test then return end
-- these events get added onto StyleFilterDefaultEvents to be watched,
-- the ones added from here should not by registered already
mod:StyleFilterRegister(nameplate,'MODIFIER_STATE_CHANGED', true)
mod:StyleFilterRegister(nameplate,'PLAYER_FOCUS_CHANGED', true)
mod:StyleFilterRegister(nameplate,'PLAYER_REGEN_DISABLED', true)
mod:StyleFilterRegister(nameplate,'PLAYER_REGEN_ENABLED', true)
mod:StyleFilterRegister(nameplate,'PLAYER_TARGET_CHANGED', true)
mod:StyleFilterRegister(nameplate,'PLAYER_UPDATE_RESTING', true)
mod:StyleFilterRegister(nameplate,'RAID_TARGET_UPDATE', true)
mod:StyleFilterRegister(nameplate,'SPELL_UPDATE_COOLDOWN', true)
mod:StyleFilterRegister(nameplate,'QUEST_LOG_UPDATE', true)
mod:StyleFilterRegister(nameplate,'UNIT_ENTERED_VEHICLE')
mod:StyleFilterRegister(nameplate,'UNIT_EXITED_VEHICLE')
mod:StyleFilterRegister(nameplate,'UNIT_FLAGS')
mod:StyleFilterRegister(nameplate,'UNIT_TARGET')
mod:StyleFilterRegister(nameplate,'UNIT_THREAT_LIST_UPDATE')
mod:StyleFilterRegister(nameplate,'UNIT_THREAT_SITUATION_UPDATE')
mod:StyleFilterRegister(nameplate,'VEHICLE_UPDATE', true)
-- object event pathing (these update after MapInfo updates),
-- these event are not added onto the nameplate itself
mod:StyleFilterRegister(nameplate,'LOADING_SCREEN_DISABLED', nil, nil, E.MapInfo)
mod:StyleFilterRegister(nameplate,'ZONE_CHANGED_NEW_AREA', nil, nil, E.MapInfo)
mod:StyleFilterRegister(nameplate,'ZONE_CHANGED_INDOORS', nil, nil, E.MapInfo)
mod:StyleFilterRegister(nameplate,'ZONE_CHANGED', nil, nil, E.MapInfo)
-- fire up the ouf injection watcher
mod:StyleFilterEventWatch(nameplate)
end
function mod:StyleFilterAddCustomCheck(name, func)
if not mod.StyleFilterCustomChecks then
mod.StyleFilterCustomChecks = {}
end
mod.StyleFilterCustomChecks[name] = func
end
function mod:StyleFilterRemoveCustomCheck(name)
if not mod.StyleFilterCustomChecks then
return
end
mod.StyleFilterCustomChecks[name] = nil
end
function mod:PLAYER_LOGOUT()
mod:StyleFilterClearDefaults(E.global.nameplate.filters)
end
function mod:StyleFilterClearDefaults(tbl)
for filterName, filterTable in pairs(tbl) do
if G.nameplate.filters[filterName] then
local defaultTable = E:CopyTable({}, E.StyleFilterDefaults)
E:CopyTable(defaultTable, G.nameplate.filters[filterName])
E:RemoveDefaults(filterTable, defaultTable)
else
E:RemoveDefaults(filterTable, E.StyleFilterDefaults)
end
end
end
function mod:StyleFilterCopyDefaults(tbl)
E:CopyDefaults(tbl, E.StyleFilterDefaults)
end
function mod:StyleFilterInitialize()
for _, filterTable in pairs(E.global.nameplate.filters) do
mod:StyleFilterCopyDefaults(filterTable)
end
end