diff --git a/overlay/oxide/plugins/RunicGatewayZones.cs b/overlay/oxide/plugins/RunicGatewayZones.cs index 33c2ecb..ed28130 100644 --- a/overlay/oxide/plugins/RunicGatewayZones.cs +++ b/overlay/oxide/plugins/RunicGatewayZones.cs @@ -1,7 +1,7 @@ // Runic Gateway Zones — a helper for ZoneManager, shipped beside the Runic Gateway bridge. // Copyright (C) Runic Gateway contributors. GPL-3.0-or-later. // -// ONE JOB (docs/modules/rust/PLAN_FIXES.md F12, D168, D181, D182): +// ITS FIRST JOB (docs/modules/rust/PLAN_FIXES.md F12, D168, D181, D182): // // ZoneManager counts a player as inside a zone only when the zone's trigger fires on ENTRY. A // zone created, updated or loaded around somebody who is already standing there never fires it, @@ -27,6 +27,17 @@ // * **It fixes every zone on the server**, not only the bridge's. That is deliberate: the fault is // ZoneManager's, and it is the same fault for an arena another plugin made. // +// A SECOND JOB (RunicNPC stage 9d walk, the org lead's call, 2026-10-07): +// +// ZoneManager subscribes the hooks that enforce its flags (`OnEntityTakeDamage` for PvpGod and the +// other damage flags, `OnEntityBuilt` for NoBuild, and so on) only for the flags some zone had when +// ZoneManager loaded, or when a flag is toggled by command. A zone made later through its API — +// which is every zone an event opens — never re-subscribes them, so on a server with no such zone +// already, an event's PvpGod or NoBuild zone enforces nothing: the 9d walk shot a player dead and +// built a foundation inside one. The same postfix that sees every zone made therefore also has +// ZoneManager run its own `UpdateHookSubscriptions` on the next tick. ZoneManager decides what to +// subscribe; this only asks it to look. +// // It is optional. The bridge never depends on it to load: without it — deleted, or loaded but // unable to find what it patches after a ZoneManager update — the bridge scores the zones it made // by `IsPositionInZone` instead (D182), and reports this helper's state at hello so the site can @@ -41,8 +52,8 @@ using UnityEngine; namespace Oxide.Plugins { - [Info("Runic Gateway Zones", "Runic Gateway", "0.1.0")] - [Description("Helper for Runic Gateway: ZoneManager counts players already inside a zone when it is created, updated or loaded.")] + [Info("Runic Gateway Zones", "Runic Gateway", "0.2.0")] + [Description("Helper for Runic Gateway: ZoneManager counts players already inside a zone when it is created, updated or loaded, and enforces a new zone's flags.")] public class RunicGatewayZones : RustPlugin { private const string HarmonyId = "runicgateway.zones"; @@ -61,6 +72,9 @@ namespace Oxide.Plugins private FieldInfo _definition; private PropertyInfo _id; + /// ZoneManager's private UpdateHookSubscriptions, or null when this ZoneManager has none (the second job is then off). + private MethodInfo _updateHooks; + /// /// What the bridge reads through : patched, unsupported /// (ZoneManager is here but not the shape this helper patches), or no-zonemanager. @@ -134,6 +148,7 @@ namespace Oxide.Plugins MethodInfo initialize = zoneType?.GetMethod("InitializeZone", any); _getZone = zmType.GetMethod("GetZoneByID", any, null, new[] { typeof(string) }, null); + _updateHooks = zmType.GetMethod("UpdateHookSubscriptions", any, null, Type.EmptyTypes, null); _definition = zoneType?.GetField("definition", any); _id = _definition?.FieldType.GetProperty("Id", any); _enter = zoneType == null @@ -169,10 +184,14 @@ namespace Oxide.Plugins _state = "patched"; _reason = null; - Puts($"patched ZoneManager {_zoneManager.Version}: a zone takes in the players already inside it"); + Puts($"patched ZoneManager {_zoneManager.Version}: a zone takes in the players already inside it" + + (_updateHooks != null ? ", and a new zone's flags are enforced" : "")); + if (_updateHooks == null) + PrintWarning($"this ZoneManager ({_zoneManager.Version}) has no UpdateHookSubscriptions: a zone made after it loaded may not enforce its flags until ZoneManager reloads"); // The zones ZoneManager loaded before this helper did were initialised unwatched — // which, at a server start, is all of them. Sweep them once now. + RefreshHooks(); SweepAll(); } @@ -258,10 +277,32 @@ namespace Oxide.Plugins var batch = new List>(_pending); _pending.Clear(); + // The second job first: the hooks that enforce the new zones' flags, then the players. + RefreshHooks(); + foreach (KeyValuePair entry in batch) Sweep(entry.Key, entry.Value); } + /// + /// Main thread. Has ZoneManager subscribe the hooks its zones' flags now need. ZoneManager + /// only adds subscriptions here, and subscribing a hook twice is a no-op. + /// + private void RefreshHooks() + { + if (_updateHooks == null || _zoneManager == null) + return; + + try + { + _updateHooks.Invoke(_zoneManager, null); + } + catch (Exception ex) + { + PrintWarning($"could not have ZoneManager refresh its hooks: {(ex.InnerException ?? ex).Message}"); + } + } + private void SweepAll() { var ids = _zoneManager.Call("GetZoneIDs") as string[]; @@ -328,7 +369,8 @@ namespace Oxide.Plugins if (arg.Connection != null && !arg.IsAdmin) return; - arg.ReplyWith($"Runic Gateway Zones {Version}: {_state}{(_reason != null ? $" ({_reason})" : "")}"); + arg.ReplyWith($"Runic Gateway Zones {Version}: {_state}{(_reason != null ? $" ({_reason})" : "")}" + + $"; new zones' flags {(_updateHooks != null ? "enforced" : "NOT refreshed (no UpdateHookSubscriptions)")}"); } } }