Static helper that manages the HUD theme configuration for the game. It reads the active map config's HudTheme, exposes flags and helper methods (Basalt theme detection, applying a CSS class to panels, Roman numeral formatting and controls), and a console command to change theme or toggle Roman numbering at runtime.
using Sandbox;
using System.Text;
namespace NZombies;
/// <summary>
/// THE HUD'S THEME — per map config (`MapConfig.HudTheme`). Blank is the ordinary HUD; "basalt" is the carved one, asked for as
/// *"i want the hud on this config to have the same vibe as the map, like geometrical and ancient looking but with modern light
/// strips"*, then *"make it more ancient … ancient with accents basically"* and *"make sure everything is in the correct place"*
/// (2026-09-27): carved stone tablets, bone and ember, the round in Roman numerals, and a few thin white light strips as the only
/// modern thing — the way the map's own strips are the only manufactured thing in it.
///
/// ⛔ A LOOK, NEVER A LAYOUT. Each panel wears <see cref="Class"/> on its root and its stylesheet restyles its own elements
/// under it: colours, borders and the face. Nothing about what the HUD shows or does changes.
///
/// ⛔ THE STONE TABLETS ARE GONE (2026-09-27, later) — *"the background for the hud elements take too much space so i want
/// to remove them"*: the slabs behind the vitals, the score and the weapon, and the room name's plaque.
///
/// ⛔ AND THE FACE IS THE MAP'S, CINZEL, ON EVERY WORD (same day) — *"the ammo and weapon name and stats screen and hit
/// numbers and all text on the wunderfizz and arsenal and all hud messages that appear on screen"*. It is set on Inter's line
/// box (`line-height: 0.898`, a share of Cinzel's own line), so no row grows taller, but it is wider on mixed-case text, which it sets in small capitals, so a
/// long line ends further along or wraps. The panels that wear the class: the survival HUD (its use prompt with it), the room
/// name, the round bar, the Wunderfizz, the Arsenal, the weapon stats, the scoreboard, the hit numbers, the points pops, the
/// revive markers, the powerup banner and timers, the boss bar and the downed screen.
///
/// ⚠️ A NEW PANEL'S TEXT JOINS IN BY WEARING <see cref="Class"/> ON ITS ROOT — or <see cref="Wear"/> from `OnUpdate`, for a
/// tree built once — and naming, under `.theme-basalt`, each of its boxes that sets Inter itself.
/// </summary>
public static class HudTheme
{
/// <summary>The theme the loaded config asks for, lower-cased — "" is the ordinary HUD.</summary>
public static string Name => (ActiveConfig.Current?.HudTheme ?? "").Trim().ToLowerInvariant();
/// <summary>Basalt's carved HUD?</summary>
public static bool Basalt => Name == "basalt";
/// <summary>The class itself, whichever theme is on: what <see cref="Wear"/> puts on and takes off.</summary>
public const string BasaltClass = "theme-basalt";
/// <summary>The class every themed panel's root wears — "theme-basalt", or "" for the ordinary HUD.</summary>
public static string Class => Basalt ? BasaltClass : "";
/// <summary>
/// Put the theme's class on a panel from code — for a tree built ONCE, as the hit numbers', the points pops' and the revive
/// markers' are (a constant `BuildHash`), where a class written in the razor would keep whatever the theme was when it was
/// built. Their `OnUpdate` calls this every frame; a class the panel already has changes nothing.
/// </summary>
public static void Wear( Sandbox.UI.Panel panel ) => panel?.SetClass( BasaltClass, Basalt );
/// <summary>
/// Does the round counter read in Roman numerals? Basalt's own hex slots number theirs I to IV. Rounds 1 to
/// <see cref="RomanUpTo"/> only, digits after. On by default; `nz_hud_theme roman 0` gives the tally and the digits back
/// (for this session).
/// </summary>
public static bool RomanRounds
{
get => _roman ?? true;
set => _roman = value;
}
static bool? _roman;
/// <summary>The round counter in Roman numerals now — the basalt theme, with them on.</summary>
public static bool Roman => Basalt && RomanRounds;
/// <summary>
/// ⛔ THE LAST ROUND IN ROMAN NUMERALS — *"Roman up to round 9 and digits from there on"* (2026-09-27): I to IX, then 10.
/// </summary>
public const int RomanUpTo = 9;
/// <summary>Does round <paramref name="n"/> read in Roman numerals on this HUD — basalt's, from I to <see cref="RomanUpTo"/>?</summary>
public static bool RomanFor( int n ) => Roman && n >= 1 && n <= RomanUpTo;
/// <summary>
/// A tier's number as the HUD writes it — Roman on basalt (the Arsenal's armor and tech tiers: "TIER 2" is "TIER II" there,
/// to match the round counter, 2026-09-27), digits on every other map.
/// </summary>
public static string TierNumeral( int n ) => Basalt ? ToRoman( n ) : n.ToString();
/// <summary>A number in Roman numerals, 1 to 3999; anything else as digits.</summary>
public static string ToRoman( int n )
{
if ( n < 1 || n > 3999 ) return n.ToString();
var sb = new StringBuilder();
foreach ( var (value, glyph) in Numerals )
{
while ( n >= value )
{
sb.Append( glyph );
n -= value;
}
}
return sb.ToString();
}
/// <summary>⚠️ A PROPERTY THAT BUILDS THE TABLE, not a `static readonly` array — INSTRUCTIONS §1: an array held in a static
/// survives a hotload with its old contents.</summary>
static (int Value, string Glyph)[] Numerals => new[]
{
(1000, "M"), (900, "CM"), (500, "D"), (400, "CD"), (100, "C"), (90, "XC"),
(50, "L"), (40, "XL"), (10, "X"), (9, "IX"), (5, "V"), (4, "IV"), (1, "I"),
};
/// <summary>
/// `nz_hud_theme [name | roman 0|1]` — the HUD's theme for the loaded config: `basalt`, or `-` for the ordinary HUD (the config
/// in memory — `nz_save` keeps it); `roman 0|1` switches the Roman round counter (this session). Bare, what is on.
/// </summary>
[ConCmd( "nz_hud_theme" )]
public static void Cmd( string name = "", int on = -1 )
{
var cfg = ActiveConfig.Current;
var n = name.Trim().ToLowerInvariant();
if ( n == "roman" )
{
if ( on >= 0 ) RomanRounds = on != 0;
}
else if ( n.Length > 0 && cfg is not null )
{
cfg.HudTheme = n is "-" or "none" or "default" ? "" : n;
Log.Info( $"[nz-hud] theme {(cfg.HudTheme.Length > 0 ? $"'{cfg.HudTheme}'" : "the ordinary HUD")} — nz_save keeps it" );
}
Log.Info( $"[nz-hud] theme: {(Name.Length > 0 ? Name : "(ordinary)")} · class '{Class}' · face {(Basalt ? "Cinzel" : "Inter")}"
+ $" · Roman round counter {(Roman ? "ON" : RomanRounds ? "on, but only with the basalt theme" : "off")}"
+ $" · round 9 reads {(RomanFor( 9 ) ? ToRoman( 9 ) : "9")}, round 10 reads 10" );
}
}