The file is a level builder script for a bank interior scene prototype. It defines various helper methods to procedurally place assets, construct walls, and set up lighting within the game world hierarchy.
namespace HorizonRP;
/// <summary>Intérieur de la maquette, sans modification des placements ni de la carte réelle.</summary>
public static class BankPrototypeInterior
{
public static void Build( GameObject root )
{
Material Mat( string p ) => Material.Load( p );
var plaster = Mat( "truenorth/materials/agency/concrete/concretewall01.vmat" );
var marble = Mat( "truenorth/materials/agency/floor/marble01a.vmat" );
var wood = Mat( "truenorth/materials/agency/floor/woodpanel01.vmat" );
var carpet = Mat( "truenorth/materials/agency/carpet/carpet_black01.vmat" );
var concrete = Mat( "truenorth/materials/concrete/concrete_ext_01.vmat" );
var ceiling = Mat( "truenorth/materials/tile/ceilingtilea.vmat" );
var navy = Mat( "materials/horizon/bank_navy.vmat" );
var glass = Mat( "materials/horizon/bank_test_glass.vmat" );
var glow = Mat( "materials/horizon/bank_light.vmat" );
var box = Model.Load( "models/dev/box.vmdl" );
GameObject Block( string name, Vector3 p, Vector3 size, Material m )
{
var go = new GameObject( root, true, name ); go.LocalPosition = BankPrototype.Point( p ); go.LocalScale = BankPrototype.Point( size ) / 50;
var r = go.Components.Create<ModelRenderer>(); r.Model = box; r.MaterialOverride = m;
var c = go.Components.Create<BoxCollider>(); c.Scale = new Vector3( 50 ); c.Static = true;
return go;
}
GameObject Prop( string name, string path, Vector3 foot, float yaw = 0, float scale = 1, bool solid = true )
{
var model = Model.Load( path );
var go = new GameObject( root, true, name );
go.LocalPosition = BankPrototype.Point( foot ) - Vector3.Up * model.Bounds.Mins.z * scale;
go.LocalRotation = Rotation.FromYaw( yaw ); go.LocalScale = new Vector3( scale );
go.Components.Create<ModelRenderer>().Model = model;
if ( solid )
{
var c = go.Components.Create<BoxCollider>(); c.Center = model.Bounds.Center; c.Scale = model.Bounds.Size; c.Static = true;
}
return go;
}
void Label( string text, Vector3 p, float size = 12, float yaw = -90 )
{
var go = new GameObject( root, true, text ); go.LocalPosition = BankPrototype.Point( p ); go.LocalRotation = Rotation.FromYaw( yaw );
var t = go.Components.Create<TextRenderer>(); t.Text = text; t.FontFamily = "Inter"; t.FontSize = 64;
t.Scale = size / 64; t.FontWeight = 600; t.Color = Color.White;
t.HorizontalAlignment = TextRenderer.HAlignment.Center; t.VerticalAlignment = TextRenderer.VAlignment.Center;
}
void Plaque( string text, Vector3 p, float width, float yaw = -90 )
{
var plate = Block( "Signalétique " + text, p, new( width, 3, 14 ), navy );
plate.LocalRotation = Rotation.FromYaw( yaw + 90 );
var normal = Rotation.FromYaw( yaw + 180 ).Forward;
Label( text, p + normal * 2, 7.5f, yaw );
}
void WallY( string name, float y, float x1, float x2, float? door = null, Material m = null )
{
m ??= plaster;
const float half = 26 / BankPrototype.PlanScale;
if ( door is not { } d ) { Block( name, new( (x1 + x2) / 2, y, 72 ), new( x2 - x1, 12, 144 ), m ); return; }
Block( name, new( (x1 + d - half) / 2, y, 72 ), new( d - half - x1, 12, 144 ), m );
Block( name, new( (x2 + d + half) / 2, y, 72 ), new( x2 - d - half, 12, 144 ), m );
Block( name + " linteau", new( d, y, 125 ), new( half * 2, 12, 38 ), m );
}
void WallX( string name, float x, float y1, float y2, float? door = null, Material m = null, float opening = 52, float openingHeight = 106 )
{
m ??= plaster;
float half = opening / (2 * BankPrototype.PlanScale);
if ( door is not { } d ) { Block( name, new( x, (y1 + y2) / 2, 72 ), new( 12, y2 - y1, 144 ), m ); return; }
Block( name, new( x, (y1 + d - half) / 2, 72 ), new( 12, d - half - y1, 144 ), m );
Block( name, new( x, (y2 + d + half) / 2, 72 ), new( 12, y2 - d - half, 144 ), m );
Block( name + " linteau", new( x, d, (144 + openingHeight) / 2 ), new( 12, half * 2, 144 - openingHeight ), m );
}
void Door( string name, Vector3 p, bool alongX = false, string path = null, float turn = -95 )
{
BankPrototypeMapDoor.Single( root, name, BankPrototype.Point( p ), alongX,
path ?? BankPrototypeMapDoor.Wood, turn );
}
void Plant( Vector3 p )
{
Prop( "Pot de plante", "truenorth/models/props_foliage/mall_pot_large01.vmdl", p, 0, 0.7f );
Prop( "Plante du hall", "truenorth/models/props_foliage/mall_bigleaves_plant03.vmdl", p + Vector3.Up * 18, 150, 0.65f, false );
}
const string Chair = "truenorth/models/props_interiors/chairlobby01.vmdl";
const string Desk = "truenorth/models/props_interiors/desk_executive.vmdl";
const string Cabinet = "truenorth/models/props/cs_office/file_cabinet1_group.vmdl";
const string Shelf = "truenorth/models/props/cs_office/shelves_metal1.vmdl";
const string Monitor = "truenorth/models/props_lab/monitor01b.vmdl";
// Finitions de sol et plafond, puis séparation public / personnel.
Block( "Sol du hall", new( 0, 186, 0.6f ), new( 1056, 372, 1.2f ), marble );
Block( "Sol bureaux et couloir", new( 0, -360, 0.5f ), new( 1056, 720, 1 ), carpet );
Block( "Sol zone sécurisée", new( -310, -510, 1 ), new( 424, 408, 2 ), concrete );
Block( "Plafond intérieur", new( 0, -168, 147 ), new( 1056, 1080, 6 ), ceiling );
WallY( "Mur personnel", 0, 145, 528, 230 );
WallY( "Retour guichets", 0, -528, -460 );
WallY( "Mur entre accueil et personnel", 0, -20, 145 );
Door( "Accès personnel", new( 230, 0, 1 ), false, BankPrototypeMapDoor.Metal );
Plaque( "PERSONNEL", new( 230, 9, 121 ), 96 );
// Trois guichets, chacun avec passe-documents et vitre de séparation.
foreach ( float x in new[] { -380f, -240f, -100f } )
{
Block( "Meuble guichet", new( x, 0, 18 ), new( 120, 28, 36 ), wood );
Block( "Plateau guichet", new( x, 3, 37.5f ), new( 128, 34, 3 ), wood );
Block( "Vitre guichet", new( x, -5, 82 ), new( 120, 3, 60 ), glass );
Block( "Linteau guichet", new( x, 0, 128 ), new( 120, 12, 32 ), plaster );
Block( "Passe-documents", new( x, 10, 39.5f ), new( 24, 14, 1 ), navy );
Prop( "Terminal de guichet", Monitor, new( x + 30, -9, 39 ), -90 );
Prop( "Terminal CB", "models/horizon/props/terminal_cb/terminal_cb.vmdl", new( x - 30, 10, 39 ), 90 );
Prop( "Siège employé", Chair, new( x, -74, 2 ), 90, 0.8f );
int number = 3 - (int)((x + 380) / 140);
Plaque( $"GUICHET 0{number}", new( x, 9, 129 ), 112 );
}
foreach ( float x in new[] { -450f, -310f, -170f, -30f } )
Block( "Montant guichets", new( x, 0, 72 ), new( 20, 16, 144 ), plaster );
// File d'attente basse : aucun obstacle sur l'axe de l'entrée.
foreach ( float x in new[] { -460f, -260f, -60f } )
{
Block( "Potelet attente", new( x, 125, 18 ), new( 3, 3, 36 ), navy );
Block( "Pied potelet", new( x, 125, 2 ), new( 16, 16, 4 ), navy );
}
Block( "Sangle attente", new( -260, 125, 32 ), new( 400, 2, 3 ), navy );
Prop( "Sièges attente", "truenorth/models/props_interiors/chairs_airport.vmdl", new( -350, 283, 2 ), 180 );
Prop( "Table attente", "truenorth/models/testmodels/coffee_table_long.vmdl", new( -350, 224, 2 ), 0, 0.65f );
Plant( new( -478, 315, 2 ) ); Plant( new( 155, 322, 2 ) );
Prop( "Corbeille hall", "truenorth/models/props_interiors/trashcan01.vmdl", new( -490, 200, 2 ), 0, 0.8f );
// Le panneau ATM fait face au hall ; modèles et interaction HZ BANK existants.
foreach ( float y in new[] { 270f, 338f } )
{
var atm = Prop( "Distributeur HZ BANK", Atm.ModelPath, new( 487, y, 2 ), 180 );
var city = atm.Components.Create<CityObject>(); city.Kind = CityKinds.Atm;
city.ModelSource = Atm.ModelPath; city.Label = "Distributeur HZ BANK · prototype";
}
Plaque( "ESPACE LIBRE-SERVICE", new( 517, 304, 105 ), 128, 0 );
// Bureau conseil accessible depuis le hall, vitré pour conserver la lumière.
WallX( "Cloison conseil", 300, 8, 220, 128, glass );
Door( "Bureau conseil", new( 300, 128, 2 ), true, BankPrototypeMapDoor.Window );
WallY( "Façade bureau conseil", 220, 300, 528, null, glass );
Block( "Sol bureau conseil", new( 415, 110, 1.2f ), new( 218, 208, 1 ), wood );
Prop( "Bureau conseiller", Desk, new( 423, 78, 2 ), 90 );
Prop( "Siège conseiller", Chair, new( 425, 31, 2 ), 90, 0.8f );
Prop( "Siège client", Chair, new( 425, 143, 2 ), -90, 0.8f );
Prop( "Écran conseiller", Monitor, new( 425, 65, 41 ), -90 );
Plaque( "CONSEIL PRIVÉ", new( 414, 228, 123 ), 172 );
// Couloir central, comptage à gauche ; direction et personnel à droite.
WallX( "Séparation comptage", -80, -300, 0, -170 );
Door( "Salle de comptage", new( -80, -170, 2 ), true, BankPrototypeMapDoor.Metal, 95 );
WallY( "Mur comptage / coffre", -300, -528, -80, null, concrete );
WallX( "Mur direction", 300, -360, 0, -180 );
Door( "Bureau direction", new( 300, -180, 2 ), true );
WallX( "Mur personnel", 300, -720, -360, -520 );
Door( "Archives et personnel", new( 300, -520, 2 ), true );
WallY( "Séparation bureaux", -360, 300, 528 );
Prop( "Table de comptage", Desk, new( -320, -155, 2 ), 90 );
Prop( "Écran comptage", Monitor, new( -320, -155, 41 ), 90 );
for ( int i = 0; i < 4; i++ )
Prop( "Liasse décorative comptage", "models/horizon/billets/liasse.vmdl", new( -350 + i * 16, -130, 41 ), i * 12 );
Prop( "Siège comptage", Chair, new( -320, -215, 2 ), 90, 0.8f );
foreach ( float x in new[] { -475f, -405f } ) Prop( "Étagère de comptage", Shelf, new( x, -260, 2 ), 90 );
Prop( "Registre des opérations", Cabinet, new( -480, -80, 2 ) );
Prop( "Bureau direction", Desk, new( 432, -185, 2 ), 90, 1.1f );
Prop( "Écran direction", Monitor, new( 432, -185, 45 ), -90 );
Prop( "Fauteuil direction", Chair, new( 432, -250, 2 ), 90 );
foreach ( float x in new[] { 395f, 465f } ) Prop( "Siège visiteur direction", Chair, new( x, -105, 2 ), -90, 0.8f );
Prop( "Dossiers direction", Cabinet, new( 486, -290, 2 ), 180 );
Plant( new( 480, -60, 2 ) );
Prop( "Table personnel", "truenorth/models/props_interiors/dining_table_round.vmdl", new( 410, -500, 2 ), 0, 0.65f );
Prop( "Siège personnel", Chair, new( 410, -445, 2 ), -90, 0.8f );
Prop( "Réfrigérateur personnel", "truenorth/models/props_interiors/refrigerator03.vmdl", new( 480, -660, 2 ), 180 );
Block( "Meuble pause", new( 400, -665, 20 ), new( 80, 38, 40 ), wood );
Prop( "Machine à café", "truenorth/models/props_unique/coffeemachine01.vmdl", new( 400, -665, 41 ), 90, 0.7f );
foreach ( float y in new[] { -600f, -670f } ) Prop( "Archives personnel", Cabinet, new( 330, y, 2 ) );
Prop( "Poste sécurité", Desk, new( 185, -615, 2 ), 90 );
Prop( "Écran sécurité", Monitor, new( 185, -615, 41 ), -90 );
Prop( "Siège sécurité", Chair, new( 185, -675, 2 ), 90, 0.8f );
Plaque( "ZONE PERSONNEL / COFFRES", new( 55, -285, 126 ), 245 );
// Chambre forte : enveloppe opaque, porte blindée et mécanisme issus de la vraie carte.
WallX( "Mur blindé", -80, -720, -300, -505, concrete, 96, 126 );
// Ouverture 96 × 126 : la porte d'origine mesure 96 × 112.
Block( "Seuil blindé", new( -80, -505, 5 ), new( 28, 96 / BankPrototype.PlanScale, 10 ), concrete );
var vault = new GameObject( root, true, "Porte blindée du coffre" );
vault.LocalPosition = BankPrototype.Point( new Vector3( -80, -505, 66 ) ) + Vector3.Left * 48;
vault.LocalRotation = Rotation.FromYaw( -90 );
void VaultPart( string path, string name )
{
var go = new GameObject( vault, true, name ); var model = Model.Load( path );
go.Components.Create<ModelRenderer>().Model = model;
var c = go.Components.Create<BoxCollider>(); c.Center = model.Bounds.Center; c.Scale = model.Bounds.Size; c.Static = false;
}
VaultPart( "truenorth/monde/bloc_122.vmdl", "Battant blindé d'origine" );
VaultPart( "truenorth/monde/bloc_123.vmdl", "Verrous de coffre d'origine" );
var wheel = Prop( "Volant de coffre", "truenorth/models/props_borealis/door_wheel001a.vmdl", new( -108, -505, 66 ), 180, 1, false );
wheel.Parent = vault; wheel.LocalPosition = new( 48, 26, 0 ); wheel.LocalRotation = Rotation.FromYaw( 90 );
vault.Components.Create<Rigidbody>().MotionEnabled = false;
var vaultDoor = vault.Components.Create<BankPrototypeDoor>(); vaultDoor.Group = "coffre"; vaultDoor.TurnDegrees = -110;
Plaque( "CHAMBRE FORTE", new( -54, -505, 135 ), 175, 180 );
// Deux espaces dans le coffre : réserve et compartiments individuels.
WallY( "Séparation coffres clients", -580, -522, -100, -305, concrete );
Door( "Coffres individuels", new( -305, -580, 2 ), false, BankPrototypeMapDoor.Metal );
foreach ( float y in new[] { -375f, -460f } ) Prop( "Rayonnage réserve", Shelf, new( -475, y, 2 ) );
Block( "Table réserve", new( -290, -420, 20 ), new( 100, 65, 40 ), navy );
for ( int i = 0; i < 8; i++ )
Prop( "Réserve de billets décorative", "models/horizon/billets/liasse.vmdl", new( -325 + (i % 4) * 20, -435 + (i / 4) * 22, 40 ), 0 );
Block( "Bâti coffres individuels", new( -310, -696, 60 ), new( 350, 20, 112 ), concrete );
for ( int col = 0; col < 5; col++ )
{
for ( int row = 0; row < 4; row++ )
{
float x = -446 + col * 68; float z = 22 + row * 26;
Block( "Compartiment individuel", new( x, -682, z ), new( 64, 6, 23 ), navy );
Block( "Serrure de compartiment", new( x + 22, -678, z ), new( 3, 2, 3 ), concrete );
Label( $"{col + 1}{row + 1}", new( x - 10, -678, z ), 5 );
}
}
Plaque( "COFFRES INDIVIDUELS", new( -310, -704, 128 ), 260 );
// Sortie arrière du personnel, 144 unités d'ouverture existante.
Door( "Sortie de service", new( 0, -720, 1 ), false, BankPrototypeMapDoor.Metal );
// Luminaires réguliers dans chaque pièce et dans le hall.
foreach ( var p in new[] { new Vector3(-340,190,206), new Vector3(40,190,206), new Vector3(420,115,206),
new Vector3(-320,-145,206), new Vector3(370,-180,206), new Vector3(50,-200,206),
new Vector3(50,-540,206), new Vector3(370,-530,206), new Vector3(-310,-430,206), new Vector3(-310,-660,206) } )
{
var fixture = p.WithZ( 137 );
Block( "Panneau lumineux", fixture, new( 65, 26, 2 ), glow );
var go = new GameObject( root, true, "Éclairage de pièce" ); go.LocalPosition = BankPrototype.Point( fixture ) - Vector3.Up * 9;
var l = go.Components.Create<PointLight>(); l.Radius = 260; l.LightColor = new Color( 1, 0.94f, 0.84f ) * 3; l.Shadows = false;
}
}
}