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.
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;
}