Editor affector for exterior stairs. It resolves exterior stair parts in an architectural plan, applies breach routing for the stair flights, and regroups layer groups to include buildings reached by those flights.
using System;
using System.Linq;
namespace Sunless.Architecture;
// A flight hung on an elevation opens whatever its runs walk through, so it is an effect on a host it does
// not own and obeys the affector contract: the archways are re-cut from where the flight stands NOW on every
// commit, and the group it forms is the scope Breach is then allowed to reach.
//
// Known gap it inherits: Breach tests RUNS only. A landing that straddles a wall - which a switchback dragged
// past the corner of an elevation can do - is not opened, and is not tested either.
public static class ArchExteriorStairAffector
{
public static int Resolve( ArchPlan plan, ArchKit kit )
{
var recut = 0;
var kinds = ArchKinds.Load();
foreach ( var building in plan.Buildings.ToList() )
{
foreach ( var part in plan.Filed<ArchExteriorStairPart>( building, kinds ).ToList() )
{
var shape = ArchExteriorStair.Resolve( part, building, kit );
if ( !shape.IsUsable )
{
continue;
}
// Any flight will do as the key: every leg of one switchback is filed under the routing's own id.
ArchStairShape.Breach( plan, shape.Flights[0].Stair, shape.Walk(), kit, ArchLayerGroups.Reach( plan, part.Id, building.Id ) );
Regroup( plan, building, part, kinds );
recut++;
}
}
return recut;
}
// The buildings the flight actually opened, gathered with it and with the one it hangs on. A flight that
// walks nowhere but its own elevation is no relationship worth a folder, so it forms none.
static void Regroup( ArchPlan plan, ArchBuilding home, ArchExteriorStairPart part, ArchKinds kinds )
{
var reached = plan.Buildings.Where( host => host != home && Opened( plan, host, part, kinds ) ).ToList();
if ( reached.Count == 0 )
{
ArchLayerGroups.Leave( plan, part.Id );
return;
}
var join = ArchLayerGroups.Holding( plan, part.Id )
?? ArchLayerGroups.Create( plan, ArchLayerGroups.Named( plan ), ArchAssemblyKind.SharedCore, 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( part.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, ArchExteriorStairPart part, ArchKinds kinds )
{
return 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 == part.Id );
}
}