ArenaLayout.cs
using Sandbox;
using System;

[Flags]
public enum ArenaLayoutUsage
{
	None = 0,
	Standard = 1 << 0,
	Lobby = 1 << 1,
	HighNoon = 1 << 2
}

public enum ArenaLayoutSection
{
	Spawn,
	Center
}

/// <summary>
/// Metadata for one arena-module layout root.
///
/// Put this component on the ROOT of each arena layout prefab.
///
/// Arena System v2.1 keeps layout prefabs out of main.scene. ArenaController
/// stores prefab references in its catalog, reads this metadata from those
/// assets, and locally clones only the currently selected Spawn A / Center /
/// Spawn B layouts at runtime.
///
/// LayoutId is stable identity; it is not a list position.
///
/// The authoring gizmos are editor-only visual guides. They do not add gameplay
/// geometry, collision, networking, or runtime presentation.
/// </summary>
[Title( "Arena Layout" )]
[Category( "One More Round" )]
[Icon( "view_quilt" )]
public sealed class ArenaLayout : Component
{
	[Property, Group( "Identity" )]
	public int LayoutId { get; set; } = 0;

	[Property, Group( "Identity" )]
	public string DisplayName { get; set; } = "";

	[Property, Group( "Usage" )]
	public ArenaLayoutUsage Usage { get; set; } =
		ArenaLayoutUsage.Standard;

	// ---------------------------------------------------------------------
	// Authoring
	// ---------------------------------------------------------------------

	/// <summary>
	/// Which module this prefab is authored for.
	/// Spawn layouts are authored from Spawn A's canonical orientation and
	/// Spawn B mirrors them by rotating its module 180 degrees around Yaw.
	/// </summary>
	[Property, Group( "Authoring" )]
	public ArenaLayoutSection Section { get; set; } =
		ArenaLayoutSection.Spawn;

	/// <summary>
	/// Draw module bounds, spawn references, clearance zones and facing arrows
	/// while this layout (or one of its children) is selected in the editor.
	/// </summary>
	[Property, Group( "Authoring" )]
	public bool ShowAuthoringGuides { get; set; } = true;

	/// <summary>
	/// Visual no-build radius around a player spawn.
	/// This is intentionally larger than the physical player radius.
	/// </summary>
	[Property, Group( "Authoring" ), Range( 24f, 160f )]
	public float SpawnClearanceRadius { get; set; } = 64f;

	/// <summary>
	/// Show the real opposing spawn references while editing a layout.
	/// Especially useful for checking long sight lines from Center layouts.
	/// </summary>
	[Property, Group( "Authoring" )]
	public bool ShowRemoteSpawnReferences { get; set; } = true;

	// Current OMR arena/player measurements.
	// These are authoring references only.
	// Matches the existing Guide_SpawnA / Guide_Center meshes in main.scene:
	// X = -480..+480, so the playable/module width is 960 units.
	private const float ArenaWidth = 960f;
	private const float SpawnSectionLength = 384f;
	private const float CenterSectionLength = 512f;
	private const float SpawnDistanceFromArenaCenter = 448f;
	private const float SpawnRootHeight = 16f;

	private const float PlayerBodyRadius = 20f;
	private const float PlayerBodyHeight = 72f;

	public bool Supports(
		ArenaLayoutUsage usage
	)
	{
		return (
			Usage & usage
		) != 0;
	}

	public string GetDisplayName()
	{
		if ( !string.IsNullOrWhiteSpace( DisplayName ) )
			return DisplayName;

		if ( GameObject is null )
			return $"LAYOUT {LayoutId}";

		return GameObject.Name
			.Replace( "_", " " );
	}

	protected override void DrawGizmos()
	{
		base.DrawGizmos();

		if ( !ShowAuthoringGuides )
			return;

		// When the prefab asset itself is open for authoring, keep the guides
		// visible continuously so they remain useful while moving/resizing child
		// geometry. In main.scene the layout is a prefab instance, so fall back
		// to selected-only drawing to avoid filling the arena with every layout's
		// authoring guides at once.
		if ( GameObject.IsPrefabInstance )
		{
			if ( !Gizmo.IsSelected && !Gizmo.IsChildSelected )
				return;
		}

		Gizmo.Transform = WorldTransform;
		Gizmo.Draw.IgnoreDepth = true;

		DrawSectionBounds();

		if ( Section == ArenaLayoutSection.Spawn )
		{
			DrawSpawnLayoutGuides();
		}
		else
		{
			DrawCenterLayoutGuides();
		}
	}

