OnSendModelState
OnSendModelState is called on the server just before a player's model state (position, rotation, pose, gestures) is broadcast to other clients through the OnModelState RPC. Returning a non-null value suppresses that broadcast for that update.
object OnSendModelState(BasePlayer player)Parameters
| Parameter | Type | Description |
|---|---|---|
player | BasePlayer | The BasePlayer whose model state is about to be sent to nearby clients. Verify it is connected and not an NPC before doing anything with it. |
Return value
Returning null (or letting the method finish without returning) lets the original ClientRPC run, so the model state is broadcast normally. Returning any non-null value (for example false) stops that code path, so the model state update is not sent to other clients for this call only.
How it works
This hook fires inside BasePlayer.SendModelState, after the current ModelState has been prepared (and the previous state has been copied to lastModelState), and right before the server calls ClientRPC on the OnModelState network group. The game calls SendModelState as part of its regular player networking, so expect this hook many times per second per player while networking is running.
Because the injection is guarded by !base.limitNetworking, the hook is not reached while the player's networking is limited. The player argument lets you identify who is about to be networked, which is useful for selective visibility effects such as hiding a player from others, freezing their visible pose, or gating updates while a custom state machine is active.
There is no way to change the ModelState payload from this hook; you can only allow or block the send. This is a hot path, so keep the callback cheap, avoid allocating, and do not log here.
Example plugin: Hider-mode invisibility for a permission group
using System.Linq;
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;
namespace Oxide.Plugins
{
[Info("Model State Hider", "RustPluginGenerator", "1.0.0")]
[Description("Lets permitted players hide their model state updates from other clients.")]
public class ModelStateHider : RustPlugin
{
private const string PermHide = "modelstatehider.hide";
private PluginConfig config;
private readonly HashSet<string> hidden = new HashSet<string>();
private class PluginConfig
{
[JsonProperty("Hide permission")]
public string HidePermission = PermHide;
}
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>
{
["NoPermission"] = "You do not have permission to use this command.",
["NowHidden"] = "Your model state updates are now hidden from other clients.",
["NowVisible"] = "Your model state updates are now visible again.",
["Usage"] = "Usage: /mstate hide or /mstate show"
}, this);
}
private string Msg(string key, string userId = null) => string.Format(lang.GetMessage(key, this, userId));
private void Init()
{
permission.RegisterPermission(config.HidePermission, this);
}
[ChatCommand("mstate")]
private void CmdModelState(BasePlayer player, string command, string[] args)
{
if (player == null) return;
if (!permission.UserHasPermission(player.UserIDString, config.HidePermission))
{
SendReply(player, Msg("NoPermission", player.UserIDString));
return;
}
if (args == null || args.Length == 0)
{
SendReply(player, Msg("Usage", player.UserIDString));
return;
}
switch (args[0].ToLower())
{
case "hide":
hidden.Add(player.UserIDString);
SendReply(player, Msg("NowHidden", player.UserIDString));
break;
case "show":
hidden.Remove(player.UserIDString);
SendReply(player, Msg("NowVisible", player.UserIDString));
break;
default:
SendReply(player, Msg("Usage", player.UserIDString));
break;
}
}
private object OnSendModelState(BasePlayer player)
{
if (player == null || player.IsNpc) return null;
// Blocking the update keeps the last known model on other clients.
if (hidden.Contains(player.UserIDString)) return false;
return null;
}
private void Unload()
{
hidden.Clear();
}
}
}The plugin reads a HidePermission from config and registers it on startup. The /mstate hide and /mstate show chat commands toggle the player's UserIDString in an in-memory set, with permission checks and localized feedback. OnSendModelState is called for every player network update, so it does a single HashSet lookup and returns false when the player is hidden, which suppresses the OnModelState broadcast to other clients so their bodies are no longer updated. Reloading the plugin or restarting the server clears the set, which is intentional so a crashed or restarted server never leaves a player permanently invisible. Timer references and CUI are not needed here.
Common use cases
Hide specific players from other clients
Return a non-null value for chosen players to stop broadcasting their model state, which is the basis for ghost/hider style effects. Combine with other hooks if you also need to hide their gear or nametag.
Freeze visible pose during cutscenes or events
While a scripted event is running, block model state updates for participants so their last known position and pose stay on screen for spectators.
Debug or rate-limit network traffic per player
Count how often each player's state is sent (outside of production logging) to investigate networking issues, or block updates for players flagged by another system.
Pitfalls
It runs extremely often
This hook is on the per-player network update path. Do not log, allocate collections, or call
Puts/PrintWarninginside it; cache decisions in sets or dictionaries instead.Returning null keeps the default send
Only a non-null return blocks the update. Returning
falseworks because it is non-null; returningnullalways lets the RPC through.Blocking updates is not the same as hiding the entity
Suppressing the model state RPC stops fresh pose/position data, but the player remains a networked entity; clients may still retain the last received model, so pair this with visibility logic if you need a true hide.
Build a plugin with OnSendModelState
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 OnSendModelState
Generate