feat(commands): rnpc here stands the NPC a metre in front, facing you (D325)
All checks were successful
PR Checks / plugin-checks (pull_request) Successful in 8s

At the admin's feet it spawned inside them, looking away (9d player walk). It now
stands 1 m ahead on the ground, turned to face the admin. If something is in the way
at chest height, there is no ground within 2 m below, or a walker would have no
navmesh, it falls back to the admin's own spot and the reply says so.

The harness's cmd.chat.here now checks the metre.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
This commit is contained in:
2026-10-09 00:11:41 -05:00
parent 6b15d68071
commit 246ad17c60
2 changed files with 41 additions and 3 deletions

View File

@@ -5179,6 +5179,39 @@ namespace Oxide.Plugins
// ---- where an admin stands and looks ----
/// <summary>The ground, a floor or a roof the admin is looking at, within <see cref="LookReach"/>.</summary>
/// <summary>D325: how far in front of the admin <c>rnpc here</c> stands the NPC, centre to centre.</summary>
private const float HereAhead = 1f;
/// <summary>
/// D325: the spot <see cref="HereAhead" /> in front of the admin, on the ground, facing them. False, with the
/// admin's own spot and facing, when it does not fit: something in the way at chest height, no ground within
/// <see cref="MaxDrop" /> below their feet, or, for one that walks, no navmesh there.
/// </summary>
private static bool HereSpot(BasePlayer player, bool sentry, out Vector3 spot, out float yaw)
{
Vector3 feet = player.transform.position;
Vector3 ahead = player.eyes.HeadForward();
ahead.y = 0f;
if (ahead.sqrMagnitude < 0.01f)
ahead = new Vector3(player.transform.forward.x, 0f, player.transform.forward.z);
ahead.Normalize();
Vector3 chest = feet + Vector3.up;
RaycastHit hit;
if (!Physics.SphereCast(chest, 0.3f, ahead, out hit, HereAhead, GroundMask, QueryTriggerInteraction.Ignore) &&
Physics.Raycast(chest + ahead * HereAhead, Vector3.down, out hit, 1f + MaxDrop, GroundMask, QueryTriggerInteraction.Ignore) &&
(sentry || OffMeshReason(hit.point) == null))
{
spot = hit.point;
yaw = Quaternion.LookRotation(-ahead).eulerAngles.y;
return true;
}
spot = feet;
yaw = player.eyes.rotation.eulerAngles.y;
return false;
}
private static bool LookSpot(BasePlayer player, out Vector3 spot)
{
RaycastHit hit;
@@ -5433,6 +5466,7 @@ namespace Oxide.Plugins
Vector3 spot;
float yaw;
string hereNote = null;
if (o.At.HasValue)
{
spot = o.At.Value;
@@ -5445,8 +5479,8 @@ namespace Oxide.Plugins
}
else if (here)
{
spot = c.Player.transform.position;
yaw = c.Player.eyes.rotation.eulerAngles.y;
if (!HereSpot(c.Player, p.IsSentry, out spot, out yaw))
hereNote = $"There is no room {HereAhead:0} m in front of you, so it stands where you do.";
}
else
{
@@ -5511,6 +5545,8 @@ namespace Oxide.Plugins
Movement m = movement ?? p.Movement;
c.Say($"Placed {id}: {data.Count} × '{name}' ({p.Role}{(p.IsSentry ? "" : ", " + m.Mode + (kind == "wander" ? $" {m.Radius:0} m" : ""))}), " +
$"respawn {data.Respawn:0} s, {data.RespawnMode}{(data.Tether != null ? $", inside zone {data.Tether}" : "")}. Rename it with rnpc rename {id} <name>.");
if (hereNote != null)
c.Say(hereNote);
if (built)
c.Say("This spot is on a player-built structure. If it is destroyed, the NPC falls back to the nearest navmesh.");
c.Say(CostWarning(PlannedTotal()));