	private void DrawSectionBounds()
	{
		float length =
			Section == ArenaLayoutSection.Spawn
				? SpawnSectionLength
				: CenterSectionLength;

		var size = new Vector3(
			ArenaWidth,
			length,
			4f
		);

		var box = BBox.FromPositionAndSize(
			new Vector3( 0f, 0f, -2f ),
			size
		);

		Gizmo.Draw.Color = Color.Cyan.WithAlpha( 0.10f );
		Gizmo.Draw.SolidBox( box );

		Gizmo.Draw.Color = Color.Cyan.WithAlpha( 0.8f );
		Gizmo.Draw.LineThickness = 2f;
		Gizmo.Draw.LineBBox( box );

		// X centerline.
		Gizmo.Draw.Color = Color.White.WithAlpha( 0.25f );
		Gizmo.Draw.LineThickness = 1f;
		Gizmo.Draw.Line(
			new Vector3( 0f, -length * 0.5f, 2f ),
			new Vector3( 0f, length * 0.5f, 2f )
		);
	}

	private void DrawSpawnLayoutGuides()
	{
		var localSpawn = new Vector3(
			0f,
			0f,
			SpawnRootHeight
		);

		// Canonical Spawn A faces toward arena center (-Y).
		DrawSpawnReference(
			localSpawn,
			new Vector3( 0f, -1f, 0f ),
			Color.Red,
			drawClearance: true
		);

		// Highlight the edge of this module that leads into Center.
		float centerEdgeY = -SpawnSectionLength * 0.5f;

		Gizmo.Draw.Color = Color.Yellow.WithAlpha( 0.9f );
		Gizmo.Draw.LineThickness = 3f;
		Gizmo.Draw.Line(
			new Vector3( -ArenaWidth * 0.5f, centerEdgeY, 3f ),
			new Vector3( ArenaWidth * 0.5f, centerEdgeY, 3f )
		);

		// Direction-to-center arrow.
		Gizmo.Draw.Color = Color.Yellow;
		Gizmo.Draw.Arrow(
			new Vector3( 0f, -48f, 36f ),
			new Vector3( 0f, -176f, 36f ),
			18f,
			8f
		);

		if ( !ShowRemoteSpawnReferences )
			return;

		// Opposing spawn relative to canonical Spawn A layout root:
		// +448 world Y -> -448 world Y = -896 local Y.
		DrawSpawnReference(
			new Vector3(
				0f,
				-SpawnDistanceFromArenaCenter * 2f,
				SpawnRootHeight
			),
			new Vector3( 0f, 1f, 0f ),
			Color.Yellow,
			drawClearance: false
		);
	}

	private void DrawCenterLayoutGuides()
	{
		if ( !ShowRemoteSpawnReferences )
			return;

		// Center module is at world origin, so the two real round spawn points
		// are directly representable in its local space.
		DrawSpawnReference(
			new Vector3(
				0f,
				SpawnDistanceFromArenaCenter,
				SpawnRootHeight
			),
			new Vector3( 0f, -1f, 0f ),
			Color.Cyan,
			drawClearance: false
		);

		DrawSpawnReference(
			new Vector3(
				0f,
				-SpawnDistanceFromArenaCenter,
				SpawnRootHeight
			),
			new Vector3( 0f, 1f, 0f ),
			Color.Yellow,
			drawClearance: false
		);
	}

	private void DrawSpawnReference(
		Vector3 spawnPosition,
		Vector3 facingDirection,
		Color color,
		bool drawClearance
	)
	{
		var bodyBottom = new Vector3(
			spawnPosition.x,
			spawnPosition.y,
			0f
		);

		var bodyTop = new Vector3(
			spawnPosition.x,
			spawnPosition.y,
			PlayerBodyHeight
		);

		// Physical player footprint reference.
		Gizmo.Draw.Color = color.WithAlpha( 0.9f );
		Gizmo.Draw.LineThickness = 2f;
		Gizmo.Draw.LineCylinder(
			bodyBottom,
			bodyTop,
			PlayerBodyRadius,
			PlayerBodyRadius,
			20
		);

		// Exact round-spawn root.
		Gizmo.Draw.LineSphere(
			new Sphere(
				spawnPosition,
				8f
			),
			12
		);

		// Facing direction.
		Gizmo.Draw.Arrow(
			new Vector3(
				spawnPosition.x,
				spawnPosition.y,
				36f
			),
			new Vector3(
				spawnPosition.x + facingDirection.x * 96f,
				spawnPosition.y + facingDirection.y * 96f,
				36f
			),
			14f,
			6f
		);

		if ( !drawClearance )
			return;

		// Authoring-only no-build zone around the real spawn.
		Gizmo.Draw.Color = Color.Red.WithAlpha( 0.18f );
		Gizmo.Draw.SolidBox(
			BBox.FromPositionAndSize(
				new Vector3(
					spawnPosition.x,
					spawnPosition.y,
					2f
				),
				new Vector3(
					SpawnClearanceRadius * 2f,
					SpawnClearanceRadius * 2f,
					4f
				)
			)
		);

		Gizmo.Draw.Color = Color.Red.WithAlpha( 0.95f );
		Gizmo.Draw.LineBBox(
			BBox.FromPositionAndSize(
				new Vector3(
					spawnPosition.x,
					spawnPosition.y,
					2f
				),
				new Vector3(
					SpawnClearanceRadius * 2f,
					SpawnClearanceRadius * 2f,
					4f
				)
			)
		);
	}
}