Editor/Data/ArchArchetype.cs
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,
Storey,
Extrude
}
// 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 WallThickness { get; set; }
public float RoofThickness { get; set; }
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; }
// The band is measured off the target storey's own floor: SillHeight is its foot, Height its head, 0 one storey.
public ArchExtrudeKind Extrude { get; set; }
public float Proud { get; set; } = 4f;
public float Course { get; set; }
public float Joint { 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;
}