From e6e86b871046a590e8e796e68d52785eefcd448f Mon Sep 17 00:00:00 2001 From: Daniel Vernon Date: Thu, 12 Mar 2026 17:26:08 +0000 Subject: [PATCH] feat: spell ID aura filtering for nameplates and unit frames Reapplies aura spell ID filtering with fixed oUF integration. Uses FilterAura(element, unit, data) API instead of the deprecated CustomFilter with positional args. - Shared filter core in EllesmereUI.lua (cache, mode, IsSpellFiltered) - Global Settings UI: mode dropdown, spell ID list editor popup - Nameplate integration: direct filter check in debuff/buff/CC loops - Unit frame integration: oUF FilterAura on Buffs/Debuffs elements - Per-system opt-in toggles in nameplate and UF options - Callback system for live refresh when filter settings change Co-Authored-By: Claude Opus 4.6 --- EUI__General_Options.lua | 62 ++++++ EllesmereUI.lua | 198 ++++++++++++++++++ .../EUI_Nameplates_Options.lua | 10 + .../EllesmereUINameplates.lua | 20 +- .../EUI_UnitFrames_Options.lua | 3 + .../EllesmereUIUnitFrames.lua | 15 ++ 6 files changed, 305 insertions(+), 3 deletions(-) diff --git a/EUI__General_Options.lua b/EUI__General_Options.lua index e1c74fe7..7f408701 100644 --- a/EUI__General_Options.lua +++ b/EUI__General_Options.lua @@ -1698,6 +1698,68 @@ initFrame:SetScript("OnEvent", function(self) _, h = W:Spacer(parent, y, 20); y = y - h + ----------------------------------------------------------------------- + -- AURA SPELL ID FILTER + ----------------------------------------------------------------------- + _, h = W:SectionHeader(parent, "AURA SPELL ID FILTER", y); y = y - h + + local auraFilterModeValues = { + disabled = "Disabled", + whitelist = "Whitelist (Show Only These)", + blacklist = "Blacklist (Hide These)", + } + local auraFilterModeOrder = { "disabled", "whitelist", "blacklist" } + + _, h = W:DualRow(parent, y, + { type="dropdown", text="Filter Mode", + values=auraFilterModeValues, + order=auraFilterModeOrder, + getValue=function() + return EllesmereUI.GetAuraFilterMode() + end, + setValue=function(v) + if not EllesmereUIDB then EllesmereUIDB = {} end + if not EllesmereUIDB.auraFilter then + EllesmereUIDB.auraFilter = { mode = "disabled", spellIDs = "" } + end + EllesmereUIDB.auraFilter.mode = v + if EllesmereUI.NotifyAuraFilterChanged then + EllesmereUI.NotifyAuraFilterChanged() + end + end }, + { type="label", text="" }); y = y - h + + -- "Edit Spell ID List" button + do + local BTN_W, BTN_H = 220, 28 + local btn = CreateFrame("Button", nil, parent) + btn:SetSize(BTN_W, BTN_H) + btn:SetPoint("TOP", parent, "TOP", 0, y) + btn:SetFrameLevel(parent:GetFrameLevel() + 5) + EllesmereUI.MakeStyledButton(btn, "Edit Spell ID List", 11, + EllesmereUI.WB_COLOURS, function() + local currentIDs = "" + if EllesmereUIDB and EllesmereUIDB.auraFilter then + currentIDs = EllesmereUIDB.auraFilter.spellIDs or "" + end + EllesmereUI:ShowAuraFilterPopup(currentIDs) + end) + y = y - BTN_H - 4 + end + + -- Helper text + do + local helpFS = EllesmereUI.MakeFont(parent, 10, "", 1, 1, 1) + helpFS:SetAlpha(0.35) + helpFS:SetPoint("TOP", parent, "TOP", 0, y) + helpFS:SetWidth(360) + helpFS:SetJustifyH("CENTER") + helpFS:SetText("Enter spell IDs separated by commas. Enable 'Show Spell ID on Tooltip' above to find IDs.") + y = y - 18 + end + + _, h = W:Spacer(parent, y, 20); y = y - h + -- Reset ALL EUI Addon Settings (wide warning button) y = y - 30 -- spacer do diff --git a/EllesmereUI.lua b/EllesmereUI.lua index 688e516a..58e7e373 100644 --- a/EllesmereUI.lua +++ b/EllesmereUI.lua @@ -4973,6 +4973,7 @@ function EllesmereUI:ResetAllModules() EllesmereUIDB.profileOrder = nil EllesmereUIDB.specProfiles = nil EllesmereUIDB.activeProfile = nil + EllesmereUIDB.auraFilter = nil end end @@ -6132,6 +6133,196 @@ do end end +------------------------------------------------------------------------------- +-- Aura Spell ID Filter (shared across nameplates + unit frames) +------------------------------------------------------------------------------- +do + local auraFilterCache = {} -- { [spellID] = true } + local auraFilterCallbacks = {} -- registered refresh callbacks + + function EllesmereUI.RegisterAuraFilterCallback(fn) + auraFilterCallbacks[#auraFilterCallbacks + 1] = fn + end + + local function NotifyAuraFilterChanged() + for _, fn in ipairs(auraFilterCallbacks) do fn() end + end + + function EllesmereUI.RebuildAuraFilterCache() + wipe(auraFilterCache) + if not EllesmereUIDB or not EllesmereUIDB.auraFilter then return end + local str = EllesmereUIDB.auraFilter.spellIDs + if not str or str == "" then return end + for id in str:gmatch("%d+") do + local num = tonumber(id) + if num then auraFilterCache[num] = true end + end + NotifyAuraFilterChanged() + end + + function EllesmereUI.NotifyAuraFilterChanged() + NotifyAuraFilterChanged() + end + + function EllesmereUI.GetAuraFilterCache() + return auraFilterCache + end + + function EllesmereUI.GetAuraFilterMode() + if not EllesmereUIDB or not EllesmereUIDB.auraFilter then return "disabled" end + return EllesmereUIDB.auraFilter.mode or "disabled" + end + + function EllesmereUI.IsSpellFiltered(spellId) + if not spellId then return false end + local mode = EllesmereUI.GetAuraFilterMode() + if mode == "disabled" then return false end + local cache = auraFilterCache + if next(cache) == nil then return false end -- empty list = no filtering + if mode == "whitelist" then + return not cache[spellId] + else -- blacklist + return cache[spellId] == true + end + end + + -- Popup for editing the spell ID list (reuses the profile popup visual style) + local auraPopupDimmer, auraPopupEditBox, auraPopupRefreshH + function EllesmereUI:ShowAuraFilterPopup(currentText) + if not auraPopupDimmer then + local POPUP_W, POPUP_H = 520, 310 + local FONT = EllesmereUI.EXPRESSWAY + + local dimmer = CreateFrame("Frame", nil, UIParent) + dimmer:SetFrameStrata("FULLSCREEN_DIALOG") + dimmer:SetAllPoints(UIParent) + dimmer:EnableMouse(true) + dimmer:EnableMouseWheel(true) + dimmer:SetScript("OnMouseWheel", function() end) + local dimTex = dimmer:CreateTexture(nil, "BACKGROUND") + dimTex:SetAllPoints() + dimTex:SetColorTexture(0, 0, 0, 0.25) + + local popup = CreateFrame("Frame", nil, dimmer) + popup:SetSize(POPUP_W, POPUP_H) + popup:SetPoint("CENTER", UIParent, "CENTER", 0, 60) + popup:SetFrameStrata("FULLSCREEN_DIALOG") + popup:SetFrameLevel(dimmer:GetFrameLevel() + 10) + popup:EnableMouse(true) + local bg = popup:CreateTexture(nil, "BACKGROUND") + bg:SetAllPoints() + bg:SetColorTexture(0.06, 0.08, 0.10, 1) + EllesmereUI.MakeBorder(popup, 1, 1, 1, 0.15, EllesmereUI.PanelPP) + + local titleFS = EllesmereUI.MakeFont(popup, 15, "", 1, 1, 1) + titleFS:SetPoint("TOP", popup, "TOP", 0, -20) + titleFS:SetText("Edit Aura Spell ID List") + + local subFS = EllesmereUI.MakeFont(popup, 11, "", 1, 1, 1) + subFS:SetAlpha(0.45) + subFS:SetPoint("TOP", titleFS, "BOTTOM", 0, -4) + subFS:SetText("Enter spell IDs separated by commas (e.g. 12345, 67890)") + + local sf = CreateFrame("ScrollFrame", nil, popup) + sf:SetPoint("TOPLEFT", popup, "TOPLEFT", 20, -58) + sf:SetPoint("BOTTOMRIGHT", popup, "BOTTOMRIGHT", -20, 52) + sf:SetFrameLevel(popup:GetFrameLevel() + 1) + sf:EnableMouseWheel(true) + + local sc = CreateFrame("Frame", nil, sf) + sc:SetWidth(sf:GetWidth() or (POPUP_W - 40)) + sc:SetHeight(1) + sf:SetScrollChild(sc) + + local editBox = CreateFrame("EditBox", nil, sc) + editBox:SetMultiLine(true) + editBox:SetAutoFocus(false) + editBox:SetFont(FONT, 11, "") + editBox:SetTextColor(1, 1, 1, 0.75) + editBox:SetPoint("TOPLEFT", sc, "TOPLEFT", 0, 0) + editBox:SetPoint("TOPRIGHT", sc, "TOPRIGHT", -14, 0) + editBox:SetHeight(1) + + sf:SetScript("OnMouseWheel", function(self, delta) + local maxScroll = tonumber(self:GetVerticalScrollRange()) or 0 + if maxScroll <= 0 then return end + local cur = self:GetVerticalScroll() + self:SetVerticalScroll(math.max(0, math.min(maxScroll, cur - delta * 30))) + end) + + sf:SetScript("OnMouseDown", function() editBox:SetFocus() end) + + local function RefreshHeight() + C_Timer.After(0.01, function() + local lineH = (editBox.GetLineHeight and editBox:GetLineHeight()) or 14 + local h = editBox:GetNumLines() * lineH + local sfH = sf:GetHeight() or 100 + if h <= sfH then + sc:SetHeight(sfH) + editBox:SetHeight(sfH) + else + sc:SetHeight(h + 4) + editBox:SetHeight(h + 4) + end + end) + end + editBox:SetScript("OnTextChanged", function() RefreshHeight() end) + + -- Save button + local saveBtn = CreateFrame("Button", nil, popup) + saveBtn:SetSize(120, 26) + saveBtn:SetPoint("BOTTOMRIGHT", popup, "BOTTOM", -4, 14) + saveBtn:SetFrameLevel(popup:GetFrameLevel() + 2) + EllesmereUI.MakeStyledButton(saveBtn, "Save", 11, + EllesmereUI.WB_COLOURS, function() + local str = editBox:GetText() or "" + if not EllesmereUIDB then EllesmereUIDB = {} end + if not EllesmereUIDB.auraFilter then + EllesmereUIDB.auraFilter = { mode = "disabled", spellIDs = "" } + end + EllesmereUIDB.auraFilter.spellIDs = str + EllesmereUI.RebuildAuraFilterCache() + dimmer:Hide() + end) + + local cancelBtn = CreateFrame("Button", nil, popup) + cancelBtn:SetSize(120, 26) + cancelBtn:SetPoint("BOTTOMLEFT", popup, "BOTTOM", 4, 14) + cancelBtn:SetFrameLevel(popup:GetFrameLevel() + 2) + EllesmereUI.MakeStyledButton(cancelBtn, "Cancel", 11, + EllesmereUI.RB_COLOURS, function() dimmer:Hide() end) + + dimmer:SetScript("OnMouseDown", function() + if not popup:IsMouseOver() then dimmer:Hide() end + end) + + popup:EnableKeyboard(true) + popup:SetScript("OnKeyDown", function(self, key) + if key == "ESCAPE" then + self:SetPropagateKeyboardInput(false) + dimmer:Hide() + else + self:SetPropagateKeyboardInput(true) + end + end) + + dimmer:HookScript("OnHide", function() + editBox:ClearFocus() + sf:SetVerticalScroll(0) + end) + + auraPopupDimmer = dimmer + auraPopupEditBox = editBox + auraPopupRefreshH = RefreshHeight + end + + auraPopupEditBox:SetText(currentText or "") + auraPopupDimmer:Show() + auraPopupRefreshH() + C_Timer.After(0.05, function() auraPopupEditBox:SetFocus() end) + end +end + ------------------------------------------------------------------------------- -- Init + Demo Modules (temporary placeholder content) ------------------------------------------------------------------------------- @@ -6216,6 +6407,13 @@ initFrame:SetScript("OnEvent", function(self, event) if EllesmereUI._applyGuildChatPrivacy then EllesmereUI._applyGuildChatPrivacy() end if EllesmereUI._applySecondaryStats then EllesmereUI._applySecondaryStats() end + -- Initialize aura filter defaults and rebuild cache + if not EllesmereUIDB then EllesmereUIDB = {} end + if not EllesmereUIDB.auraFilter then + EllesmereUIDB.auraFilter = { mode = "disabled", spellIDs = "" } + end + EllesmereUI.RebuildAuraFilterCache() + -- Re-read theme settings from SavedVariables (belt-and-suspenders for persistence) if EllesmereUIDB then -- Migrate legacy keys to new activeTheme model diff --git a/EllesmereUINameplates/EUI_Nameplates_Options.lua b/EllesmereUINameplates/EUI_Nameplates_Options.lua index 6cb2a113..4f4c3c6b 100644 --- a/EllesmereUINameplates/EUI_Nameplates_Options.lua +++ b/EllesmereUINameplates/EUI_Nameplates_Options.lua @@ -2307,6 +2307,16 @@ initFrame:SetScript("OnEvent", function(self) end, tooltip="Scales enemy nameplates while they are casting. 100% = no change." }); y = y - h + _, h = W:DualRow(parent, y, + { type="toggle", text="Use Aura Spell ID Filter", + getValue=function() return DBVal("useAuraFilter") == true end, + setValue=function(v) + DB().useAuraFilter = v + RefreshAllAuras() + end, + tooltip="Apply the global Aura Spell ID Filter (configured in Global Settings) to nameplates." }, + { type="label", text="" }); y = y - h + -- Helper: pandemic glow is off when style is "None" local function pandemicOff() return DBVal("pandemicGlow") ~= true diff --git a/EllesmereUINameplates/EllesmereUINameplates.lua b/EllesmereUINameplates/EllesmereUINameplates.lua index 4200caca..9150f6e9 100644 --- a/EllesmereUINameplates/EllesmereUINameplates.lua +++ b/EllesmereUINameplates/EllesmereUINameplates.lua @@ -3629,6 +3629,7 @@ function NameplateFrame:UpdateAuras(updateInfo) local debuffSlotVal, buffSlotVal, ccSlotVal = GetAuraSlots() local dIdx = 1 local db = EllesmereUINameplatesDB + local npUseAuraFilter = db and db.useAuraFilter and EllesmereUI.IsSpellFiltered if debuffSlotVal ~= "none" then local showAll = db and db.showAllDebuffs -- Build the important set from Blizzard's debuffList synchronously. @@ -3652,7 +3653,8 @@ function NameplateFrame:UpdateAuras(updateInfo) for _, aura in ipairs(allDebuffs) do if dIdx > 4 then break end local id = aura and aura.auraInstanceID - if id and aura.icon and (showAll or (importantSet and importantSet[id])) then + if id and aura.icon and (showAll or (importantSet and importantSet[id])) + and not (npUseAuraFilter and npUseAuraFilter(aura.spellId)) then local slot = self.debuffs[dIdx] slot.icon:SetTexture(aura.icon) slot.icon:SetTexCoord(0.08, 0.92, 0.08, 0.92) @@ -3711,7 +3713,8 @@ function NameplateFrame:UpdateAuras(updateInfo) for _, aura in ipairs(allBuffs) do if bIdx > 4 then break end local id = aura and aura.auraInstanceID - if id and type(aura.dispelName) ~= "nil" and aura.icon then + if id and type(aura.dispelName) ~= "nil" and aura.icon + and not (npUseAuraFilter and npUseAuraFilter(aura.spellId)) then local slot = self.buffs[bIdx] slot.icon:SetTexture(aura.icon) slot.icon:SetTexCoord(0.08, 0.92, 0.08, 0.92) @@ -3743,7 +3746,8 @@ function NameplateFrame:UpdateAuras(updateInfo) local GetDur = C_UnitAuras.GetAuraDuration for _, aura in ipairs(ccAuras) do if ccShown >= 2 then break end - if aura and aura.auraInstanceID and aura.icon then + if aura and aura.auraInstanceID and aura.icon + and not (npUseAuraFilter and npUseAuraFilter(aura.spellId)) then ccShown = ccShown + 1 local slot = self.cc[ccShown] slot.icon:SetTexture(aura.icon) @@ -4201,6 +4205,16 @@ manager:RegisterEvent("PLAYER_REGEN_ENABLED") manager:RegisterEvent("DISPLAY_SIZE_CHANGED") manager:RegisterEvent("UI_SCALE_CHANGED") +-- Register for aura filter changes so nameplates refresh when mode/list changes +if EllesmereUI and EllesmereUI.RegisterAuraFilterCallback then + EllesmereUI.RegisterAuraFilterCallback(function() + local plates = ns.plates + for _, plate in pairs(plates) do + plate:UpdateAuras() + end + end) +end + local pendingUnits = {} ns.pendingUnits = pendingUnits local currentMouseoverPlate = nil diff --git a/EllesmereUIUnitFrames/EUI_UnitFrames_Options.lua b/EllesmereUIUnitFrames/EUI_UnitFrames_Options.lua index 31fbb7e4..593ab7cf 100644 --- a/EllesmereUIUnitFrames/EUI_UnitFrames_Options.lua +++ b/EllesmereUIUnitFrames/EUI_UnitFrames_Options.lua @@ -6180,6 +6180,9 @@ initFrame:SetScript("OnEvent", function(self) { type="toggle", label="Show Own Only", get=function() return SValSupported("onlyPlayerDebuffs", false) end, set=function(v) SSetSupported("onlyPlayerDebuffs", v) end }, + { type="toggle", label="Use Aura Spell ID Filter", + get=function() return SValSupported("useAuraFilter", false) end, + set=function(v) SSetSupported("useAuraFilter", v) end }, }, }) local debuffCogShow = debuffCogShowRaw diff --git a/EllesmereUIUnitFrames/EllesmereUIUnitFrames.lua b/EllesmereUIUnitFrames/EllesmereUIUnitFrames.lua index 342dbe82..f7b84c39 100644 --- a/EllesmereUIUnitFrames/EllesmereUIUnitFrames.lua +++ b/EllesmereUIUnitFrames/EllesmereUIUnitFrames.lua @@ -2517,6 +2517,12 @@ local function CreateUnifiedBorder(frame, unit) end +-- Shared oUF FilterAura for spell ID filtering +local function AuraSpellIDFilter(element, unit, data) + if not data or not data.spellId then return true end + return not EllesmereUI.IsSpellFiltered(data.spellId) +end + local function CreateTargetAuras(frame, unit) local function SetupAuraIcon(_, button) if not button then return end @@ -2584,6 +2590,9 @@ local function CreateTargetAuras(frame, unit) buffs:Hide() buffs.num = 0 end + if settings.useAuraFilter then + buffs.FilterAura = AuraSpellIDFilter + end frame.Buffs = buffs local maxDebuffs = (settings and settings.maxDebuffs) or 28 @@ -2612,6 +2621,9 @@ local function CreateTargetAuras(frame, unit) if settings and settings.onlyPlayerDebuffs then debuffs.onlyShowPlayer = true end + if settings.useAuraFilter then + debuffs.FilterAura = AuraSpellIDFilter + end frame.Debuffs = debuffs end @@ -2737,6 +2749,9 @@ local function StyleFullFrame(frame, unit) PP.CreateBorder(button.Border, 0, 0, 0, 1) end end + if settings.useAuraFilter then + buffs.FilterAura = AuraSpellIDFilter + end frame.Buffs = buffs end elseif unit == "target" then