Editor/Roof/ArchRoofLightKinds.cs

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.

File Access
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 )
		};
	}
}