chore(release): cut edge over to main — protocols 3–12 and the first release (rust phase 18, D145) #14

Merged
whitlocktech merged 29 commits from edge into main 2026-09-26 00:44:47 -05:00
Showing only changes of commit 0a7ad570f7 - Show all commits

View File

@@ -981,6 +981,10 @@ namespace Oxide.Plugins
hello["bootId"] = _bootId;
hello["plugin"] = Version.ToString();
// The website compares boot ids to decide when to reconcile the events ledger, and
// must not do it until the world it would be asking about exists (§28.6).
hello["worldReady"] = _worldReady;
EmitFrame(hello);
}
@@ -1300,6 +1304,7 @@ namespace Oxide.Plugins
// Only now is the wipe known, so only now can what an earlier map owned be dropped;
// and this hook also fires on a hotload, which is exactly when ZoneManager has just
// erased every zone the previous load of this plugin owned (D92).
_worldReady = true;
DropWorldFromOtherWipes();
RecreateZones("load");
@@ -1309,6 +1314,11 @@ namespace Oxide.Plugins
if (_cadence == null)
_cadence = timer.Every(BoardCadenceSeconds, Cadence);
// Say the world is ready now rather than at the next tick: the website holds its
// reconcile until it hears this.
if (_connected)
SendHello();
Dictionary<string, object> frame = Frame("server.initialized", "event");
frame["initial"] = initial;
EmitFrame(frame);
@@ -5977,6 +5987,22 @@ namespace Oxide.Plugins
/// <summary>A counter for zone ids, so two zones one run opens in one tick never collide.</summary>
private int _zoneSerial;
/// <summary>
/// Whether the world is loaded for THIS load of the plugin — set at
/// <c>OnServerInitialized</c>, which fires at the end of a real start and at once on a
/// hotload. Until then every world command is refused <c>not-ready</c>.
///
/// <para>
/// Found by the phase 13a walk, and it is the worst kind of wrong: the link starts in
/// <c>Init</c>, BEFORE the save loads, so a website that saw the new boot id asked
/// <c>world.owned</c> into an empty world — and the plugin, finding no entity behind
/// any net id, pruned three live crates from its own registry. They stayed in the
/// world owned by nothing, and a revert would have refused them. An empty world at
/// boot is not an answer; it is the question arriving too early.
/// </para>
/// </summary>
private bool _worldReady;
private string _lastWorldSummary = "none yet";
private Timer _worldTimer;
@@ -6179,8 +6205,22 @@ namespace Oxide.Plugins
return true;
}
/// <summary>Main thread. Refuses a world command until the world is loaded (see <c>_worldReady</c>).</summary>
private bool WorldReady(string reqId)
{
if (_worldReady)
return true;
EmitWorldError(reqId, "not-ready",
"the server is still loading its world; ask again once it has started");
return false;
}
private bool WorldWritesAllowed(string reqId)
{
if (!WorldReady(reqId))
return false;
if (_config != null && _config.EventsEnabled)
return true;
@@ -6219,6 +6259,9 @@ namespace Oxide.Plugins
private void HandleWorldMonuments(string reqId)
{
if (!WorldReady(reqId))
return;
var rows = new List<object>();
foreach (KeyValuePair<string, List<MonumentInfo>> kv in MonumentGroups())
@@ -6414,6 +6457,11 @@ namespace Oxide.Plugins
/// </summary>
private void RecreateZones(string why)
{
// At a real start ZoneManager loads before the world does and initialises its own
// zones at OnTerrainInitialized; ours wait for OnServerInitialized, which calls here.
if (!_worldReady)
return;
Plugin zm = ZoneManagerPlugin();
if (zm == null)
@@ -6593,6 +6641,11 @@ namespace Oxide.Plugins
/// </summary>
private void HandleWorldRevert(Dictionary<string, object> cmd, string reqId)
{
// Before the world loads, every entity looks gone, and a revert would forget things
// that are about to exist again.
if (!WorldReady(reqId))
return;
string runId = StringField(cmd, "runId");
string key = StringField(cmd, "key");
var ids = new List<string>(Strings(cmd, "ids"));
@@ -6693,6 +6746,9 @@ namespace Oxide.Plugins
/// </summary>
private void HandleWorldOwned(Dictionary<string, object> cmd, string reqId)
{
if (!WorldReady(reqId))
return;
string runId = StringField(cmd, "runId");
RecreateZones("owned");