fix: escort end, line of sight and rockets; test(harness): stage 5 group (stage 5)

What the rigs' stage 5 runs found in the plugin:

- an escort whose charge was destroyed was never released: Unity's ==
  says a destroyed entity is null, so CheckEscort returned before it
  looked. It now tests the reference itself (D269);
- our own fight's line of sight no longer uses Rust's CanSeeTarget for an
  NPC target: its rays are unbounded, so on open ground they run past the
  target into the terrain behind it. A bounded linecast to the target's
  centre, which counts the target's own collider as seen;
- a rocket's line of sight: Rust's sensed one for a real player, ours for
  an NPC or an animal, which are never in Rust's player memory.

tools/RunicNpcTest.cs 0.5.0 adds `rnt.run s5`: 52 checks of the faction
table and its refusals, faction fights, the leash, shooting back, the alarm,
allies spared and defended (players and buildings), escort, the guard,
the tether, PVE, turrets (`ignore` through the hook, `always` on the map's
own sentries with the org lead's condition: no stock scientist or bandit
guard ever targeted), and the kit's extras used up. Also `rnt.duel`,
`rnt.kitprobe`, `rnt.rocketprobe` and `rnt.list`, for looking.

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-01 08:39:33 -05:00
parent 21d3c477dc
commit 4ea50c4fd2
2 changed files with 707 additions and 19 deletions

View File

@@ -2063,14 +2063,14 @@ namespace Oxide.Plugins
}
/// <summary>
/// Rust's <c>CanSeeTarget</c> for a player; for anything else (which Rust answers "seen" without
/// a ray), a line from its eyes that counts the target's own collider as seen.
/// A line from its eyes to the target's centre that counts the target's own collider as seen.
/// Not Rust's <c>CanSeeTarget</c>, even for a scientist or one of ours: its rays are unbounded,
/// so on open ground they run past the target into the terrain behind it, and an NPC 25 m off
/// read as unseen until ours had walked right up to it (stage 5 build). Players are Rust's
/// design's to see; this is only for what our own fight takes on.
/// </summary>
private static bool Los(RunicNpcPlayer npc, BaseCombatEntity t)
{
BasePlayer p = t as BasePlayer;
if (p != null)
return npc.CanSeeTarget(p);
RaycastHit hit;
if (!Physics.Linecast(npc.eyes.position, t.CenterPoint(), out hit, LosMask, QueryTriggerInteraction.Ignore))
return true;
@@ -2335,15 +2335,17 @@ namespace Oxide.Plugins
/// <summary>D269: whom it escorts died or left the server; it walks back to its spot.</summary>
private void CheckEscort(RunicNpcPlayer npc)
{
// Unity's == says a destroyed entity is null, which is exactly the case to catch here.
BaseEntity e = npc.Escort;
if (e == null)
if (ReferenceEquals(e, null))
return;
BasePlayer p = e as BasePlayer;
BaseCombatEntity c = e as BaseCombatEntity;
bool gone = e.IsDestroyed || (c != null && c.IsDead()) || (p != null && !p.IsNpc && !p.IsConnected);
bool destroyed = e == null || e.IsDestroyed;
BasePlayer p = destroyed ? null : e as BasePlayer;
BaseCombatEntity c = destroyed ? null : e as BaseCombatEntity;
bool gone = destroyed || (c != null && c.IsDead()) || (p != null && !p.IsNpc && !p.IsConnected);
if (!gone)
return;
string who = p != null ? p.displayName : e.ShortPrefabName;
string who = p != null ? p.displayName : destroyed ? "its charge" : e.ShortPrefabName;
npc.Escort = null;
NoteDefender(npc);
npc.RnBrain?.Navigator?.SetDestination(npc.Home, BaseNavigator.NavigationSpeed.Normal);
@@ -2491,10 +2493,17 @@ namespace Oxide.Plugins
}
}
if (k.Rockets && distance >= 15f && distance <= 80f && now >= npc.NextRocket && npc.TargetLos)
if (k.Rockets && distance >= 15f && distance <= 80f && now >= npc.NextRocket)
{
npc.NextRocket = now + 10f;
if (FireRocket(npc, target))
npc.NextRocket = now + 2f;
// A real player's line of sight is the one Rust's design shoots on; an NPC (a scientist, one of
// ours) or an animal is never in Rust's player memory, so it is ours.
BaseCombatEntity aim = target as BaseCombatEntity;
BasePlayer human = aim as BasePlayer;
bool seen = human != null && !human.IsNpc
? brain.Senses?.Memory != null && brain.Senses.Memory.IsLOS(aim)
: aim != null && Los(npc, aim);
if (seen && FireRocket(npc, target))
{
npc.KitBusyUntil = now + 1.5f;
return;
@@ -2594,6 +2603,7 @@ namespace Oxide.Plugins
ammo.UseItem(1);
launcher.GetHeldEntity()?.SignalBroadcast(BaseEntity.Signal.Attack, string.Empty);
npc.Rockets++;
npc.NextRocket = Time.time + 10f;
timer.Once(1.2f, () =>
{
if (npc != null && !npc.IsDestroyed && !npc.IsDead())