Buyables/MysteryBoxSkins.cs

Defines MysteryBoxSkin record and a static MysteryBoxSkins class that lists available box skins (Original and Origins), holds current selection from config, provides lookup by name, console commands to change skin and tune offer lift/yaw, and rebuilds boxes via MysteryBoxManager.

File Access
using Sandbox;
using System.Linq;

namespace NZombies;

/// <summary>
/// One look for the mystery box: its crate, the base under it, where the offer rises, and the sounds that belong to it.
/// </summary>
/// <param name="Name">What `Gameplay.BoxSkin` calls it.</param>
/// <param name="Model">The crate, with its idle / open / close / arrive / leave.</param>
/// <param name="Platform">What stands at a spot the box is not at (`MysteryBoxManager.BuildPlatform`).</param>
/// <param name="PlatformYaw">That platform's turn from the spot's yaw: the original's footlockers are authored across the opening.</param>
/// <param name="BaseUnderBox">Does the box stand ON its platform? The Origins crate rises out of its base and sinks back into it.</param>
/// <param name="OfferLift">How much higher the weapon rises than out of the original crate — the Origins chest sits 17 up, and
/// its lid 26 above the original's rim.</param>
/// <param name="OfferYaw">The weapon's turn from the original's pose: GMod's `WEAPONPANG` for Origins is −90 (random_box:115).</param>
public sealed record MysteryBoxSkin( string Name, string Model, string Platform, float PlatformYaw, bool BaseUnderBox,
	float OfferLift, float OfferYaw,
	string OpenSound, string CloseSound, string ArriveSound, string LeaveSound, string TeddySound, string HumSound )
{
	/// <summary>The box the game has always had — every other field is what the code already did.</summary>
	public bool IsOriginal => Name == MysteryBoxSkins.OriginalName;
}

/// <summary>
/// THE MYSTERY BOX'S SKINS — `Gameplay.BoxSkin` chooses one per map. Asked for as *"I want to get the box skin from origins in
/// Gmod"* (2026-09-28): the Origins stone chest on its pedestal, from the user's `nz_moo_misc.gma`
/// (`models/moo/_codz_ports_props/t6/zm/p6_zm_tm_magic_box/`), decompiled, converted to `models/nz/magicbox_origins/`, with its
/// own sounds (`sound/nz_moo/mysterybox/tomb/`). Basalt wears it.
///
/// ⛔ ORIGINS' BASE IS PART OF THE BOX, NOT A PLATFORM ONLY. Its chest (Z 17..45) nests into the base (Z −9..25), and the base's two
/// doors open as the chest rises out of it and sinks back in — so the box builds its own base and plays `arrive`/`leave` on both
/// (`MysteryBox.PlayClip`), while the spots it is not at show the same base, shut.
///
/// ⚠️ THE OFFER'S LIFT AND TURN ARE A STARTING POINT, TO BE TUNED BY EYE: `nz_box_skin_offer lift yaw` moves them live.
/// </summary>
public static class MysteryBoxSkins
{
	public const string OriginalName = "original";
	public const string OriginsName = "origins";

	/// <summary>Every name `Gameplay.BoxSkin` can take.</summary>
	public static string[] Names => new[] { OriginalName, OriginsName };

	public static MysteryBoxSkin Original => new( OriginalName,
		"models/nz/magicbox/magic_box.vmdl", "models/nz/magicbox/magic_box_platform.vmdl", 90f, false,
		0f, 0f,
		"", "", "", "", "", "" );

	public static MysteryBoxSkin Origins => new( OriginsName,
		"models/nz/magicbox_origins/magic_box_origins.vmdl", "models/nz/magicbox_origins/magic_box_origins_base.vmdl", 0f, true,
		_liftOverride ?? 22f, _yawOverride ?? -90f,
		"nz.box.tomb.open", "nz.box.tomb.close", "nz.box.tomb.arrive", "nz.box.tomb.leave", "nz.box.tomb.bear", "nz.box.tomb.hum" );

	static float? _liftOverride, _yawOverride;

	/// <summary>A skin by name; anything unknown or blank is the original.</summary>
	public static MysteryBoxSkin For( string name ) => (name ?? "").Trim().ToLowerInvariant() switch
	{
		OriginsName or "tomb" => Origins,
		_ => Original,
	};

	/// <summary>This map's: its config's `Gameplay.BoxSkin`.</summary>
	public static MysteryBoxSkin Current => For( ActiveConfig.Current?.Gameplay?.BoxSkin );

	/// <summary>Rebuild every box and platform in the current skin — after a skin or an offer change.</summary>
	static void Rebuild()
	{
		var m = MysteryBoxManager.Instance;
		if ( m.IsValid() ) m.Rebuild();
	}

	/// <summary>
	/// `nz_box_skin [original|origins]` — this map's mystery box. Bare, which it is and what each is. A name switches it (the config in
	/// memory — `nz_save` keeps it) and rebuilds every box and platform.
	/// </summary>
	[ConCmd( "nz_box_skin" )]
	public static void Cmd( string name = "" )
	{
		var cfg = ActiveConfig.Current;

		if ( !string.IsNullOrWhiteSpace( name ) && cfg?.Gameplay is not null )
		{
			var skin = For( name );
			cfg.Gameplay.BoxSkin = skin.IsOriginal ? "" : skin.Name;
			Rebuild();
		}

		var now = Current;
		Log.Info( $"[box] skin '{now.Name}' — {now.Model} on {now.Platform}{(now.BaseUnderBox ? " (the box stands on its base)" : "")}"
			+ $" · offer lift {now.OfferLift:0.#}, turn {now.OfferYaw:0.#}° · {string.Join( ", ", Names )} · nz_save keeps it" );
	}

	/// <summary>
	/// `nz_box_skin_offer [lift] [yaw]` — tune the Origins box's offer by eye: how much higher than the original's the weapon rises,
	/// and its turn. For this session; say the numbers and they become the defaults. Rebuilds the boxes.
	/// </summary>
	[ConCmd( "nz_box_skin_offer" )]
	public static void OfferCmd( float lift = float.NaN, float yaw = float.NaN )
	{
		if ( !float.IsNaN( lift ) ) _liftOverride = lift;
		if ( !float.IsNaN( yaw ) ) _yawOverride = yaw;
		Rebuild();

		Log.Info( $"[box] Origins offer: lift {Origins.OfferLift:0.#}, turn {Origins.OfferYaw:0.#}°"
			+ (Current.Name == OriginsName ? "" : " — this map's box is not Origins (nz_box_skin origins)") );
	}
}