A Razor UI panel for a Difficulty settings fullscreen page in the lobby. It renders preset tabs, section tabs, a list of settings with step controls, a right-hand description and comparison column, and handles user interactions by calling DifficultyState and playing UI sounds.
@using Sandbox;
@using Sandbox.UI;
@using System.Linq;
@using NZombies;
@inherits Panel
@*
DIFFICULTY — a fullscreen page over the lobby, laid out like Map select (2026-10-05). The user: "after selecting a map and
gamemode, i want a new option on the lobby that allows me to customize dificulty of the game … 3 default dificulty presets,
and then be able to change the individual things in them", then "let's design the ui for it, not wired to any of the option
yet".
Left: the title, the three presets as tabs, a line on the chosen one, then the four sections as tabs too ("the same way you
did easy hard and normal", 2026-10-05) with the chosen section's settings under them, a step down and up on each, then Back and
Reset. Right: the setting pointed at, its name, what it does, and its value in all three presets with the current one marked.
Touching a setting makes the difficulty Custom; the preset tab it started from stays lit, and its section tab gets a gold dot.
⚠️ WIRED SINCE 2026-10-05, THROUGH `Difficulty`: a game started from the lobby takes these, for everyone in it. The bottom
right says when they apply; a setting the gamemode has nothing for says so under its description.
⚠️ A child Panel of the lobby, as Map select is: no GameObject, no scene edit. The lobby keeps the cursor up while it is open.
*@
@if ( DifficultyState.Showing )
{
var shown = DifficultyState.Shown;
<div class="difpage">
<div class="difbody">
<div class="difleft">
<div class="diftitle">Difficulty</div>
<div class="diftabs">
@foreach ( var p in Presets )
{
var preset = p;
<div class="diftab @(DifficultyState.Base == preset ? "on" : "")"
onmouseover=@Hover
onclick=@( () => Choose( preset ) )>@DifficultyState.NameOf( preset )</div>
}
@if ( DifficultyState.IsCustom )
{
<div class="difcustom">Custom</div>
}
</div>
<div class="difhint">@Hint</div>
@* ⚠️ THE SECTIONS ARE TABS TOO, THE PRESETS' OWN (2026-10-05): one section's settings at a time. A gold dot on a tab
whose settings moved off the preset, since its rows are out of sight. *@
<div class="diftabs difsections">
@foreach ( var g in DifficultyState.Groups )
{
var group = g;
<div class="diftab @(DifficultyState.Section == group ? "on" : "")"
onmouseover=@Hover
onclick=@( () => PickSection( group ) )>
<div class="diftabname">@group</div>
@if ( DifficultyState.SectionChanged( group ) )
{
<div class="difdot"></div>
}
</div>
}
</div>
<div class="diflist">
@foreach ( var st in DifficultyState.InGroup( DifficultyState.Section ) )
{
var s = st;
<div class="difrow @(s.Id == shown?.Id ? "on" : "") @(DifficultyState.IsChanged( s ) ? "changed" : "")"
onmouseover=@( () => Point( s ) )>
<div class="diflabel">@s.Label</div>
<div class="difarrow @(AtMin( s ) ? "off" : "")" onclick=@( () => Step( s, -1 ) )>‹</div>
<div class="difvalue">@DifficultyState.Format( s, DifficultyState.ValueOf( s ) )</div>
<div class="difarrow @(AtMax( s ) ? "off" : "")" onclick=@( () => Step( s, 1 ) )>›</div>
</div>
}
</div>
<div class="diffoot">
<div class="difbtn" onmouseover=@Hover onclick=@( () => Back() )>‹ Back</div>
@if ( DifficultyState.IsCustom )
{
<div class="difbtn" onmouseover=@Hover onclick=@( () => Reset() )>@ResetText</div>
}
</div>
</div>
<div class="difright">
@if ( shown is not null )
{
<div class="difname">@shown.Label</div>
<div class="difdesc">@shown.Description</div>
var note = DifficultyState.GamemodeNote( shown );
if ( !string.IsNullOrEmpty( note ) )
{
<div class="difwarn">@note</div>
}
<div class="difcompare">
@foreach ( var p in Presets )
{
var preset = p;
<div class="difcol @(DifficultyState.Base == preset ? "base" : "") @(Matches( shown, preset ) ? "match" : "")">
<div class="difcolname">@DifficultyState.NameOf( preset )</div>
<div class="difcolval">@DifficultyState.Format( shown, DifficultyState.PresetValue( shown, preset ) )</div>
</div>
}
</div>
<div class="difnow @(DifficultyState.IsChanged( shown ) ? "changed" : "")">@NowText( shown )</div>
}
@* ⚠️ SAID ON THE PAGE: when these apply, and to whom (`Difficulty.StartMatch`) *@
<div class="difnote">Takes effect when the game starts, for everyone in it.</div>
</div>
</div>
</div>
}
@code
{
static DifficultyState.Preset[] Presets => new[]
{
DifficultyState.Preset.Easy,
DifficultyState.Preset.Normal,
DifficultyState.Preset.Hard,
};
/// <summary>The line under the tabs: the preset, what it is for; a custom one, what it started from.</summary>
static string Hint => DifficultyState.IsCustom
? "Custom: changed from " + DifficultyState.NameOf( DifficultyState.Base ) + "."
: DifficultyState.BlurbOf( DifficultyState.Base );
static string ResetText => "Reset to " + DifficultyState.NameOf( DifficultyState.Base );
/// <summary>Under the comparison: the value now, and where it stands against the preset it started from.</summary>
static string NowText( DifficultyState.Setting s )
{
var now = DifficultyState.Format( s, DifficultyState.ValueOf( s ) );
return DifficultyState.IsChanged( s ) ? "Now " + now + " · changed" : "Now " + now;
}
static bool Matches( DifficultyState.Setting s, DifficultyState.Preset p )
=> DifficultyState.Same( DifficultyState.ValueOf( s ), DifficultyState.PresetValue( s, p ) );
static bool AtMin( DifficultyState.Setting s ) => DifficultyState.ValueOf( s ) <= DifficultyState.MinOf( s ) + 0.001f;
static bool AtMax( DifficultyState.Setting s ) => DifficultyState.ValueOf( s ) >= DifficultyState.MaxOf( s ) - 0.001f;
static void Hover() => NZSound.PlayUi( NZSound.UiHover );
/// <summary>Pointing at a setting describes it on the right. The sound only on a change, as Map select does it.</summary>
static void Point( DifficultyState.Setting s )
{
if ( DifficultyState.Hovered == s.Id ) return;
DifficultyState.Hovered = s.Id;
Hover();
}
static void Choose( DifficultyState.Preset p )
{
NZSound.PlayUi( NZSound.UiClick );
DifficultyState.Choose( p );
}
static void PickSection( string group )
{
if ( DifficultyState.Section == group ) return;
NZSound.PlayUi( NZSound.UiClick );
DifficultyState.ChooseSection( group );
}
static void Step( DifficultyState.Setting s, int dir )
{
if ( dir < 0 ? AtMin( s ) : AtMax( s ) ) return;
NZSound.PlayUi( NZSound.UiClick );
DifficultyState.Step( s, dir );
}
static void Reset()
{
NZSound.PlayUi( NZSound.UiClick );
DifficultyState.Reset();
}
static void Back()
{
NZSound.PlayUi( NZSound.UiClick );
DifficultyState.Close();
}
// ⚠️ EVERYTHING THE MARKUP READS: the page open, the setting pointed at, the preset, the section, and every edit (`Version`)
protected override int BuildHash()
=> System.HashCode.Combine( DifficultyState.Showing, DifficultyState.Hovered, DifficultyState.Base, DifficultyState.Section,
DifficultyState.Version );
}