Editor/Wall/ArchWallRoles.cs

Editor roles for architectural fixtures. Contains three sealed classes that implement preview and placement behavior for ladders, balconies, and wall-mounted mods, drawing gizmos and ghost geometry for editor visualization and delegating placement to Arch* static helpers.

File Access
using System.Collections.Generic;
using Sandbox;

namespace Sunless.Architecture;

public sealed class ArchLadderRole : IArchLadders
{
	public bool Preview( ArchPlan plan, ArchKit kit, ArchFixtureStation? at, ArchLadderPart draft )
	{
		if ( ArchLadder.Place( plan, kit, at, draft, out _ ) is not { } sketch )
		{
			return false;
		}

		var shape = ArchLadder.Resolve( sketch, kit );

		foreach ( var side in new[] { -1f, 1f } )
		{
			var stile = shape.Stile( side ) + shape.Facing * shape.Back;

			Gizmo.Draw.Line( new Vector3( stile.x, stile.y, shape.Foot ), new Vector3( stile.x, stile.y, shape.Top ) );
		}

		foreach ( var height in shape.Rungs )
		{
			var left = shape.Stile( -1f ) + shape.Facing * shape.Back;
			var right = shape.Stile( 1f ) + shape.Facing * shape.Back;

			Gizmo.Draw.Line( new Vector3( left.x, left.y, height ), new Vector3( right.x, right.y, height ) );
		}

		ArchGhost.Note(
			new Vector3( shape.Anchor.x, shape.Anchor.y, shape.Top + 8f ),
			$"ladder — {shape.Head - shape.Foot:0} high, {shape.Rungs.Count} rungs" );

		return true;
	}

	public ArchLadderPart Place( ArchPlan plan, ArchKit kit, ArchFixtureStation? at, ArchLadderPart draft, out ArchBuilding host )
		=> ArchLadder.Place( plan, kit, at, draft, out host );

	public ArchLadderPart Place( IEnumerable<ArchBuilding> buildings, ArchPlan plan, ArchKit kit, Vector2 point, ArchLadderPart draft, out ArchBuilding host )
		=> ArchLadder.Place( buildings, plan, kit, point, draft, out host );
}

public sealed class ArchBalconyRole : IArchBalconies
{
	// The same resolve the generator reads, so the deck previewed is the deck poured.
	public bool Preview( ArchPlan plan, ArchKit kit, ArchFixtureStation? at, ArchBalconyPart draft )
	{
		if ( ArchBalcony.Place( plan, kit, at, draft, out var host ) is not { } sketch )
		{
			return false;
		}

		var shape = ArchBalcony.Resolve( sketch, host, kit );

		if ( shape.Projects )
		{
			ArchGhost.Prism( shape.Slab(), shape.Soffit, shape.Deck );
		}

		if ( shape.IsRecessed )
		{
			ArchGhost.Prism( shape.Recess(), shape.Deck, shape.Head );
		}

		return true;
	}

	public ArchBalconyPart Place( ArchPlan plan, ArchKit kit, ArchFixtureStation? at, ArchBalconyPart draft, out ArchBuilding host )
		=> ArchBalcony.Place( plan, kit, at, draft, out host );

	public void Remove( ArchPlan plan, ArchBuilding building, ArchBalconyPart balcony )
		=> ArchBalcony.Remove( plan, building, balcony );
}

public sealed class ArchWallModRole : IArchWallMods
{
	public bool Preview( ArchWallModPart part, ArchWall wall, ArchKit kit, float wallHeight, float floor )
	{
		var shape = ArchWallModShape.Resolve( part, wall, kit, wallHeight );

		if ( !shape.Stands )
		{
			return false;
		}

		var half = ArchWallSection.Thickness( wall, kit ) * 0.5f;
		var held = Gizmo.Draw.Color;

		Gizmo.Draw.Color = ArchGhost.Nth( 2 );

		foreach ( var sign in shape.Faces() )
		{
			foreach ( var slab in shape.Blocks )
			{
				var face = half * sign;
				var reach = shape.Carves ? face - sign * shape.Depth : face + sign * slab.Foot;

				ArchGhost.Prism( Loop( wall, slab.From, slab.To, face, reach ), floor + slab.Bottom, floor + slab.Top );
			}
		}

		Gizmo.Draw.Color = held;

		return true;
	}

	static List<Vector2> Loop( ArchWall wall, float from, float to, float near, float far )
	{
		return new List<Vector2>
		{
			Point( wall, from, near ),
			Point( wall, to, near ),
			Point( wall, to, far ),
			Point( wall, from, far )
		};
	}

	static Vector2 Point( ArchWall wall, float along, float across ) => wall.PointAt( along ) + wall.Normal * across;
}