Skip to content
RustPluginGenerator

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.

Signature
object OnRemoteIdentifierUpdate(PoweredRemoteControlEntity poweredRemoteControlEntity, string newID)

Parameters

Hook parameters
ParameterTypeDescription
poweredRemoteControlEntityPoweredRemoteControlEntityThe 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.
newIDstringThe 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

RemoteIdPolicy.cs
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.

Prompt: Whitelist identifier prefixes using the OnRemoteIdentifierUpdate 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 OnRemoteIdentifierUpdate

Generate