Skip to content
RustPluginGenerator

OnCodeChanged

OnCodeChanged is called after a player submits a new code on a CodeLock through the lock UI, once the lock has already stored the value. It fires for both owner code changes and guest code changes.

Signature
void OnCodeChanged(BasePlayer player, CodeLock codeLock, string text, bool flag)

Parameters

Hook parameters
ParameterTypeDescription
playerBasePlayerThe player who changed the code, taken from the network RPC sender. Null-check it before touching inventory or chat.
codeLockCodeLockThe CodeLock entity whose code settings were modified. Use it to read OwnerID, the parent door/box or its position.
textstringThe code string that was submitted. It is the new guest code when flag is true, and the owner code otherwise.
flagboolTrue when a guest code was set, false when the lock code itself was changed.

Return value

The hook is called for its side effects only; any value returned from your handler is ignored and the original RPC_ChangeCode path continues (effect, network update, parent box update). Returning null or a value makes no difference.

How it works

The hook fires inside CodeLock.RPC_ChangeCode on the server, right after the lock applies the submitted code and before it plays the effect and sends the network update. Because RPC_ChangeCode is one of the handful of RPC handlers exposed to any client that opens the lock UI, the hook fires for every successful text submission, not only from the owner.

The fourth parameter flag distinguishes the two paths: true means a guest code was written and the guest list was reset to just that player, false means the lock code itself changed. text is the submitted string, so you can validate length, reject duplicates across your server, or log it. The lock is already updated at this point, so you cannot prevent the change here; treat the hook as a notification.

Declaring player as BasePlayer is fine since the call site always passes a player; declaring codeLock as CodeLock is required to receive code change events, as Oxide skips calls where the argument is not an instance of the parameter type. Use codeLock.GetParentEntity() to reach the container or door the lock is attached to.

Example plugin: Log code changes and alert admins to guest code resets

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

namespace Oxide.Plugins
{
    [Info("Code Lock Audit", "RustPluginGenerator", "1.0.0")]
    [Description("Logs CodeLock code changes and alerts admins when a guest code is set.")]
    public class CodeLockAudit : RustPlugin
    {
        private const string PermAlert = "codelockaudit.alert";

        private PluginConfig config;
        private readonly Dictionary<ulong, float> lastChange = new Dictionary<ulong, float>();
        private readonly HashSet<ulong> muted = new HashSet<ulong>();

        private class PluginConfig
        {
            [JsonProperty("Alert admins on guest code change")]
            public bool AlertOnGuestCode { get; set; } = true;

            [JsonProperty("Minimum seconds between log entries per lock")]
            public float ThrottleSeconds { get; set; } = 2f;
        }

        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>
            {
                ["GuestCodeSet"] = "{0} set a guest code on the lock at {1}",
                ["AdminAlert"] = "{0} changed a lock code at {1} (guest code: {2})",
                ["Muted"] = "You will no longer receive alerts for this lock.",
                ["Unmuted"] = "Alerts for this lock are enabled again.",
                ["NoPermission"] = "You do not have permission to do that."
            }, 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(PermAlert, this);
        }

        private void OnCodeChanged(BasePlayer player, CodeLock codeLock, string text, bool flag)
        {
            if (codeLock == null) return;

            ulong lockId = codeLock.net != null ? codeLock.net.ID.Value : 0UL;
            float now = UnityEngine.Time.realtimeSinceStartup;
            float previous;
            if (lastChange.TryGetValue(lockId, out previous) && now - previous < config.ThrottleSeconds) return;
            lastChange[lockId] = now;

            string playerName = player != null ? player.displayName : "unknown";
            string pos = codeLock.transform.position.ToString("F0");

            if (flag)
            {
                Puts(Msg("GuestCodeSet", null, playerName, pos));
            }
            else
            {
                Puts(string.Concat(playerName, " changed the code on lock at ", pos));
            }

            if (!flag || !config.AlertOnGuestCode) return;

            foreach (BasePlayer admin in BasePlayer.activePlayerList)
            {
                if (admin == null || admin == player) continue;
                if (!permission.UserHasPermission(admin.UserIDString, PermAlert)) continue;
                if (muted.Contains(admin.userID.Get())) continue;
                SendReply(admin, Msg("AdminAlert", admin.UserIDString, playerName, pos, text));
            }
        }

        [ChatCommand("lockaudit")]
        private void CmdLockAudit(BasePlayer player, string command, string[] args)
        {
            if (player == null) return;
            if (!permission.UserHasPermission(player.UserIDString, PermAlert))
            {
                SendReply(player, Msg("NoPermission", player.UserIDString));
                return;
            }

            ulong id = player.userID.Get();
            if (muted.Remove(id))
                SendReply(player, Msg("Unmuted", player.UserIDString));
            else
            {
                muted.Add(id);
                SendReply(player, Msg("Muted", player.UserIDString));
            }
        }

        private void Unload()
        {
            lastChange.Clear();
            muted.Clear();
        }
    }
}

CodeLockAudit listens for OnCodeChanged and stores the last change per lock in a small runtime dictionary, keyed by the lock's network id, so rapid edits do not spam logs. Owner changes are written to the plugin log with the player name and the lock position; guest code changes are additionally broadcast to admins with permission codelockaudit.alert. Both events can be muted per lock with /lockaudit mute (admin only), which the plugin remembers for the session. All player-facing text goes through the language file, and the permission is registered in Init.

Common use cases

  • Audit trail for raid investigations

    Persist a per-lock history of code changes with player ID, timestamp and coordinates so staff can see who reset a base lock after a raid.

  • Warn the owner about guest code resets

    Guest codes reset the guest list to a single player, which is often how insiders take a base; notify the owner by chat or Discord bridge when flag is true.

  • Enforce code length policies

    Use the hook as a notification to remind players when their new code is shorter than your server minimum, since the change has already been applied and cannot be blocked.

Pitfalls

  • The change is already applied

    The hook fires after the lock stored the new code and reset the guest list, so returning a value or throwing does not undo it. If you must block weak codes, gate CanUseLockedEntity or use the client-side prompt instead.

  • RPC is callable by non-owners

    Any client that opens the lock UI can trigger RPC_ChangeCode, so do not assume player is the owner. Compare player.userID with codeLock.OwnerID before treating the event as authoritative.

  • Too chatty for large servers

    Every code edit fires the hook, including rapid retries from players fumbling the UI. Throttle or deduplicate by lock id before writing to log files or chat.

Build a plugin with OnCodeChanged

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

Prompt: Audit trail for raid investigations using the OnCodeChanged 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 OnCodeChanged

Generate