4 Commits

Author SHA1 Message Date
florian.berthold d241fd1b5e feat(filters): match aura whitelist/blacklist by spell ID, not just name
release / release (push) Successful in 3s
CoA's 3.3.5 client returns spellId as the 11th UnitAura value (stock 3.3.5a
stops at 10). The scan loop now captures it and the whitelist/blacklist match
on name OR spellId. The filter 'add' box accepts a numeric spell ID (stored as
a numeric key so it matches UnitAura's spellId) and the list view resolves IDs
to 'Name (ID)' via GetSpellInfo. Fixes ambiguity from name-only matching where
CoA custom spells share display names across ranks/variants.
2026-05-31 15:11:21 +02:00
florian.berthold ad2a536efe fix(dispels): add Curse to Witch Doctor dispel set (Hexbreak 806240)
release / release (push) Successful in 3s
WITCHDOCTOR could only flag Disease/Poison (Cleansing Idol). Hexbreak
(806240) is a single-target Curse remover, so add Curse=true. highlight.lua
reads this via GetCoaDispels, so the dispellable-debuff highlight picks it up.
2026-05-31 14:45:28 +02:00
florian.berthold 50f6e30f71 fix(libs): pcall AceGUI OnGamePadButtonDown (3.3.5 has no gamepad script type)
release / release (push) Successful in 3s
2026-05-29 20:23:33 +02:00
florian.berthold efbc200ba7 fix(dispels): drop C_Player.IsCustomClass gate from getCoaDispels
release / release (push) Successful in 4s
Witchdoctor (and likely other CoA classes intermittently) never got the
"On curable debuff" border highlight on raid/party frames: the
C_Player.IsCustomClass() check in getCoaDispels could return false at
scan time and cache playerCoaDispels = false, after which the highlight
manual-scan path was never entered and the fallback RAID filter (which
doesn't know about COA dispel spells) silently returned nothing.

COA_CLASS_DISPELS only contains custom-class tokens (CHRONOMANCER,
WITCHDOCTOR, MONK = Templar, PROPHET = Venomancer, etc.) so any token
match already implies the player is a dispelling custom class — the
IsCustomClass call is redundant. Trust the table directly. Vanilla
classes whose tokens aren't in the table still get false → RAID filter
path, unchanged.

Bump v3.3.0-coa.2.
2026-05-27 22:55:04 +02:00
5 changed files with 35 additions and 20 deletions
+14 -3
View File
@@ -3760,14 +3760,17 @@ local function loadFilterOptions()
add = {
order = 0,
type = "input",
name = L["Aura name"],
name = L["Aura name or spell ID"],
desc = L["Enter an exact aura name, or a numeric spell ID for precise matching (CoA's client exposes aura spell IDs)."],
--dialogControl = "Aura_EditBox",
hidden = false,
set = function(info, value)
local filterType = info[#(info) - 3]
local filter = filterMap[info[#(info) - 2]]
ShadowUF.db.profile.filters[filterType][filter][value] = true
-- A purely-numeric entry is stored as a number so it matches the
-- spellId returned by UnitAura; anything else stays a name string.
ShadowUF.db.profile.filters[filterType][filter][tonumber(value) or value] = true
reloadUnitAuras()
rebuildFilters()
@@ -3846,7 +3849,15 @@ local function loadFilterOptions()
-- on the other hand we can't set width to "normal" so tricking it
width = "",
fontSize = "medium",
name = function(info) return spellMap[info[#(info)]] end,
name = function(info)
local entry = spellMap[info[#(info)]]
-- Numeric entries are spell IDs; show "Name (ID)" when the client can resolve it.
if( type(entry) == "number" ) then
local spellName = GetSpellInfo(entry)
return spellName and string.format("%s (%d)", spellName, entry) or tostring(entry)
end
return entry
end,
}
local spellRow = {
@@ -199,7 +199,7 @@ local function Constructor()
button:SetScript("OnKeyDown", Keybinding_OnKeyDown)
button:SetScript("OnMouseDown", Keybinding_OnMouseDown)
button:SetScript("OnMouseWheel", Keybinding_OnMouseWheel)
button:SetScript("OnGamePadButtonDown", Keybinding_OnKeyDown)
pcall(button.SetScript, button, "OnGamePadButtonDown", Keybinding_OnKeyDown)
button:SetPoint("BOTTOMLEFT")
button:SetPoint("BOTTOMRIGHT")
button:SetHeight(24)
+1 -1
View File
@@ -2,7 +2,7 @@
## Title: Shadowed Unit Frames
## Notes: An apple a day keeps the raptor away, or so they say
## Author: Shadowed
## Version: v3.3.0
## Version: v3.3.0-coa.2
## SavedVariables: ShadowedUFDB
## OptionalDeps: Ace3, LibSharedMedia-3.0, LibHealComm-4.0, AceGUI-3.0-SharedMediaWidgets
## X-Curse-Packaged-Version: v3.2.12
+2
View File
@@ -48,6 +48,8 @@ L["Ascending"] = "Ascending"
L["Aura border style"] = "Aura border style"
L["Aura filters"] = "Aura filters"
L["Aura name"] = "Aura name"
L["Aura name or spell ID"] = "Aura name or spell ID"
L["Enter an exact aura name, or a numeric spell ID for precise matching (CoA's client exposes aura spell IDs)."] = "Enter an exact aura name, or a numeric spell ID for precise matching (CoA's client exposes aura spell IDs)."
L["Auras"] = "Auras"
L["Aura types to filter"] = "Aura types to filter"
L["B"] = "B"
+15 -13
View File
@@ -21,23 +21,23 @@ local COA_CLASS_DISPELS = {
["WITCHHUNTER"] = { Curse = true },
["SUNCLERIC"] = { Magic = true, Disease = true, Poison = true }, -- Sanctify
["WILDWALKER"] = { Disease = true, Poison = true }, -- Primalist (Soothing Touch — DBC says Magic, gameplay is Poison/Disease)
["WITCHDOCTOR"] = { Disease = true, Poison = true }, -- Cleansing Idol (AoE)
["WITCHDOCTOR"] = { Curse = true, Disease = true, Poison = true }, -- Hexbreak (806240, single-target Curse) + Cleansing Idol (504840, AoE Disease/Poison)
["TINKER"] = { Disease = true, Poison = true }, -- Nanobot Cleanser
}
local function getCoaDispels()
if playerCoaDispels ~= nil then return playerCoaDispels end
local cp = _G.C_Player
if not cp or not cp.IsCustomClass then
-- C_Player not yet initialised (called before PLAYER_LOGIN); don't
-- cache — retry on the next scan so we pick it up once in-world.
-- Trust the COA_CLASS_DISPELS table directly: it only contains custom-class
-- tokens, so any token-match implies the player IS a dispelling custom
-- class. Avoids C_Player.IsCustomClass timing/availability issues (the
-- API isn't reliable for every class on every login — Witchdoctor in
-- particular was getting cached as false on raid frames).
local _, token = UnitClass("player")
if not token or token == "" then
-- UnitClass not ready yet (very early init); don't cache — retry on
-- the next scan so we pick it up once in-world.
return nil
end
if cp:IsCustomClass() then
local _, token = UnitClass("player")
playerCoaDispels = token and COA_CLASS_DISPELS[token] or false
else
playerCoaDispels = false
end
playerCoaDispels = COA_CLASS_DISPELS[token] or false
return playerCoaDispels
end
-- Expose for other modules (highlight.lua) so the dispel set stays single-source.
@@ -531,10 +531,12 @@ local function scan(parent, frame, type, config, filter)
local index = 0
while( true ) do
index = index + 1
local name, rank, texture, count, auraType, duration, endTime, caster, isStealable = UnitAura(frame.parent.unit, index, filter)
-- CoA's 3.3.5 client returns spellId as the 11th value (stock 3.3.5a stops at 10),
-- which lets the whitelist/blacklist match by ID as well as by name.
local name, rank, texture, count, auraType, duration, endTime, caster, isStealable, _, spellId = UnitAura(frame.parent.unit, index, filter)
if( not name ) then break end
if( ( not coaFilter or (auraType and coaFilter[auraType]) ) and ( not config.player or playerUnits[caster] ) and ( not parent.whitelist[type] and not parent.blacklist[type] or parent.whitelist[type] and parent.whitelist[name] or parent.blacklist[type] and not parent.blacklist[name] ) ) then
if( ( not coaFilter or (auraType and coaFilter[auraType]) ) and ( not config.player or playerUnits[caster] ) and ( not parent.whitelist[type] and not parent.blacklist[type] or parent.whitelist[type] and ( parent.whitelist[name] or parent.whitelist[spellId] ) or parent.blacklist[type] and not ( parent.blacklist[name] or parent.blacklist[spellId] ) ) ) then
-- Create any buttons we need
frame.totalAuras = frame.totalAuras + 1
if( #(frame.buttons) < frame.totalAuras ) then