Console command helpers for the Arsenal system. Provides commands to place, list, clear, check model, set model yaw, and buy armor/rarity using existing in-game APIs and managers for testing and editor use.
using Sandbox;
using System.Linq;
namespace NZombies;
/// <summary>
/// Console access to the Arsenal. Every button in the tool panel has an equivalent
/// here, so the machine can be placed and inspected without clicking — the standing
/// rule that a feature reachable only by clicking cannot be tested remotely.
/// </summary>
public static class ArsenalCommands
{
static MapEditor Editor
=> Game.ActiveScene?.GetAllComponents<MapEditor>().FirstOrDefault();
static NZPlayer Player
=> NZPlayer.Local;
/// <summary>Drop one at your feet: nz_arsenal</summary>
[ConCmd( "nz_arsenal" )]
public static void Place()
{
var ed = Editor;
var p = Player;
if ( !ed.IsValid() || !p.IsValid() ) { Log.Warning( "[nz-arsenal] no editor/player" ); return; }
// ⚠️ At the PLAYER's feet with an UP normal, not a crosshair trace — the
// command exists so this can be driven headlessly, and a trace needs
// somewhere to be aiming. Same reasoning as nz_fizz.
ed.AddArsenalAt( p.WorldPosition, Vector3.Up );
}
/// <summary>What is placed: nz_arsenal_list</summary>
[ConCmd( "nz_arsenal_list" )]
public static void List()
{
var list = ActiveConfig.Current.Arsenals;
if ( list.Count == 0 )
{
Log.Info( "[nz-arsenal] none placed — Q > Placeables > Player assisting > "
+ "Arsenal position, or nz_arsenal" );
return;
}
for ( int i = 0; i < list.Count; i++ )
{
var a = list[i];
Log.Info( $"[nz-arsenal] [{i}] armor "
+ $"{a.ArmorTier1Price}/{a.ArmorTier2Price}/{a.ArmorTier3Price} salvage"
+ (a.StartRound > 1 ? $" from round {a.StartRound}" : "")
+ (a.RequiresPower ? " ⚡power" : "")
+ $" flag {DoorLinks.Display( a.Link )}" );
}
var mgr = ArsenalManager.Instance;
// ⚠️ CONFIGURED vs STANDING, said separately. A machine whose model failed to
// load leaves a spot in the config and nothing in the world, and those two
// numbers disagreeing is the only cheap way to notice.
Log.Info( $"[nz-arsenal] {list.Count} configured, "
+ $"{(mgr.IsValid() ? mgr.Built.ToString() : "?")} standing" );
}
/// <summary>Remove them all: nz_arsenal_clear</summary>
[ConCmd( "nz_arsenal_clear" )]
public static void Clear()
{
var n = ActiveConfig.Current.Arsenals.Count;
ActiveConfig.Current.Arsenals.Clear();
ArsenalManager.Ensure( Game.ActiveScene )?.Rebuild();
Log.Info( $"[nz-arsenal] removed {n}" );
}
/// <summary>
/// Is the machine's MODEL usable? nz_arsenal_model
///
/// ⚠️ Checks for the ERROR MODEL as well as null. A compiled-but-broken model
/// loads fine and renders as a checkerboard, so a null-only check reports success
/// on a machine that is visibly wrong.
/// </summary>
[ConCmd( "nz_arsenal_model" )]
public static void CheckModel()
{
var m = Model.Load( ArsenalManager.ModelPath );
if ( m is null )
{
Log.Warning( $"[nz-arsenal] {ArsenalManager.ModelPath} — NOT FOUND" );
return;
}
if ( m.IsError )
{
Log.Warning( $"[nz-arsenal] {ArsenalManager.ModelPath} — ERROR MODEL "
+ "(compiled but broken; recompile it)" );
return;
}
Log.Info( $"[nz-arsenal] {ArsenalManager.ModelPath} ok — "
+ $"bounds {m.Bounds.Size}, {m.Bounds.Center} centre" );
}
/// <summary>
/// Turn the model to face the right way: nz_arsenal_yaw [degrees]
///
/// ⚠️ REBUILDS, because the rotation is baked when the machine is built rather
/// than read every frame. Changing the number without a rebuild would look like
/// the command doing nothing.
///
/// ⚠️ Try 90 and -90 first. The model's front lies along ±Y and which sign is
/// correct cannot be read off the bounds — only off the machine.
/// </summary>
[ConCmd( "nz_arsenal_yaw" )]
public static void Yaw( float degrees = float.NaN )
{
if ( !float.IsNaN( degrees ) )
{
ArsenalManager.ModelYaw = degrees;
ArsenalManager.Ensure( Game.ActiveScene )?.Rebuild();
}
Log.Info( $"[nz-arsenal] model yaw correction {ArsenalManager.ModelYaw:0.#}°"
+ " — try 90 / -90 / 180 / 0 if the front is not facing you" );
}
/// <summary>
/// Buy the next armor tier from the nearest machine: nz_arsenal_buy
///
/// ⚠️ Goes through the REAL path — Arsenal.BuyArmorTier, the same method the use
/// key calls — rather than granting a tier directly. A test that skips the
/// machine proves the arithmetic and nothing about whether standing at it works.
/// </summary>
[ConCmd( "nz_arsenal_buy" )]
public static void Buy()
{
var p = Player;
if ( !p.IsValid() ) { Log.Warning( "[nz-arsenal] no player" ); return; }
var a = Arsenal.Near( p.WorldPosition );
if ( a is null )
{
Log.Info( $"[nz-arsenal] nothing within {Arsenal.UseRange:0}u — "
+ $"{Arsenal.All.Count( x => x.IsValid() )} standing on the map" );
return;
}
var result = a.BuyArmorTier( p );
Log.Info( string.IsNullOrEmpty( result )
? $"[nz-arsenal] bought — tier {p.ArmorTier}, {p.Salvage:N0} salvage left"
: $"[nz-arsenal] refused — {result}" );
}
/// <summary>
/// Buy the next rarity tier for the held weapon: nz_arsenal_rarity
///
/// ⚠️ Goes through the REAL path — Arsenal.BuyRarityTier, the same method the
/// card click calls. A command that raised the tier directly would prove the
/// arithmetic and nothing about the machine, the salvage or the held-weapon lookup,
/// which is where every refusal actually lives. `nz_rarity_set` is the one that
/// skips the machine, and it is deliberately a different command.
/// </summary>
[ConCmd( "nz_arsenal_rarity" )]
public static void BuyRarity()
{
var p = Player;
if ( !p.IsValid() ) { Log.Warning( "[nz-arsenal] no player" ); return; }
var a = Arsenal.Near( p.WorldPosition );
if ( a is null )
{
Log.Info( $"[nz-arsenal] nothing within {Arsenal.UseRange:0}u — "
+ $"{Arsenal.All.Count( x => x.IsValid() )} standing on the map" );
return;
}
var result = a.BuyRarityTier( p );
if ( !string.IsNullOrEmpty( result ) )
{
Log.Info( $"[nz-arsenal] refused — {result}" );
return;
}
// ⚠️ Hands off to nz_rarity, which reports the stored tier AND the live
// multiplier off the gun. Printing the tier here would confirm only that the
// dictionary changed — §13, and the whole question after a purchase is whether
// the weapon in your hands actually got stronger.
Rarity.Report();
}
}