feat(drill): the staging drill, the week before each forced wipe (stage 9c, D307-D309, D317-D319)
A Gitea job that checks each new Rust staging build in the seven days before the monthly forced wipe: - tools/drill/static.sh: staging's managed assemblies (DepotDownloader) and Oxide's staging build; whether Oxide has caught up (fieldlist --oxide-behind), the swap's field list compared, and the plugin compiled. - tools/drill/drill.js: window (D318), gate (one check per depot manifest), live (rust-staging runs rnt.run all; rust-carbon is stopped and restarted when both 6000-map rigs run, D309) and report (one issue per build, state on the drill branch; a build waits up to three days for Oxide, D317). - tools/drill/rig-start.sh: the rig's startup; installs the branch and Oxide's build for it, and refuses to start a mismatched pair. - RunicNpcTest 0.7.2: stage 5's five fights look for a field with room for their 280 m line (D319), and the spawn check names what did not spawn. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
This commit is contained in:
@@ -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.0")]
|
||||
[Info("RunicNpcTest", "Runic Gateway", "0.7.2")]
|
||||
[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
|
||||
{
|
||||
@@ -147,11 +147,17 @@ namespace Oxide.Plugins
|
||||
private static MonumentInfo[] Monuments() => _monuments ?? (_monuments = UnityEngine.Object.FindObjectsOfType<MonumentInfo>());
|
||||
|
||||
/// <summary>A flat, dry spot on the navmesh far from every monument; fixed seed, so reruns land in the same field.</summary>
|
||||
private static bool FindField(float clear, int seed, out Vector3 at)
|
||||
/// <summary>
|
||||
/// An open field on the navmesh. With <paramref name="line"/>, the points every <c>line / 2</c> m out to
|
||||
/// ±<paramref name="line"/> along x, and 25 m past each, must be on it too: stage 5's five fights stand
|
||||
/// that far apart. On a 6000 map the first field had room; on the staging drill's maps it is rare (D319).
|
||||
/// </summary>
|
||||
private static bool FindField(float clear, int seed, out Vector3 at, float line = 0f)
|
||||
{
|
||||
var rng = new System.Random(seed);
|
||||
float half = World.Size / 2f - 300f;
|
||||
for (int i = 0; i < 4000; i++)
|
||||
// The line is rare on a small map: 5 samples in 20000 have it on the drill's 3500 map (D319).
|
||||
for (int i = 0; i < (line > 0f ? 20000 : 4000); i++)
|
||||
{
|
||||
var p = new Vector3((float)(rng.NextDouble() * 2 - 1) * half, 0f, (float)(rng.NextDouble() * 2 - 1) * half);
|
||||
p.y = TerrainMeta.HeightMap.GetHeight(p);
|
||||
@@ -168,6 +174,16 @@ namespace Oxide.Plugins
|
||||
open = SampleNavmesh(hit.position + new Vector3(Mathf.Cos(a), 0f, Mathf.Sin(a)) * 35f, 2f, out h2);
|
||||
}
|
||||
|
||||
for (int k = -2; k <= 2 && open && line > 0f; k++)
|
||||
{
|
||||
Vector3 q = hit.position + new Vector3(k * line / 2f, 0f, 0f);
|
||||
Vector3 r = q + new Vector3(0f, 0f, 25f);
|
||||
q.y = TerrainMeta.HeightMap.GetHeight(q);
|
||||
r.y = TerrainMeta.HeightMap.GetHeight(r);
|
||||
NavMeshHit h2;
|
||||
open = SampleNavmesh(q, 3f, out h2) && SampleNavmesh(r, 3f, out h2);
|
||||
}
|
||||
|
||||
if (!open) continue;
|
||||
at = hit.position;
|
||||
return true;
|
||||
@@ -1710,7 +1726,7 @@ namespace Oxide.Plugins
|
||||
private IEnumerator Stage5()
|
||||
{
|
||||
Vector3 f;
|
||||
if (!FindField(300f, 2025, out f))
|
||||
if (!FindField(300f, 2025, out f, 140f))
|
||||
{
|
||||
Check("s5.setup.field", false, "no second open field on the navmesh");
|
||||
yield break;
|
||||
@@ -1755,7 +1771,13 @@ namespace Oxide.Plugins
|
||||
ScientistNPC far = SpawnScientist(d + new Vector3(0, 0, 25));
|
||||
BasePlayer victim = Spawn(e, "t_roam");
|
||||
BasePlayer bully = Spawn(OnMesh(e + new Vector3(0, 0, 15)), "t_bully");
|
||||
Check("s5.fight.spawned", red && blue && hunter && prey && neutral && bystander && leashed && far && victim && bully, "");
|
||||
var fighters = new Dictionary<string, BaseEntity>
|
||||
{
|
||||
{ "red", red }, { "blue", blue }, { "hunter", hunter }, { "prey", prey }, { "neutral", neutral },
|
||||
{ "bystander", bystander }, { "leashed", leashed }, { "far", far }, { "victim", victim }, { "bully", bully },
|
||||
};
|
||||
string[] unspawned = fighters.Where(kv => kv.Value == null).Select(kv => kv.Key).ToArray();
|
||||
Check("s5.fight.spawned", unspawned.Length == 0, unspawned.Length == 0 ? "" : $"not spawned: {string.Join(", ", unspawned)} (field {f}, map {World.Size})");
|
||||
if (!(red && blue && hunter && prey && neutral && bystander && leashed && far && victim && bully)) yield break;
|
||||
|
||||
float farHealth = far.health, bystanderHealth = bystander.health, preyHealth = prey.health;
|
||||
|
||||
Reference in New Issue
Block a user