Editor/Stair/ArchStairAffector.cs
using System.Collections.Generic;
using System.Linq;
namespace Sunless.Architecture;
// A flight that walks out of the building it was filed in is an affector on the one it arrives in, so
// it obeys the same rules a link does: its shaft and its archways are re-cut from where it stands now
// on every commit, and it is grouped with the buildings it actually opened. The group is then the scope
// ArchStairShape.Pierce reads back, so a flight can no longer reach across the whole plan by accident.
public static class ArchStairAffector {
public static int Resolve( ArchPlan plan, ArchKit kit, ArchBuilding building, ArchRoom room, ArchStairPart stair ) {
if ( building is null || stair is null ) {
return 0;
}
var kinds = ArchKinds.Load();
ArchStairShape.Pierce( plan, building, room, stair, kit );
Regroup( plan, building, stair, kinds );
return 1;
}
public static int Resolve( ArchPlan plan, ArchKit kit ) {
var recut = 0;
var kinds = ArchKinds.Load();
// Only flights that pierced something: one drawn on grade opens no slab and has no shaft to keep
// in step, and standing one for it would put a hole under every ground-floor stair in the plan.
var pierced = plan.Buildings
.SelectMany( building => building.Cutouts )
.Where( cutout => cutout.OwnerId != 0 )
.Select( cutout => cutout.OwnerId )
.ToHashSet();
foreach ( var building in plan.Buildings.ToList() ) {
foreach ( var room in building.Rooms.ToList() ) {
foreach ( var stair in plan.Filed<ArchStairPart>( room, kinds ).Where( stair => pierced.Contains( stair.Id ) ).ToList() ) {
ArchStairShape.Pierce( plan, building, room, stair, kit );
Regroup( plan, building, stair, kinds );
recut++;
}
}
}
return recut;
}
// The buildings the flight actually opened, gathered with it and with the one it was filed in. A
// flight that stays inside its own building is no relationship worth a folder, so it forms none -
// and one that stops reaching out leaves the group, the way a link that reaches nothing does.
static void Regroup( ArchPlan plan, ArchBuilding home, ArchStairPart stair, ArchKinds kinds ) {
var reached = plan.Buildings
.Where( host => host != home && Opened( plan, host, stair, kinds ) )
.ToList();
if ( reached.Count == 0 ) {
ArchLayerGroups.Leave( plan, stair.Id );
return;
}
var join = ArchLayerGroups.Holding( plan, stair.Id )
?? ArchLayerGroups.Create( plan, ArchLayerGroups.Named( plan ), ArchAssemblyKind.SharedCore, System.Array.Empty<int>() );
// The affector is filed last, because it acts on what sits above it in the stack.
var members = reached.Select( host => host.Id ).Prepend( home.Id ).Append( stair.Id ).ToList();
foreach ( var member in members ) {
ArchLayerGroups.Leave( plan, member );
}
join.Children.Clear();
join.Children.AddRange( members );
}
static bool Opened( ArchPlan plan, ArchBuilding host, ArchStairPart stair, ArchKinds kinds ) {
return host.Cutouts.Any( cutout => cutout.OwnerId == stair.Id )
|| host.Rooms
.SelectMany( room => plan.Filed( ArchKind.Wall, room, kinds ) )
.SelectMany( wall => plan.Filed( ArchKind.Opening, wall, kinds ) )
.Any( opening => opening is ArchOpening archway && archway.OwnerId == stair.Id );
}
// Deleting the flight takes its shaft, its archways and its own membership - never the group.
public static void Remove( ArchPlan plan, ArchStairPart stair ) {
var kinds = ArchKinds.Load();
foreach ( var building in plan.Buildings ) {
building.Cutouts.RemoveAll( cutout => cutout.OwnerId == stair.Id );
}
foreach ( var wall in plan.AllRooms().SelectMany( room => plan.Filed( ArchKind.Wall, room, kinds ) ) ) {
plan.RemoveAll( ArchKind.Opening, wall, opening => opening is ArchOpening archway && archway.OwnerId == stair.Id, kinds );
}
ArchLayerGroups.Leave( plan, stair.Id );
}
}