Merge pull request 'feat(bridge): every NPC an event places needs RunicNPC (runicnpc stage 9, D310)' (#27) from feat/runicnpc-stage9 into edge

Reviewed-on: #27
This commit is contained in:
2026-10-07 02:43:10 +00:00
2 changed files with 40 additions and 11 deletions

View File

@@ -86,6 +86,7 @@ requires_plugins = ["Kits", "ZoneManager"]
# two equal. The release copies it into manifest.json, and the installer's
# bundle carries the latest RunicNPC release that answers at least it (D224).
#
# RunicNPC is optional to the bridge until its stage 9: without it every npc.*
# command answers `runicnpc-missing`, and events place Rust's own scientists.
# RunicNPC is required since its stage 9 (D310): without it, or with one older
# than this, every npc.* command and every NPC an event places, Rust's own
# scientists included, is refused as `runicnpc-missing` or `runicnpc-old`.
runicnpc_api = 6

View File

@@ -9067,6 +9067,11 @@ namespace Oxide.Plugins
return;
}
// RunicNPC stage 9 (D310): Rust's own scientists need RunicNPC too, so a module that
// missed the hello cannot place NPCs on a server the site would call incomplete.
if (prefab.Kind == WorldKindNpc && !RunicNpcForWorld(reqId))
return;
int count = IntField(cmd, "count");
int max = prefab.Kind == WorldKindNpc
? (_config != null ? _config.EventsMaxNpcs : WorldHardMaxNpcs)
@@ -10365,8 +10370,8 @@ namespace Oxide.Plugins
// RunicNPC is Runic Gateway's own NPC plugin. The bridge is its only link to the site
// (runicnpc PLAN.md §4): it calls RunicNPC's API for the site's commands and turns its hooks
// into frames. RunicNPC never talks to the sidecar, so "the sidecar is a dumb forwarder"
// stays true. It is optional until RunicNPC's stage 9; without it every `npc.*` command
// answers `runicnpc-missing`, and events keep Rust's own scientists (D243).
// stays true. It is required since RunicNPC's stage 9 (D310): without it every `npc.*` command
// answers `runicnpc-missing`, and so does every NPC an event places, Rust's own included.
/// <summary>
/// The RunicNPC API these commands call: 3 added placements from a website (D249), 4 the
@@ -10443,6 +10448,33 @@ namespace Oxide.Plugins
return npc;
}
/// <summary>
/// Main thread. Whether an event may place NPCs here (stage 9, D310): RunicNPC loaded and
/// answering the API this bridge needs, for a profile and for Rust's own scientists alike.
/// Answers <c>world.error</c> with the reason when not.
/// </summary>
private bool RunicNpcForWorld(string reqId)
{
Plugin npc = RunicNpcPlugin();
if (npc == null)
{
EmitWorldError(reqId, "runicnpc-missing", "this server needs RunicNPC to place NPCs, and it is not loaded");
return false;
}
int api = RunicNpcApi();
if (api < RunicNpcApiNeeded)
{
EmitWorldError(reqId, "runicnpc-old",
$"this server needs RunicNPC to place NPCs: RunicNPC {npc.Version} answers API {api}, and this bridge needs {RunicNpcApiNeeded}");
return false;
}
return true;
}
/// <summary>
/// Main thread. <c>npc.profiles</c>: the server's profile set as RunicNPC holds it — whether
/// a site manages it, every profile, and those refused and why. The site reads this before
@@ -10919,14 +10951,10 @@ namespace Oxide.Plugins
/// </summary>
private void HandleWorldPlaceProfile(Dictionary<string, object> cmd, string reqId, string key, string runId, string profile)
{
Plugin npc = RunicNpcPlugin();
if (npc == null)
{
EmitWorldError(reqId, "runicnpc-missing", $"RunicNPC is not loaded, so there is no profile '{profile}' here");
if (!RunicNpcForWorld(reqId))
return;
}
Plugin npc = RunicNpcPlugin();
var profiles = npc.Call("RunicNpc_Profiles") as Newtonsoft.Json.Linq.JObject;
Newtonsoft.Json.Linq.JToken known = profiles != null ? profiles["profiles"]?[profile] : null;
@@ -11228,7 +11256,7 @@ namespace Oxide.Plugins
Plugin npc = RunicNpcPlugin();
arg.ReplyWith(npc == null
? "RunicNPC: not loaded. npc.* commands answer runicnpc-missing; events place Rust's own scientists."
? "RunicNPC: not loaded. npc.* commands and every NPC an event places answer runicnpc-missing (it is required)."
: $"RunicNPC {npc.Version}, API {RunicNpcApi()} (this bridge needs {RunicNpcApiNeeded}). last: {_lastNpcSummary ?? "none yet"}");
}