Static helper class that defines properties and behavior for different roof light forms (RoofLightForm). It provides extension methods that answer boolean traits (e.g. Glazes, Pierces), UI labels/glyphs/slug/advice, default stock values, and constructs an ArchPillarPart for stand-alone plant types.
using System;
using Sandbox;
namespace Sunless.Architecture;
// THE ONE TABLE for what a form of roof light actually is, asked by the generator, the drag preview, the property
// sheet and the MCP tools alike - so "does this glaze" is answered here and nowhere else. A chimney is a monitor's
// carve with the glazing off, a hatch is a kerbed light with a lid instead of a pane, and a vent or a tank is a
// section standing on the deck. Every one of them is an existing generator called differently.
public static class ArchRoofLightKinds
{
// A flue and a hatch open the deck; a stack seated on its own flashing and a tank on its plinth stand on it.
public static bool Pierces( this RoofLightForm form )
=> form is RoofLightForm.Kerbed or RoofLightForm.Monitor or RoofLightForm.Chimney or RoofLightForm.Hatch
or RoofLightForm.Dormer;
public static bool Kerbed( this RoofLightForm form ) => form.Pierces();
// A housing tall enough to read as a wall takes the wall skin.
public static bool Clad( this RoofLightForm form )
=> form is RoofLightForm.Monitor or RoofLightForm.Chimney or RoofLightForm.Dormer;
// A coping FINISHES a housing; a dormer is finished by the roof over it instead.
public static bool Coped( this RoofLightForm form ) => form is RoofLightForm.Monitor or RoofLightForm.Chimney;
public static bool Glazes( this RoofLightForm form )
=> form is RoofLightForm.Kerbed or RoofLightForm.Monitor or RoofLightForm.Dormer;
// A dormer's pane stands in its FRONT cheek and looks out over the eave; every other glazed form lies on its
// own head and looks at the sky.
public static bool Fronted( this RoofLightForm form ) => form == RoofLightForm.Dormer;
// A pitched lid with a gable end over it, instead of a coping or a flat pane.
public static bool Roofed( this RoofLightForm form ) => form == RoofLightForm.Dormer;
public static bool Lidded( this RoofLightForm form ) => form == RoofLightForm.Hatch;
// Stood through ArchPillarGen: a pipe and a tank are the same column with different sections.
public static bool Stands( this RoofLightForm form ) => form is RoofLightForm.Vent or RoofLightForm.Tank;
public static bool Cowled( this RoofLightForm form ) => form == RoofLightForm.Vent;
public static bool Round( this RoofLightForm form ) => form.Stands();
// What the roof carries it as: plant is what stands ON a deck, a light is what is cut out of one.
public static bool Plant( this RoofLightForm form ) => !form.Glazes();
public static string Piece( this RoofLightForm form ) => form.Plant() ? ArchPieces.Plant : ArchPieces.Lights;
public static string Label( this RoofLightForm form ) => form switch
{
RoofLightForm.Monitor => "Monitor",
RoofLightForm.Chimney => "Chimney",
RoofLightForm.Vent => "Vent stack",
RoofLightForm.Tank => "Water tank",
RoofLightForm.Hatch => "Roof hatch",
RoofLightForm.Dormer => "Dormer",
_ => "Roof light"
};
// The name a placed part is filed under - one word, because a layer row is read at a glance.
public static string Tag( this RoofLightForm form ) => form switch
{
RoofLightForm.Monitor => "Monitor",
RoofLightForm.Chimney => "Chimney",
RoofLightForm.Vent => "Vent",
RoofLightForm.Tank => "Tank",
RoofLightForm.Hatch => "Hatch",
RoofLightForm.Dormer => "Dormer",
_ => "RoofLight"
};
public static string Advice( this RoofLightForm form ) => form switch
{
RoofLightForm.Monitor => "Drag the housing footprint on a Flat, Shed or Gable deck.",
RoofLightForm.Chimney => "Drag the flue footprint on a Flat, Shed or Gable deck — it stands off the deck plane under it.",
RoofLightForm.Vent => "Drag the stack footprint on a Flat, Shed or Gable deck — a round pipe on a flashing.",
RoofLightForm.Tank => "Drag the tank footprint on a Flat, Shed or Gable deck — it stands on a plinth, not on an authored height.",
RoofLightForm.Hatch => "Drag the hatch footprint on a Flat, Shed or Gable deck — a kerbed opening with a lid on it.",
RoofLightForm.Dormer => "Drag the dormer footprint on any slope — Shed, Gable or Hip. It breaks out facing downhill, with its window in the front cheek.",
_ => "Drag a rectangle on a Flat, Shed or Gable deck — the light is cut out of it."
};
public static string Slug( this RoofLightForm form ) => $"roof_{form.ToString().ToLowerInvariant()}";
// Classic Material Icons only - the editor ships an older set than the web font, and a glyph it does not know
// draws an empty box rather than falling back.
public static string Glyph( this RoofLightForm form ) => form switch
{
RoofLightForm.Monitor => "domain",
RoofLightForm.Chimney => "whatshot",
RoofLightForm.Vent => "settings_input_antenna",
RoofLightForm.Tank => "opacity",
RoofLightForm.Hatch => "crop_square",
RoofLightForm.Dormer => "window",
_ => "wb_twilight"
};
// What the two numbers MEAN on this form, so one pair of fields reads honestly on all six.
public static string RiseLabel( this RoofLightForm form ) => form switch
{
RoofLightForm.Monitor => "Housing rise",
RoofLightForm.Chimney => "Flue rise",
RoofLightForm.Vent => "Stack rise",
RoofLightForm.Tank => "Tank rise",
RoofLightForm.Dormer => "Cheek rise",
_ => "Kerb rise"
};
public static string ReachLabel( this RoofLightForm form ) => form switch
{
RoofLightForm.Monitor or RoofLightForm.Chimney or RoofLightForm.Dormer => "Wall thickness",
RoofLightForm.Vent => "Flashing reach",
RoofLightForm.Tank => "Plinth reach",
_ => "Kerb width"
};
// Stock, not a control: what a form is restocked with when it is picked, and what stands it when nothing was typed.
public static float RiseStock( this RoofLightForm form, ArchKit kit ) => form switch
{
RoofLightForm.Monitor => 40f,
RoofLightForm.Dormer => 44f,
RoofLightForm.Chimney or RoofLightForm.Vent or RoofLightForm.Tank => kit.PlantHeight,
_ => 8f
};
public static float ReachStock( this RoofLightForm form, ArchKit kit ) => form switch
{
RoofLightForm.Monitor => 10f,
RoofLightForm.Dormer => 6f,
RoofLightForm.Chimney or RoofLightForm.Vent or RoofLightForm.Tank => kit.PlantWidth,
_ => 6f
};
public static float PaneStock( this RoofLightForm form ) => form == RoofLightForm.Monitor ? 96f : 0f;
// Round, sectioned by the drag, dressed off the same coping course the housings are finished with: a tank is a
// fat column on a plinth and a stack is a narrow one under a cowl, which is why neither needs a generator of its own.
public static ArchPillarPart Pillar( this RoofLightForm form, Vector2 centre, Vector2 size, float seat, float rise, float reach, ArchKit kit )
{
var course = MathF.Max( 1f, kit.WallCapHeight );
return new ArchPillarPart
{
Placement = PillarPlacement.Freestanding,
Origin = centre,
BaseHeight = seat,
Height = rise,
Width = MathF.Max( 1f, size.x ),
Depth = MathF.Max( 1f, size.y ),
Sides = form.Round() ? 12 : 4,
Footing = PillarFooting.None,
Plinth = true,
PlinthHeight = course,
PlinthOversize = reach,
Capital = form.Cowled(),
CapitalHeight = course,
// A cowl is a hood over the bore, not a second flashing round it.
CapitalOversize = MathF.Max( 1f, reach * 0.5f )
};
}
}