From a785ad91efed55d549d986971a3e9c4edd8c4c86 Mon Sep 17 00:00:00 2001 From: wtclaude Date: Wed, 7 Oct 2026 13:41:57 -0500 Subject: [PATCH] fix(zones): an event's zone enforces its flags (RunicGatewayZones 0.2.0, runicnpc 9d) ZoneManager subscribes the hooks that enforce its flags only for the zones it had when it loaded; a zone made later through its API, which is every zone an event opens, never re-subscribes them, so an event's PvpGod or NoBuild zone enforced nothing. The helper's postfix, which already sees every zone created, updated or loaded, now has ZoneManager run its own UpdateHookSubscriptions on the next tick, before entering the players already inside. A ZoneManager without that method is warned about and reported by rgz.status; the first job still works. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY --- overlay/oxide/plugins/RunicGatewayZones.cs | 52 +++++++++++++++++++--- 1 file changed, 47 insertions(+), 5 deletions(-) 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)")}"); } } }