OnRemoteIdentifierUpdate
OnRemoteIdentifierUpdate is called on the server whenever a powered remote control entity (RC car, RC boat) is about to have its control identifier updated via UpdateIdentifier. Returning a non-null value blocks the update.
object OnRemoteIdentifierUpdate(PoweredRemoteControlEntity poweredRemoteControlEntity, string newID)Parameters
| Parameter | Type | Description |
|---|---|---|
poweredRemoteControlEntity | PoweredRemoteControlEntity | The remote control entity (RC car, boat, or similar powered drone) whose identifier is being changed. Cast to a specific subtype like RcCar if you only care about one kind of vehicle. |
newID | string | The identifier string the game is about to assign to the entity. You can inspect it to accept, reject, or log the change. |
Return value
Return null to let the identifier update proceed normally. Return any non-null value to stop the original method before the identifier is written, effectively cancelling the update; the returned value is discarded because the wrapped method returns void.
How it works
The hook fires inside PoweredRemoteControlEntity.UpdateIdentifier(string newID, bool clientSend = false), which is invoked when the game assigns or changes the remote-control identifier of an RC vehicle. That happens when a player binds a controller to an RC entity, or when the server re-syncs the identifier after loading or ownership changes.
poweredRemoteControlEntity is the entity being modified - it is a PoweredRemoteControlEntity, so declare a specific subtype such as RcCar or RcBoat if you only want those. Oxide dispatch skips the call when the argument is not an instance of your declared type, which is a clean way to narrow the hook.
newID is the string being assigned. Use it to filter by format, length, or a whitelist, or to log which entity received which identifier. This is an update hook, not a spawn hook: it may fire multiple times for the same entity.
The signature is void on the game side, so the return value is only used as a veto. Because this fires on a rare control action it is safe to do light work (chat feedback, data writes, permission checks); avoid heavy per-tick logic.
Example plugin: Restrict RC car identifiers to a permitted prefix
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;
using UnityEngine;
namespace Oxide.Plugins
{
[Info("RemoteIdPolicy", "RustPluginGenerator", "1.0.0")]
[Description("Enforces a required prefix on RC vehicle control identifiers and audits accepted updates.")]
public class RemoteIdPolicy : RustPlugin
{
private const string PermExempt = "remoteidpolicy.exempt";
private PluginConfig config;
private RemoteIdAudit audit = new RemoteIdAudit();
private class PluginConfig
{
[JsonProperty("Required identifier prefix (empty = allow all)")]
public string RequiredPrefix = "";
[JsonProperty("Maximum identifier length")]
public int MaxLength = 24;
}
private class RemoteIdAudit
{
[JsonProperty("Entries")]
public Dictionary<ulong, IdentifierEntry> Entries = new Dictionary<ulong, IdentifierEntry>();
}
private class IdentifierEntry
{
[JsonProperty("Identifier")]
public string Identifier;
[JsonProperty("OwnerId")]
public string OwnerId;
[JsonProperty("Timestamp")]
public string Timestamp;
}
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>
{
["Rejected:Prefix"] = "Remote control identifiers must start with '{0}'.",
["Rejected:Length"] = "Remote control identifiers cannot be longer than {0} characters.",
["Accepted"] = "Remote control identifier set to '{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(PermExempt, this);
audit = Interface.Oxide.DataFileSystem.ReadObject<RemoteIdAudit>(Name) ?? new RemoteIdAudit();
}
private void Unload()
{
Interface.Oxide.DataFileSystem.WriteObject(Name, audit);
}
private BasePlayer ResolveOwner(BaseEntity entity)
{
if (entity == null) return null;
ulong ownerId = entity.OwnerID;
if (ownerId == 0 || ownerId == 0UL) return null;
return BasePlayer.FindByID(ownerId);
}
private object OnRemoteIdentifierUpdate(PoweredRemoteControlEntity poweredRemoteControlEntity, string newID)
{
if (poweredRemoteControlEntity == null || newID == null) return null;
BasePlayer owner = ResolveOwner(poweredRemoteControlEntity);
bool exempt = owner != null && permission.UserHasPermission(owner.UserIDString, PermExempt);
if (!exempt && config.MaxLength > 0 && newID.Length > config.MaxLength)
{
if (owner != null) owner.ChatMessage(Msg("Rejected:Length", owner.UserIDString, config.MaxLength));
return false;
}
if (!exempt && !string.IsNullOrEmpty(config.RequiredPrefix) && !newID.StartsWith(config.RequiredPrefix, StringComparison.Ordinal))
{
if (owner != null) owner.ChatMessage(Msg("Rejected:Prefix", owner.UserIDString, config.RequiredPrefix));
return false;
}
audit.Entries[poweredRemoteControlEntity.net.ID.Value] = new IdentifierEntry
{
Identifier = newID,
OwnerId = owner != null ? owner.UserIDString : "0",
Timestamp = DateTime.UtcNow.ToString("u")
};
if (owner != null) owner.ChatMessage(Msg("Accepted", owner.UserIDString, newID));
return null;
}
}
}The plugin intercepts remote-control identifier updates and enforces a configurable prefix and length policy. If the configured prefix is empty, only the length limit applies. OnRemoteIdentifierUpdate returns false to cancel a change when newID exceeds MaxLength or does not start with the required prefix, and sends the owner a localized chat message when that player is connected. Players with the remoteidpolicy.exempt permission bypass both checks. Accepted identifiers are stored in memory and saved to a data file in Unload. LoadConfig falls back to defaults when the file is malformed, so the prefix and maximum length can be changed without recompiling.
Common use cases
Whitelist identifier prefixes
Enforce a naming convention on RC vehicles by cancelling any update whose new identifier does not start with an approved prefix.
Audit RC control transfers
Record entity ID, new identifier, and the owning player's ID in oxide/data so admins can trace who bound which RC vehicle and when.
Block length-based abuse
Reject identifiers longer than a configurable maximum to keep chat and UI output tidy and prevent oversized strings reaching save data.
Pitfalls
Cancelling on a client-synced update
UpdateIdentifier can also fire with clientSend true when the server pushes state to a client. Rejecting that call leaves clients out of sync with the server, so always base the decision on newID, not on the entity you assume is initiating it.
Owner may be offline or unset
GetOwnerPlayer() returns null when the entity is unowned or its owner is sleeping/disconnected. Null-check before calling chat or permission APIs.
Fires more than once per entity
This is an update hook, not a spawn hook. The same vehicle can be updated several times across a wipe, so audit data should key on the entity ID and overwrite rather than append indefinitely.
Build a plugin with OnRemoteIdentifierUpdate
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 OnRemoteIdentifierUpdate
Generate