From 2d9e9329db0d5fa76e4b6ddb9dc62f3b20177722 Mon Sep 17 00:00:00 2001 From: Florian Berthold Date: Mon, 8 Jun 2026 18:10:33 +0200 Subject: [PATCH 1/4] feat(necromancer): add Necromancer mana bar (mana + runic power at once) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mirrors the Druid mana bar for the CoA Necromancer (class token NECROMANCER): the main power bar shows the active power type while a second bar shows the other one, so mana and runic power are both visible at the same time. Adaptive — recolors to whichever resource it is currently showing, so it works regardless of which power CoA treats as primary. Off by default; toggle under Player bars (auto-hidden for non-Necromancers). --- ShadowedUF_Options/config.lua | 12 +++- ShadowedUnitFrames/ShadowedUnitFrames.lua | 1 + ShadowedUnitFrames/ShadowedUnitFrames.toc | 1 + ShadowedUnitFrames/localization/enUS.lua | 2 + ShadowedUnitFrames/modules/defaultlayout.lua | 1 + ShadowedUnitFrames/modules/necromancer.lua | 70 ++++++++++++++++++++ 6 files changed, 85 insertions(+), 2 deletions(-) create mode 100644 ShadowedUnitFrames/modules/necromancer.lua diff --git a/ShadowedUF_Options/config.lua b/ShadowedUF_Options/config.lua index 91f346a..b66b6d3 100644 --- a/ShadowedUF_Options/config.lua +++ b/ShadowedUF_Options/config.lua @@ -2029,14 +2029,14 @@ local function loadUnitOptions() }, sep2 = {order = 1.75, type = "description", name = "", hidden = function(info) local moduleKey = info[#(info) - 1] - return ( moduleKey ~= "healthBar" and moduleKey ~= "powerBar" and moduleKey ~= "druidBar" ) or not ShadowUF.db.profile.advanced + return ( moduleKey ~= "healthBar" and moduleKey ~= "powerBar" and moduleKey ~= "druidBar" and moduleKey ~= "necromancerBar" ) or not ShadowUF.db.profile.advanced end}, invert = { order = 2, type = "toggle", name = L["Invert colors"], desc = L["Flips coloring so the bar color is shown as the background color and the background as the bar"], - hidden = function(info) return ( info[#(info) - 1] ~= "healthBar" and info[#(info) - 1] ~= "powerBar" and info[#(info) - 1] ~= "druidBar" ) or not ShadowUF.db.profile.advanced end, + hidden = function(info) return ( info[#(info) - 1] ~= "healthBar" and info[#(info) - 1] ~= "powerBar" and info[#(info) - 1] ~= "druidBar" and info[#(info) - 1] ~= "necromancerBar" ) or not ShadowUF.db.profile.advanced end, arg = "$parent.invert", }, order = { @@ -2997,6 +2997,14 @@ local function loadUnitOptions() hidden = hideRestrictedOption, arg = "druidBar.enabled", }, + necromancerBar = { + order = 1.5, + type = "toggle", + name = string.format(L["Enable %s"], L["Necromancer mana bar"]), + desc = L["Adds a second power bar to the player frame showing your mana and runic power at the same time."], + hidden = hideRestrictedOption, + arg = "necromancerBar.enabled", + }, xpBar = { order = 2, type = "toggle", diff --git a/ShadowedUnitFrames/ShadowedUnitFrames.lua b/ShadowedUnitFrames/ShadowedUnitFrames.lua index d0db7dc..c7878a0 100644 --- a/ShadowedUnitFrames/ShadowedUnitFrames.lua +++ b/ShadowedUnitFrames/ShadowedUnitFrames.lua @@ -243,6 +243,7 @@ function ShadowUF:LoadUnitDefaults() self.defaults.profile.units.player.runeBar = {enabled = false} self.defaults.profile.units.player.totemBar = {enabled = false} self.defaults.profile.units.player.druidBar = {enabled = false} + self.defaults.profile.units.player.necromancerBar = {enabled = false} self.defaults.profile.units.player.xpBar = {enabled = false} self.defaults.profile.units.player.fader = {enabled = false, combatAlpha = 1.0, inactiveAlpha = 0.60} self.defaults.profile.units.player.indicators.lfdRole = {enabled = true, size = 0, x = 0, y = 0} diff --git a/ShadowedUnitFrames/ShadowedUnitFrames.toc b/ShadowedUnitFrames/ShadowedUnitFrames.toc index 61eaa58..f7ccf8c 100644 --- a/ShadowedUnitFrames/ShadowedUnitFrames.toc +++ b/ShadowedUnitFrames/ShadowedUnitFrames.toc @@ -51,5 +51,6 @@ modules\incheal.lua modules\range.lua modules\empty.lua modules\druid.lua +modules\necromancer.lua CoAClassColors.lua diff --git a/ShadowedUnitFrames/localization/enUS.lua b/ShadowedUnitFrames/localization/enUS.lua index e895bce..1638551 100644 --- a/ShadowedUnitFrames/localization/enUS.lua +++ b/ShadowedUnitFrames/localization/enUS.lua @@ -192,6 +192,8 @@ L["Down"] = "Down" L["Druid form"] = "Druid form" L["Druid form (Short)"] = "Druid form (Short)" L["Druid mana bar"] = "Druid mana bar" +L["Necromancer mana bar"] = "Necromancer mana bar" +L["Adds a second power bar to the player frame showing your mana and runic power at the same time."] = "Adds a second power bar to the player frame showing your mana and runic power at the same time." L["Due to the nature of fake units, cast bars for %s are not super efficient and can take at most 0.10 seconds to notice a change in cast."] = "Due to the nature of fake units, cast bars for %s are not super efficient and can take at most 0.10 seconds to notice a change in cast." L["Dungeon role"] = "Dungeon role" L["Edge size"] = "Edge size" diff --git a/ShadowedUnitFrames/modules/defaultlayout.lua b/ShadowedUnitFrames/modules/defaultlayout.lua index 64a8985..1f4d573 100644 --- a/ShadowedUnitFrames/modules/defaultlayout.lua +++ b/ShadowedUnitFrames/modules/defaultlayout.lua @@ -214,6 +214,7 @@ function ShadowUF:LoadDefaultLayout(useMerge) healthBar = {background = true, colorType = "class", reactionType = "npc", height = 1.20, order = 10}, powerBar = {background = true, height = 1.0, order = 20}, druidBar = {background = true, height = 0.40, order = 25}, + necromancerBar = {background = true, height = 0.40, order = 26}, xpBar = {background = true, height = 0.25, order = 55}, castBar = {background = true, height = 0.60, order = 40, icon = "HIDE", name = {enabled = true, size = 0, anchorTo = "$parent", rank = true, anchorPoint = "CLI", x = 1, y = 0}, time = {enabled = true, size = 0, anchorTo = "$parent", anchorPoint = "CRI", x = -1, y = 0}}, runeBar = {background = false, height = 0.40, order = 70}, diff --git a/ShadowedUnitFrames/modules/necromancer.lua b/ShadowedUnitFrames/modules/necromancer.lua new file mode 100644 index 0000000..d9b6d78 --- /dev/null +++ b/ShadowedUnitFrames/modules/necromancer.lua @@ -0,0 +1,70 @@ +local Necromancer = {} +ShadowUF:RegisterModule(Necromancer, "necromancerBar", ShadowUF.L["Necromancer mana bar"], true, "NECROMANCER") + +-- Power type enums (3.3.5): 0 = mana, 6 = runic power. +local MANA, RUNIC_POWER = 0, 6 + +-- The Necromancer juggles mana and runic power. The main power bar shows +-- whichever is the active power type; this secondary bar shows the other one +-- so both are visible at once (same idea as the Druid mana bar in forms). +local function secondaryPower(unit) + local active = UnitPowerType(unit) + if( active == RUNIC_POWER ) then return MANA end + if( active == MANA ) then return RUNIC_POWER end + return nil +end + +function Necromancer:OnEnable(frame) + frame.necromancerBar = frame.necromancerBar or ShadowUF.Units:CreateBar(frame) + + frame:RegisterUnitEvent("UNIT_MAXMANA", self, "Update") + frame:RegisterUnitEvent("UNIT_MANA", self, "Update") + frame:RegisterUnitEvent("UNIT_RUNIC_POWER", self, "Update") + frame:RegisterUnitEvent("UNIT_MAXRUNIC_POWER", self, "Update") + frame:RegisterUnitEvent("UNIT_DISPLAYPOWER", self, "PowerChanged") + + frame:RegisterUpdateFunc(self, "PowerChanged") + frame:RegisterUpdateFunc(self, "Update") +end + +function Necromancer:OnDisable(frame) + frame:UnregisterAll(self) +end + +function Necromancer:UpdateColor(frame, power) + local color = ShadowUF.db.profile.powerColors[power == RUNIC_POWER and "RUNIC_POWER" or "MANA"] + + if( not ShadowUF.db.profile.units[frame.unitType].necromancerBar.invert ) then + frame.necromancerBar:SetStatusBarColor(color.r, color.g, color.b, ShadowUF.db.profile.bars.alpha) + if( not frame.necromancerBar.background.overrideColor ) then + frame.necromancerBar.background:SetVertexColor(color.r, color.g, color.b, ShadowUF.db.profile.bars.backgroundAlpha) + end + else + frame.necromancerBar.background:SetVertexColor(color.r, color.g, color.b, ShadowUF.db.profile.bars.alpha) + + color = frame.necromancerBar.background.overrideColor or color + frame.necromancerBar:SetStatusBarColor(color.r, color.g, color.b, ShadowUF.db.profile.bars.backgroundAlpha) + end +end + +function Necromancer:OnLayoutApplied(frame) + if( frame.visibility.necromancerBar ) then + self:UpdateColor(frame, secondaryPower(frame.unit) or MANA) + end +end + +-- Show the bar whenever there is a second power type to display, and recolor it +-- to match whichever resource that currently is. +function Necromancer:PowerChanged(frame) + local power = secondaryPower(frame.unit) + ShadowUF.Layout:SetBarVisibility(frame, "necromancerBar", power ~= nil) + if( power ) then + self:UpdateColor(frame, power) + end +end + +function Necromancer:Update(frame) + local power = secondaryPower(frame.unit) or MANA + frame.necromancerBar:SetMinMaxValues(0, UnitPowerMax(frame.unit, power)) + frame.necromancerBar:SetValue(UnitIsDeadOrGhost(frame.unit) and 0 or not UnitIsConnected(frame.unit) and 0 or UnitPower(frame.unit, power)) +end From 319bd0593099ac41dd2f9e840e7f9cbf61b9e464 Mon Sep 17 00:00:00 2001 From: Florian Berthold Date: Mon, 8 Jun 2026 20:42:10 +0200 Subject: [PATCH 2/4] fix(necromancer): ship bar layout in defaults so existing profiles don't crash The necromancerBar height/order/background only reached a profile via the defaultlayout merge, which runs on profile reset. Enabling the bar on a pre-existing profile left height nil, crashing Layout:PositionWidgets ("attempt to compare number with nil" at layout.lua:441). Carry the layout in the AceDB defaults so it resolves via the defaults metatable for old and new profiles alike. --- ShadowedUnitFrames/ShadowedUnitFrames.lua | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ShadowedUnitFrames/ShadowedUnitFrames.lua b/ShadowedUnitFrames/ShadowedUnitFrames.lua index c7878a0..7fb1109 100644 --- a/ShadowedUnitFrames/ShadowedUnitFrames.lua +++ b/ShadowedUnitFrames/ShadowedUnitFrames.lua @@ -243,7 +243,11 @@ function ShadowUF:LoadUnitDefaults() self.defaults.profile.units.player.runeBar = {enabled = false} self.defaults.profile.units.player.totemBar = {enabled = false} self.defaults.profile.units.player.druidBar = {enabled = false} - self.defaults.profile.units.player.necromancerBar = {enabled = false} + -- Carry the layout (height/order/background) in the defaults, not only in the + -- defaultlayout merge: that merge only runs on profile reset, so a bar added to + -- an existing profile would otherwise have a nil height and crash + -- Layout:PositionWidgets ("attempt to compare number with nil" in layout.lua). + self.defaults.profile.units.player.necromancerBar = {enabled = false, height = 0.40, order = 26, background = true} self.defaults.profile.units.player.xpBar = {enabled = false} self.defaults.profile.units.player.fader = {enabled = false, combatAlpha = 1.0, inactiveAlpha = 0.60} self.defaults.profile.units.player.indicators.lfdRole = {enabled = true, size = 0, x = 0, y = 0} From 4d2ef28ad05d60f6a6747c01ff3d355851b3ba2d Mon Sep 17 00:00:00 2001 From: Florian Berthold Date: Wed, 10 Jun 2026 02:11:47 +0200 Subject: [PATCH 3/4] ci(release): sync release.yml from coa-template MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit hide_archive_links is only honored by Gitea on release edit, not create — add the PATCH step after create/lookup so auto-generated source archive links actually stay hidden (coa-template 90874c5). --- .gitea/workflows/release.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index 709a7cd..2f93975 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -37,10 +37,14 @@ jobs: RID=$(curl -sf -X POST -H "Authorization: token $GITEA_TOKEN" \ -H "Content-Type: application/json" \ "$API/repos/$REPO/releases" \ - -d "$(jq -nc --arg t "$TAG" '{tag_name:$t,name:$t,draft:false,prerelease:false}')" \ + -d "$(jq -nc --arg t "$TAG" '{tag_name:$t,name:$t,draft:false,prerelease:false,hide_archive_links:true}')" \ | jq -r '.id') fi echo "release id: $RID" + # Gitea honors hide_archive_links only on edit, not create — PATCH it + # so the auto-generated Source Code (zip/tar.gz) links stay hidden. + curl -sf -X PATCH -H "Authorization: token $GITEA_TOKEN" -H "Content-Type: application/json" \ + "$API/repos/$REPO/releases/$RID" -d '{"hide_archive_links":true}' >/dev/null || true # Upload every dist/*.zip. Per-asset failures don't fail the job — # we want partial releases to still publish rather than block the # whole pipeline on one big file. From 2aad7af12d302a6fb483261cfc2cbcb070849da2 Mon Sep 17 00:00:00 2001 From: Florian Berthold Date: Wed, 10 Jun 2026 02:16:14 +0200 Subject: [PATCH 4/4] chore: bump toc Version to v3.3.0-coa.7 to match latest release tag --- ShadowedUnitFrames/ShadowedUnitFrames.toc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ShadowedUnitFrames/ShadowedUnitFrames.toc b/ShadowedUnitFrames/ShadowedUnitFrames.toc index f7ccf8c..7d3a63a 100644 --- a/ShadowedUnitFrames/ShadowedUnitFrames.toc +++ b/ShadowedUnitFrames/ShadowedUnitFrames.toc @@ -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-coa.2 +## Version: v3.3.0-coa.7 ## SavedVariables: ShadowedUFDB ## OptionalDeps: Ace3, LibSharedMedia-3.0, LibHealComm-4.0, AceGUI-3.0-SharedMediaWidgets ## X-Curse-Packaged-Version: v3.2.12