From 110874694934fabca3a1187b428e7ca565d8f5e8 Mon Sep 17 00:00:00 2001 From: Brues <5278969+brues-code@users.noreply.github.com> Date: Sun, 13 Sep 2026 15:19:42 -0500 Subject: [PATCH] castbar: mark a cast that none of your interrupts can stop ClassicAPI carries notInterruptible on C_Spell.UnitCastingInfo (arg 8) and UnitChannelInfo (arg 7); both call sites already stepped over it positionally. The target, focus and nameplate bars now paint it. There is no shield art to hang on the icon -- this client ships three CastingBar textures (Border, Flash, Spark), and Blizzard's shield arrived in 3.x -- so the state is carried by color instead: the bar and the icon's border both take a new appearance.castbar.shieldcolor, which keeps it legible on a bar configured without an icon. One api helper paints both so the two modules cannot drift apart on it. Dusty rose (.8,.45,.45) rather than a steel grey. Grey is the retail convention, but pfUI's casting color is already a near-grey pale blue, so the two separated on brightness alone and washed out against the bar texture. The rose sits above failcolor in luminance and well below it in saturation, and the two are further told apart by motion: a failed cast flashes the bar full and fades, a shielded one fills normally and tints the icon border, which failcolor never touches. Not on the player's own bar. The flag is measured against the reader's own interrupts and silences, so on your own cast it answers a question nobody asked. Stamped once at cast start, with the rest of the cast data, rather than re-polled. ClassicAPI evaluates lazily from the caster's live auras but does not yet fire the 3.3.5 UNIT_SPELLCAST_INTERRUPTIBLE / _NOT_INTERRUPTIBLE change events, and polling per frame would undo the event-driven rework these bars just got -- so a caster who bubbles mid-cast keeps the bar it started with until the next one. Two limits worth knowing when it stays dark. The value is relative to your own kit -- with no interrupt and no silence known it is never reported, whatever the cast -- and a creature's built-in interrupt immunity lives in creature_template, which 1.12's SMSG_SPELL_START does not carry, so a boss with that immunity reads interruptible while your kick still fails on it. --- api/api.lua | 26 ++++++++++++++++++++++++++ api/config.lua | 1 + modules/castbar.lua | 13 ++++++++----- modules/gui.lua | 1 + modules/nameplates.lua | 7 ++++--- 5 files changed, 40 insertions(+), 8 deletions(-) diff --git a/api/api.lua b/api/api.lua index faa12971..70c29697 100644 --- a/api/api.lua +++ b/api/api.lua @@ -1333,6 +1333,32 @@ function pfUI.api.CreateBackdropShadow(f) f.backdrop_shadow:SetBackdropBorderColor(0, 0, 0, tonumber(pfUI_config.appearance.border.shadow_intensity)) end +-- [ Set Castbar Shield ] +-- Colors a castbar for a cast that none of the player's own interrupts or +-- silences can stop. The bar and the icon's border both take the shield color, +-- so the state still reads on a bar configured without an icon. Not shielded +-- restores the normal cast/channel color and the configured border. +-- 'bar' [frame] the castbar statusbar +-- 'icon' [frame] the icon frame, or nil for a bar without one +-- 'shielded' [bool] whether the cast is uninterruptible +-- 'isChannel' [bool] selects the unshielded color +function pfUI.api.SetCastbarShield(bar, icon, shielded, isChannel) + if shielded then + bar:SetStatusBarColor(pfUI.api.GetStringColor(C.appearance.castbar.shieldcolor)) + else + bar:SetStatusBarColor(pfUI.api.GetStringColor(C.appearance.castbar[isChannel and "channelcolor" or "castbarcolor"])) + end + + local iconborder = icon and icon.backdrop + if not iconborder then return end + + if shielded then + iconborder:SetBackdropBorderColor(pfUI.api.GetStringColor(C.appearance.castbar.shieldcolor)) + else + iconborder:SetBackdropBorderColor(pfUI.api.GetStringColor(C.appearance.border.color)) + end +end + -- [ Bar Layout Options ] -- -- 'barsize' size of bar in number of buttons -- returns: array of options as strings for pfUI.gui.bar diff --git a/api/config.lua b/api/config.lua index f7d74e68..3f4ff2fc 100644 --- a/api/config.lua +++ b/api/config.lua @@ -156,6 +156,7 @@ function pfUI:LoadConfig() pfUI:UpdateConfig("appearance", "castbar", "castbarcolor", ".7,.7,.9,.8") pfUI:UpdateConfig("appearance", "castbar", "channelcolor", ".9,.9,.7,.8") pfUI:UpdateConfig("appearance", "castbar", "failcolor", "1,.2,.2,.8") + pfUI:UpdateConfig("appearance", "castbar", "shieldcolor", ".8,.45,.45,.8") pfUI:UpdateConfig("appearance", "castbar", "texture", "Interface\\AddOns\\pfUI\\img\\bar") pfUI:UpdateConfig("appearance", "infight", "screen", "0") pfUI:UpdateConfig("appearance", "infight", "aggro", "0") diff --git a/modules/castbar.lua b/modules/castbar.lua index d52e7ba0..7429eab3 100644 --- a/modules/castbar.lua +++ b/modules/castbar.lua @@ -86,7 +86,7 @@ pfUI:RegisterModule("castbar", function () -- Stamp the bar with cast data and render text/icon/lag once. OnUpdate -- then animates the fill from this state without touching C_Spell. - local function StampBar(cb, name, tex, startMs, endMs, spellID, isChannel, delayMs, isTradeskill, rank) + local function StampBar(cb, name, tex, startMs, endMs, spellID, isChannel, delayMs, isTradeskill, rank, noInterrupt) cb.startTime = startMs cb.endTime = endMs cb.isChannel = isChannel @@ -97,7 +97,10 @@ pfUI:RegisterModule("castbar", function () cb:SetAlpha(1) cb.fadeout = nil - cb.bar:SetStatusBarColor(GetStringColor(C.appearance.castbar[isChannel and "channelcolor" or "castbarcolor"])) + -- Shield an uninterruptible cast. Not on the player's own bar: the flag is + -- measured against the reader's own interrupts and silences, so on your own + -- cast it answers a question nobody asked. + SetCastbarShield(cb.bar, cb.icon, noInterrupt and cb.unitstr ~= "player", isChannel) -- Rank: prefer the value the UNIT_SPELLCAST_* event delivered (arg5, passed -- through by RefreshBar). Only the retarget re-poll has no event in hand, so @@ -152,10 +155,10 @@ pfUI:RegisterModule("castbar", function () ClearBar(cb) return end - local name, _, tex, startMs, endMs, isTradeskill, _, _, spellID, _, delayMs = C_Spell.UnitCastingInfo(query) + local name, _, tex, startMs, endMs, isTradeskill, _, noInterrupt, spellID, _, delayMs = C_Spell.UnitCastingInfo(query) local isChan if not name then - name, _, tex, startMs, endMs, _, _, spellID = C_Spell.UnitChannelInfo(query) + name, _, tex, startMs, endMs, _, noInterrupt, spellID = C_Spell.UnitChannelInfo(query) isChan = true end -- Synthetic fallback for abilities the engine treats as instant-cast but @@ -169,7 +172,7 @@ pfUI:RegisterModule("castbar", function () end end if name and startMs and endMs then - StampBar(cb, name, tex, startMs, endMs, spellID, isChan, delayMs, isTradeskill, rank) + StampBar(cb, name, tex, startMs, endMs, spellID, isChan, delayMs, isTradeskill, rank, noInterrupt) else ClearBar(cb) end diff --git a/modules/gui.lua b/modules/gui.lua index 8dcd85e2..457465d3 100644 --- a/modules/gui.lua +++ b/modules/gui.lua @@ -2792,6 +2792,7 @@ pfUI:RegisterModule("gui", function () CreateConfig(nil, T["Use Unit Fonts"], C.castbar, "use_unitfonts", "checkbox") CreateConfig(nil, T["Casting Color"], C.appearance.castbar, "castbarcolor", "color") CreateConfig(nil, T["Channeling Color"], C.appearance.castbar, "channelcolor", "color") + CreateConfig(nil, T["Uninterruptible Color"], C.appearance.castbar, "shieldcolor", "color") CreateConfig(nil, T["Castbar Texture"], C.appearance.castbar, "texture", "dropdown", pfUI.gui.dropdowns.uf_bartexture) CreateConfig(nil, T["Disable Blizzard Castbar"], C.castbar.player, "hide_blizz", "checkbox") end) diff --git a/modules/nameplates.lua b/modules/nameplates.lua index 450dda63..9a2be61d 100644 --- a/modules/nameplates.lua +++ b/modules/nameplates.lua @@ -77,10 +77,10 @@ pfUI:RegisterModule("nameplates", function () -- vs channel itself. Never called per frame. local function PollCastInfo(unit) if not unit then return nil end - local name, _, texture, startMs, endMs, _, _, _, spellID = C_Spell.UnitCastingInfo(unit) + local name, _, texture, startMs, endMs, _, _, noInterrupt, spellID = C_Spell.UnitCastingInfo(unit) local isChannel if not name then - name, _, texture, startMs, endMs, _, _, spellID = C_Spell.UnitChannelInfo(unit) + name, _, texture, startMs, endMs, _, noInterrupt, spellID = C_Spell.UnitChannelInfo(unit) isChannel = true end if not name or not startMs or not endMs then return nil end @@ -92,6 +92,7 @@ pfUI:RegisterModule("nameplates", function () endTime = endMs / 1000, duration = (endMs - startMs) / 1000, isChannel = isChannel, + noInterrupt = noInterrupt, } end @@ -1751,7 +1752,7 @@ nameplates:RegisterEvent("PLAYER_GUILD_UPDATE") -- Relative 0..duration range to avoid float precision loss with large -- absolute timestamps. nameplate.castbar:SetMinMaxValues(0, duration) - nameplate.castbar:SetStatusBarColor(GetStringColor(C.appearance.castbar[(isChannel and "channelcolor" or "castbarcolor")])) + SetCastbarShield(nameplate.castbar, nameplate.castbar.icon, castInfo.noInterrupt, isChannel) if castInfo.icon then nameplate.castbar.icon.tex:SetTexture(castInfo.icon) nameplate.castbar.icon.tex:SetTexCoord(.1,.9,.1,.9)