chore(release): RunicNPC cutover for the 2026-11-05 wipe, Rust 547616323393184824 #29

Merged
whitlocktech merged 13 commits from edge into main 2026-11-06 08:13:38 -06:00
Showing only changes of commit df2a258f64 - Show all commits

View File

@@ -45,7 +45,7 @@ namespace Oxide.Plugins
/// a made-up user id (11400000001 and up), so <c>IsNpc</c> is false. They take no damage.
/// </para>
/// </summary>
[Info("RunicNpcTest", "Runic Gateway", "0.7.4")]
[Info("RunicNpcTest", "Runic Gateway", "0.7.5")]
[Description("RunicNPC stage 2 to 7 tests, the stage 6 spike and stage 9's cost check. A developer tool: never ship it.")]
internal class RunicNpcTest : RustPlugin
{
@@ -1753,6 +1753,31 @@ namespace Oxide.Plugins
return false;
}
/// <summary>
/// Where a stage 5 fight's second NPC stands: <paramref name="dist"/> m from the first, on the navmesh,
/// with a clear line between their eyes. North first, as the fights were laid out; then the other seven
/// ways round. On the drill's 3500 map a cliff rock stood between the victim and the bully north of it,
/// so neither ever saw the other and s5.fight.shootBack failed on every run (issue #20).
/// </summary>
private Vector3 Partner(Vector3 at, float dist)
{
for (int k = 0; k < 8; k++)
{
float angle = k * Mathf.PI / 4f;
Vector3 p = OnMesh(at + new Vector3(Mathf.Sin(angle), 0f, Mathf.Cos(angle)) * dist);
NavMeshHit hit;
if (!SampleNavmesh(p, 1f, out hit)) continue;
// Each one's eyes to the other's centre, both ways, as RunicNPC's own line of sight runs.
if (Physics.Linecast(at + Vector3.up * 1.6f, p + Vector3.up * 0.9f, 1218519041, QueryTriggerInteraction.Ignore) ||
Physics.Linecast(p + Vector3.up * 1.6f, at + Vector3.up * 0.9f, 1218519041, QueryTriggerInteraction.Ignore)) continue;
if (k > 0) Note($"s5: the partner {dist:0} m from {at} stands {k * 45}° round from north (no clear line north)");
return p;
}
Note($"s5: no clear line {dist:0} m round {at}; the partner stands north as before");
return OnMesh(at + new Vector3(0, 0, dist));
}
private int HorrorLeft() => BaseNetworkable.serverEntities.OfType<BaseCombatEntity>().Count(c => !c.IsDestroyed && c.faction == BaseCombatEntity.Faction.Horror);
private IEnumerator Stage5()
@@ -1793,16 +1818,18 @@ namespace Oxide.Plugins
Vector3 d = OnMesh(f + new Vector3(70, 0, 0));
Vector3 e = OnMesh(f + new Vector3(140, 0, 0));
// Each fight's second NPC where the two can see each other (issue #20), found before anyone stands there.
Vector3 aTo = Partner(a, 15f), bTo = Partner(b, 15f), cTo = Partner(c, 15f), dTo = Partner(d, 25f), eTo = Partner(e, 15f);
BasePlayer red = Spawn(a, "t_red");
BasePlayer blue = Spawn(OnMesh(a + new Vector3(0, 0, 15)), "t_blue");
BasePlayer blue = Spawn(aTo, "t_blue");
BasePlayer hunter = Spawn(b, "t_hunter");
ScientistNPC prey = SpawnScientist(b + new Vector3(0, 0, 15));
ScientistNPC prey = SpawnScientist(bTo);
BasePlayer neutral = Spawn(c, "t_roam");
ScientistNPC bystander = SpawnScientist(c + new Vector3(0, 0, 15));
ScientistNPC bystander = SpawnScientist(cTo);
BasePlayer leashed = Spawn(d, "t_leashed");
ScientistNPC far = SpawnScientist(d + new Vector3(0, 0, 25));
ScientistNPC far = SpawnScientist(dTo);
BasePlayer victim = Spawn(e, "t_roam");
BasePlayer bully = Spawn(OnMesh(e + new Vector3(0, 0, 15)), "t_bully");
BasePlayer bully = Spawn(eTo, "t_bully");
var fighters = new Dictionary<string, BaseEntity>
{
{ "red", red }, { "blue", blue }, { "hunter", hunter }, { "prey", prey }, { "neutral", neutral },
@@ -1823,7 +1850,16 @@ namespace Oxide.Plugins
if (factionsAt < 0f && (Gone(red) || Gone(blue))) factionsAt = t;
if (scientistAt < 0f && Gone(prey)) scientistAt = t;
if (!Gone(neutral) && TargetOf(Describe(neutral)) != "none") neutralFought = true;
if (!Gone(leashed) && TargetOf(Describe(leashed)) != "none") leashedFought = true;
if (!leashedFought && !Gone(leashed) && TargetOf(Describe(leashed)) != "none")
{
leashedFought = true;
// Run 49 failed here once and it has not been seen since: say how it came to fight. A target its
// own sense found is within its chase (8 m) of home; a hurt one was shot; otherwise an ally's alarm.
Dictionary<string, object> dl = Describe(leashed);
Vector3 leashHome = V(dl["home"]);
Note($"s5.fight.leash: fighting at {t} s, target {dl["target"]}, health {Convert.ToSingle(dl["health"]):0}/{Convert.ToSingle(dl["maxHealth"]):0}, " +
$"{Flat(V(dl["position"]), leashHome):0.0} m from home; the scientist {(Gone(far) ? "gone" : $"{Flat(far.transform.position, leashHome):0.0} m from its home, {far.health:0} hp")}");
}
if (!Gone(victim) && TargetOf(Describe(victim)) != "none") victimAnswered = true;
foreach (BasePlayer x in new[] { red, blue, hunter })
{