Editor tool for building architecture in the Editor. It provides UI to change the current storey level, add a new floor by copying the room footprint and walls from the current level, and adjusts roofs and active room state.
using System;
using System.Collections.Generic;
using System.Linq;
using Editor;
using Sandbox;
namespace Sunless.Architecture;
[EditorTool( "sunless.architecture" )]
[Title( "Architecture" )]
[Icon( "domain" )]
[Alias( "architecture" )]
public sealed class ArchBuildingTool : ArchTool
{
public override string Shell => ArchShells.Buildings;
// Navigating the plan is the layer stack's job now. What is left here is the storey: which one
// you are working on and stacking the next, which belong together and belong to a building.
public override void BuildTargets( Layout header, Action refresh )
{
if ( ActiveBuilding() is not { HasContent: true } building )
{
header.Add( ArchPartUi.Wrapped( "Block out a shell with the Building tool, or pick one in the Plan Layers stack." ) );
return;
}
var storey = header.AddRow();
storey.Spacing = 4;
storey.Add( new Label( $"{building.Name} · Level {Level}" ) );
storey.Add( Step( "keyboard_arrow_down", "Down a storey", -1, refresh ) );
storey.Add( Step( "keyboard_arrow_up", "Up a storey", 1, refresh ) );
storey.Add( new Button( "Add Floor", "layers" ) { Clicked = () => AddStorey( refresh ) } );
storey.AddStretchCell();
}
IconButton Step( string icon, string tooltip, int delta, Action refresh )
{
var button = new IconButton( icon ) { ToolTip = tooltip, IconSize = 16, FixedSize = 24 };
button.OnClick = () =>
{
Level += delta;
ApplyVisibility();
refresh();
};
return button;
}
void AddStorey( Action refresh )
{
var building = ActiveBuilding();
var source = building?.Rooms.FirstOrDefault( room => room.Floor == Level );
if ( building is null || source is null )
{
Log.Warning( "Architecture: nothing on this level to stack a floor onto." );
return;
}
var next = Level + 1;
var copy = new ArchRoom
{
Id = Plan.AllocateId(),
Name = $"Level{next}",
Floor = next,
BaseHeight = FloorOf( next ),
HasFloor = true,
Footprint = new List<Vector2>( source.Footprint )
};
foreach ( var wall in source.Walls )
{
copy.Walls.Add( new ArchWall
{
Id = Plan.AllocateId(),
Start = wall.Start,
End = wall.End,
Exterior = wall.Exterior,
Thickness = wall.Thickness
} );
}
building.Rooms.Add( copy );
// A roof carries its STOREY up, not just its height: left claiming the one below, the storey just stacked
// covers it and a ceiling is built in its place - which with no ceiling asked for is nothing at all. Only the roofs over the storey being stacked move; a porch canopy stays on the porch.
foreach ( var roof in building.Roofs.Where( roof => roof.Level == Level ) )
{
roof.Level = next;
roof.BaseHeight = FloorOf( next ) + Kit.WallHeight;
}
Level = next;
ActiveRoomId = copy.Id;
Commit();
refresh();
}
}