Editor subtool for placing porches in the architecture tool. It provides preview drawing, sketch resolution, drag placement, and UI options for porch parts and settings.
using System;
using System.Collections.Generic;
using System.Linq;
using Editor;
using Sandbox;
namespace Sunless.Architecture;
[Title( "Porch" ), Icon( "deck" ), Group( "13" )]
public sealed class ArchPorchSubtool( ArchTool owner ) : ArchSubtool( owner )
{
protected override ArchKind? DraftKind => ArchKind.Porch;
public override ArchSurface[] Surfaces => new[] { ArchSurface.Deck, ArchSurface.Railing, ArchSurface.Baseboard, ArchSurface.Roof, ArchSurface.Soffit };
readonly ArchPorchPart draft = new() { GradeHeight = -24f };
protected override string Title() => "Porch";
protected override string Advice() => "Drag the deck over the wall it hangs off. Cover a corner and it wraps it; touch a porch already there and it joins that one. Inside a room it stands free.";
// Resolved from the shape, not the drag, so the ghost matches the generator.
protected override void DrawPreview()
{
var min = Min( DragStart, DragCurrent );
var max = Max( DragStart, DragCurrent );
var room = Owner.RoomAt( (min + max) * 0.5f, out var building );
var deck = Owner.LevelHeight;
var legs = ArchPorch.Preview( building, room, Owner.Kit, min, max, draft.Standing, out var standing, out var joining );
if ( legs.Count == 0 )
{
ArchGhost.Plate( min, max, deck, 8 );
ArchGhost.Note( new Vector3( max.x, max.y, deck + 60f ), "porch — nowhere to stand a deck there" );
return;
}
var sketch = Sketch( legs, standing );
var shape = ArchPorchShape.Resolve( sketch, room, building, Owner.Kit );
var head = shape.Head;
var rail = deck + Owner.Kit.PorchRailHeight;
foreach ( var leg in legs )
{
if ( draft.Deck )
{
ArchGhost.Volume( leg.Min, leg.Max, deck - draft.DeckDrop, deck );
}
ArchGhost.Plate( leg.Min, leg.Max, deck, 8 );
}
foreach ( var bay in ArchPorchGen.Bays( shape, sketch, Owner.Kit ) )
{
if ( draft.Posts )
{
ArchGhost.Post( bay.From.Point, Owner.Kit.PorchPostSize, deck, head );
if ( bay.Last )
{
ArchGhost.Post( bay.To.Point, Owner.Kit.PorchPostSize, deck, head );
}
}
if ( draft.Railings && !bay.Gated )
{
Gizmo.Draw.Line( bay.From.Raised.WithZ( rail ), bay.To.Raised.WithZ( rail ) );
}
}
if ( draft.Roofed )
{
ArchGhost.Ring( shape.Footprint, head + Owner.Kit.PorchBeamDepth );
}
ArchGhost.Note( new Vector3( max.x, max.y, head ), Note( legs, standing, joining ) );
}
static string Note( IReadOnlyList<ArchPorchLeg> legs, PorchStanding standing, ArchPorchPart joining )
{
if ( joining is not null )
{
return $"porch — joining {joining.Name}";
}
if ( standing == PorchStanding.Free )
{
return "porch — free-standing, no wall to lean on";
}
return legs.Count > 1 ? $"porch — {legs.Count} legs, wrapping the corner" : "porch";
}
// Unattached to the plan, so the ghost resolves exactly what Attach will - the gates in its railing
// included, which is why it carries the flight the placement is about to seed.
ArchPorchPart Sketch( List<ArchPorchLeg> legs, PorchStanding standing )
{
var deck = Owner.LevelHeight;
var sketch = new ArchPorchPart
{
Legs = legs,
Standing = standing,
BaseHeight = deck,
GradeHeight = deck - draft.DeckDrop,
HeadHeight = MathF.Max( 60f, draft.HeadHeight ),
PostSpacing = MathF.Max( 32f, Owner.Kit.PorchPostSize * 8f ),
Deck = draft.Deck,
Posts = draft.Posts,
Rafters = draft.Rafters,
Braces = draft.Braces,
Pitch = draft.Pitch,
Railings = draft.Railings,
Steps = draft.Steps,
Roofed = draft.Roofed
};
return sketch;
}
protected override void OnDrag( Vector2 from, Vector2 to )
{
var min = Min( from, to );
var max = Max( from, to );
if ( (max - min).Length < 24f )
{
return;
}
var room = Owner.RoomAt( (min + max) * 0.5f, out var building );
var placement = ArchPorch.Attach( Owner.Plan, building, room, Owner.Kit, min, max, draft );
if ( placement is null )
{
Log.Info( "Architecture: no room under that drag for a porch to stand in." );
return;
}
// A porch is a cluster of legs, not one part - the draft's work is done either way.
FinishDraft();
Owner.Commit( "Create Porch" );
}
protected override void BuildOptions( ToolSidebarWidget panel )
{
ArchPorchUi.Build( panel, draft, Refresh, null );
ArchPorchUi.Sizes( panel, draft, null );
}
}