A UI PanelComponent that displays a large carved round number in the center of the screen for a short animation when a new round starts, only for the Basalt HUD theme. It listens to RoundManager state and round number, computes opacity and scale easing over time, and exposes a console command to force the carve.
@using Sandbox;
@using Sandbox.UI;
@using System.Linq;
@using NZombies;
@inherits PanelComponent
@*
BASALT'S ROUND, CARVED IN — a new round's numeral struck large in the middle of the screen for a moment, glowing like the rock,
then gone as the corner counter takes it. The user's pick (2026-09-27): *"a new round's numeral is struck big in the centre
for about a second, glowing ember, then fades as the corner counter ticks"*. The round's own sound plays with it
(`Gameplay.RoundStartSound`, Ancient Evil's on basalt).
⚠️ BASALT'S THEME ONLY (`HudTheme.Basalt`): every other map's HUD is as it was. Roman from I to IX and digits after, exactly
as the corner counter reads (`HudTheme.RomanFor`).
⚠️ ITS OWN PanelComponent, mounted as the round bar is (`SurvivalHud.OnUpdate`), so no scene edit. ⚠️ EVERY MACHINE FROM THE
ROUND IT IS TOLD — `RoundManager.State` and `Round`, which a client holds mirrored — so nothing crosses the wire.
*@
<root class="carve-hud @HudTheme.Class">
@if ( Showing )
{
<div class="carve @(Roman ? "roman" : "digits")" style="opacity: @(Css( Opacity )); transform: scale( @(Css( Scale )) )">@Text</div>
}
</root>
@code
{
/// <summary>How long it holds the screen, all told: struck, held, gone.</summary>
const float Seconds = 2.2f;
int _round; // the round being carved
TimeSince _since; // since it was struck
bool _struck;
bool _wasActive;
bool _primed; // ⚠️ the first look only records, so a hotload mid-round does not strike it again
static RoundManager Rm => RoundManager.Instance;
float T => _struck ? (float)_since : 99f;
/// <summary>Struck in 0.18 s, held to 1.3 s, gone by 2.2 s.</summary>
float Opacity
{
get
{
var t = T;
if ( t < 0.18f ) return t / 0.18f;
if ( t < 1.3f ) return 1f;
return System.Math.Clamp( (Seconds - t) / (Seconds - 1.3f), 0f, 1f );
}
}
/// <summary>1.4 at the strike, easing to 1 by 0.35 s, then settling back to 0.94 as it goes.</summary>
float Scale
{
get
{
var t = T;
if ( t < 0.35f )
{
var p = 1f - t / 0.35f;
return 1f + 0.4f * p * p;
}
return 1f - 0.06f * System.Math.Clamp( (t - 0.35f) / (Seconds - 0.35f), 0f, 1f );
}
}
bool Roman => HudTheme.RomanFor( _round );
string Text => Roman ? HudTheme.ToRoman( _round ) : _round.ToString();
bool Showing => HudTheme.Basalt && _struck && T < Seconds && _round > 0
&& NZGame.Mode == GameMode.Survival && !NZGame.IsCreative;
/// <summary>⚠️ A POINT FOR THE DECIMAL, whatever the machine's language — this goes into a style.</summary>
static string Css( float v ) => v.ToString( "0.###", System.Globalization.CultureInfo.InvariantCulture );
protected override void OnUpdate()
{
var rm = Rm;
if ( !rm.IsValid() ) return;
// ⚠️ A ROUND BEGINS WHEN THE STATE TURNS ACTIVE — `BeginRound`, or on a client the mirror of it — or when the number
// moves while it is (`nz_round_set`)
var active = rm.State == RoundState.Active;
if ( _primed && active && rm.Round > 0 && (!_wasActive || rm.Round != _round) )
{
_round = rm.Round;
_since = 0f;
_struck = true;
}
if ( !_primed ) _round = rm.Round;
_primed = true;
_wasActive = active;
}
/// <summary>⚠️ REBUILDS THROUGH THE STRIKE IN STEPS, then never again until the next one.</summary>
protected override int BuildHash() => System.HashCode.Combine( Showing, _round, (int)(Opacity * 30f), (int)(Scale * 100f), HudTheme.Class );
/// <summary>`nz_round_carve [round]` — strike a numeral now, to see it without waiting for a round (basalt's theme only).</summary>
[ConCmd( "nz_round_carve" )]
public static void Cmd( int round = -1 )
{
var hud = Game.ActiveScene?.GetAllComponents<RoundCarveHud>().FirstOrDefault();
if ( !hud.IsValid() ) { Log.Info( "[nz-hud] no round carving on this HUD" ); return; }
hud._round = round > 0 ? round : Rm.IsValid() && Rm.Round > 0 ? Rm.Round : 1;
hud._since = 0f;
hud._struck = true;
Log.Info( $"[nz-hud] round {hud._round} carved in as '{hud.Text}'"
+ (HudTheme.Basalt ? "" : " — but only basalt's theme draws it (nz_hud_theme basalt)") );
}
}