OnInventoryNetworkUpdate
OnInventoryNetworkUpdate is called inside PlayerInventory.SendUpdatedInventoryInternal just before the player's container update is written to the network layer. Returning a non-null value aborts the send, keeping the stale state on the client.
object OnInventoryNetworkUpdate(PlayerInventory playerInventory, ItemContainer container, ProtoBuf.UpdateItemContainer updateItemContainer, PlayerInventory.Type type, PlayerInventory.NetworkInventoryMode mode)Parameters
| Parameter | Type | Description |
|---|---|---|
playerInventory | PlayerInventory | The PlayerInventory instance that is building this network update; use its playerOwner to reach the BasePlayer. |
container | ItemContainer | The ItemContainer being serialized into the outgoing message (main, belt, wear, or a separate container such as a lootable). |
updateItemContainer | ProtoBuf.UpdateItemContainer | The ProtoBuf.UpdateItemContainer message being prepared for the client, including the already-saved container list. |
type | PlayerInventory.Type | PlayerInventory.Type indicating which of the player's own containers this update covers (Main, Belt, Wear, Backpack). |
mode | PlayerInventory.NetworkInventoryMode | PlayerInventory.NetworkInventoryMode controlling how much of the container is serialized, e.g. LocalPlayer, Eyes, Slots, or LocalPlayerUpdate. |
Return value
Return null to let the update be sent as normal. Returning any non-null object short-circuits the method through the injection guard, so the prepared update is never passed on and the client keeps its previous view of the container.
How it works
The hook fires every time the server is about to push a container snapshot to a client, inside PlayerInventory.SendUpdatedInventoryInternal. That method runs for the player's own main, belt and wear containers, and also when container contents need resending after changes. The container has already been marked clean and serialized into updateItemContainer.container when the hook runs, and mode decides which slots are included (for example wear containers are only fully serialized for the local player).
Use playerInventory.playerOwner to identify the player and container to inspect the item list. Because this is a per-client network path, it fires frequently for active players and during inventory churn, so keep the body cheap and never print to console here. Declaring playerInventory more specifically is not possible since the type is exact. You can read type and mode to scope logic to a single container without scanning every slot.
Returning non-null stops the original send. That means the client keeps whatever it previously received, so the container appears frozen until a later update is allowed through. Use this only for deliberate cases such as masking a container from a viewer.
Example plugin: Block wear-container network updates for masked players
using System.Linq;
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;
namespace Oxide.Plugins
{
[Info("InventoryUpdateMask", "RustPluginGenerator", "1.0.0")]
[Description("Blocks wear-container network updates for players added to a staff-managed mask list")]
public class InventoryUpdateMask : RustPlugin
{
private const string PermUse = "inventoryupdatemask.use";
private PluginConfig config;
private HashSet<string> masked;
private class PluginConfig
{
[JsonProperty("Maximum masked players")]
public int MaxMasked = 5;
}
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>
{
["Masked"] = "Container updates for {0} are now blocked.",
["Unmasked"] = "Container updates for {0} are restored.",
["Full"] = "Mask list is full. Unmask a player first.",
["Unknown"] = "No player found for '{0}'.",
["List"] = "Masked players: {0}"
}, 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(PermUse, this);
masked = Interface.Oxide.DataFileSystem.ReadObject<HashSet<string>>(Name) ?? new HashSet<string>();
}
private void Unload()
{
Interface.Oxide.DataFileSystem.WriteObject(Name, masked);
}
[ChatCommand("inventorymask")]
private void CmdMask(BasePlayer player, string command, string[] args)
{
if (player == null || !player.IsAdmin) return;
if (!permission.UserHasPermission(player.UserIDString, PermUse)) return;
if (args == null || args.Length == 0)
{
string list = masked.Count == 0 ? "none" : string.Join(", ", masked);
SendReply(player, Msg("List", player.UserIDString, list));
return;
}
BasePlayer target = BasePlayer.FindAwakeOrSleeping(args[0]);
if (target == null)
{
SendReply(player, Msg("Unknown", player.UserIDString, args[0]));
return;
}
string id = target.UserIDString;
if (masked.Remove(id))
{
SendReply(player, Msg("Unmasked", player.UserIDString, target.displayName));
}
else
{
if (masked.Count >= config.MaxMasked)
{
SendReply(player, Msg("Full", player.UserIDString));
return;
}
masked.Add(id);
SendReply(player, Msg("Masked", player.UserIDString, target.displayName));
}
Interface.Oxide.DataFileSystem.WriteObject(Name, masked);
}
private object OnInventoryNetworkUpdate(PlayerInventory playerInventory, ItemContainer container, ProtoBuf.UpdateItemContainer updateItemContainer, PlayerInventory.Type type, PlayerInventory.NetworkInventoryMode mode)
{
if (playerInventory == null || container == null || masked == null) return null;
if (type != PlayerInventory.Type.Wear) return null;
BasePlayer owner = playerInventory.baseEntity as BasePlayer;
if (owner == null || !owner.IsConnected) return null;
if (!masked.Contains(owner.UserIDString)) return null;
return true;
}
}
}The plugin keeps a set of user IDs whose player-inventory network updates are dropped, managed with the admin-only inventorymask chat command that also requires the inventoryupdatemask.use permission. On each OnInventoryNetworkUpdate call it checks the owner from playerInventory.baseEntity; if the owner is connected and on the list, it returns a non-null value to stop the send. Only PlayerInventory.Type.Wear updates are blocked, so other container updates continue normally. Running the command with a name or Steam ID toggles the entry: an existing entry is removed and restored, otherwise the player is added up to the Maximum masked players config value. The set is saved to the InventoryUpdateMask data file when it changes and on plugin unload.
Common use cases
Mask container contents for spectators
Stop specific players from receiving updates for a given container so their client keeps showing the previous state while staff review a scene.
Audit high-frequency inventory churn
Count how often a container is reserialized per player for a short diagnostic window, keeping the logic allocation-free inside the hook.
Gate sends by inventory mode
Only allow updates for
NetworkInventoryMode.LocalPlayeron a container to keep other modes from being delivered, useful when testing custom inventory handling.
Pitfalls
Runs on a hot network path
SendUpdatedInventoryInternal fires repeatedly for every active player. Do not log, allocate lists, or iterate every item unless you have measured the cost on a populated server.
Client state goes stale, not empty
Returning non-null skips the send but does not clear the client cache. The viewer continues to see the last snapshot, so blocked updates can look like a frozen container rather than an empty one.
Container is already serialized
By the time the hook runs the container has been saved into the protobuf message and marked clean. Mutating items at this point will not be reflected in that message.
Build a plugin with OnInventoryNetworkUpdate
Describe what should happen and get a compiled plugin that uses this hook.
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 OnInventoryNetworkUpdate
Generate