Static state for the tool settings panel. Stores the current context string, exposes methods to show a tool or group, follows the actively armed tool each frame by reading MapEditor.ActiveTool, and provides console commands to open the settings panel and nudge individual settings.
using Sandbox;
using System.Linq;
namespace NZombies;
/// <summary>
/// What the tool settings panel is showing.
///
/// A static rather than a property on the panel, for the same reason LobbyState
/// exists: razor components are generated types and cannot be referenced from a
/// plain .cs file, so the dev menu and the console both need a seam to poke.
/// </summary>
public static class ToolPanelState
{
/// <summary>A tool id (NZTools.*), a ToolSettings group, or "" for hidden.</summary>
public static string Context { get; set; } = "";
/// <summary>Show the settings for a tool, and arm that tool.</summary>
public static void ShowTool( string tool )
{
ToolCommands.Tool( tool );
Context = tool;
}
/// <summary>Show a settings group without touching the armed tool.</summary>
public static void ShowGroup( string group ) => Context = group;
/// <summary>
/// Is this a settings group rather than a tool id?
///
/// ⚠️ The naming convention IS the contract — every group constant in
/// <see cref="ToolSettings"/> is `settings_*` and no `NZTools` id is. Anything
/// added outside that convention will be treated as a tool and closed on the
/// next frame, so keep the prefix.
/// </summary>
public static bool IsGroup( string context )
=> !string.IsNullOrEmpty( context ) && context.StartsWith( "settings_" );
/// <summary>
/// Follow the armed tool.
///
/// ⚠️ Called every frame by the dev menu so holstering a tool (RMB, or
/// arming another from the console) closes or retargets the panel. Without
/// it the panel would keep showing settings for a tool you are no longer
/// holding, and edits would silently apply to the next thing you placed.
/// </summary>
public static void FollowActiveTool()
{
// Settings groups are not tools — they stay open until closed.
//
// ⛔ MATCHED BY PREFIX, NOT BY AN EXPLICIT LIST. This was three hardcoded
// constants, and adding a fourth group (`WeaponStats`) without knowing about
// them produced a perfect silent failure: the click set `Context`, this ran
// on the very next frame, saw a value that was not one of the three, decided
// it must be a stale TOOL id and reset it to the armed tool — "". The button
// worked, the panel was built, and nothing appeared. Reported as *"clicking
// does nothing, do i need to restart the editor?"*
//
// ⚠️ Every group constant is `settings_*` and every tool id is not, so the
// prefix IS the distinction the list was approximating. A new group now works
// without editing this method, which is the only way this does not recur.
if ( IsGroup( Context ) ) return;
var editor = Game.ActiveScene?.GetAllComponents<MapEditor>().FirstOrDefault();
var active = editor?.ActiveTool ?? "";
if ( Context != active ) Context = active;
}
// ── console ──────────────────────────────────────────────────────────────
/// <summary>Open the settings panel: nz_settings [players|zombies|gameplay|tool].</summary>
[ConCmd( "nz_settings" )]
public static void Open( string which = "" )
{
Context = which.ToLowerInvariant() switch
{
"players" or "player" => ToolSettings.Players,
"zombies" or "zombie" => ToolSettings.Zombies,
"gameplay" or "points" => ToolSettings.Gameplay,
"pap" or "packapunch" => ToolSettings.Pap,
"ammo" or "ammobox" => ToolSettings.Ammo,
"rooms" or "room" or "roomnames" => ToolSettings.Rooms,
"" or "close" => "",
_ => which,
};
if ( string.IsNullOrEmpty( Context ) ) { Log.Info( "[nz] settings panel closed" ); return; }
var rows = ToolSettings.For( Context );
Log.Info( $"[nz] {ToolSettings.TitleFor( Context )} — {rows.Count} setting(s)" );
foreach ( var r in rows )
Log.Info( $"[nz] {r.Label,-22} {r.Value()}"
+ (string.IsNullOrEmpty( r.Hint ) ? "" : $" ({r.Hint})") );
}
/// <summary>Nudge one setting from the console: nz_setting <label> [dir].
/// Matches on a label prefix, so `nz_setting price 1` works.</summary>
[ConCmd( "nz_setting" )]
public static void Nudge( string label = "", int dir = 1 )
{
if ( string.IsNullOrEmpty( Context ) ) { Log.Warning( "[nz] no settings open — nz_settings first" ); return; }
var rows = ToolSettings.For( Context );
var row = rows.FirstOrDefault( r =>
r.Label.StartsWith( label, System.StringComparison.OrdinalIgnoreCase ) );
if ( row is null )
{
Log.Warning( $"[nz] no setting matching '{label}' — have: "
+ string.Join( ", ", rows.Select( r => r.Label ) ) );
return;
}
row.Nudge( dir );
Log.Info( $"[nz] {row.Label} -> {row.Value()}" );
}
}