Files
BlizzNameplatesPlus/modules/castbar.lua
T
2026-09-27 19:05:08 +02:00

789 lines
25 KiB
Lua

BNP = BNP or {}
local casts = {}
local castFeedback = {}
local BAR_HEIGHT = 6
local BAR_GAP = 4
local MODERN_ICON_BORDER_SCALE = 23 / 14
local CLASSIC_FRAME_INSET = 3
local CLASSIC_GOLD_R, CLASSIC_GOLD_G, CLASSIC_GOLD_B = 1.0, 0.8, 0.0
local CAST_SPARK_SIZE = 20
local CAST_SUCCESS_FEEDBACK_DURATION = 0.60
local CAST_FAIL_FEEDBACK_DURATION = 0.30
local TEST_CAST_FEEDBACK_DURATION = 0.60
local CLASSIC_BACKDROP = {
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
tile = true,
tileSize = 8,
edgeSize = 12,
insets = { left = 3, right = 3, top = 3, bottom = 3 },
}
-- Runtime-only visual preview. This is intentionally not saved in BNP_DB so
-- a /reload always returns to normal cast detection.
local castbarTestMode = false
local testCastStart = 0
local TEST_CAST_DURATION = 4
local TEST_CAST_TEXTURE = "Interface\\Icons\\Spell_Fire_Fireball02"
local testCast = {
name = "Test Cast",
texture = TEST_CAST_TEXTURE,
startTime = 0,
endTime = 0,
duration = TEST_CAST_DURATION,
channel = false,
}
local function ResetTestCast(now)
now = now or GetTime()
testCastStart = now
testCast.startTime = now
testCast.endTime = now + TEST_CAST_DURATION
testCast.duration = TEST_CAST_DURATION
end
local function GetTestCast(now)
if testCastStart == 0 then
ResetTestCast(now)
end
if now < testCast.endTime then
return testCast, nil
end
local feedbackEnd = testCast.endTime + TEST_CAST_FEEDBACK_DURATION
if now < feedbackEnd then
return nil, {
state = "success",
name = testCast.name,
texture = testCast.texture,
duration = testCast.duration,
value = testCast.duration,
percent = 1,
endTime = feedbackEnd,
flashDuration = TEST_CAST_FEEDBACK_DURATION,
}
end
ResetTestCast(now)
return testCast, nil
end
local function GetCastbarHeight()
if BNP.GetCastbarHeight then return BNP:GetCastbarHeight() end
return BAR_HEIGHT
end
local function GetCastbarFontSize()
if BNP.GetCastbarFontSize then return BNP:GetCastbarFontSize() end
return 10
end
local function GetCastbarStyle()
if BNP.GetCastbarStyle then return BNP:GetCastbarStyle() end
return "modern"
end
local function IsClassicCastbarStyle()
return GetCastbarStyle() == "classic"
end
local function IsCastbarIconEnabled()
if BNP.IsCastbarIconEnabled then return BNP:IsCastbarIconEnabled() end
return not BNP_DB or BNP_DB.castbarIcon ~= false
end
local function GetCastbarIconSize()
if IsClassicCastbarStyle() then
-- The Classic castbar backdrop extends three pixels above and below the
-- configured bar height. Give the icon the same complete outer height.
return GetCastbarHeight() + (CLASSIC_FRAME_INSET * 2)
end
-- Modern keeps the spell icon exactly square with the configured castbar
-- height. Both frames share the same top anchor, so their visible rows stay
-- aligned at every slider value from 4 to 20.
return GetCastbarHeight()
end
local function GetCastbarStartOffset()
if not IsCastbarIconEnabled() then return 0 end
if IsClassicCastbarStyle() then return GetCastbarIconSize() end
return GetCastbarIconSize() + 1
end
local function GetCastbarXOffset()
if BNP.GetCastbarXOffset then return BNP:GetCastbarXOffset() end
return 0
end
local function GetCastbarYOffset()
if BNP.GetCastbarYOffset then return BNP:GetCastbarYOffset() end
return 0
end
local function SetCastbarSpark(bar, state, alpha)
if not bar or not bar.spark then return end
-- BNP stretches the StatusBar between two anchors instead of assigning a
-- fixed SetWidth like ShaguTweaks does. On the Vanilla 1.12 client GetWidth()
-- can lag behind / disagree with that anchor-defined render width. Keep the
-- exact logical width calculated from the same healthbar anchors instead.
local minValue, maxValue = bar:GetMinMaxValues()
local currentValue = bar:GetValue() or minValue or 0
minValue = minValue or 0
maxValue = maxValue or 1
local percent = 0
if maxValue > minValue then
percent = (currentValue - minValue) / (maxValue - minValue)
end
if percent < 0 then percent = 0 end
if percent > 1 then percent = 1 end
local width = bar.BNPRenderWidth or bar:GetWidth() or 0
bar.spark:ClearAllPoints()
bar.spark:SetPoint("CENTER", bar, "LEFT", width * percent, 0)
if state == "success" then
bar.spark:SetVertexColor(0.20, 1.00, 0.20)
elseif state == "fail" then
bar.spark:SetVertexColor(1.00, 0.20, 0.20)
else
-- Untinted during the cast, matching ShaguTweaks' original spark.
bar.spark:SetVertexColor(1.00, 1.00, 1.00)
end
bar.spark:SetAlpha(alpha or 1)
bar.spark:Show()
end
local function ApplyCastbarFont(text)
if not text or not text.GetFont or not text.SetFont then return end
local font, currentSize, flags = text:GetFont()
if not font then return end
local size = GetCastbarFontSize()
if text.BNPModernFontFlags == nil then text.BNPModernFontFlags = flags or "" end
local wantedFlags = IsClassicCastbarStyle() and "THINOUTLINE" or text.BNPModernFontFlags
if currentSize ~= size or (flags or "") ~= wantedFlags then
-- Keep Blizzard's font face. Classic adds ShaguTweaks' thin outline;
-- Modern restores the original font flags.
text:SetFont(font, size, wantedFlags)
end
end
local BOTTOM_AURA_EDGE_GAP = 4
local function FrameShown(frame)
return frame and frame.IsShown and frame:IsShown() and true or false
end
local function IsBottomDebuffPosition(position)
return position == "bottom_mid" or position == "bottom_left" or position == "bottom_right"
end
local function GetBottomAuraClearance(plate)
if not plate then return 0 end
local debuffPosition = BNP.GetDebuffPosition and BNP:GetDebuffPosition() or "top"
local debuffsEnabled = not BNP.AreDebuffsEnabled or BNP:AreDebuffsEnabled()
local auraSize = BNP.GetIconSize and BNP:GetIconSize() or 18
if debuffsEnabled and IsBottomDebuffPosition(debuffPosition) and FrameShown(plate.BNPAuraContainer) then
return BOTTOM_AURA_EDGE_GAP + auraSize
end
return 0
end
local function RegionShown(region)
if not region then return false end
if region.IsShown then return region:IsShown() end
return true
end
local function GetCastbarEndExtension(plate)
-- Preserve the classic +14px room while either native right-side level
-- region is actually visible. When Hide Level removes the numeric text and
-- there is no boss/level icon, the castbar ends flush with the healthbar.
if plate and (RegionShown(plate.level) or RegionShown(plate.levelicon)) then
return 14
end
return 0
end
local function ApplyCastbarLayout(plate, bar)
if not plate or not plate.healthbar or not bar then return end
local gap = BAR_GAP + GetBottomAuraClearance(plate)
local xOffset = GetCastbarXOffset()
local yOffset = GetCastbarYOffset()
local barStartOffset = GetCastbarStartOffset()
local endExtension = GetCastbarEndExtension(plate)
bar:ClearAllPoints()
bar:SetPoint("TOPLEFT", plate.healthbar, "BOTTOMLEFT", barStartOffset + xOffset, -gap + yOffset)
bar:SetPoint("TOPRIGHT", plate.healthbar, "BOTTOMRIGHT", endExtension + xOffset, -gap + yOffset)
-- Store the width implied by those two anchors. xOffset cancels because it
-- moves both edges equally. This is also the exact travel distance for the
-- cast spark.
local healthWidth = plate.healthbar:GetWidth() or 0
bar.BNPRenderWidth = healthWidth + endExtension - barStartOffset
if bar.BNPRenderWidth < 0 then bar.BNPRenderWidth = 0 end
if bar.icon then
bar.icon:ClearAllPoints()
local iconYOffset = IsClassicCastbarStyle() and CLASSIC_FRAME_INSET or 0
bar.icon:SetPoint("TOPLEFT", plate.healthbar, "BOTTOMLEFT", xOffset, -gap + yOffset + iconYOffset)
end
bar.BNPBottomAuraClearance = GetBottomAuraClearance(plate)
end
local function Enabled()
if BNP.AreCastbarsEnabled then return BNP:AreCastbarsEnabled() end
return not BNP_DB or BNP_DB.castbars ~= false
end
local function GetPlateGUID(plate)
if not plate or not plate.GetName then return nil end
local token = plate:GetName(1)
if not token then return nil end
local exists, guid = UnitExists(token)
if exists and guid then return guid end
-- On SuperWoW the plate token itself can already be the GUID.
return token
end
local function SpellData(spellID)
if SpellInfo and spellID then
local name, _, texture = SpellInfo(spellID)
return name or "Casting", texture
end
return "Casting", nil
end
-- Only the presentation changes between styles. Cast state and animation stay
-- on BNP's GUID-based path instead of ShaguTweaks' name-based fallback.
local function ApplyCastbarStyle(bar)
if not bar then return end
local classic = IsClassicCastbarStyle()
if bar.classicBackdrop then
if classic then bar.classicBackdrop:Show() else bar.classicBackdrop:Hide() end
end
if bar.icon then
local iconSize = GetCastbarIconSize()
bar.icon:SetWidth(iconSize)
bar.icon:SetHeight(iconSize)
if bar.icon.texture then
bar.icon.texture:ClearAllPoints()
if classic then
bar.icon.texture:SetPoint("CENTER", bar.icon, "CENTER", 0, 0)
bar.icon.texture:SetWidth(GetCastbarHeight())
bar.icon.texture:SetHeight(GetCastbarHeight())
else
bar.icon.texture:SetAllPoints(bar.icon)
end
end
if bar.icon.border then
if classic then
bar.icon.border:Hide()
else
local borderSize = math.floor((iconSize * MODERN_ICON_BORDER_SCALE) + 0.5)
bar.icon.border:SetWidth(borderSize)
bar.icon.border:SetHeight(borderSize)
bar.icon.border:Show()
end
end
if bar.icon.classicBackdrop then
if classic then bar.icon.classicBackdrop:Show() else bar.icon.classicBackdrop:Hide() end
end
if not IsCastbarIconEnabled() then bar.icon:Hide() end
end
ApplyCastbarFont(bar.text)
end
local function CreateCastbar(plate)
if plate.BNPCastbar then return plate.BNPCastbar end
if not plate.healthbar then return nil end
local parent = plate.BNPScaleWrapper or plate
local bar = CreateFrame("StatusBar", nil, parent)
bar:SetHeight(GetCastbarHeight())
-- Center the castbar under the complete Blizzard nameplate.
-- Extend beneath the level badge instead of stopping at the healthbar edge.
-- Compact row: with the optional icon enabled, icon + castbar together span
-- the complete plate width. Without it, the bar starts at the left edge and
-- uses the freed space itself.
local gap = BAR_GAP + GetBottomAuraClearance(plate)
local xOffset = GetCastbarXOffset()
local yOffset = GetCastbarYOffset()
local initialStartOffset = GetCastbarStartOffset()
bar:SetPoint("TOPLEFT", plate.healthbar, "BOTTOMLEFT", initialStartOffset + xOffset, -gap + yOffset)
bar:SetPoint("TOPRIGHT", plate.healthbar, "BOTTOMRIGHT", 14 + xOffset, -gap + yOffset)
local initialHealthWidth = plate.healthbar:GetWidth() or 0
bar.BNPRenderWidth = initialHealthWidth + 14 - initialStartOffset
if bar.BNPRenderWidth < 0 then bar.BNPRenderWidth = 0 end
bar:SetStatusBarTexture("Interface\\TargetingFrame\\UI-StatusBar")
bar:SetStatusBarColor(1.0, 0.8, 0.0)
bar:SetMinMaxValues(0, 1)
bar:SetValue(0)
bar:SetFrameLevel((plate.healthbar:GetFrameLevel() or 1) + 1)
bar:Hide()
local bg = bar:CreateTexture(nil, "BACKGROUND")
bg:SetTexture("Interface\\TargetingFrame\\UI-StatusBar")
bg:SetVertexColor(0.10, 0.10, 0.00, 0.85)
bg:SetAllPoints(bar)
local text = bar:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
ApplyCastbarFont(text)
text:SetPoint("CENTER", bar, "CENTER", 0, 0)
text:SetTextColor(1, 1, 1)
bar.text = text
-- ShaguTweaks-style spark attached to the actual BNP cast progress.
-- This is presentation-only: it shares the existing frame-perfect updater
-- and does not add another scan or OnUpdate loop.
local spark = bar:CreateTexture(nil, "OVERLAY")
spark:SetTexture("Interface\\CastingBar\\UI-CastingBar-Spark")
spark:SetWidth(CAST_SPARK_SIZE)
spark:SetHeight(CAST_SPARK_SIZE)
spark:SetBlendMode("ADD")
spark:Hide()
bar.spark = spark
-- ShaguTweaks-inspired Classic frame. It remains hidden in the default
-- Modern style and adds no second progress or animation layer.
local classicBackdrop = CreateFrame("Frame", nil, bar)
classicBackdrop:SetFrameLevel(bar:GetFrameLevel())
classicBackdrop:SetPoint("TOPLEFT", bar, "TOPLEFT", -3, 3)
classicBackdrop:SetPoint("BOTTOMRIGHT", bar, "BOTTOMRIGHT", 3, -3)
classicBackdrop:SetBackdrop(CLASSIC_BACKDROP)
classicBackdrop:SetBackdropBorderColor(CLASSIC_GOLD_R, CLASSIC_GOLD_G, CLASSIC_GOLD_B)
classicBackdrop:Hide()
bar.classicBackdrop = classicBackdrop
-- Spell icon to the left of the castbar.
local icon = CreateFrame("Frame", nil, parent)
icon:SetWidth(14)
icon:SetHeight(14)
local iconYOffset = IsClassicCastbarStyle() and CLASSIC_FRAME_INSET or 0
icon:SetPoint("TOPLEFT", plate.healthbar, "BOTTOMLEFT", xOffset, -gap + yOffset + iconYOffset)
icon:SetFrameLevel(bar:GetFrameLevel() + 1)
local texture = icon:CreateTexture(nil, "ARTWORK")
texture:SetAllPoints(icon)
icon.texture = texture
local border = icon:CreateTexture(nil, "OVERLAY")
border:SetTexture("Interface\\Buttons\\UI-Quickslot2")
border:SetPoint("CENTER", icon, "CENTER", 0, 0)
border:SetWidth(23)
border:SetHeight(23)
icon.border = border
local iconClassicBackdrop = CreateFrame("Frame", nil, icon)
iconClassicBackdrop:SetFrameLevel(icon:GetFrameLevel() + 1)
iconClassicBackdrop:SetAllPoints(icon)
iconClassicBackdrop:SetBackdrop(CLASSIC_BACKDROP)
iconClassicBackdrop:SetBackdropBorderColor(CLASSIC_GOLD_R, CLASSIC_GOLD_G, CLASSIC_GOLD_B)
iconClassicBackdrop:Hide()
icon.classicBackdrop = iconClassicBackdrop
icon:Hide()
bar.icon = icon
plate.BNPCastbar = bar
ApplyCastbarStyle(bar)
return bar
end
local function Hide(plate)
if plate and plate.BNPCastbar then
if plate.BNPCastbar.spark then
plate.BNPCastbar.spark:Hide()
plate.BNPCastbar.spark:SetAlpha(1)
plate.BNPCastbar.spark:SetVertexColor(1.00, 1.00, 1.00)
end
plate.BNPCastbar:Hide()
if plate.BNPCastbar.icon then
plate.BNPCastbar.icon:Hide()
end
end
end
-- Exact SuperWoW event path used by ShaguTweaks' superwow module:
-- START / CAST / CHANNEL create cast state; FAIL removes it.
local eventFrame = CreateFrame("Frame")
eventFrame:RegisterEvent("UNIT_CASTEVENT")
eventFrame:SetScript("OnEvent", function()
local guid = arg1
local eventType = arg3
local spellID = arg4
local timerMS = tonumber(arg5) or 0
if not guid then return end
-- SuperWoW CAST is the successful completion event for a cast already
-- started with START/CHANNEL. Do not use arg5/timerMS to identify completion:
-- SuperWoW may still provide a non-zero timer on CAST. Match the running spell
-- instead, mirroring SuperAPI_Castlib's live-event handling.
if eventType == "CAST" then
local previous = casts[guid]
local sameSpell = previous and (previous.spellID == nil or spellID == nil or tostring(previous.spellID) == tostring(spellID))
if previous and sameSpell and previous.duration and previous.duration > 0 then
local now = GetTime()
castFeedback[guid] = {
state = "success",
name = previous.name,
texture = previous.texture,
duration = previous.duration,
value = previous.channel and 0 or previous.duration,
percent = previous.channel and 0 or 1,
endTime = now + CAST_SUCCESS_FEEDBACK_DURATION,
flashDuration = CAST_SUCCESS_FEEDBACK_DURATION,
}
casts[guid] = nil
end
-- A CAST without a matching running cast is an instant/proc/non-bar event.
-- It must not create or restart a castbar.
return
end
if eventType == "START" or eventType == "CHANNEL" then
if timerMS <= 0 then
-- Instant casts do not need a visible castbar.
casts[guid] = nil
castFeedback[guid] = nil
return
end
local name, texture = SpellData(spellID)
local now = GetTime()
local duration = timerMS / 1000
-- A fresh cast immediately replaces any short success/fail flash that may
-- still be visible from the previous cast on this GUID.
castFeedback[guid] = nil
casts[guid] = {
name = name,
texture = texture,
spellID = spellID,
startTime = now,
endTime = now + duration,
duration = duration,
channel = eventType == "CHANNEL",
}
elseif eventType == "FAIL" then
local previous = casts[guid]
if previous and previous.duration and previous.duration > 0 then
local now = GetTime()
local current = now - previous.startTime
if current < 0 then current = 0 end
if current > previous.duration then current = previous.duration end
local value = previous.channel and (previous.duration - current) or current
if value < 0 then value = 0 end
castFeedback[guid] = {
state = "fail",
name = previous.name,
texture = previous.texture,
duration = previous.duration,
value = value,
percent = value / previous.duration,
endTime = now + CAST_FAIL_FEEDBACK_DURATION,
flashDuration = CAST_FAIL_FEEDBACK_DURATION,
}
end
casts[guid] = nil
end
end)
local activeCastPlates = {}
local function MarkCastPlateActive(plate)
if not plate then return end
activeCastPlates[plate] = true
end
local function MarkCastPlateInactive(plate)
if not plate then return end
activeCastPlates[plate] = nil
Hide(plate)
end
-- Lightweight discovery pass. This does NOT animate bars; it only checks
-- visible plates for active cast state at 20 Hz.
local discovery = CreateFrame("Frame")
local discoveryElapsed = 0
discovery:SetScript("OnUpdate", function()
discoveryElapsed = discoveryElapsed + arg1
if discoveryElapsed < 0.05 then return end
discoveryElapsed = 0
if not Enabled() and not castbarTestMode then
local plate
for plate in pairs(activeCastPlates) do
MarkCastPlateInactive(plate)
end
return
end
local now = GetTime()
-- Drop expired feedback even if its nameplate disappeared before the short
-- flash could be rendered. This keeps the GUID table bounded over time.
local feedbackGUID, staleFeedback
for feedbackGUID, staleFeedback in pairs(castFeedback) do
if not staleFeedback.endTime or now >= staleFeedback.endTime then
castFeedback[feedbackGUID] = nil
end
end
local plate
for plate in pairs(BNP.plates) do
if plate:IsShown() then
if castbarTestMode then
-- Preview every visible nameplate. No GUID/cast lookup is needed.
MarkCastPlateActive(plate)
else
local guid = GetPlateGUID(plate)
local cast = guid and casts[guid]
local feedback = guid and castFeedback[guid]
-- If the 20 Hz discovery pass happens to run on the exact frame a cast
-- completes, preserve the active plate and hand it a success flash.
-- Otherwise discovery could hide the bar before the frame updater gets
-- a chance to render the green endpoint.
if guid and cast and now >= cast.endTime then
if activeCastPlates[plate] then
castFeedback[guid] = {
state = "success",
name = cast.name,
texture = cast.texture,
duration = cast.duration,
value = cast.channel and 0 or cast.duration,
percent = cast.channel and 0 or 1,
endTime = now + CAST_SUCCESS_FEEDBACK_DURATION,
flashDuration = CAST_SUCCESS_FEEDBACK_DURATION,
}
feedback = castFeedback[guid]
end
casts[guid] = nil
cast = nil
end
if (cast and now < cast.endTime) or (feedback and now < feedback.endTime) then
MarkCastPlateActive(plate)
elseif activeCastPlates[plate] then
MarkCastPlateInactive(plate)
end
end
elseif activeCastPlates[plate] then
MarkCastPlateInactive(plate)
end
end
end)
-- Frame-perfect animation remains unchanged visually, but only active
-- castbars are updated every rendered frame. The Shagu-style spark rides this
-- same update, so it adds no extra per-frame scan.
local updater = CreateFrame("Frame")
updater:SetScript("OnUpdate", function()
if not Enabled() and not castbarTestMode then return end
local now = GetTime()
local plate
for plate in pairs(activeCastPlates) do
if not plate:IsShown() then
MarkCastPlateInactive(plate)
else
local guid
local cast
local feedback
if castbarTestMode then
cast, feedback = GetTestCast(now)
else
guid = GetPlateGUID(plate)
cast = guid and casts[guid]
feedback = guid and castFeedback[guid]
end
-- A naturally completed timed cast is considered successful unless
-- SuperWoW already sent FAIL, which removes the cast and creates the red
-- feedback state in the event handler above.
if (not castbarTestMode) and guid and cast and now >= cast.endTime then
castFeedback[guid] = {
state = "success",
name = cast.name,
texture = cast.texture,
duration = cast.duration,
value = cast.channel and 0 or cast.duration,
percent = cast.channel and 0 or 1,
endTime = now + CAST_SUCCESS_FEEDBACK_DURATION,
flashDuration = CAST_SUCCESS_FEEDBACK_DURATION,
}
casts[guid] = nil
cast = nil
feedback = castFeedback[guid]
end
if feedback and now >= feedback.endTime then
if guid then castFeedback[guid] = nil end
feedback = nil
end
if not cast and not feedback then
MarkCastPlateInactive(plate)
else
local bar = CreateCastbar(plate)
if bar then
local bottomClearance = GetBottomAuraClearance(plate)
if bar.BNPBottomAuraClearance ~= bottomClearance then
ApplyCastbarLayout(plate, bar)
end
local name
local texture
local value
local duration
local percent
local sparkState
local sparkAlpha = 1
if cast then
local current = now - cast.startTime
if current < 0 then current = 0 end
if current > cast.duration then current = cast.duration end
duration = cast.duration
if cast.channel then
value = cast.duration - current
else
value = current
end
if duration > 0 then percent = value / duration else percent = 0 end
name = cast.name
texture = cast.texture
else
duration = feedback.duration
value = feedback.value
percent = feedback.percent
name = feedback.name
texture = feedback.texture
sparkState = feedback.state
-- Keep the success/fail flash crisp at first, then softly fade the
-- spark during the final part of its short lifetime.
local remaining = feedback.endTime - now
local flashDuration = feedback.flashDuration or CAST_FAIL_FEEDBACK_DURATION
sparkAlpha = remaining / flashDuration
if sparkAlpha < 0 then sparkAlpha = 0 end
if sparkAlpha > 1 then sparkAlpha = 1 end
end
bar:SetMinMaxValues(0, duration or 1)
bar:SetValue(value or 0)
bar.text:SetText(name or "Casting")
bar:SetAlpha(1)
-- Make cast feedback unmistakable without adding any extra updater.
-- The normal bar stays Blizzard-gold; a finished cast briefly turns
-- the filled bar + spark green, while a failed/interrupted cast turns
-- them red. The next active cast restores the normal color immediately.
if sparkState == "success" then
bar:SetStatusBarColor(0.10, 1.00, 0.10)
elseif sparkState == "fail" then
bar:SetStatusBarColor(1.00, 0.15, 0.15)
else
bar:SetStatusBarColor(1.00, 0.80, 0.00)
end
if bar.icon then
if IsCastbarIconEnabled() and texture then
bar.icon.texture:SetTexture(texture)
bar.icon:SetAlpha(1)
bar.icon:Show()
else
bar.icon:Hide()
end
end
SetCastbarSpark(bar, sparkState, sparkAlpha)
bar:Show()
end
end
end
end
end)
function BNP:IsCastbarTestMode()
return castbarTestMode and true or false
end
function BNP:SetCastbarTestMode(enabled)
castbarTestMode = enabled and true or false
if castbarTestMode then
ResetTestCast(GetTime())
else
-- Remove every preview immediately. Real casts are rediscovered on the
-- next lightweight discovery pass without touching their stored state.
local plate
for plate in pairs(activeCastPlates) do
MarkCastPlateInactive(plate)
end
end
if self.optionsFrame and self.optionsFrame.castbarTestCheck then
self.optionsFrame.castbarTestCheck:SetChecked(castbarTestMode)
end
end
function BNP:RefreshCastbarLayout()
local plate
local height = GetCastbarHeight()
for plate in pairs(BNP.plates or {}) do
if plate and plate.BNPCastbar then
plate.BNPCastbar:SetHeight(height)
ApplyCastbarStyle(plate.BNPCastbar)
ApplyCastbarLayout(plate, plate.BNPCastbar)
end
end
end
function BNP:RefreshCastbarStyle()
self:RefreshCastbarLayout()
end
function BNP:RefreshCastbarHeights()
self:RefreshCastbarLayout()
end
function BNP:SetCastbarsEnabled(enabled)
BNP_DB = BNP_DB or {}
BNP_DB.castbars = enabled and true or false
if not enabled then
local plate
for plate in pairs(BNP.plates) do Hide(plate) end
end
self:Print("Nameplate castbars " .. (enabled and "enabled." or "disabled."))
end