Editor/Cabinet/ArchCabinetStage.cs
using System;
using System.Collections.Generic;
using System.Linq;
namespace Sunless.Architecture;
// One staging of a cabinet type, shared by the designer's turntable and the browser's card art. A short RUN rather
// than a single carcass, because a module, the reveal between two of them and the counter running over both are the
// things an author is judging - and a prefabbed type stands its own art here through the same fitting pass the
// scene uses, so what a card shows is what the room gets.
public static class ArchCabinetStage {
public const int Modules = 3;
public static ArchStaged Of( ArchCabinetType type ) {
if ( type is null ) {
return new ArchStaged( null, Vector3.Zero, 320f );
}
var module = MathF.Max( 6f, type.Width );
var run = module * Modules;
var height = MathF.Max( 24f, Stood( type ) );
// FACING THE CAMERA. The preview looks along +y at its quarter view, so a run whose fronts also point +y
// shows the card its open back - the doors, the drawers and the whole reason to browse are on the far side.
var facing = new Vector2( 0f, -1f );
var cabinets = new ArchCabinetPart {
Id = 2,
Name = "Cabinets",
Type = type.Name,
Anchor = Vector2.Zero,
Outward = facing,
Width = run
};
var room = new ArchRoom {
Id = 1,
WallHeight = MathF.Max( 96f, height + 24f ),
HasFloor = false,
RaisedFoundation = false,
Cabinets = new List<ArchCabinetPart> { cabinets }
};
var building = new ArchBuilding {
Id = 1,
Rooms = new List<ArchRoom> { room },
StoreyHeight = room.WallHeight
};
// A hanging run seats itself off the standard datum with nothing under it, so the frame has to reach the
// top of where it hangs rather than the top of the carcass alone.
var seat = type.Tier == CabinetTier.Hanging ? Datum( type ) : 0f;
var middle = facing * (MathF.Max( 4f, type.Depth ) * 0.5f);
return new ArchStaged(
new ArchPlan { Units = new List<ArchUnit> { building } },
new Vector3( middle.x, middle.y, seat + height * 0.5f ),
MathF.Max( 90f, MathF.Max( run, seat + height ) * 1.75f ) );
}
// The draft a designer is editing is not in the kit yet - and may be about to be renamed into it - so the stage
// is handed a kit that offers exactly it. The palette comes from the real one, or a card would preview the
// blockout materials instead of the map's.
public static ArchKit Staging( ArchKit kit, ArchCabinetType draft ) {
var staged = new ArchKit {
Cabinets = new List<ArchCabinetType> { draft }
};
if ( kit is not null ) {
staged.Palette = kit.Palette;
staged.WallHeight = kit.WallHeight;
staged.WallThickness = kit.WallThickness;
}
return staged;
}
public static string Identity( ArchCabinetType type ) {
return type is null ? "cabinet.none" : ArchStageKey.Of( "cabinet", type.Name ?? "", type );
}
public static string Describe( ArchCabinetType type ) {
if ( type is null ) {
return "";
}
if ( !string.IsNullOrWhiteSpace( type.Detail ) ) {
return type.Detail;
}
var made = type.Prefabbed
? "art"
: ArchCabinetUi.Named( type.On( ArchCabinetMounts.Front )?.Front ?? CabinetFront.Slab );
return $"{type.Width:0} × {type.Depth:0} · {made}";
}
// What a card is CATEGORISED by: how it is made first, because a prefabbed type and a generated one are
// authored in completely different halves of the designer.
public static string Category( ArchCabinetType type ) {
if ( type is null ) {
return "Cabinet";
}
return type.Prefabbed ? "Prefab" : type.Tier switch {
CabinetTier.Hanging => "Wall",
CabinetTier.Tall => "Tall",
_ => "Base"
};
}
public static string Glyph( ArchCabinetType type ) {
return type is { Prefabbed: true } ? "category" : ArchCabinetUi.Glyph( type?.Tier ?? CabinetTier.Base );
}
static float Stood( ArchCabinetType type ) {
return type.Plinth + type.Carcass + type.Counter;
}
static float Datum( ArchCabinetType type ) {
return type.Datum > 1f ? type.Datum : ArchCabinetShape.HungDatum;
}
}