Config/Edition.cs

Static helper that tracks whether this copy is treated as published or an editor preview, controls a persistent "preview" (player-side) flag, and provides console commands to toggle it. It updates related systems when switching, like forgetting map headers, gamemodes, changing game mode out of Creative, and opening the lobby.

Networking
using Sandbox;

namespace NZombies;

/// <summary>
/// WHICH COPY OF THE GAME THIS IS: the editor, where maps are built, or the published game, where they are played.
///
/// ⛔ THE USER, 2026-10-05: *"in the editor I want to be able to edit maps as usual, but not in the published version … there
/// is no load and save config, instead it will be replaced with a 'gamemode' tab where I can select the config"*. So a
/// published copy has no Creative, no build menu (Q) and no Load, Save or Delete config. Its lobby lists the configs the map
/// SHIPS as gamemodes (`Gamemodes`), and it reads no config but those (`MapConfig.Readers`).
///
/// ⚠️ `Game.IsEditor` DECIDES, AND NOTHING TYPED IN A PUBLISHED COPY CHANGES IT. `nz_side` switches the editor between its own
/// side and the player's, so the published lobby can be tried before a publish (*"A command that switched between the
/// developer side and player side"*). In a published copy the command refuses: a console switch back to building would be
/// one every player could type.
/// </summary>
public static class Edition
{
	/// <summary>The published game, or the editor on the player's side (<see cref="Preview"/>).</summary>
	public static bool IsPublished => !Game.IsEditor || Preview;

	/// <summary>May maps be built here: Creative, the build menu, Load / Save / Delete config? In the editor, on its own side.</summary>
	public static bool CanBuild => !IsPublished;

	/// <summary>
	/// The editor on the player's side (`nz_side`).
	///
	/// ⚠️ A STATIC, SO IT OUTLIVES A HOTLOAD AND A PLAY RESTART: the player's side stays on until it is switched back, which
	/// is what trying the lobby over several restarts wants. The lobby says so while it is on (`LobbyMenu`, `.sidetag`), or
	/// a lobby with no Creative in it would look broken.
	/// </summary>
	public static bool Preview { get; private set; }

	/// <summary>The editor on the player's side: what the lobby's tag is drawn for. Never true in a published copy.</summary>
	public static bool Previewing => Game.IsEditor && Preview;

	/// <summary>
	/// `nz_side [player|dev]` — switch the editor between the developer's side (its own lobby: Load/Save config, Creative, the
	/// build menu) and the player's (the published game's lobby: a Gamemode row, nothing to build). No argument flips it.
	/// </summary>
	[ConCmd( "nz_side" )]
	public static void SideCmd( string side = "" )
	{
		var s = (side ?? "").Trim().ToLowerInvariant();

		if ( s is "" or "toggle" or "flip" ) Switch( !Preview );
		else if ( s is "player" or "players" or "p" or "published" or "1" ) Switch( true );
		else if ( s is "dev" or "developer" or "editor" or "d" or "0" ) Switch( false );
		else Log.Info( $"[nz-side] '{side}'? nz_side flips it; nz_side player or nz_side dev picks one" );
	}

	/// <summary>`nz_published [0|1]` — the same switch under its first name. No argument flips it.</summary>
	[ConCmd( "nz_published" )]
	public static void PreviewCmd( int on = -1 ) => Switch( on < 0 ? !Preview : on != 0 );

	/// <summary>
	/// Put the editor on the player's side, or back on its own, and show the lobby so the change can be seen at once.
	/// </summary>
	public static void Switch( bool player )
	{
		if ( !Game.IsEditor )
		{
			Log.Info( "[nz-side] this is the published game: the player's side is all it has — maps are built in the editor" );
			return;
		}

		Preview = player;

		// ⚠️ EVERY HEADER READ SO FAR GOES, EVEN WHEN NOTHING CHANGED. What the lists read changes with the switch (the
		// player's lobby reads only what ships), and switching to the player's side again is how a gamemode name that
		// `ship_data.py` has just copied into Assets shows up.
		MapConfig.ForgetHeaders();
		Gamemodes.Forget();

		// ⚠️ OUT OF CREATIVE, which the published game has none of, and into the lobby, which `SetMode` opens. The config
		// stays as it is, unsaved edits and all: the player's side loads a gamemode only where nothing playable is loaded.
		if ( player && NZGame.IsCreative ) NZGame.SetMode( GameMode.Lobby );

		// ⚠️ AND THE LOBBY UP, from wherever this was typed, so the side just switched to is what is on screen
		LobbyState.SetOpen?.Invoke( true );

		Log.Info( player
			? "[nz-side] PLAYER SIDE — the published game's lobby: a Gamemode row in place of Load/Save config, no Creative, "
				+ "no build menu. It lists Assets/configs, which is what ships: ship_data.py --apply for configs saved since. "
				+ "nz_side to go back"
			: "[nz-side] DEVELOPER SIDE — the editor's lobby: Load/Save config, Creative and the build menu. nz_side for the player's" );
	}
}