Console command implementation that reports the local player's world position, eye and feet traces in six directions, and describes any hit objects including scene hierarchy path, components, surface resource, and model renderer info. It helps identify invisible colliders and nearby blocking geometry.
using Sandbox;
using System;
using System.Linq;
namespace NZombies;
/// <summary>
/// Where you are standing, and WHAT IS AROUND YOU. `nz_where`.
///
/// ⛔ THE POSITION ALONE DOES NOT ANSWER "WHY CAN I NOT WALK HERE". An invisible
/// wall is a collider whose renderer draws nothing, and the coordinate of the
/// player standing next to it says nothing about which object that is. So this
/// TRACES, and names what each trace hit — the object, its components, and the
/// material of the mesh it belongs to. That turns "there is something here" into
/// a scene-hierarchy path you can select and delete.
///
/// ⚠️ SIX DIRECTIONS FROM THE EYE AND SIX FROM THE FEET. A clip brush that stops
/// you walking is usually chest-high and misses a single eye-level trace
/// entirely; a skybox brush sealing a deck is overhead. Tracing one ray finds
/// one of those and reports the other two as clear.
///
/// ⚠️ RANGE IS SHORT ON PURPOSE. 96 units finds what you are pressed against
/// rather than the far wall of the room, which is the difference between "here
/// is your invisible wall" and a list of every surface in view.
/// </summary>
public static class WhereAmI
{
static NZPlayer Me => NZPlayer.Local;
/// <summary>`nz_where [range]` — position, angles, and what surrounds you.</summary>
[ConCmd( "nz_where" )]
public static void Where( float range = 96f )
{
var p = Me;
if ( !p.IsValid() )
{
Log.Warning( "[nz-where] no local player" );
return;
}
var pos = p.WorldPosition;
Log.Info( $"[nz-where] position {pos.x:0.#} {pos.y:0.#} {pos.z:0.#}" );
Log.Info( $"[nz-where] setpos {pos.x:0.##},{pos.y:0.##},{pos.z:0.##}" );
// ⚠️ Eye height is taken from the player rather than assumed — a crouched
// or downed player traces from where they actually are.
var eye = p.Components.Get<CameraComponent>( FindMode.EverythingInSelfAndDescendants )
?.WorldPosition ?? pos + Vector3.Up * 64f;
// ⚠️ Through Rotation, not Angles — `Angles` exposes Forward but has no Left/Right.
var rot = p.EyeAngles.ToRotation();
var dirs = new (string name, Vector3 dir)[]
{
("forward", rot.Forward),
("back", rot.Backward),
("left", rot.Left),
("right", rot.Right),
("up", Vector3.Up),
("down", Vector3.Down),
};
foreach ( var (label, origin) in new[] { ("eye", eye), ("feet", pos + Vector3.Up * 8f) } )
{
Log.Info( $"[nz-where] --- from {label} ---" );
foreach ( var (name, dir) in dirs )
{
var tr = Game.ActiveScene.Trace
.Ray( origin, origin + dir.Normal * range )
.IgnoreGameObjectHierarchy( p.GameObject )
.Run();
if ( !tr.Hit )
{
Log.Info( $"[nz-where] {name,-8} clear" );
continue;
}
Describe( name, tr );
}
}
}
/// <summary>
/// One hit, named as fully as the scene can name it.
///
/// ⛔ THE HIERARCHY PATH, NOT JUST THE OBJECT NAME. A BSP import calls every
/// world chunk "Mesh" and every prop "prop_static", so the name alone
/// identifies nothing — `cs_cruise.bsp/Meshes/Mesh 37` does.
/// </summary>
static void Describe( string name, SceneTraceResult tr )
{
var go = tr.GameObject;
var path = go.IsValid() ? PathOf( go ) : "(world)";
var dist = tr.Distance;
var comps = go.IsValid()
? string.Join( ", ", go.Components.GetAll( FindMode.EverythingInSelf )
.Select( c => c.GetType().Name ).Distinct() )
: "-";
Log.Info( $"[nz-where] {name,-8} {dist,5:0}u {path}" );
Log.Info( $"[nz-where] components: {comps}" );
// ⚠️ THE SURFACE IS THE ACTUAL ANSWER WHEN THE THING IS INVISIBLE. A mesh
// wearing tools/toolsskybox or tools/toolsclip draws nothing and still
// blocks — naming the surface is what distinguishes "a wall I forgot" from
// "a Source tools brush the importer brought through solid".
if ( tr.Surface is not null )
Log.Info( $"[nz-where] surface: {tr.Surface.ResourceName}" );
var mr = go.IsValid() ? go.Components.Get<ModelRenderer>( FindMode.EverythingInSelf ) : null;
if ( mr.IsValid() )
Log.Info( $"[nz-where] model: {mr.Model?.ResourceName ?? "-"}"
+ $" enabled={mr.Enabled}" );
}
static string PathOf( GameObject go )
{
var parts = new System.Collections.Generic.List<string>();
var cur = go;
while ( cur.IsValid() && parts.Count < 12 )
{
parts.Insert( 0, cur.Name );
cur = cur.Parent;
}
return string.Join( "/", parts );
}
}