6bf3864891
Switching to another saved specialization never confirms a build, so CHARACTER_ADVANCEMENT_UPDATE_ENTRIES_RESULT doesn't fire for it and the cached talent state stayed stale for the rest of the session. That path only fires PENDING_BUILD_UPDATED and SUGGESTIONS_UPDATED, so listen to both. PENDING_BUILD_UPDATED also fires on every click inside the talent UI, so the handler recomputes the cached rank and rebuilds the unitframes only when the answer actually changed. Also fall back to matching the talent by name among the learned entries when the entry ID reports nothing, so an ID gone stale after a server rebalance doesn't silently switch the feature off.
166 lines
6.3 KiB
Lua
166 lines
6.3 KiB
Lua
local E, L, V, P, G = unpack(ElvUI)
|
|
local UF = E:GetModule("UnitFrames")
|
|
local CoA = E:GetModule("CoA")
|
|
|
|
-- Base dispel types each custom class can remove, independent of talents.
|
|
-- CHRONOMANCER is a special case: its dispel removes the last debuff applied
|
|
-- to the target regardless of type, so it's never filtered out here -- we
|
|
-- can only approximate this as "treat every typed debuff as dispellable",
|
|
-- since the underlying oUF scan only ever surfaces typed debuffs anyway.
|
|
local CLASS_DISPEL_TYPES = {
|
|
CHRONOMANCER = true,
|
|
SUNCLERIC = {Magic = true, Poison = true, Disease = true},
|
|
STARCALLER = {Poison = true, Disease = true},
|
|
PROPHET = {Poison = true},
|
|
WITCHDOCTOR = {Curse = true},
|
|
CULTIST = {Magic = true},
|
|
PYROMANCER = {},
|
|
}
|
|
|
|
-- Extra dispel types unlocked by a talent choice, keyed to the Character
|
|
-- Advancement entry that grants them. The IDs are stable and shared across
|
|
-- every class tree, so querying one from a class that can't take it simply
|
|
-- reports rank 0 rather than erroring.
|
|
local TALENT_DISPEL_TYPES = {
|
|
PROPHET = {talentID = 6324, talentName = "Blight Antidote", types = {Curse = true}},
|
|
CULTIST = {talentID = 12982, talentName = "Devour Curse", types = {Curse = true}},
|
|
PYROMANCER = {talentID = 31276, talentName = "Burn Impurities", types = {Magic = true, Disease = true, Bleed = true}},
|
|
}
|
|
|
|
-- Cached result of the talent lookup for the player's current build.
|
|
-- PostUpdate_DebuffHighlight runs once per aura per frame, so the query is
|
|
-- hoisted out of that path and refreshed only when the build can change.
|
|
local hasTalentDispel = false
|
|
|
|
-- C_CharacterAdvancement is a server addition with no API documentation, so
|
|
-- every entry point is guarded and a failed call reads as "talent not taken",
|
|
-- which degrades to the same behaviour as before auto-detection existed.
|
|
local function HasTalent(talent)
|
|
local api = C_CharacterAdvancement
|
|
if not api then return false end
|
|
|
|
if type(api.GetTalentRankByID) == "function" then
|
|
local ok, rank = pcall(api.GetTalentRankByID, talent.talentID)
|
|
if ok and type(rank) == "number" and rank > 0 then return true end
|
|
end
|
|
|
|
-- Fall back to matching the talent by name, so an entry ID gone stale after
|
|
-- a server rebalance doesn't silently switch the feature off. Names are the
|
|
-- more fragile key of the two, but GetKnownTalentEntries only ever lists
|
|
-- entries the player has actually learned, which makes this a genuinely
|
|
-- independent check rather than a second opinion on the same ID. The list
|
|
-- has been seen with holes in it, so it's walked with pairs.
|
|
if type(api.GetKnownTalentEntries) == "function" then
|
|
local ok, entries = pcall(api.GetKnownTalentEntries)
|
|
if ok and type(entries) == "table" then
|
|
for _, entry in pairs(entries) do
|
|
if type(entry) == "table" and entry.Name == talent.talentName then return true end
|
|
end
|
|
end
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
-- Returns whether the talent state actually moved, so callers reacting to the
|
|
-- chattier advancement events can skip the unitframe rebuild when it didn't.
|
|
local function RefreshTalentState()
|
|
local _, class = UnitClass("player")
|
|
local talent = TALENT_DISPEL_TYPES[class]
|
|
local hasTalent = (talent and HasTalent(talent)) or false
|
|
|
|
if hasTalent == hasTalentDispel then return false end
|
|
|
|
hasTalentDispel = hasTalent
|
|
|
|
return true
|
|
end
|
|
|
|
function CoA:CanDispel(debuffType)
|
|
local _, class = UnitClass("player")
|
|
local baseTypes = CLASS_DISPEL_TYPES[class]
|
|
|
|
if baseTypes == true then return true end
|
|
if baseTypes and baseTypes[debuffType] then return true end
|
|
|
|
local talent = TALENT_DISPEL_TYPES[class]
|
|
if talent and hasTalentDispel and CoA.db.profile.dispelHighlightDetectTalents and talent.types[debuffType] then
|
|
return true
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
local function SuppressHighlight(object)
|
|
if object.DebuffHighlightBackdrop and object.DBHGlow then
|
|
object.DBHGlow:Hide()
|
|
elseif object.DebuffHighlightUseTexture then
|
|
object.DebuffHighlight:SetTexture(nil)
|
|
else
|
|
object.DebuffHighlight:SetVertexColor(0, 0, 0, 0)
|
|
end
|
|
end
|
|
|
|
local origPostUpdate = UF.PostUpdate_DebuffHighlight
|
|
|
|
local function DispelAwarePostUpdate(dbh, object, debuffType, texture, wasFiltered, style, color)
|
|
origPostUpdate(dbh, object, debuffType, texture, wasFiltered, style, color)
|
|
|
|
if CoA.db.profile.dispelHighlightOnlyMine and debuffType and not wasFiltered and not CoA:CanDispel(debuffType) then
|
|
SuppressHighlight(object)
|
|
end
|
|
end
|
|
|
|
UF.PostUpdate_DebuffHighlight = DispelAwarePostUpdate
|
|
|
|
hooksecurefunc(UF, "Configure_DebuffHighlight", function(_, frame)
|
|
local dbh = frame.DebuffHighlight
|
|
if dbh then
|
|
dbh.PostUpdate = UF.PostUpdate_DebuffHighlight
|
|
end
|
|
end)
|
|
|
|
function CoA:UpdateDispelHighlight()
|
|
RefreshTalentState()
|
|
UF:Update_AllFrames()
|
|
end
|
|
|
|
function CoA:RefreshDispelTalents()
|
|
if RefreshTalentState() then
|
|
UF:Update_AllFrames()
|
|
end
|
|
end
|
|
|
|
-- On Ascension, UnitClass("player")'s second return only reliably reports the
|
|
-- real custom class (CULTIST, PYROMANCER, ...) a short while after login --
|
|
-- immediately at ADDON_LOADED/PLAYER_LOGIN it can still read back the generic
|
|
-- "HERO" base class, and the advancement data lags in the same way. If a
|
|
-- debuff highlight gets evaluated before that data syncs, CoA:CanDispel
|
|
-- wrongly returns false and the highlight stays wrongly suppressed until the
|
|
-- next aura change. Force one extra refresh shortly after entering the world
|
|
-- so the very first debuff isn't judged too early.
|
|
--
|
|
-- CHARACTER_ADVANCEMENT_UPDATE_ENTRIES_RESULT fires when a build is confirmed
|
|
-- in the talent UI, but switching to another saved specialization doesn't
|
|
-- confirm anything -- that path only fires PENDING_BUILD_UPDATED and
|
|
-- SUGGESTIONS_UPDATED, so both are needed or the ranks read stale for the rest
|
|
-- of the session. PENDING_BUILD_UPDATED also fires on every click inside the
|
|
-- talent UI and SUGGESTIONS_UPDATED is only incidentally related, which is why
|
|
-- they go through RefreshDispelTalents: it costs one rank lookup and rebuilds
|
|
-- the unitframes only when the answer changed.
|
|
function CoA:InitializeDispelHighlight()
|
|
CoA:RegisterEvent("PLAYER_ENTERING_WORLD", function()
|
|
CoA:ScheduleTimer("UpdateDispelHighlight", 2)
|
|
end)
|
|
|
|
for _, event in ipairs({
|
|
"CHARACTER_ADVANCEMENT_UPDATE_ENTRIES_RESULT",
|
|
"CHARACTER_ADVANCEMENT_PENDING_BUILD_UPDATED",
|
|
"CHARACTER_ADVANCEMENT_SUGGESTIONS_UPDATED",
|
|
}) do
|
|
CoA:RegisterEvent(event, function()
|
|
CoA:RefreshDispelTalents()
|
|
end)
|
|
end
|
|
end
|