Defines editor data types for wall modifiers. Contains enums for modifier kinds and placement side, extension methods giving behaviour, labels and glyphs, and a plain data class ArchWallModPart that stores a modifier's authored properties (id, name, kind, side, station along the wall, size, field and palette).
using System;
using System.Collections.Generic;
using Sandbox;
namespace Sunless.Architecture;
// Placed work on a wall. A pilaster run is a rhythm the whole wall wears; a modifier stands at a
// station, can be selected, moved, disabled and deleted on its own.
//
// APPEND ONLY - a member inserted mid-list renumbers every authored modifier after it.
public enum ArchWallMod
{
Pilaster,
Quoin,
Buttress,
Indent,
Sign
}
// Which elevation the block belongs to. A shopfront's corner pier reads on both; a recess only ever
// bites the face it was cut into. OnSurface is a placement mode - the drag decides, and what gets
// authored is Outside or Inside - so a placed part never carries it.
public enum ArchWallSide
{
Outside,
Inside,
Both,
OnSurface
}
// What a kind of modifier actually is - asked here, never re-tested inline in a generator or a form.
public static class ArchWallModKinds
{
// How tall a plate stands when the author has not said. A fascia board, not a storey.
public const float PlateHeight = 24f;
public static bool Carves( this ArchWallMod kind ) => kind == ArchWallMod.Indent;
// A rusticated corner is a STACK of blocks, alternately long and short.
public static bool Coursed( this ArchWallMod kind ) => kind == ArchWallMod.Quoin;
// A buttress leans back as it climbs; everything else stands square.
public static bool Tapers( this ArchWallMod kind ) => kind == ArchWallMod.Buttress;
// A plate is a board at a station rather than a strip dressing the whole elevation, so it is authored
// with a band of its own - a sign climbing four storeys is a billboard nobody asked for.
public static bool Plated( this ArchWallMod kind ) => kind == ArchWallMod.Sign;
// Where a plate seats when nothing is authored: its head on the wall's, where a fascia board goes.
public static float Seat( this ArchWallMod kind, float authored, float wallHeight )
{
if ( !kind.Plated() || authored > 0.5f )
{
return authored;
}
return MathF.Max( 0f, wallHeight - PlateHeight );
}
public static float Rise( this ArchWallMod kind, float authored )
{
if ( !kind.Plated() || authored > 0.5f )
{
return authored;
}
return PlateHeight;
}
// Only a sign comes out as a piece of its own: it is the one modifier an author hides, collides and
// repaints apart from the elevation carrying it. Null leaves the block on the wall's fittings.
public static string Piece( this ArchWallMod kind ) => kind == ArchWallMod.Sign ? ArchPieces.Sign : null;
public static ArchSurface Field( this ArchWallMod kind ) => kind switch
{
ArchWallMod.Indent => ArchSurface.WallBase,
ArchWallMod.Quoin => ArchSurface.WallBase,
ArchWallMod.Sign => ArchSurface.Sign,
_ => ArchSurface.Pillar
};
public static string Label( this ArchWallMod kind ) => kind switch
{
ArchWallMod.Pilaster => "Pilaster — an engaged strip standing proud of the elevation",
ArchWallMod.Quoin => "Quoin — rusticated blocks, alternately long and short",
ArchWallMod.Buttress => "Buttress — a proud pier leaning back as it climbs",
ArchWallMod.Sign => "Sign — a proud plate saying what the building was",
_ => "Indent — a recess bitten out of the outside face"
};
public static string Glyph( this ArchWallMod kind ) => kind switch
{
ArchWallMod.Pilaster => "view_column",
ArchWallMod.Quoin => "grid_view",
ArchWallMod.Buttress => "signal_cellular_4_bar",
ArchWallMod.Sign => "label",
_ => "check_box_outline_blank"
};
public static string Label( this ArchWallSide side ) => side switch
{
ArchWallSide.Outside => "Outside — stands on the street elevation",
ArchWallSide.Inside => "Inside — stands in the room",
ArchWallSide.Both => "Both — a pier reading on either face",
_ => "On surface — whichever face the drag lands on"
};
public static string Glyph( this ArchWallSide side ) => side switch
{
ArchWallSide.Outside => "villa",
ArchWallSide.Inside => "flip_to_back",
ArchWallSide.Both => "flip",
_ => "ads_click"
};
}
public sealed class ArchWallModPart : IArchPainted, IArchNamed
{
public int Id { get; set; }
public string Name { get; set; } = "Pilaster";
public ArchWallMod Kind { get; set; } = ArchWallMod.Pilaster;
public ArchWallSide Side { get; set; } = ArchWallSide.Outside;
// A STATION on the wall run - never a world point, so the modifier survives the wall being dragged.
// The station is what is clamped, not the block: one standing ON the end wraps the corner, which is
// how a shopfront pier reads across two elevations.
public float Along { get; set; }
public float Width { get; set; } = 16f;
public float Base { get; set; }
// Zero climbs to the wall head, so one preset dresses walls of several heights.
public float Height { get; set; }
public float Depth { get; set; } = 3f;
public ArchSurface Field { get; set; } = ArchSurface.Pillar;
public ArchPalette Palette { get; set; } = new();
public bool Carves => Kind.Carves();
public float Left => Along - Width * 0.5f;
public float Right => Along + Width * 0.5f;
}