diff --git a/plugin/RunicNPC.cs b/plugin/RunicNPC.cs index 5b0de53..67b29a8 100644 --- a/plugin/RunicNPC.cs +++ b/plugin/RunicNPC.cs @@ -4121,8 +4121,16 @@ namespace Oxide.Plugins { /// D284: set on the first load that wrote them, and never cleared. [JsonProperty("examplesWritten")] public bool ExamplesWritten; + + /// D329: the example kits written that Kits has not loaded yet. The reload is owed until it has. + [JsonProperty("kitsReloadOwed")] public List KitsReloadOwed = new List(); + + /// D329: the loads that have asked for that reload. At most three, so a kit Kits never takes cannot loop. + [JsonProperty("kitsReloadTries")] public int KitsReloadTries; } + private const int KitsReloadTriesMax = 3; + /// Placements wait while Kits reloads with the example kits (D283 reloads RunicNPC with it). private bool _holdForKits; @@ -4206,8 +4214,8 @@ namespace Oxide.Plugins /// /// D284: on the first load only, the four example kits go into Kits' own data file (Kits has no /// API that creates one, D280) and the four profiles into ours, each only where its name is free. - /// The flag is set before anything is written, so this runs once whatever happens. Kits is then - /// reloaded, which reloads RunicNPC too (D283); placements wait for it. + /// The flag is set before anything is written, so this runs once whatever happens. The kits it wrote + /// are then owed a Kits reload (D329), which SettleKitsReload asks for; it reloads RunicNPC too (D283). /// private void WriteExamplesOnce() { @@ -4286,11 +4294,43 @@ namespace Oxide.Plugins if (addedKits.Count == 0) return; - // Kits reads its file only when it loads (stage 6 spike, finding 5). The reload takes RunicNPC down and - // back with it (D283); if this framework does not, OnPluginLoaded("Kits") picks the profiles up. + // Kits reads its file only when it loads (stage 6 spike, finding 5), so a reload is owed; SettleKitsReload asks for it. + state.KitsReloadOwed = addedKits; + state.KitsReloadTries = 0; + Interface.Oxide.DataFileSystem.WriteObject(DataState, state); + } + + /// + /// D329: asks Kits to reload while the example kits written are still owed, and clears the debt once Kits + /// knows them all. The reload runs on a timer, which dies with RunicNPC: in the 9d session a second load of + /// RunicNPC within that second lost it, and the examples stayed refused until Kits was reloaded by hand. + /// Owing it in the state file lets any later load (or Kits coming back) finish the job. + /// + private void SettleKitsReload() + { + StateFile state = ReadData(DataState); + if (state.KitsReloadOwed == null || state.KitsReloadOwed.Count == 0) + return; + + string command = Framework() == "carbon" ? "c.reload" : "oxide.reload"; + if (state.KitsReloadOwed.All(KitExists) || state.KitsReloadTries >= KitsReloadTriesMax) + { + if (state.KitsReloadTries >= KitsReloadTriesMax && !state.KitsReloadOwed.All(KitExists)) + PrintWarning($"Kits has still not loaded the example kits ({string.Join(", ", state.KitsReloadOwed.ToArray())}) after " + + $"{KitsReloadTriesMax} reloads. Run `{command} Kits` once."); + state.KitsReloadOwed = new List(); + state.KitsReloadTries = 0; + Interface.Oxide.DataFileSystem.WriteObject(DataState, state); + return; + } + + state.KitsReloadTries++; + Interface.Oxide.DataFileSystem.WriteObject(DataState, state); + + // The reload takes RunicNPC down and back with it (D283); if this framework does not, OnPluginLoaded("Kits") + // picks the profiles up. Placements wait for it. _holdForKits = true; timer.Once(60f, () => _holdForKits = false); - string command = Framework() == "carbon" ? "c.reload" : "oxide.reload"; timer.Once(1f, () => ConsoleSystem.Run(ConsoleSystem.Option.Server, command, "Kits")); } @@ -4475,6 +4515,7 @@ namespace Oxide.Plugins ResolveFields(); LoadData(); WriteExamplesOnce(); + SettleKitsReload(); RevalidateProfiles(); RebuildPlacements(); TrackWorld(); @@ -4526,6 +4567,7 @@ namespace Oxide.Plugins if (plugin == null || plugin.Name != "Kits" || !_ready) return; _holdForKits = false; + SettleKitsReload(); RevalidateProfiles(); TickPlacements(); } diff --git a/tools/RunicNpcTest.cs b/tools/RunicNpcTest.cs index ed7b7ba..a16a4a2 100644 --- a/tools/RunicNpcTest.cs +++ b/tools/RunicNpcTest.cs @@ -45,7 +45,7 @@ namespace Oxide.Plugins /// a made-up user id (11400000001 and up), so IsNpc is false. They take no damage. /// /// - [Info("RunicNpcTest", "Runic Gateway", "0.7.2")] + [Info("RunicNpcTest", "Runic Gateway", "0.7.3")] [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 { @@ -2422,6 +2422,9 @@ namespace Oxide.Plugins "each needs kits.runicnpc, which nobody is granted"); JObject state = Interface.Oxide.DataFileSystem.ReadObject("RunicNPC/state"); Check("s6.examples.once", state?["examplesWritten"]?.Type == JTokenType.Boolean && (bool)state["examplesWritten"], "state.json: " + state?.ToString(Formatting.None)); + // D329: once Kits knows the examples, no reload is owed (a state from before D329 has no list at all). + JArray owed = state?["kitsReloadOwed"] as JArray; + Check("s6.examples.reloadSettled", owed == null || owed.Count == 0, "kitsReloadOwed: " + (owed?.ToString(Formatting.None) ?? "absent")); JObject items = kitFile?["_kits"]?["rnpc_juggernaut"] as JObject; int itemCount = items == null ? 0 : ((JArray)items["MainItems"]).Count + ((JArray)items["WearItems"]).Count + ((JArray)items["BeltItems"]).Count; Check("s6.examples.items", itemCount == 6, $"rnpc_juggernaut carries {itemCount} of its 6 items (a short name this Rust lacks would be left out)");