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)");