Buyables/TradeTableCommands.cs

Console command helpers for managing in-game trade tables. Provides commands to place, list, clear, empty, and use trade tables tied to the active config and runtime TradeTable instances.

File Access
using System.Linq;
using Sandbox;

namespace NZombies;

/// <summary>
/// Console access to the trading table.
///
/// ⚠️ THE ONLY WAY TO PLACE ONE TODAY, like the ammo box — the map-editor tool waits on the real
/// model (upstream's is `models/zmb/bo2/tranzit/zm_work_bench.mdl`). Tables placed with `nz_trade`
/// save into the config identically, so they survive into whatever the tool becomes.
/// </summary>
public static class TradeTableCommands
{
	static NZPlayer Player
		=> PlayerCharacters.Local()
			?? NZPlayer.Local;

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

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

		cfg.TradeTables.Add( new TradeTableSpot
		{
			Position = p.WorldPosition,
			Yaw = p.EyeAngles.yaw + 180f,
			Normal = Vector3.Up,
		} );

		TradeTableManager.Ensure()?.Rebuild();
		Log.Info( $"[nz-trade] placed — {cfg.TradeTables.Count} total" );
	}

	/// <summary>What is placed, and what is on each: `nz_trade_list`.</summary>
	[ConCmd( "nz_trade_list" )]
	public static void List()
	{
		var list = ActiveConfig.Current?.TradeTables;
		if ( list is null || list.Count == 0 )
		{
			Log.Info( "[nz-trade] none placed — nz_trade to drop one" );
			return;
		}

		for ( int i = 0; i < list.Count; i++ )
		{
			var s = list[i];
			Log.Info( $"[nz-trade] [{i}] {s.Position}"
				+ (s.RequiresPower ? "  ⚡power" : "")
				+ $"  flag {DoorLinks.Display( s.Link )}" );
		}

		// ⚠️ THE CONTENTS ARE READ OFF THE LIVE TABLES, not the config — what a table is holding is
		// runtime state and deliberately not saved with the map.
		foreach ( var t in TradeTable.All.Where( t => t.IsValid() ) )
		{
			Log.Info( $"[nz-trade]   {t.WorldPosition} holding "
				+ (t.HasWeapon
					? $"{t.StoredName} (MK{t.StoredPap}, {Rarity.NameFor( t.StoredRarity )}, "
						+ $"{t.StoredReserve} spare)"
					: "nothing") );
		}

		var mgr = TradeTableManager.Instance;
		Log.Info( $"[nz-trade] {list.Count} configured, "
			+ (mgr.IsValid() ? $"{mgr.Built} standing" : "no manager — none standing") );
	}

	/// <summary>Remove them all: `nz_trade_clear`.</summary>
	[ConCmd( "nz_trade_clear" )]
	public static void Clear()
	{
		var cfg = ActiveConfig.Current;
		if ( cfg is null ) return;

		int n = cfg.TradeTables.Count;
		cfg.TradeTables.Clear();
		TradeTableManager.Ensure()?.Rebuild();
		Log.Info( $"[nz-trade] cleared {n}" );
	}

	/// <summary>
	/// Empty every table without removing them: `nz_trade_empty`.
	///
	/// ⚠️ SEPARATE FROM `nz_trade_clear`, which removes the tables themselves. Getting a stuck
	/// weapon back out is a different job from un-placing the furniture.
	/// </summary>
	[ConCmd( "nz_trade_empty" )]
	public static void Empty()
	{
		int n = 0;
		foreach ( var t in TradeTable.All.Where( t => t.IsValid() && t.HasWeapon ) )
		{
			t.Empty();
			n++;
		}

		Log.Info( $"[nz-trade] emptied {n} table(s)" );
	}

	/// <summary>
	/// Use the nearest table without walking to it: `nz_trade_use`.
	///
	/// ⚠️ FALLS BACK TO ANY PLACED TABLE, because a feature reachable only by standing in the right
	/// spot cannot be tested remotely — the same rule `nz_ammobox_buy` follows.
	/// </summary>
	[ConCmd( "nz_trade_use" )]
	public static void Use()
	{
		var p = Player;
		if ( !p.IsValid() ) { Log.Warning( "[nz-trade] no player" ); return; }

		var table = TradeTable.Near( p.WorldPosition )
			?? TradeTable.All.FirstOrDefault( t => t.IsValid() );

		if ( !table.IsValid() ) { Log.Warning( "[nz-trade] no table placed — nz_trade" ); return; }

		var msg = table.Use( p );
		Log.Info( $"[nz-trade] {(string.IsNullOrWhiteSpace( msg ) ? "(nothing happened)" : msg)}" );
	}
}