Skip to content
RustPluginGenerator

OnNetworkGroupLeft

OnNetworkGroupLeft is called when a server entity drops its subscription to a network visibility group inside BaseNetworkable.OnNetworkGroupLeave.

Signature
void OnNetworkGroupLeft(BaseNetworkable entity, Network.Visibility.Group group)

Parameters

Hook parameters
ParameterTypeDescription
entityBaseNetworkableThe BaseNetworkable whose visibility group subscription is being dropped, usually a player, building block or deployable. Check IsDestroyed before acting on it.
groupNetwork.Visibility.GroupThe Network.Visibility.Group that the entity is leaving. Compare it with other known groups, or use its ID, to identify which network grid cell was unsubscribed.

Return value

The hook's return value is ignored; the original method always continues to run its occlusion unsubscribe logic. Returning anything has no effect.

How it works

The hook fires from BaseNetworkable.OnNetworkGroupLeave, which Rust calls whenever an entity stops being subscribed to a Network.Visibility.Group — the network grid cells the server uses to decide which clients receive updates. This happens as players move between cells, when entities are destroyed, and during cleanup of subscriptions. Because the method is virtual, the hook also fires for subclasses that override the leave logic without marking their own network state.

The entity parameter is the network object that lost the subscription; group is the cell it left. Both are useful mainly for telemetry: you can count how often a player, building or deployable is churning through visibility groups, or attribute the event to a specific grid position by comparing the group against nearby entities.

Declaring the first parameter as a more specific type such as BasePlayer narrows the call to players only; Oxide skips invocations where the entity is a different type. Do not treat the event as a despawn signal — the entity still exists after the subscription is removed. This hook fires very frequently on busy servers, so keep the body cheap and avoid logging per call.

Example plugin: Count network group churn per player

NetworkGroupWatch.cs
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;
using UnityEngine;

namespace Oxide.Plugins
{
    [Info("Network Group Watch", "RustPluginGenerator", "1.0.0")]
    [Description("Counts how often players leave network visibility groups and flags heavy churn")]
    public class NetworkGroupWatch : RustPlugin
    {
        private const string PermAdmin = "networkgroupwatch.admin";

        private PluginConfig config;
        private readonly Dictionary<string, int> counters = new Dictionary<string, int>();
        private Timer checkTimer;

        private class PluginConfig
        {
            [JsonProperty("Check interval in seconds")]
            public float CheckInterval = 60f;

            [JsonProperty("Leaves per interval before warning")]
            public int WarningThreshold = 40;
        }

        protected override void LoadDefaultConfig() => config = new PluginConfig();

        protected override void LoadConfig()
        {
            base.LoadConfig();
            try
            {
                config = Config.ReadObject<PluginConfig>();
                if (config == null) throw new Exception();
            }
            catch
            {
                PrintWarning("Configuration file is invalid, using defaults");
                LoadDefaultConfig();
            }
            SaveConfig();
        }

        protected override void SaveConfig() => Config.WriteObject(config);

        protected override void LoadDefaultMessages()
        {
            lang.RegisterMessages(new Dictionary<string, string>
            {
                ["Stats"] = "{0} has left {1} visibility groups.",
                ["NoData"] = "No churn recorded for that player.",
                ["NoPermission"] = "You do not have permission to use this command."
            }, this);
        }

        private string Msg(string key, string userId = null, params object[] args) =>
            string.Format(lang.GetMessage(key, this, userId), args);

        private void Init()
        {
            permission.RegisterPermission(PermAdmin, this);
        }

        private void OnServerInitialized()
        {
            checkTimer = timer.Every(config.CheckInterval, CheckCounters);
        }

        private void Unload()
        {
            checkTimer?.Destroy();
            counters.Clear();
        }

        private void OnNetworkGroupLeft(BaseNetworkable entity, Network.Visibility.Group group)
        {
            if (!(entity is BasePlayer)) return;
            var player = (BasePlayer)entity;
            if (player == null || player.IsNpc) return;

            string id = player.UserIDString;
            int count;
            counters.TryGetValue(id, out count);
            counters[id] = count + 1;
        }

        private void CheckCounters()
        {
            if (counters.Count == 0) return;

            foreach (var entry in counters)
            {
                if (entry.Value < config.WarningThreshold) continue;

                var player = BasePlayer.FindByID(ulong.Parse(entry.Key));
                string name = player != null ? player.displayName : entry.Key;
                PrintWarning($"{name} ({entry.Key}) left {entry.Value} visibility groups in the last interval");
            }

            counters.Clear();
        }

        [ConsoleCommand("ngw.stats")]
        private void CmdStats(ConsoleSystem.Arg arg)
        {
            var player = arg.Player();
            if (player != null && !permission.UserHasPermission(player.UserIDString, PermAdmin))
            {
                arg.ReplyWith(Msg("NoPermission", player.UserIDString));
                return;
            }

            string target = arg.GetString(0, player != null ? player.UserIDString : "");
            int count;
            if (string.IsNullOrEmpty(target) || !counters.TryGetValue(target, out count))
            {
                arg.ReplyWith(Msg("NoData", player?.UserIDString));
                return;
            }

            var found = BasePlayer.FindAwakeOrSleeping(target);
            string display = found != null ? found.displayName : target;
            arg.ReplyWith(Msg("Stats", player?.UserIDString, display, count));
        }
    }
}

The plugin tracks how many visibility-group leaves each player accumulates and flags players that exceed a configurable threshold within a sampling window. Only calls where entity is a BasePlayer are recorded, since Oxide skips invocations whose argument does not match the declared parameter type. Counters live in a dictionary keyed by UserIDString, and a repeating timer checks the map on a configurable interval, printing a console warning with the display name of any player over the limit, then clearing the counters. Admins can inspect totals with the ngw.stats console command, which accepts an optional player name or ID. All state stays in memory, timers are destroyed in Unload, and player-facing output goes through the language file.

Common use cases

  • Detect players moving through network cells suspiciously fast

    Count group leaves per player per minute. Unusually high churn can indicate teleport-style movement or client manipulation and is worth flagging to admins during investigations.

  • Correlate base activity with visibility churn

    When a specific group shows repeated leaves for building blocks, check whether nearby players are moving in and out of the area, useful for confirming activity around a suspect base.

  • Monitor large deployables for subscription thrash

    Narrow the parameter to a container or deployable type and record how often it leaves groups. Persistent thrash can point to entities being rebuilt or replaced repeatedly.

Pitfalls

  • Treating it as a despawn event

    The entity still exists after leaving a group and may re-enter others. Only OnEntityKill or similar signals reliably confirm removal.

  • Logging inside the hook

    Group subscriptions change constantly for every nearby player. Printing on each call will flood the console and cost server time.

  • Assuming one leave per move

    Entities can be subscribed to several groups, and occlusion toggling affects the timing of leaves. Never assume a one-to-one mapping with a player leaving a grid cell.

Build a plugin with OnNetworkGroupLeft

Describe what should happen and get a compiled plugin that uses this hook.

Prompt: Detect players moving through network cells suspiciously fast using the OnNetworkGroupLeft hook, with a config file and a permission
Generate this plugin

Edit the prompt on the next screen. 2 free plugins a month, no card.

Last updated Oct 26, 2026. Found a mistake? Email support@rustplugingenerator.com.

Build a plugin with OnNetworkGroupLeft

Generate