Component that places decorative decal carvings (engravings) on basalt pillars. It loads decal definitions, creates non-networked GameObjects with Decal components at precomputed Spots, and exposes tunables and a console command to toggle and adjust them.
using Sandbox;
using System;
using System.Collections.Generic;
using System.Linq;
namespace NZombies;
/// <summary>
/// BASALT'S ENGRAVINGS — carvings cut into its pillars: *"add some engravings on the vertical pillars and walls etc"* (2026-09-28). A
/// frieze of runes round each broad pillar side (three variants), a hexagonal medallion on the narrower, a column of script on the
/// slim posts; and on the tall columns a second band higher up. `Gameplay.Engravings` asks for them; basalt's config does.
///
/// ⛔ DECALS, NOT GEOMETRY: each is a `Decal` pressed into the face (height, normal, rough/metal/occlusion, parallax), so the map's
/// geometry, collision and navmesh are untouched and nothing new casts a shadow. The carvings are `Tools/basalt_engravings.py`'s,
/// and so is where they go — read from the map's own BSP, every exposed concrete side of a pillar, a person's height above what it
/// stands on (or above the lava it rises from): `BasaltEngravings.Spots.cs`, generated.
/// ⛔ NOT THE EASTER EGG'S GLYPHS: an alphabet of their own, so no carving reads as a clue to the shield's code.
/// ⚠️ PERMANENT, NOT TRANSIENT: `Transient` off, no lifetime, and never in `BulletDecals`' queue, which clears as rounds end.
/// ⚠️ SHALLOW AND ANGLED: a few units deep and fading off faces that turn away (`AttenuationAngle`), so a carving on one hex side
/// does not smear round the 120° corner onto the next.
/// ⚠️ LOCAL, BUILT AS A CONFIG IS SHOWN ON BASALT (`NZGame.ShowConfig`), and nothing sent — every machine lays its own.
/// ⚠️ THE TUNABLES ARE NULLABLE-BACKED (INSTRUCTIONS §1); the table of designs is a property that builds it.
/// </summary>
public sealed partial class BasaltEngravings : Component
{
public static BasaltEngravings Instance { get; private set; }
protected override void OnAwake() => Instance = this;
protected override void OnDestroy() { if ( Instance == this ) Instance = null; }
public static BasaltEngravings Ensure( Scene scene = null )
{
if ( Instance.IsValid() ) return Instance;
scene ??= Game.ActiveScene;
if ( !scene.IsValid() ) return null;
var go = scene.CreateObject();
go.Name = "Basalt Engravings";
go.Flags |= GameObjectFlags.NotSaved;
return go.Components.Create<BasaltEngravings>();
}
/// <summary>The carvings, index for index with the generator's designs: three friezes, the medallion, the script.</summary>
static string[] Designs => new[]
{
"decals/nz/basalt/frieze_a.decal",
"decals/nz/basalt/frieze_b.decal",
"decals/nz/basalt/frieze_c.decal",
"decals/nz/basalt/medallion.decal",
"decals/nz/basalt/script.decal",
};
/// <summary>`nz_engravings 0` takes them all away (this session).</summary>
public static bool On
{
get => _on ?? true;
set => _on = value;
}
static bool? _on;
/// <summary>How deep a carving reaches into its face, in units.</summary>
public static float Depth
{
get => _depth ?? 6f;
set => _depth = value;
}
static float? _depth;
/// <summary>
/// A carving's turn about the face, in degrees. ⚠️ 0 IS "UPRIGHT" AS FAR AS THE CODE CAN TELL: the decal looks into the face with
/// the world's up as its own; if the friezes come out standing on end, `nz_engravings turn 90` turns every one at once.
/// </summary>
public static float Turn
{
get => _turn ?? 0f;
set => _turn = value;
}
static float? _turn;
/// <summary>How strong the carving's relief is, times the decal's own parallax.</summary>
public static float Parallax
{
get => _parallax ?? 1f;
set => _parallax = value;
}
static float? _parallax;
readonly List<GameObject> _built = new();
/// <summary>How many carvings stand.</summary>
public int Built => _built.Count( g => g.IsValid() );
/// <summary>How many the map's placements hold.</summary>
public static int Planned => Spots.Length;
/// <summary>Every carving laid again: gone off basalt, or when the map asks for none.</summary>
public void Rebuild()
{
foreach ( var g in _built ) if ( g.IsValid() ) g.Destroy();
_built.Clear();
if ( !On || !HexPlatforms.OnBasalt || !(ActiveConfig.Current?.Gameplay?.Engravings ?? false) ) return;
var defs = Designs.Select( p => ResourceLibrary.Get<DecalDefinition>( p ) ).ToArray();
if ( defs.All( d => d is null ) )
{
Log.Warning( "[nz-engrave] none of the carvings loaded (decals/nz/basalt/*.decal) — has the editor compiled them?" );
return;
}
var missing = 0;
foreach ( var (at, normal, design) in Spots )
{
var def = design >= 0 && design < defs.Length ? defs[design] : null;
if ( def is null ) { missing++; continue; }
var go = Scene.CreateObject();
go.Name = "Engraving";
go.Flags |= GameObjectFlags.NotSaved;
go.NetworkMode = NetworkMode.Never; // ⛔ THIS MACHINE'S OWN — out of a joiner's snapshot, where it would stand frozen (NZNetListener)
go.SetParent( GameObject );
// ⚠️ A HAIR OUT OF THE FACE, LOOKING INTO IT, the world's up its own — `BulletDecals`' `LookAt( -normal )`, with an up
go.WorldPosition = at + normal * 2f;
go.WorldRotation = Rotation.LookAt( -normal, Vector3.Up );
var d = go.Components.Create<Decal>();
d.Decals = new List<DecalDefinition> { def };
d.Transient = false;
d.Looped = false;
d.Depth = Depth;
d.AttenuationAngle = 0.5f;
d.Size = Vector2.One;
d.Rotation = Turn;
d.Parallax = Parallax;
_built.Add( go );
}
Log.Info( $"[nz-engrave] {Built} carving(s) on basalt's pillars{(missing > 0 ? $" ({missing} of a design that did not load)" : "")}" );
}
/// <summary>
/// `nz_engravings [0|1 | depth n | turn degrees | parallax n | near]` — basalt's carvings. `0` takes them away and `1` lays them
/// again (this session); `depth`, `turn` and `parallax` retune them all and lay them again; `near` names the five nearest you.
/// Bare: how many stand.
/// </summary>
[ConCmd( "nz_engravings" )]
public static void Cmd( string what = "", float value = float.NaN )
{
var m = Ensure();
if ( !m.IsValid() ) { Log.Warning( "[nz-engrave] no scene" ); return; }
switch ( what.Trim().ToLowerInvariant() )
{
case "0": On = false; break;
case "1": On = true; break;
case "depth" when !float.IsNaN( value ): Depth = Math.Clamp( value, 0.5f, 64f ); break;
case "turn" when !float.IsNaN( value ): Turn = value; break;
case "parallax" when !float.IsNaN( value ): Parallax = Math.Clamp( value, 0f, 4f ); break;
case "near":
var me = NZPlayer.Local;
if ( !me.IsValid() ) { Log.Warning( "[nz-engrave] no player" ); return; }
foreach ( var s in Spots.OrderBy( s => s.At.Distance( me.WorldPosition ) ).Take( 5 ) )
Log.Info( $"[nz-engrave] {Designs[s.Design]} at {s.At:0} facing {s.Normal:0.##}, {s.At.Distance( me.WorldPosition ):0}u off" );
return;
case "":
break;
default:
Log.Warning( "[nz-engrave] nz_engravings [0|1 | depth n | turn degrees | parallax n | near]" );
return;
}
if ( what.Trim().Length > 0 ) m.Rebuild();
Log.Info( $"[nz-engrave] {(On ? "on" : "OFF")} · {m.Built} of {Planned} carving(s) standing · depth {Depth:0.#}, turn {Turn:0}°,"
+ $" parallax x{Parallax:0.##} · the map asks for them: {ActiveConfig.Current?.Gameplay?.Engravings ?? false}" );
}
}