Editor/Data/ArchArchetype.cs

Data model for building archetypes used in editor tooling. Defines enums and classes that describe plot rectangles, section rules, steps, option groups, and the archetype container with defaults and simple helper Wants and HasLayout members.

File Access
using System.Collections.Generic;
using Sandbox;

namespace Sunless.Architecture;

// A step names a side, not a wall id - ids are allocated as the plan grows.
public enum ArchSide
{
	MinX,
	MaxX,
	MinY,
	MaxY
}

// Every step is one call into an authoring service that already exists; never new geometry.
public enum ArchStepKind
{
	Shell,
	Wing,
	Canopy,
	Opening,
	Pillars,
	Monitor,
	Ladder
}

// Fractions alone can't hold a bay to its real width; inches alone can't fit any plot.
public sealed class ArchPlotRect
{
	public Vector2 Min { get; set; } = new( 0f, 0f );
	public Vector2 Max { get; set; } = new( 1f, 1f );
	public Vector2 MinInset { get; set; }
	public Vector2 MaxInset { get; set; }
}

// Null = the authoring service's default, not "off" - "no fascia" without restating every trim.
public sealed class ArchSectionRules
{
	public float WallHeight { get; set; }
	public RoofStyle? Roof { get; set; }
	public RidgeRun Ridge { get; set; } = RidgeRun.Auto;
	public float RoofPitch { get; set; }
	public float Overhang { get; set; }

	public bool? Floor { get; set; }
	// Off = no slab and no plinth; the terrain runs straight through the doors.
	public bool? Foundation { get; set; }
	public bool? Ceiling { get; set; }
	public bool? Gutters { get; set; }
	public bool? Fascia { get; set; }
	public bool? Soffit { get; set; }
	public bool? Frame { get; set; }
	public float FrameSpacing { get; set; }

	public bool FloorBoards { get; set; }
	public float FloorBoardYaw { get; set; }

	// Null leaves the walls as stood - not the same as a bare treatment.
	public ArchWallPreset Wall { get; set; }

	// What the section collides as. A far-off silhouette is set to None from here, so the vista trick is
	// one type on disk rather than a per-room edit repeated across a skyline.
	public ArchCollisionMode? Collision { get; set; }

	// On a flat roof it's the whole silhouette - a factory type must be able to say it.
	public bool? Parapet { get; set; }
	public float ParapetHeight { get; set; }
	// Edge protection round a deck anyone is meant to walk on.
	public bool? Guardrail { get; set; }
	public float GuardHeight { get; set; }
	// Keyed by ArchSurface name, so a skin ships without a map palette loaded.
	public ArchPalette Palette { get; set; } = new();
}

public sealed class ArchArchetypeStep
{
	public ArchStepKind Kind { get; set; }
	// Registers the room so a later step can name it; empty acts on the last room.
	public string Name { get; set; }
	public string Target { get; set; }

	public ArchPlotRect Rect { get; set; } = new();
	public ArchSectionRules Rules { get; set; } = new();

	public SectionRoof WingRoof { get; set; } = SectionRoof.Continue;
	public float EaveDrop { get; set; }
	public int SawtoothBays { get; set; }

	public float HeadHeight { get; set; } = 160f;
	public float DeckDrop { get; set; }
	public bool Deck { get; set; }
	public bool Posts { get; set; } = true;
	public bool Braces { get; set; }
	public bool Rafters { get; set; } = true;
	public bool Railings { get; set; }
	public bool Steps { get; set; }
	public float PostSpacing { get; set; }

	public ArchSide Side { get; set; }
	public string Preset { get; set; }
	public float Along { get; set; } = 0.5f;
	public int Count { get; set; } = 1;
	// 0 leaves the preset's own size - a recipe stretches a door it already names.
	public float Width { get; set; }
	public float Height { get; set; }
	public float SillHeight { get; set; }
	public bool? Leaf { get; set; }
	public bool? Cased { get; set; }
	public bool? Glazed { get; set; }
	public bool? StartOpen { get; set; }

	// Widest pane a Monitor's lid carries before a glazing bar splits it; 0 leaves one sheet.
	public float PaneSpan { get; set; }

	// Spacing is a MAXIMUM bay - the last column lands on the far edge.
	public Vector2 Spacing { get; set; } = new( 240f, 240f );
	public PillarSpan Span { get; set; } = PillarSpan.None;
	// Empty takes the building type's own first kind, so a recipe need not restate it.
	public string Pillar { get; set; } = "";
}

// Declared as data so a new type gates its own panel with no code.
public enum ArchOptionGroup
{
	Section,
	Foundation,
	RoofStyle,
	Ridge,
	WingRoof,
	Approach,
	Boundary,
	Yard
}

// The building remembers its type, so a wing added later still obeys it.
public sealed class ArchArchetype
{
	public int Version { get; set; } = 1;
	public string Name { get; set; } = "archetype";
	public string Title { get; set; } = "Archetype";
	// A type core ships cannot be deleted, so this is how it is removed: the file says so and the shelf drops it.
	public bool Hidden { get; set; }
	public string Description { get; set; } = "";
	public string Icon { get; set; } = "domain";
	public List<string> Openings { get; set; } = new();
	// Empty offers every kind.
	public List<string> Pillars { get; set; } = new();

	public ArchSectionRules Shell { get; set; } = new();
	public ArchSectionRules Wing { get; set; } = new();
	public SectionRoof WingRoof { get; set; } = SectionRoof.Continue;
	public float WingEaveDrop { get; set; }

	// Empty means every group, so a pre-existing type keeps its panel.
	public List<ArchOptionGroup> Shows { get; set; } = new();

	public bool Wants( ArchOptionGroup group ) => Shows.Count == 0 || Shows.Contains( group );

	public float DriveWidth { get; set; } = 132f;
	public float DriveSplay { get; set; } = 36f;
	public ApproachKind DriveKind { get; set; } = ApproachKind.Ramp;

	public Vector2 MinimumPlot { get; set; } = new( 240f, 240f );
	public List<ArchArchetypeStep> Steps { get; set; } = new();

	public bool HasLayout => Steps.Count > 0;
}