diff --git a/overlay.toml b/overlay.toml index 570b602..55d57c2 100644 --- a/overlay.toml +++ b/overlay.toml @@ -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 diff --git a/overlay/oxide/plugins/RunicGateway.cs b/overlay/oxide/plugins/RunicGateway.cs index 2e60acf..32b679c 100644 --- a/overlay/oxide/plugins/RunicGateway.cs +++ b/overlay/oxide/plugins/RunicGateway.cs @@ -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. /// /// The RunicNPC API these commands call: 3 added placements from a website (D249), 4 the @@ -10443,6 +10448,33 @@ namespace Oxide.Plugins return npc; } + /// + /// 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 world.error with the reason when not. + /// + 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; + } + /// /// Main thread. npc.profiles: 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 /// private void HandleWorldPlaceProfile(Dictionary 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"}"); }