ElvUI/Modules/Nameplates/StyleFilter.lua

1388 lines
50 KiB
Lua
Raw Permalink Normal View History

2020-11-13 14:27:50 -05:00
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