Editor/Data/ArchStoreyEdit.cs
using System;
using System.Collections.Generic;
using System.Linq;
using Sandbox;
namespace Sunless.Architecture;
public readonly record struct ArchStoreyEditResult( ArchRoom Room, int Level, string Error )
{
public bool Changed => Room is not null && string.IsNullOrWhiteSpace( Error );
}
public static class ArchStoreyEdit
{
public static ArchStoreyEditResult Add( ArchPlan plan, ArchBuilding building, ArchKit kit, int sourceLevel, int direction )
{
if ( plan is null || building is null || direction is not (-1 or 1) )
{
return new ArchStoreyEditResult( null, sourceLevel, "A storey can only be added immediately above or below a standing one." );
}
var source = building.Rooms
.Where( room => room.Floor == sourceLevel && room.HasFootprint )
.OrderByDescending( room => MathF.Abs( ArchFootprint.SignedArea( ArchFloorGen.Footprint( room ) ) ) )
.FirstOrDefault();
if ( source is null )
{
return new ArchStoreyEditResult( null, sourceLevel, "Nothing stands on this level to copy." );
}
var targetLevel = sourceLevel + direction;
if ( building.Rooms.Any( room => room.Floor == targetLevel && room.HasContent ) )
{
return new ArchStoreyEditResult( null, targetLevel, $"{Name( targetLevel )} already exists." );
}
var room = new ArchRoom
{
Id = plan.AllocateId(),
Name = Name( targetLevel ),
Floor = targetLevel,
BaseHeight = ArchBuild.FloorOf( building, kit, targetLevel ),
WallHeight = source.WallHeight,
HasFloor = true,
HasCeiling = source.HasCeiling,
CeilingDepth = source.CeilingDepth,
RaisedFoundation = false,
Footprint = new List<Vector2>( ArchFloorGen.Footprint( source ) )
};
foreach ( var sourceWall in source.Walls )
{
var wall = new ArchWall
{
Id = plan.AllocateId(),
Start = sourceWall.Start,
End = sourceWall.End,
Exterior = sourceWall.Exterior,
Thickness = sourceWall.Thickness
};
room.Walls.Add( wall );
}
building.Rooms.Add( room );
if ( direction > 0 )
{
var lift = ArchBuild.FloorOf( building, kit, targetLevel ) - ArchBuild.FloorOf( building, kit, sourceLevel );
foreach ( var roof in building.Roofs.Where( roof => roof.Level == sourceLevel ) )
{
roof.Level = targetLevel;
roof.BaseHeight += lift;
}
}
return new ArchStoreyEditResult( room, targetLevel, null );
}
public static ArchStoreyEditResult Remove( ArchPlan plan, ArchBuilding building, ArchKit kit, int level )
{
if ( plan is null || building is null )
{
return new ArchStoreyEditResult( null, level, "No building is active." );
}
var rooms = building.Rooms.Where( room => room.Floor == level ).ToList();
var remainingLevels = building.Rooms
.Where( room => room.Floor != level )
.Select( room => room.Floor )
.Distinct()
.ToList();
if ( rooms.Count == 0 )
{
return new ArchStoreyEditResult( null, level, $"{Name( level )} does not exist." );
}
if ( remainingLevels.Count == 0 )
{
return new ArchStoreyEditResult( null, level, "A building must keep at least one storey." );
}
if ( level == 0 )
{
return new ArchStoreyEditResult( null, level, "The ground floor anchors this building. Remove upper floors and basements from their outer ends." );
}
if ( level < 0 && remainingLevels.Any( candidate => candidate < level ) )
{
return new ArchStoreyEditResult( null, level, $"Remove {Name( remainingLevels.Min() )} first so no unsupported basement level is left below it." );
}
if ( level > 0 && remainingLevels.Any( candidate => candidate > level ) )
{
return new ArchStoreyEditResult( null, level, $"Remove {Name( remainingLevels.Max() )} first so no unsupported floor is left above it." );
}
var nextLevel = remainingLevels
.OrderBy( candidate => Math.Abs( candidate - level ) )
.ThenByDescending( candidate => candidate )
.First();
var shift = ArchBuild.FloorOf( building, kit, nextLevel ) - ArchBuild.FloorOf( building, kit, level );
foreach ( var roof in building.Roofs.Where( roof => roof.Level == level ) )
{
roof.Level = nextLevel;
roof.BaseHeight += shift;
}
foreach ( var room in rooms )
{
ArchAffectors.Close( plan, room );
building.Rooms.Remove( room );
}
var nextRoom = building.Rooms.FirstOrDefault( room => room.Floor == nextLevel );
return new ArchStoreyEditResult( nextRoom, nextLevel, null );
}
public static string Name( int level )
{
return level switch
{
< 0 => $"Basement {Math.Abs( level )}",
0 => "Ground floor",
_ => $"Floor {level}"
};
}
}