1388 lines
50 KiB
Lua
1388 lines
50 KiB
Lua
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
|