Editor/Tool/ArchBuildingTool.cs

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.

File AccessNetworking
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();
	}
}