Architecture editor rules for building archetypes. Applies section-level authored rules and kit defaults to rooms, walls and roofs, copies palette materials and texture settings, and computes standing wall/plate heights.
using System;
using Sandbox;
namespace Sunless.Architecture;
// The only place a type touches geometry; a building keeps obeying its type.
public static class ArchArchetypeRules
{
// An unstated wall height is the kit's storey, never "leave what is standing" - a type that says
// nothing has to be able to take a taller type's walls back down when it is adopted.
public static float Standing( float authored, ArchKit kit ) => authored > 1f ? authored : kit.WallHeight;
public static float Standing( ArchSectionRules rules, ArchKit kit ) => Standing( rules.WallHeight, kit );
public static void Apply( ArchSection section, ArchSectionRules rules, ArchKit kit )
{
if ( section?.Room is null || rules is null )
{
return;
}
// Merged wings keep the height Extend already measured, else they'd stand open along their eave.
if ( !section.Merged )
{
section.Room.WallHeight = Standing( rules, kit );
}
section.Room.HasCeiling = rules.Ceiling ?? section.Room.HasCeiling;
section.Room.HasFloor = rules.Floor ?? section.Room.HasFloor;
section.Room.RaisedFoundation = rules.Foundation ?? section.Room.RaisedFoundation;
section.Room.Collision = rules.Collision ?? section.Room.Collision;
section.Room.FloorBoards = rules.FloorBoards;
section.Room.FloorBoardYaw = rules.FloorBoardYaw;
// One copier: a band added later reaches old elevations; no shared band list.
if ( rules.Wall is { } treatment )
{
foreach ( var wall in section.Room.Walls )
{
ArchWallTreatments.Dress( treatment, wall );
}
}
Skin( section.Room.Palette, rules.Palette );
if ( section.Roof is null )
{
return;
}
if ( !section.Merged )
{
section.Room.WallHeight = Plate( section.Room );
section.Roof.BaseHeight = section.Room.BaseHeight + section.Room.WallHeight;
}
Roof( section.Roof, rules );
}
// The roof sits on the walls that actually stand - a treatment height moves it too.
static float Plate( ArchRoom room )
{
var authored = 0f;
foreach ( var wall in room.Walls )
{
authored = MathF.Max( authored, wall.Height );
}
return authored > 1f ? authored : room.WallHeight;
}
public static void Roof( ArchRoofPart roof, ArchSectionRules rules )
{
if ( rules.RoofPitch > 0.5f )
{
roof.Pitch = rules.RoofPitch;
}
if ( rules.Overhang > 0.01f )
{
roof.Overhang = rules.Overhang;
}
if ( rules.FrameSpacing > 4f )
{
roof.FrameSpacing = rules.FrameSpacing;
}
if ( rules.ParapetHeight > 0.5f )
{
roof.ParapetHeight = rules.ParapetHeight;
}
if ( rules.GuardHeight > 0.5f )
{
roof.GuardHeight = rules.GuardHeight;
}
roof.Ceiling = rules.Ceiling ?? roof.Ceiling;
roof.Gutters = rules.Gutters ?? roof.Gutters;
roof.Fascia = rules.Fascia ?? roof.Fascia;
roof.Soffit = rules.Soffit ?? roof.Soffit;
roof.Frame = rules.Frame ?? roof.Frame;
roof.Parapet = rules.Parapet ?? roof.Parapet;
roof.Guardrail = rules.Guardrail ?? roof.Guardrail;
Skin( roof.Palette, rules.Palette );
}
// Copied, not assigned - a shared dictionary would silently re-skin every part.
public static void Skin( ArchPalette target, ArchPalette source )
{
foreach ( var entry in source.Materials )
{
target.Materials[entry.Key] = entry.Value;
}
foreach ( var entry in source.TexelScales )
{
target.TexelScales[entry.Key] = entry.Value;
}
foreach ( var entry in source.TextureOffsets ?? new Dictionary<string, Vector2>() )
{
target.TextureOffsets[entry.Key] = entry.Value;
}
}
}