EasterEgg/BuildTableCommands.cs

Console command helpers for the building table Easter egg. Provides commands to place a build table at the player's feet, list configured tables, clear all configured tables, and force a rebuild of table entities from the config.

NetworkingFile Access
using Sandbox;

namespace NZombies;

/// <summary>
/// Console access to the building table.
///
/// ⚠️ EVERY BUTTON GETS ONE — the Q-menu tool and these commands place the same config record, so
/// a table dropped either way saves and loads identically. The tool is for aiming at a spot; the
/// console is for dropping one where you stand without leaving the keyboard.
/// </summary>
public static class BuildTableCommands
{
	static NZPlayer Player
		=> PlayerCharacters.Local()
			?? NZPlayer.Local;

	/// <summary>Drop one at your feet: `nz_buildtable`.</summary>
	[ConCmd( "nz_buildtable" )]
	public static void Place()
	{
		var p = Player;
		if ( !p.IsValid() ) { Log.Warning( "[nz-build] no player" ); return; }

		var cfg = ActiveConfig.Current;
		if ( cfg is null ) { Log.Warning( "[nz-build] no config" ); return; }

		// ⚠️ `+ 180` SO IT FACES YOU. The yaw stored is the table's heading, and dropping one at
		// your feet with your own heading puts its back to you — the same turn `nz_trade` makes.
		cfg.BuildTables.Add( new BuildTableSpot
		{
			Position = p.WorldPosition,
			Yaw = p.EyeAngles.yaw + 180f,
			Normal = Vector3.Up,
		} );

		BuildTableManager.Ensure()?.Rebuild();
		Log.Info( $"[nz-build] placed — {cfg.BuildTables.Count} total" );
	}

	/// <summary>What is placed: `nz_buildtable_list`.</summary>
	[ConCmd( "nz_buildtable_list" )]
	public static void List()
	{
		var list = ActiveConfig.Current?.BuildTables;
		if ( list is null || list.Count == 0 )
		{
			Log.Info( "[nz-build] none placed — nz_buildtable, or"
				+ " Q > Easter egg > Collection > Building table" );
			return;
		}

		for ( int i = 0; i < list.Count; i++ )
		{
			var s = list[i];
			// ⚠ THE MODE AND THE SPENT FLAG BOTH SHOW, because a one-time bench that has already
			// been claimed is indistinguishable from an unbuilt one by walking up to it — it is
			// silent on purpose unless you are carrying a full set.
			var live = BuildTable.Find( i );

			Log.Info( $"[nz-build] [{i}] {s.Position}  facing {s.Yaw:0}°"
				+ (s.Permanent ? "  permanent" : "  one-time")
				+ (s.RequiresPower ? "  ⚡power" : "")
				+ $"  flag {DoorLinks.Display( s.Link )}"
				+ (live is null ? "  (not standing)"
					: live.Spent ? "  — SPENT"
					: live.Built ? $"  — holding the {BuildParts.WeaponName}" : "") );
		}

		// ⚠️ STANDING vs CONFIGURED, said separately. A model that fails to load leaves a spot in
		// the config and nothing in the world; one number would hide that.
		Log.Info( $"[nz-build] {list.Count} configured, "
			+ $"{BuildTableManager.Instance?.Built ?? 0} standing" );
	}

	/// <summary>Remove them all: `nz_buildtable_clear`.</summary>
	[ConCmd( "nz_buildtable_clear" )]
	public static void Clear()
	{
		var list = ActiveConfig.Current?.BuildTables;
		if ( list is null || list.Count == 0 ) { Log.Info( "[nz-build] none placed" ); return; }

		var n = list.Count;
		list.Clear();

		BuildTableManager.Ensure()?.Rebuild();
		Log.Info( $"[nz-build] cleared {n}" );
	}

	/// <summary>
	/// Rebuild them from the config: `nz_buildtable_rebuild`.
	/// </summary>
	///
	/// ⚠️ IT EXISTS FOR THE MODEL. The one failure this placeable has is the mesh not loading, and
	/// after fixing a path you want to see it without reloading the map.
	[ConCmd( "nz_buildtable_rebuild" )]
	public static void RebuildCmd()
	{
		BuildTableManager.Ensure()?.Rebuild();
		Log.Info( $"[nz-build] model {BuildTableManager.ModelPath}" );
	}
}