diff --git a/MidnightSimpleUnitFrames/Castbars/MSUF_CastbarDriver.lua b/MidnightSimpleUnitFrames/Castbars/MSUF_CastbarDriver.lua index c9dd8694..3b267be0 100644 --- a/MidnightSimpleUnitFrames/Castbars/MSUF_CastbarDriver.lua +++ b/MidnightSimpleUnitFrames/Castbars/MSUF_CastbarDriver.lua @@ -341,8 +341,13 @@ local function CreateCastBar(name, unit) rawNI = true end + local ur, ug, ub, ua, readyBool, useUnavailable = nil, nil, nil, nil, nil, false + if type(_G.MSUF_Castbar_GetInterruptUnavailableTintArgs) == "function" then + ur, ug, ub, ua, readyBool, useUnavailable = _G.MSUF_Castbar_GetInterruptUnavailableTintArgs(self) + end + if type(_G.MSUF_Castbar_ApplyNonInterruptibleTint) == "function" then - _G.MSUF_Castbar_ApplyNonInterruptibleTint(self, rawNI, nr, ng, nb, 1, ir, ig, ib, 1, isNonInterruptible) + _G.MSUF_Castbar_ApplyNonInterruptibleTint(self, rawNI, nr, ng, nb, 1, ir, ig, ib, 1, isNonInterruptible, ur, ug, ub, ua, readyBool, useUnavailable) else if isNonInterruptible then _G.MSUF_SetStatusBarColorIfChanged(self.statusBar, nr, ng, nb, 1) diff --git a/MidnightSimpleUnitFrames/Castbars/MSUF_CastbarUtils.lua b/MidnightSimpleUnitFrames/Castbars/MSUF_CastbarUtils.lua index 1620c408..131823cf 100644 --- a/MidnightSimpleUnitFrames/Castbars/MSUF_CastbarUtils.lua +++ b/MidnightSimpleUnitFrames/Castbars/MSUF_CastbarUtils.lua @@ -265,6 +265,7 @@ end -- These colors only change when the user modifies settings, not per-cast. local _tintCacheNonInt = { r = nil, g = nil, b = nil, a = nil, obj = nil } local _tintCacheInt = { r = nil, g = nil, b = nil, a = nil, obj = nil } +local _tintCacheUnavailable = { r = nil, g = nil, b = nil, a = nil, obj = nil } local function _GetCachedColor(cache, r, g, b, a) if cache.r == r and cache.g == g and cache.b == b and cache.a == a and cache.obj then @@ -280,12 +281,15 @@ end function _G.MSUF_Castbar_ApplyNonInterruptibleTint(frame, rawNotInterruptible, nonIntR, nonIntG, nonIntB, nonIntA, intR, intG, intB, intA, - fallbackIsNonInterruptible) + fallbackIsNonInterruptible, + unavailableR, unavailableG, unavailableB, unavailableA, + interruptReadyBool, useUnavailableColor) local sb = frame and frame.statusBar if not sb then return false end local wantNI = (fallbackIsNonInterruptible == true) + local useUnavailable = (useUnavailableColor == true and unavailableR ~= nil and unavailableG ~= nil and unavailableB ~= nil) local ar = wantNI and nonIntR or intR local ag = wantNI and nonIntG or intG @@ -303,13 +307,18 @@ function _G.MSUF_Castbar_ApplyNonInterruptibleTint(frame, rawNotInterruptible, -- Quick Win #13: reuse cached color objects (only re-created when RGBA changes). local nonCol = _GetCachedColor(_tintCacheNonInt, nonIntR, nonIntG, nonIntB, nonIntA or 1) local intCol = _GetCachedColor(_tintCacheInt, intR, intG, intB, intA or 1) + local activeIntCol = intCol + if useUnavailable and C_CurveUtil and C_CurveUtil.EvaluateColorFromBoolean then + local unavailableCol = _GetCachedColor(_tintCacheUnavailable, unavailableR, unavailableG, unavailableB, unavailableA or 1) + activeIntCol = C_CurveUtil.EvaluateColorFromBoolean(interruptReadyBool, intCol, unavailableCol) + end local v = rawNotInterruptible if v == nil then v = (wantNI == true) end - tex:SetVertexColorFromBoolean(v, nonCol, intCol) + tex:SetVertexColorFromBoolean(v, nonCol, activeIntCol) usedC = true end @@ -320,12 +329,17 @@ function _G.MSUF_Castbar_ApplyNonInterruptibleTint(frame, rawNotInterruptible, -- IMPORTANT (0-regression fix): keep the StatusBar color caches in sync even when -- we color via vertex tint. Otherwise later calls that rely on cached colors -- (e.g. interrupt feedback) may early-return and skip applying red on "2nd interrupt". - sb._msufLastColorR, sb._msufLastColorG, sb._msufLastColorB, sb._msufLastColorA = ar, ag, ab, aa - sb._msufLastR, sb._msufLastG, sb._msufLastB, sb._msufLastA = ar, ag, ab, aa + if useUnavailable then + sb._msufLastColorR, sb._msufLastColorG, sb._msufLastColorB, sb._msufLastColorA = nil, nil, nil, nil + sb._msufLastR, sb._msufLastG, sb._msufLastB, sb._msufLastA = nil, nil, nil, nil + else + sb._msufLastColorR, sb._msufLastColorG, sb._msufLastColorB, sb._msufLastColorA = ar, ag, ab, aa + sb._msufLastR, sb._msufLastG, sb._msufLastB, sb._msufLastA = ar, ag, ab, aa + end end -- Also keep glow-base tracking stable across both tint paths. - if not sb._msufGlowSkipBase then + if not sb._msufGlowSkipBase and not useUnavailable then local br, bg, bb, ba = sb._msufGlowBaseR, sb._msufGlowBaseG, sb._msufGlowBaseB, sb._msufGlowBaseA if br ~= ar or bg ~= ag or bb ~= ab or ba ~= aa then sb._msufGlowBaseR, sb._msufGlowBaseG, sb._msufGlowBaseB, sb._msufGlowBaseA = ar, ag, ab, aa @@ -585,6 +599,9 @@ function _G.MSUF_Castbar_ApplyActiveDuration(frame, state, opts) if frame.UpdateColorForInterruptible then frame:UpdateColorForInterruptible() end + if type(_G.MSUF_KickReady_TrackFillFrame) == "function" then + _G.MSUF_KickReady_TrackFillFrame(frame, state) + end if type(_G.MSUF_RegisterCastbar) == "function" and opts.skipRegister ~= true then _G.MSUF_RegisterCastbar(frame) @@ -762,6 +779,73 @@ function _G.MSUF_GetNonInterruptibleCastColor() end end +function _G.MSUF_GetInterruptUnavailableCastColor() + _EnsureDBLazy() + local g = (MSUF_DB and MSUF_DB.general) or {} + local r = tonumber(g.castbarInterruptUnavailableR) + local gg = tonumber(g.castbarInterruptUnavailableG) + local b = tonumber(g.castbarInterruptUnavailableB) + if r and gg and b then + return r, gg, b, 1 + end +end + +local function _MSUF_CastbarUnitSupportsInterruptUnavailableTint(frame, g) + local unit = frame and frame.unit + if type(unit) ~= "string" then return false end + local shouldUse = _G.MSUF_ShouldUseMSUFCastbar + local function owns(which) + if type(shouldUse) == "function" then + return shouldUse(which, g) == true + end + return true + end + if unit == "target" then return g.kickReadyShowTarget == true and owns("target") end + if unit == "focus" then return g.kickReadyShowFocus == true and owns("focus") end + if unit:sub(1, 4) == "boss" then return g.kickReadyShowBoss == true and owns("boss") end + return false +end + +function _G.MSUF_Castbar_ShouldUseInterruptUnavailableColor(frame) + _EnsureDBLazy() + local g = (MSUF_DB and MSUF_DB.general) or {} + if g.kickReadyStyle ~= "fill" then return false end + return _MSUF_CastbarUnitSupportsInterruptUnavailableTint(frame, g) +end + +function _G.MSUF_ResolveInterruptUnavailableCastColor() + _EnsureDBLazy() + local g = (MSUF_DB and MSUF_DB.general) or {} + local r, gg, b + if type(_G.MSUF_GetInterruptUnavailableCastColor) == "function" then + r, gg, b = _G.MSUF_GetInterruptUnavailableCastColor() + end + if not (r and gg and b) then + local key = g.castbarInterruptUnavailableColor + local c = (key and type(_G.MSUF_GetColorFromKey) == "function") and _G.MSUF_GetColorFromKey(key) or nil + if c and c.GetRGB then + r, gg, b = c:GetRGB() + end + end + if not (r and gg and b) then + r, gg, b = 1.0, 0.494117647, 0.137254902 + end + return r, gg, b, 1 +end + +function _G.MSUF_Castbar_GetInterruptUnavailableTintArgs(frame) + if not _G.MSUF_Castbar_ShouldUseInterruptUnavailableColor(frame) then return nil end + if type(_G.MSUF_KickReady_IsReady) ~= "function" then return nil end + if type(_G.MSUF_KickReady_GetSpellID) == "function" and not _G.MSUF_KickReady_GetSpellID() then return nil end + if not (_G.C_Spell and _G.C_Spell.GetSpellCooldownDuration and _G.C_CurveUtil and _G.C_CurveUtil.EvaluateColorFromBoolean) then return nil end + + local readyBool = _G.MSUF_KickReady_IsReady() + if readyBool == nil then return nil end + + local r, gg, b, a = _G.MSUF_ResolveInterruptUnavailableCastColor() + return r, gg, b, a or 1, readyBool, true +end + -- "Glow effect" (Options -> Castbars -> Behavior): end-of-cast fade to white. -- NOTE: This is intentionally texture-agnostic and does not rely on background/foreground textures matching. -- It simply blends the current fill color towards white as the cast approaches completion. @@ -928,6 +1012,9 @@ function _G.MSUF_CB_ApplyColor(frame, state) if _G.MSUF_KickReady_RefreshFrame then _G.MSUF_KickReady_RefreshFrame(frame, state) end + if type(_G.MSUF_KickReady_TrackFillFrame) == "function" then + _G.MSUF_KickReady_TrackFillFrame(frame, state) + end return r end end @@ -1212,4 +1299,4 @@ function _G.MSUF_CB_ResetStateOnStop(frame, reasonOrState, opts) -- INTERRUPTED: do not apply colors here (interrupt/SSoT handles it). -- Text/Show/Hold timer remain in the caller (SetInterrupted), matching the old code. -end \ No newline at end of file +end diff --git a/MidnightSimpleUnitFrames/Core/MSUF_ColorsCore.lua b/MidnightSimpleUnitFrames/Core/MSUF_ColorsCore.lua index d37b6071..37d08fa2 100644 --- a/MidnightSimpleUnitFrames/Core/MSUF_ColorsCore.lua +++ b/MidnightSimpleUnitFrames/Core/MSUF_ColorsCore.lua @@ -510,13 +510,16 @@ local function ResetCastbarBackgroundColor() g.castbarBgR, g.castbarBgG, g.castbarBgB, g.castbarBgA = nil, nil, nil, nil; PushVisualUpdates() end ---- - Cast Colors (interruptible / non-interruptible / feedback) - +--- - Cast Colors (interruptible / non-interruptible / interrupt unavailable / feedback) - local function GetInterruptibleCastColor() return _getRGBPalette("castbarInterruptibleR", "castbarInterruptibleG", "castbarInterruptibleB", "castbarInterruptibleColor", "turquoise", 0, 0.9, 0.8) end MSUF_GetInterruptibleCastColor = GetInterruptibleCastColor local function SetInterruptibleCastColor(r, g, b) _setRGB("castbarInterruptibleR", "castbarInterruptibleG", "castbarInterruptibleB", r, g, b, 0, 0.9, 0.8) end local function GetNonInterruptibleCastColor() return _getRGBTonumber("castbarNonInterruptibleR", "castbarNonInterruptibleG", "castbarNonInterruptibleB", "castbarNonInterruptibleColor", "red", 0.4, 0.01, 0.01) end MSUF_GetNonInterruptibleCastColor = GetNonInterruptibleCastColor local function SetNonInterruptibleCastColor(r, g, b) _setRGB("castbarNonInterruptibleR", "castbarNonInterruptibleG", "castbarNonInterruptibleB", r, g, b, 0.4, 0.01, 0.01) end +local function GetInterruptUnavailableCastColor() return _getRGBTonumber("castbarInterruptUnavailableR", "castbarInterruptUnavailableG", "castbarInterruptUnavailableB", "castbarInterruptUnavailableColor", nil, 1.0, 0.494117647, 0.137254902) end +MSUF_GetInterruptUnavailableCastColor = GetInterruptUnavailableCastColor +local function SetInterruptUnavailableCastColor(r, g, b) _setRGB("castbarInterruptUnavailableR", "castbarInterruptUnavailableG", "castbarInterruptUnavailableB", r, g, b, 1.0, 0.494117647, 0.137254902) end local function GetInterruptFeedbackCastColor() return _getRGBTonumber("castbarInterruptFeedbackR", "castbarInterruptFeedbackG", "castbarInterruptFeedbackB", "castbarInterruptFeedbackColor", "yellow", 1.0, 0.82, 0.0) end local function SetInterruptFeedbackCastColor(r, g, b) _setRGB("castbarInterruptFeedbackR", "castbarInterruptFeedbackG", "castbarInterruptFeedbackB", r, g, b, 1.0, 0.82, 0.0) end @@ -681,6 +684,8 @@ MSUF._colorsAPI = { SetInterruptibleCastColor = SetInterruptibleCastColor, GetNonInterruptibleCastColor = GetNonInterruptibleCastColor, SetNonInterruptibleCastColor = SetNonInterruptibleCastColor, + GetInterruptUnavailableCastColor = GetInterruptUnavailableCastColor, + SetInterruptUnavailableCastColor = SetInterruptUnavailableCastColor, GetInterruptFeedbackCastColor = GetInterruptFeedbackCastColor, SetInterruptFeedbackCastColor = SetInterruptFeedbackCastColor, GetPlayerCastbarOverrideEnabled = GetPlayerCastbarOverrideEnabled, diff --git a/MidnightSimpleUnitFrames/Menu2/Pages/MSUF_Menu2_AdvancedColors.lua b/MidnightSimpleUnitFrames/Menu2/Pages/MSUF_Menu2_AdvancedColors.lua index 3c1833cf..cb689619 100644 --- a/MidnightSimpleUnitFrames/Menu2/Pages/MSUF_Menu2_AdvancedColors.lua +++ b/MidnightSimpleUnitFrames/Menu2/Pages/MSUF_Menu2_AdvancedColors.lua @@ -70,6 +70,7 @@ local function ApplyCastbarColors() ApplyColors() if MSUF and type(MSUF.MSUF_UpdateCastbarVisuals) == "function" then pcall(MSUF.MSUF_UpdateCastbarVisuals) end if MSUF and type(MSUF.MSUF_UpdateCastbarTextures_Immediate) == "function" then pcall(MSUF.MSUF_UpdateCastbarTextures_Immediate) end + CallGlobal("MSUF_KickReady_RefreshAll") end local function ApplyGameplayColors() @@ -892,6 +893,9 @@ local function BuildColors(ctx) ColorValueAt(ctx, castbar, "Not ready color (kick on cooldown)", 12, -310, function() return TableRGB(G(), "kickNotReadyColor", 1, 0, 0) end, function(r, g, c) SetTableRGB(G(), "kickNotReadyColor", r, g, c); ApplyCastbarColors() end) + ColorValueAt(ctx, castbar, "Unavailable fill color", 12, -346, + function() return ApiRGB("GetInterruptUnavailableCastColor", 1.0, 0.494117647, 0.137254902) end, + function(r, g, c) ApiSetRGB("SetInterruptUnavailableCastColor", r, g, c); ApplyCastbarColors() end) ButtonAt(castbar, "Reset castbar colors", 12, -470, 170, function() local api = ColorAPI() if type(api.ResetCastbarTextColorToGlobal) == "function" then pcall(api.ResetCastbarTextColorToGlobal) end @@ -900,6 +904,7 @@ local function BuildColors(ctx) local g = G() g.castbarInterruptibleR, g.castbarInterruptibleG, g.castbarInterruptibleB = nil, nil, nil g.castbarNonInterruptibleR, g.castbarNonInterruptibleG, g.castbarNonInterruptibleB = nil, nil, nil + g.castbarInterruptUnavailableR, g.castbarInterruptUnavailableG, g.castbarInterruptUnavailableB = nil, nil, nil g.castbarInterruptFeedbackR, g.castbarInterruptFeedbackG, g.castbarInterruptFeedbackB = nil, nil, nil g.playerCastbarOverrideEnabled = false g.playerCastbarOverrideMode = "CLASS" diff --git a/MidnightSimpleUnitFrames/Menu2/Pages/MSUF_Menu2_GlobalCastbars.lua b/MidnightSimpleUnitFrames/Menu2/Pages/MSUF_Menu2_GlobalCastbars.lua index dd60162c..d3393d04 100644 --- a/MidnightSimpleUnitFrames/Menu2/Pages/MSUF_Menu2_GlobalCastbars.lua +++ b/MidnightSimpleUnitFrames/Menu2/Pages/MSUF_Menu2_GlobalCastbars.lua @@ -697,6 +697,15 @@ local function BuildCastbars(ctx) local ok, r, g, b = pcall(_G.MSUF_ResolveCastbarColors) if ok and r then ir, ig, ib = r, g or ig, b or ib end end + local kickKey = KickReadyKey(unit) + if kind ~= "interrupted" + and kickKey and ReadGBool(kickKey, false) + and ReadG("kickReadyStyle", "border") == "fill" + and (unit == "target" or unit == "focus" or unit == "boss") + and type(_G.MSUF_ResolveInterruptUnavailableCastColor) == "function" then + local ok, r, g, b = pcall(_G.MSUF_ResolveInterruptUnavailableCastColor) + if ok and r and g and b then ir, ig, ib = r, g, b end + end if now < (self.interruptUntil or 0) then ir, ig, ib = 0.90, 0.14, 0.20 end @@ -704,7 +713,6 @@ local function BuildCastbars(ctx) if self.bar.SetBackdropBorderColor then self.bar:SetBackdropBorderColor(T.colors.borderSoft[1], T.colors.borderSoft[2], T.colors.borderSoft[3], T.colors.borderSoft[4] or 0.7) - local kickKey = KickReadyKey(unit) if kickKey and ReadGBool(kickKey, false) and ReadG("kickReadyStyle", "border") == "border" then self.bar:SetBackdropBorderColor(0.24, 0.86, 0.46, 0.95) end @@ -1281,11 +1289,18 @@ local function BuildCastbars(ctx) local style = W.Dropdown(kick, "Indicator style", { { value = "border", text = "Castbar border" }, { value = "box", text = "Color box next to cast" }, + { value = "fill", text = "Unavailable cast fill" }, }, 260) W.MoveWidget(style, kick, kickRightX, -88, 300) M.BindDropdown(ctx, style, function() return ReadG("kickReadyStyle", "border") end, - function(v) SetG("kickReadyStyle", v or "border", "MSUF2_KICK_READY_STYLE", { castbar = true, preview = true }); ApplyCastbars("MSUF2_KICK_READY_STYLE"); RefreshCastPreview() end) + function(v) + SetG("kickReadyStyle", v or "border", "MSUF2_KICK_READY_STYLE", { castbar = true, preview = true }) + ApplyCastbars("MSUF2_KICK_READY_STYLE") + Call("MSUF_KickReady_RefreshAll") + RefreshCastPreview() + if syncKickReady then syncKickReady() end + end) local size = W.Slider(kick, "Indicator size", 8, 32, 1, 300) W.MoveWidget(size, kick, kickRightX, -142, 320) M.BindSlider(ctx, size, @@ -1301,7 +1316,7 @@ local function BuildCastbars(ctx) RefreshCastPreview() if syncKickReady then syncKickReady() end end) - local colorHint = W.Text(kick, "Ready / cooldown colors: Colors menu > Interrupt Ready Indicator", kickRightX, -228, 370, T.colors.muted) + local colorHint = W.Text(kick, "Colors: Colors menu > Castbar Colors", kickRightX, -228, 370, T.colors.muted) W.LabelAt(kick, "Placement", kickLeftX, -178, 160, "GameFontNormalSmall", T.colors.accent) local anchor = W.Dropdown(kick, "Anchor", { { value = "RIGHT", text = "Right" }, @@ -1326,8 +1341,11 @@ local function BuildCastbars(ctx) syncKickReady = function() local enabled = ReadGBool("kickReadyShowTarget", false) or ReadGBool("kickReadyShowFocus", false) or ReadGBool("kickReadyShowBoss", false) local autoOn = ReadGBool("kickReadyAutoSize", true) - SetControlsEnabled({ style, auto, anchor, offX, offY }, enabled) - SetControlEnabled(size, enabled and not autoOn) + local isFill = ReadG("kickReadyStyle", "border") == "fill" + SetControlEnabled(style, enabled) + SetControlEnabled(auto, enabled and not isFill) + SetControlEnabled(size, enabled and not isFill and not autoOn) + SetControlsEnabled({ anchor, offX, offY }, enabled and not isFill) SetControlEnabled(colorHint, enabled) end M.AddRefresher(ctx, syncKickReady) diff --git a/MidnightSimpleUnitFrames/Menu2/Search/MSUF_Menu2_Search_Data.lua b/MidnightSimpleUnitFrames/Menu2/Search/MSUF_Menu2_Search_Data.lua index 723a4c31..a0bd9ef0 100644 --- a/MidnightSimpleUnitFrames/Menu2/Search/MSUF_Menu2_Search_Data.lua +++ b/MidnightSimpleUnitFrames/Menu2/Search/MSUF_Menu2_Search_Data.lua @@ -28,7 +28,7 @@ Data.KEYWORDS = { auras3_filters = "aura filters blacklist blacklisting filter rules custom filters unit aura blacklist spell id ignore list group frame category blacklist declassified aura categories base filter raid helpful all mine only player dispellable stealable own buffs own debuffs category hiding", auras3_styling = "aura styling style colors text stack stacks cooldown timer colors cooldown text stack count font size anchor safe warning urgent own buff own debuff group frame custom aura style buffs debuffs defensives masque dynamic scale cooldown swipe", auras3_private = "private auras private aura player private aura group frame private auras blizzard private aura ownership countdown numbers private aura max size anchor growth placement", - opt_castbar = "global style castbar textures outline shake fill direction empowered casts empower stages evoker augmentation devastation preservation hold release interrupt ready focus kick kick cooldown demon hunter demonhunter dh havoc vengeance devour consume magic disrupt counterspell pummel rebuke wind shear mind freeze skull bash muzzle spear hand strike counter shot quell silence name shortening latency spark channel ticks boss castbar target castbar focus castbar player castbar", + opt_castbar = "global style castbar textures outline shake fill direction empowered casts empower stages evoker augmentation devastation preservation hold release interrupt ready focus kick kick cooldown interrupt unavailable unavailable kick unavailable cooldown color demon hunter demonhunter dh havoc vengeance devour consume magic disrupt counterspell pummel rebuke wind shear mind freeze skull bash muzzle spear hand strike counter shot quell silence name shortening latency spark channel ticks gcd global cooldown boss castbar target castbar focus castbar player castbar", opt_colors = "global style colors class bar colors background backgrond backround bg backdrop tint opacity alpha unitframe colors npc type colors bar colors bar outline border color unit frame border group frame border dispel castbar mouseover highlight gameplay superellipse color swatches portrait colors power colors font color health color reaction color aura colors crosshair colors dark mode custom color missing health white background bar background tint preserve hp color hp track black mana rage energy focus runic power insanity fury pain essence astral power lunar power maelstrom combo points holy power soul shards chi arcane charges runes stagger class power", opt_misc = "global style miscellaneous misc language localization localisation locale translation range fade range check range checker distance check out of range unit frame range check ui behavior tooltip tooltips combat settings general blizzard frames default frames hide blizzard disable blizzard update intervals performance minimap minimap icon target sounds version check menu behavior snap edge snap", classpower = "class resources combo points holy power soul shards chi maelstrom eclipse essence evoker runes runic power stagger brewmaster resource prediction auto hide detached power bar alternative mana behavior style quick actions class power resource bar alternate mana monk druid rogue paladin warlock death knight", diff --git a/MidnightSimpleUnitFrames/Modules/MSUF_InterruptReady.lua b/MidnightSimpleUnitFrames/Modules/MSUF_InterruptReady.lua index 21aeb206..7b6989eb 100644 --- a/MidnightSimpleUnitFrames/Modules/MSUF_InterruptReady.lua +++ b/MidnightSimpleUnitFrames/Modules/MSUF_InterruptReady.lua @@ -93,6 +93,8 @@ local _state = { local _registeredFrames = {} local _activeFrames = {} local _activeFrameCount = 0 +local _fillActiveFrames = {} +local _fillActiveFrameCount = 0 local _eventFrame local _UpdateCooldownEventRegistration @@ -163,6 +165,17 @@ local function _ShowOnUnit(cfg, unit) return false end +local function _FillColorEnabled(cfg) + return cfg and cfg.kickReadyStyle == "fill" +end + +local function _FillAppliesToUnit(unit, cfg) + if type(unit) ~= "string" then return false end + if unit == "target" or unit == "focus" then return _MSUFOwnsCastbar(unit, cfg) end + if unit:sub(1, 4) == "boss" then return _MSUFOwnsCastbar("boss", cfg) end + return false +end + --- ============================================================================= --- Spell resolution --- ============================================================================= @@ -233,6 +246,14 @@ local function _GetReadyBoolSecret() return dur:IsZero() --- secret bool ? DO NOT compare in Lua end +local function _FillTintRuntimeAvailable() + if not _state.spellID then Resolve() end + if not _state.spellID then return false end + return type(C_Spell and C_Spell.GetSpellCooldownDuration) == "function" + and type(C_CurveUtil and C_CurveUtil.EvaluateColorFromBoolean) == "function" + and type(_G.CreateColor) == "function" +end + --- ============================================================================= --- Color resolution (secret-safe via EvaluateColorFromBoolean) --- ============================================================================= @@ -510,7 +531,7 @@ end local function _GetStyle(cfg) local s = cfg and cfg.kickReadyStyle - if s == "box" or s == "border" then return s end + if s == "box" or s == "border" or s == "fill" then return s end return "border" --- default end @@ -538,6 +559,25 @@ local function _MarkInactiveFrame(frame) end end +local function _MarkFillActiveFrame(frame) + if not frame or _fillActiveFrames[frame] then return end + _fillActiveFrames[frame] = true + _fillActiveFrameCount = _fillActiveFrameCount + 1 + if _UpdateCooldownEventRegistration then + _UpdateCooldownEventRegistration() + end +end + +local function _MarkFillInactiveFrame(frame) + if not frame or not _fillActiveFrames[frame] then return end + _fillActiveFrames[frame] = nil + _fillActiveFrameCount = _fillActiveFrameCount - 1 + if _fillActiveFrameCount < 0 then _fillActiveFrameCount = 0 end + if _UpdateCooldownEventRegistration then + _UpdateCooldownEventRegistration() + end +end + --- Public: create / reposition the indicator on `frame`. Called from --- MSUF_Castbars.lua after every visuals update. Cheap, idempotent. --- Hide all visual side-effects on a frame (used when feature off, unit out @@ -559,6 +599,28 @@ local function _CastAllowsKickIndicator(frame) return not (frame and (frame.isNotInterruptible == true or frame.MSUF_kickInterruptibleConfirmed == false)) end +local function _TrackFillFrame(frame, state, cfg) + if not frame then return false end + cfg = cfg or _GetCfg() + local unit = frame.unit + + local active = false + if state ~= nil then + active = (state.active == true) + elseif frame.MSUF_castActive == true then + active = true + end + + if not (_FillColorEnabled(cfg) and active and _FillAppliesToUnit(unit, cfg) and _CastAllowsKickIndicator(frame) and _FillTintRuntimeAvailable()) then + _MarkFillInactiveFrame(frame) + return false + end + + _RegisterFrame(frame) + _MarkFillActiveFrame(frame) + return true +end + local function _EnsureBox(frame, cfg) if not frame or not frame.statusBar then return nil end if not frame.kickReadyBox then @@ -609,6 +671,7 @@ local _refreshTickerArmed = false local _refreshTimer = nil local _refreshTickerToken = 0 local _TickerStep --- forward decl +local RefreshFillCooldownFrames local function _PlainNumberOrNil(v) local cav = canaccessvalue @@ -775,11 +838,21 @@ local function RefreshFrame(frame, state, cfg, readyBool, style, readyMixin, cdM if not frame then return end cfg = cfg or _GetCfg() + local fillTracked = _TrackFillFrame(frame, state, cfg) + local function ArmFillRefresh() + if fillTracked then + if frame.UpdateColorForInterruptible then + frame:UpdateColorForInterruptible() + end + _ArmRefreshTicker(_GetCooldownPollDelay(), false) + end + end if not cfg then _HideIndicator(frame); return end local unit = frame.unit if not unit or not _ShowOnUnit(cfg, unit) then _HideIndicator(frame) + ArmFillRefresh() return end @@ -813,6 +886,11 @@ local function RefreshFrame(frame, state, cfg, readyBool, style, readyMixin, cdM if readyBool == nil then readyBool = _GetReadyBoolSecret() end style = style or _GetStyle(cfg) + if style == "fill" then + _HideIndicator(frame) + ArmFillRefresh() + return + end if style == "box" then _EnsureBox(frame, cfg) end @@ -831,29 +909,35 @@ function _TickerStep() _refreshTimer = nil local cfg = _GetCfg() - if not cfg or not _AnyShowEnabled(cfg) then return end - if _activeFrameCount <= 0 then return end - local readyBool = _GetReadyBoolSecret() - local style = _GetStyle(cfg) - local readyMixin, cdMixin = _ResolveColorPair(cfg) - local userMixin = (style == "border") and _GetUserOutlineMixin() or nil + if not cfg then return end local anyActive = false - local frame = next(_activeFrames) - while frame do - local nextFrame = next(_activeFrames, frame) - if frame.MSUF_castActive == true - and not (frame.isNotInterruptible == true) - and _CastAllowsKickIndicator(frame) then - if cfg and frame.unit and _ShowOnUnit(cfg, frame.unit) then - _PaintFrame(frame, readyBool, cfg, style, readyMixin, cdMixin, userMixin) - anyActive = true + + if _AnyShowEnabled(cfg) and _activeFrameCount > 0 then + local readyBool = _GetReadyBoolSecret() + local style = _GetStyle(cfg) + local readyMixin, cdMixin = _ResolveColorPair(cfg) + local userMixin = (style == "border") and _GetUserOutlineMixin() or nil + local frame = next(_activeFrames) + while frame do + local nextFrame = next(_activeFrames, frame) + if frame.MSUF_castActive == true + and not (frame.isNotInterruptible == true) + and _CastAllowsKickIndicator(frame) then + if cfg and frame.unit and _ShowOnUnit(cfg, frame.unit) then + _PaintFrame(frame, readyBool, cfg, style, readyMixin, cdMixin, userMixin) + anyActive = true + else + _HideIndicator(frame) + end else _HideIndicator(frame) end - else - _HideIndicator(frame) + frame = nextFrame end - frame = nextFrame + end + + if RefreshFillCooldownFrames and RefreshFillCooldownFrames(cfg) then + anyActive = true end if anyActive then @@ -867,34 +951,71 @@ local function RefreshAll() end end -local function RefreshActiveCooldownFrames() - if _activeFrameCount <= 0 then return false end - - local cfg = _GetCfg() - if not cfg or not _AnyShowEnabled(cfg) then return end +RefreshFillCooldownFrames = function(cfg) + if _fillActiveFrameCount <= 0 then return false end + cfg = cfg or _GetCfg() - local readyBool = _GetReadyBoolSecret() - local style = _GetStyle(cfg) - local readyMixin, cdMixin = _ResolveColorPair(cfg) - local userMixin = (style == "border") and _GetUserOutlineMixin() or nil local didRefresh = false - local frame = next(_activeFrames) + local frame = next(_fillActiveFrames) while frame do - local nextFrame = next(_activeFrames, frame) - if frame.MSUF_castActive == true + local nextFrame = next(_fillActiveFrames, frame) + if _FillColorEnabled(cfg) + and frame.MSUF_castActive == true and not (frame.isNotInterruptible == true) and _CastAllowsKickIndicator(frame) - and frame.unit and _ShowOnUnit(cfg, frame.unit) then - RefreshFrame(frame, nil, cfg, readyBool, style, readyMixin, cdMixin, userMixin) - didRefresh = true + and _FillAppliesToUnit(frame.unit, cfg) then + if frame.UpdateColorForInterruptible then + frame:UpdateColorForInterruptible() + didRefresh = true + end else - _HideIndicator(frame) + _MarkFillInactiveFrame(frame) end frame = nextFrame end return didRefresh end +local function RefreshActiveCooldownFrames() + local cfg = _GetCfg() + if not cfg then return false end + + local didRefresh = false + + if _AnyShowEnabled(cfg) and _activeFrameCount > 0 then + local readyBool = _GetReadyBoolSecret() + local style = _GetStyle(cfg) + local readyMixin, cdMixin = _ResolveColorPair(cfg) + local userMixin = (style == "border") and _GetUserOutlineMixin() or nil + local frame = next(_activeFrames) + while frame do + local nextFrame = next(_activeFrames, frame) + if frame.MSUF_castActive == true + and not (frame.isNotInterruptible == true) + and _CastAllowsKickIndicator(frame) + and frame.unit and _ShowOnUnit(cfg, frame.unit) then + RefreshFrame(frame, nil, cfg, readyBool, style, readyMixin, cdMixin, userMixin) + didRefresh = true + else + _HideIndicator(frame) + end + frame = nextFrame + end + elseif _activeFrameCount > 0 then + local frame = next(_activeFrames) + while frame do + local nextFrame = next(_activeFrames, frame) + _HideIndicator(frame) + frame = nextFrame + end + end + + if RefreshFillCooldownFrames(cfg) then + didRefresh = true + end + return didRefresh +end + local _cooldownRefreshQueued = false local function _CooldownRefreshFlush() _cooldownRefreshQueued = false @@ -906,7 +1027,7 @@ local function _CooldownRefreshFlush() end local function _QueueCooldownRefresh() - if _activeFrameCount <= 0 then return end + if _activeFrameCount <= 0 and _fillActiveFrameCount <= 0 then return end if _cooldownRefreshQueued then return end _cooldownRefreshQueued = true if C_Timer and C_Timer.After then @@ -994,11 +1115,13 @@ _eventFrame:RegisterEvent("ACTIVE_TALENT_GROUP_CHANGED") _eventFrame:RegisterEvent("TRAIT_CONFIG_UPDATED") --- SPELL_UPDATE_COOLDOWN is hot. Only register it while a supported castbar is ---- actively showing an interrupt indicator. FocusKick owns its own watcher, so ---- this module stays completely cold when no MSUF castbar needs cooldown flips. +--- actively showing an interrupt indicator or using the interrupt-unavailable +--- fill tint. FocusKick owns its own watcher, so this module stays cold when no +--- MSUF castbar needs cooldown flips. _UpdateCooldownEventRegistration = function() local cfg = _GetCfg() - local want = _activeFrameCount > 0 and _AnyShowEnabled(cfg) + local want = (_activeFrameCount > 0 and _AnyShowEnabled(cfg)) + or (_fillActiveFrameCount > 0 and _FillColorEnabled(cfg)) if want and not _state.eventsOn then _eventFrame:RegisterEvent("SPELL_UPDATE_COOLDOWN") _state.eventsOn = true @@ -1063,6 +1186,15 @@ function _G.MSUF_KickReady_RefreshFrame(frame, state) return RefreshFrame(frame, state) end +function _G.MSUF_KickReady_TrackFillFrame(frame, state) + local tracked = _TrackFillFrame(frame, state) + _UpdateCooldownEventRegistration() + if tracked then + _ArmRefreshTicker(_GetCooldownPollDelay(), false) + end + return tracked +end + function _G.MSUF_KickReady_ApplyLayout(frame) _ApplyEventGating() _InstallOutlineHook() @@ -1119,6 +1251,8 @@ function _G.MSUF_KickReady_Debug() showTarget = cfg.kickReadyShowTarget == true, showFocus = cfg.kickReadyShowFocus == true, showBoss = cfg.kickReadyShowBoss == true, + fillTint = cfg.kickReadyStyle == "fill", + fillTracked = _fillActiveFrameCount, size = cfg.kickReadySize, anchor = cfg.kickReadyAnchor, offsetX = cfg.kickReadyOffsetX, diff --git a/MidnightSimpleUnitFrames/Modules/MidnightSimpleUnitFrames_BossCastbars.lua b/MidnightSimpleUnitFrames/Modules/MidnightSimpleUnitFrames_BossCastbars.lua index 26e746e1..2fadebf4 100644 --- a/MidnightSimpleUnitFrames/Modules/MidnightSimpleUnitFrames_BossCastbars.lua +++ b/MidnightSimpleUnitFrames/Modules/MidnightSimpleUnitFrames_BossCastbars.lua @@ -654,8 +654,13 @@ end nr, ng, nb = 0.9, 0.1, 0.1 end + local ur, ug, ub, ua, readyBool, useUnavailable = nil, nil, nil, nil, nil, false + if type(_G.MSUF_Castbar_GetInterruptUnavailableTintArgs) == "function" then + ur, ug, ub, ua, readyBool, useUnavailable = _G.MSUF_Castbar_GetInterruptUnavailableTintArgs(self) + end + if type(_G.MSUF_Castbar_ApplyNonInterruptibleTint) == "function" then - _G.MSUF_Castbar_ApplyNonInterruptibleTint(self, rawNI, nr, ng, nb, na, ir, ig, ib, ia, isNI) + _G.MSUF_Castbar_ApplyNonInterruptibleTint(self, rawNI, nr, ng, nb, na, ir, ig, ib, ia, isNI, ur, ug, ub, ua, readyBool, useUnavailable) else local r = (isNI and nr or ir) local gcol = (isNI and ng or ig)