Editor/Tool/Subtools/ArchPorchSubtool.cs
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.";
public override void OnEnabled() {
base.OnEnabled();
ArchWorkflow.Restart( Scope( "flow" ) );
}
// 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" );
}
// One list: what the deck leans on and whether anything covers it - both picks - and then the three typed rows
// that can never answer. A porch is a cluster of legs rather than one part, so nothing here is ever pointed at
// a selection: a standing porch is edited from its own sheet in Select.
protected override void BuildOptions( ToolSidebarWidget panel ) {
using var flow = ArchWorkflow.In( panel, Scope( "flow" ), Refresh );
flow.Step( "Standing", ArchPorchUi.StandingName( draft ), ArchPorchUi.StandingGlyph( draft ), step =>
ArchPorchUi.Standing( step.Layout, draft, step.Chose, null ) );
flow.Step( "Roof", draft.Roofed ? "Roofed" : "Open", draft.Roofed ? "roofing" : "deck", step =>
ArchPorchUi.Roof( step.Layout, draft, step.Chose, null ) );
flow.Step( "Include", IncludeName, "checklist", step => ArchPorchUi.Include( step.Layout, draft, null ) );
flow.Step( "Frame", FrameName, "view_column", step => ArchPorchUi.Frame( step.Layout, draft, null ) );
flow.Step( "Placement", $"{draft.DeckDrop:0} drop", "height", step => ArchPorchUi.Placement( step.Layout, draft, null ) );
}
string IncludeName => ArchPartUi.Listed(
(draft.Deck, "Deck"), (draft.Rafters, "Rafters"), (draft.Braces, "Braces"), (draft.Railings, "Railing"), (draft.Door, "Door") );
string FrameName => ArchPartUi.Listed( (draft.Plinth, "Plinth"), (draft.Posts, "Posts"), (draft.Beam, "Beam") );
}