Editor utility that generates a diagnostic report for an ArchStairPart. It resolves the stair shape and returns an anonymous object describing origin, orientation, support, step/run/pad details, handrail and capping information used for editor reporting or debugging.
using System;
using System.Collections.Generic;
using System.Linq;
using Sandbox;
namespace Sunless.Architecture;
public static class ArchStairReport
{
// From the numbers the generator reads: a short rail shows as Handrail.To short of RunLength.
public static object StairReport( ArchStairPart stair, ArchRoom room, ArchBuilding building, ArchKit kit )
{
var shape = ArchStairShape.Resolve( stair, room, kit, building?.Rooms );
var height = MathF.Max( 12f, kit.HandrailHeight );
var post = MathF.Max( 1f, kit.NewelSize ) * 0.5f;
var proud = ArchContact.Proud( kit );
return new
{
stair.Name,
Origin = $"{stair.Origin.x:0.#},{stair.Origin.y:0.#}",
stair.Yaw,
Support = stair.Support.ToString(),
Under = stair.Under.ToString(),
Guard = stair.Guard.ToString(),
LeftRailing = stair.LeftRailing.ToString(),
RightRailing = stair.RightRailing.ToString(),
stair.WallRail,
stair.WellGuard,
Steps = stair.StepCount,
Rise = shape.Rise,
shape.Going,
shape.Width,
shape.BaseHeight,
shape.TopHeight,
OpenEdge = shape.OpenEdge,
ClosedEdge = shape.ClosedEdge,
HandrailHeight = height,
Runs = shape.Runs.Select( run => new
{
run.Index,
run.Steps,
run.Length,
Housed = Housed( run ),
run.BaseHeight,
run.TopHeight,
run.WalledLeft,
run.WalledRight,
RakeFootZ = run.Rake( 0f ),
RakeHeadZ = run.Rake( run.Length ),
Handrail = Handrail( shape, run, height, post ),
Capping = new
{
Sides = Sides( !run.WalledRight, !run.WalledLeft ),
FootZ = run.BaseHeight + proud,
HeadZ = run.TopHeight + proud
}
} ).ToList(),
Pads = shape.Pads.Select( pad => new
{
Turn = pad.Turn.ToString(),
pad.Arrival,
pad.AlongFrom,
pad.AlongTo,
pad.AcrossFrom,
pad.AcrossTo,
pad.Height,
pad.WalledFrom,
pad.WalledTo,
pad.WalledHead,
Rails = Railed( pad )
} ).ToList(),
Wells = shape.Wells().Count()
};
}
// What the shell took off the flight, so a run that comes out short says which side it was taken on rather
// than merely reading shorter than the handle its author let go of.
static string Housed( ArchStairRun run )
{
var lost = new List<string>();
if ( run.Drawn - run.Length > 1f )
{
lost.Add( $"{run.Drawn - run.Length:0.#} off the head of {run.Drawn:0.#}" );
}
if ( run.DrawnWidth - run.Width > 1f )
{
lost.Add( $"{run.DrawnWidth - run.Width:0.#} off the flanks of {run.DrawnWidth:0.#}" );
}
return lost.Count == 0 ? "stands as drawn" : $"taken back to the shell - {string.Join( ", ", lost )}";
}
// Mirrors the stations ArchStairGen.Climbing lays - a lying report is worse than none.
static object Handrail( ArchStairShape shape, ArchStairRun run, float height, float post )
{
var sides = Sides( shape.GuardRight && !run.WalledRight, shape.GuardLeft && !run.WalledLeft );
if ( sides == "none" )
{
return new { Sides = sides, Reason = "guard is off, or both flanks run against a wall" };
}
var from = post;
var to = run.Last ? run.Under( height ) : run.Length;
var padAfter = PadAfter( shape, run );
var ends = run.Last
? "buried in the slab above, the well rail takes over"
: padAfter is { Level: null }
? "carries through the landing at its own height"
: "ends at the landing's edge — the well ring's corner post takes the rail on";
return new
{
Sides = sides,
From = from,
To = to,
RunLength = run.Length,
Covers = run.Length > 0.5f ? $"{MathX.Clamp( (to - from) / run.Length, 0f, 1f ) * 100f:0}% of the run" : "nothing",
TopAtFoot = run.Nosing( from ) + height,
TopAtEnd = run.Nosing( to ) + height,
SeatAtFoot = run.Rake( from ),
SeatAtEnd = run.Rake( to ),
Ends = ends,
SlabTop = run.TopHeight,
Emitted = to - from >= 1f
};
}
// The landing that hands a run to the next — the same match the generator's walk makes.
static ArchStairPad PadAfter( ArchStairShape shape, ArchStairRun run )
{
foreach ( var pad in shape.Pads )
{
if ( MathF.Abs( pad.AlongFrom - run.Length ) < 2f && MathF.Abs( pad.Height - run.TopHeight ) < 2f )
{
return pad;
}
}
return null;
}
// The landing's own edges, read off the one answer the rail reads - the arrival is never among them.
static string Railed( ArchStairPad pad )
{
var edges = new List<string>();
if ( pad.RailsFrom ) edges.Add( "right" );
if ( pad.RailsTo ) edges.Add( "left" );
if ( pad.RailsHead ) edges.Add( "head" );
return edges.Count == 0 ? "none" : string.Join( "+", edges );
}
static string Sides( bool right, bool left )
{
var sides = new List<string>();
if ( right ) sides.Add( "right" );
if ( left ) sides.Add( "left" );
return sides.Count == 0 ? "none" : string.Join( "+", sides );
}
}