UI/RoundBarState.cs

Static helper for the round progress UI. It reads RoundManager and input state to compute kills, left, total, percent fill, a human reason why the bar is hidden, and exposes a console command nz_roundbar to query or set visibility mode.

Http CallsNetworking
using Sandbox;

namespace NZombies;

/// <summary>
/// The round bar's numbers, its switch and its console command (`RoundBarHud`).
///
/// ⚠️ A STATIC CLASS IN A .cs FILE, as `PowerupBannerState` is: a razor component is a GENERATED type that plain code cannot
/// reference, so what the bar shows is worked out here — and the command prints the very numbers the panel draws.
/// </summary>
public static class RoundBarState
{
	/// <summary>
	/// When the bar is drawn: 0 never, 1 with the scoreboard — only while TAB is held, the default — 2 always, to look at it.
	/// Asked for as *"make it only visible in the scoreboard — as in it only appears when im holding TAB"* (2026-09-27).
	///
	/// ⚠️ A NULLABLE BEHIND IT, so the default is 1 without leaning on an initializer a hotload may not run.
	/// </summary>
	public static int Mode
	{
		get => _mode ?? 1;
		set => _mode = System.Math.Clamp( value, 0, 2 );
	}

	static int? _mode;

	static string ModeText => Mode switch { 0 => "OFF", 2 => "always on", _ => "with the scoreboard (TAB)" };

	static RoundManager Rm => RoundManager.Instance;

	/// <summary>This round's zombies killed so far — the host's, on every screen (`RoundManager.RoundKills`).</summary>
	public static int Kills => Rm.IsValid() ? Rm.RoundKills : 0;

	/// <summary>This round's zombies not yet dead: still to spawn, and alive (`RoundManager.ZombiesLeft`).</summary>
	public static int Left => Rm.IsValid() ? Rm.ZombiesLeft : 0;

	/// <summary>
	/// Of how many: killed and left. ⚠️ NOT THE WAVE'S SIZE — an ambient Shrieker holds a round open without being in its wave,
	/// so the wave alone would read 41 of 40. This is full exactly when the round can end.
	/// </summary>
	public static int Total => Kills + Left;

	/// <summary>The fill, in percent, to a tenth: full with nothing left.</summary>
	public static float Percent => Total <= 0 ? 100f : System.MathF.Round( Kills * 1000f / Total ) / 10f;

	/// <summary>
	/// Why the bar is hidden now, or null while it shows. Shown from round 1, through the round and the break after it (full
	/// then), while TAB is held; hidden before the first round, at a game over, and through basalt's boss fight from his arrival
	/// to everyone home — the round stands frozen, his health bar sits in the same place, and the fight's zombies are not the
	/// round's.
	/// </summary>
	public static string HiddenBecause
	{
		get
		{
			if ( Mode <= 0 ) return "switched off — nz_roundbar 1 shows it with the scoreboard";
			if ( !Rm.IsValid() ) return "no round manager — no game running";
			if ( Rm.Round < 1 || Rm.WaveTotal <= 0 ) return "before round 1";
			if ( Rm.State is not (RoundState.Active or RoundState.Prep) ) return $"the game is {Rm.State}";
			if ( HexPlatforms.FightShownOn ) return "basalt's boss fight is on";

			// ⚠️ THE KEY LAST, so the console — where TAB is never held — still says what else would hide it. The scoreboard's
			// own test (`Scoreboard.Showing`), asked here because a razor type is not something plain code can read.
			if ( Mode == 1 && !Input.Down( "Score" ) ) return "the scoreboard is closed — it shows while TAB is held";

			return null;
		}
	}

	/// <summary>
	/// `nz_roundbar [0|1|2]` — 0 hides the bar, 1 shows it with the scoreboard (while TAB is held), 2 always; and what it shows
	/// now: this round's zombies killed, and left, of how many.
	/// </summary>
	[ConCmd( "nz_roundbar" )]
	public static void Cmd( int mode = -1 )
	{
		if ( mode >= 0 ) Mode = mode;

		var hidden = HiddenBecause;
		var rm = Rm;
		Log.Info( $"[nz-roundbar] {ModeText} · {(hidden is null ? "shown" : $"hidden: {hidden}")}"
			+ (!rm.IsValid() ? "" : $" · round {rm.Round}, {rm.State} · {Kills} killed, {Left} left ({rm.Remaining} still to spawn)"
				+ $" — reads {Kills} / {Total}, {Percent}% full · the wave was {rm.WaveTotal}") );
	}
}