Editor/Cabinet/ArchCabinetPart.cs
using System;
using System.Collections.Generic;
using System.Linq;
using Sandbox;
namespace Sunless.Architecture;
// A RUN of carcasses along one wall, not a cabinet: the drag names a stretch of elevation and ArchDivide cuts it
// into modules, so the count is never typed and no sliver is left at either end.
//
// Stored as an anchor and a facing rather than a wall id, the way every other wall fixture is - a wall renumbered,
// thickened or dragged leaves the run standing where the author put it, and the resolve finds the wall again.
//
// Neither the seat nor the head is stored unless the author pinned one. A hanging run reaches for the counter
// below it and a tall one for whatever covers it, and both answers move when the thing they read moves.
public sealed class ArchCabinetPart : IArchCollides, IArchPainted, IArchNamed {
public ArchCollisionMode? Collision { get; set; }
public int Id { get; set; }
public string Name { get; set; } = "Cabinets";
public int Level { get; set; }
public Vector2 Anchor { get; set; }
// Out of the wall and into the room, which is the way the fronts look.
public Vector2 Outward { get; set; } = new( 1f, 0f );
public float Width { get; set; } = 96f;
public CabinetTier Tier { get; set; } = CabinetTier.Auto;
public string Type { get; set; } = "";
// Every one of these is zero for "the type's own answer", so a run tuned on one wall says only what it
// disagrees with and a retyped run picks the rest up.
public float ModuleWidth { get; set; }
public float Depth { get; set; }
public float TopDepth { get; set; }
public float Height { get; set; }
public float BaseHeight { get; set; }
// The author set that foot by hand and means it, which is the only thing that stops the seat deriving.
public bool Seated { get; set; }
public bool Interior { get; set; }
// Null is the type's answer. Nullable rather than zero-means-inherit, because no shelves, no drawers, no
// handles and no counter are all things an author means on purpose.
public int? Shelves { get; set; }
public int? DrawerRows { get; set; }
public bool? Handles { get; set; }
public float? TopThickness { get; set; }
public float? TopEndOversail { get; set; }
public float? TopBackOversail { get; set; }
// Which group the art in this run wears, where the type is made of prefabs. Empty follows the type, so a row
// is re-finished by naming one group here rather than by authoring a second type.
public string MaterialGroup { get; set; } = "";
public ArchPalette Palette { get; set; } = new();
// Overrides for THIS run's mounts, on top of whatever the type says - one door swapped for a prefab without
// authoring a type nobody else will use.
public List<ArchCabinetFitting> Fittings { get; set; } = new();
public Vector2 Facing => Outward.Length < 0.01f ? new Vector2( 1f, 0f ) : Outward.Normal;
// ArchMesh.Beam takes its offsets along ArchRegion.Outward of the line it is given, so the run has to travel
// the way that answers the facing - or every carcass comes out behind its own wall.
public Vector2 Along {
get {
var facing = Facing;
return new Vector2( -facing.y, facing.x );
}
}
public float Run => MathF.Max( 6f, Width );
// What the footprint stands out of the wall when the question is asked with no kit to reach the type through -
// the layer tree and the hit stack both ask that way, so the TIER answers for the type they cannot see. A wall
// unit read at base depth picks a box twice as deep as the carcass a click is aiming at.
public float Reach => Depth > 1f
? Depth
: Tier == CabinetTier.Hanging ? ArchCabinetType.HungDepth : ArchCabinetType.NominalDepth;
public Vector2 Start => Anchor - Along * (Run * 0.5f);
public Vector2 End => Anchor + Along * (Run * 0.5f);
public ArchCabinetFitting On( string mount ) {
return Fittings?.FirstOrDefault( fitting => ArchCabinetMounts.Same( fitting.Mount, mount ) );
}
}