Merge pull request 'fix(examples): a Kits reload owed for the example kits survives an interrupted load (D329)' (#17) from fix/kits-reload-owed into edge
All checks were successful
PR Checks / plugin-checks (pull_request) Successful in -2m2s

Reviewed-on: #17
This commit is contained in:
2026-10-09 07:09:37 +00:00
2 changed files with 51 additions and 6 deletions

View File

@@ -4121,8 +4121,16 @@ namespace Oxide.Plugins
{
/// <summary>D284: set on the first load that wrote them, and never cleared.</summary>
[JsonProperty("examplesWritten")] public bool ExamplesWritten;
/// <summary>D329: the example kits written that Kits has not loaded yet. The reload is owed until it has.</summary>
[JsonProperty("kitsReloadOwed")] public List<string> KitsReloadOwed = new List<string>();
/// <summary>D329: the loads that have asked for that reload. At most three, so a kit Kits never takes cannot loop.</summary>
[JsonProperty("kitsReloadTries")] public int KitsReloadTries;
}
private const int KitsReloadTriesMax = 3;
/// <summary>Placements wait while Kits reloads with the example kits (D283 reloads RunicNPC with it).</summary>
private bool _holdForKits;
@@ -4206,8 +4214,8 @@ namespace Oxide.Plugins
/// <summary>
/// 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).
/// </summary>
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);
}
/// <summary>
/// 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.
/// </summary>
private void SettleKitsReload()
{
StateFile state = ReadData<StateFile>(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<string>();
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();
}

View File

@@ -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.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<JObject>("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)");