Editor/Designers/ArchCorniceStage.cs
using System;
using System.Collections.Generic;
using System.Linq;
using Sandbox;
namespace Sunless.Architecture;
// One staging of a cornice, shared by the designer's turntable and the browser's card art. A whole crowned block,
// because a cornice is judged at a corner: how the mitre closes and where a pier breaks the line are the two things
// a straight run cannot show.
public static class ArchCorniceStage {
// The kit is what a roof names its cornice through, so a draft nobody has saved has to stand on the shelf under
// a name of its own for the length of one restage. Every picker hides it again.
public const string Drafting = ".drafting";
public static readonly Vector2 Plot = new( 288f, 216f );
public static bool Hidden( ArchCorniceStyle style ) => string.Equals( style?.Name, Drafting, StringComparison.OrdinalIgnoreCase );
// The card path: the style is already on the shelf under its own name, so the roof names it and nothing is
// stood in a scratch slot a paint pass would race over.
public static ArchStaged Of( ArchCorniceStyle style, ArchKit kit ) => Standing( style, kit, style.Name );
// The designer path: an unsaved draft has no name a roof could find it by, so it takes the reserved one for
// the length of one restage. Every picker filters that entry back out.
public static ArchStaged Drafted( ArchCorniceStyle draft, ArchKit kit ) {
var staged = draft.Copy();
staged.Name = Drafting;
kit.Cornices.RemoveAll( Hidden );
kit.Cornices.Add( staged );
return Standing( draft, kit, Drafting );
}
static ArchStaged Standing( ArchCorniceStyle style, ArchKit kit, string named ) {
var plan = new ArchPlan();
var building = new ArchBuilding { Id = plan.AllocateId(), Name = "Cornice" };
var shell = ArchBuild.Shell( plan, building, kit, 0, 0f, Vector2.Zero, Plot, false, RoofStyle.Flat, false );
if ( shell?.Roof is null ) {
return new ArchStaged( null, Vector3.Zero, 480f );
}
plan.Units.Add( building );
// Pinned to the kit's own storey, or Head cannot answer for the card art what the shell answered here.
shell.Room.WallHeight = kit.WallHeight;
shell.Roof.Parapet = true;
shell.Roof.ParapetHeight = Parapet( style );
shell.Roof.Cornice = named;
var head = Head( style, kit );
return new ArchStaged(
plan,
new Vector3( Plot.x * 0.5f, Plot.y * 0.5f, head * 0.82f ),
MathF.Max( 320f, MathF.Max( Plot.x, head ) * 1.25f ) );
}
// Enough parapet under the stack to read as a wall the cornice crowns rather than as a lid on a box.
public static float Parapet( ArchCorniceStyle style ) => MathF.Max( 16f, style.Rise + 14f );
public static float Head( ArchCorniceStyle style, ArchKit kit ) => kit.WallHeight + Parapet( style );
// The near CORNER, not the whole ring: a card framed on the block shows a hairline of trim on a grey box,
// where the thing being chosen is how the courses stack and how they turn.
public static BBox Interest( ArchCorniceStyle style, ArchKit kit ) {
var head = Head( style, kit );
var reach = style.Projection + kit.WallThickness + 8f;
var span = MathF.Max( 96f, style.Rise * 4f );
return new BBox(
new Vector3( -reach, -reach, head - style.Rise - span * 0.35f ),
new Vector3( span, span, head + (style.Pier.Stands ? style.Pier.Rise : 0f) + 6f ) );
}
// Serialised rather than a typed-out field list: a style carries a nested course list, and a key that forgot
// one would show yesterday's crown for today's dentils.
public static string Identity( ArchCorniceStyle style ) => ArchStageKey.Of( "cornice", style.Name, style );
public static string Describe( ArchCorniceStyle style ) {
if ( !string.IsNullOrWhiteSpace( style.Detail ) ) {
return style.Detail;
}
return $"{style.Courses.Count} course{(style.Courses.Count == 1 ? "" : "s")}, {style.Rise:0.#} in deep";
}
}