Skip to content
RustPluginGenerator

OnDoorClosed

OnDoorClosed is called when a player closes a door, after the game has already started its close animation and blockage check. It fires from Door.RPC_CloseDoor.

Signature
void OnDoorClosed(Door door, BasePlayer player)

Parameters

Hook parameters
ParameterTypeDescription
doorDoorThe Door entity that was just closed. Use `.net.ID.Value` to identify it, or cast to a subtype such as `Door`'s building relation via `door.GetBuildingPrivilege()`.
playerBasePlayerThe BasePlayer who triggered the close RPC. It can be null in edge cases (e.g. a desynced/dead connection), so null-check before reading `UserIDString`.

Return value

The return value is ignored. The original RPC_CloseDoor method returns void and the hook is called with continue semantics, so returning null, a value, or nothing at all has no effect on the door closing.

How it works

This hook fires from Door.RPC_CloseDoor, the client RPC a player sends when they press use on an open door to shut it. By the time it runs, the server has already set the door to closing (SetNavMeshLinkEnabled(false)), started StartCheckingForBlockages, and fired the door's own closed event. The arguments are the Door entity itself and the BasePlayer who closed it. Use the door to read identity (door.net.ID.Value), position, or owner via door.OwnerID, and the player to check permissions, teams, or inventory. Declaring the first parameter as a more specific type (for example Door subtypes are limited, so leave it as Door) does not change much here; Oxide dispatch just requires arguments to match your declared types. The hook is for reaction, not prevention: because it runs after the door is already closing, you cannot veto the close by returning a value. Common uses are logging door activity, closing nearby doors as a group, or reacting to security events.

Example plugin: Log every door close to a per-player activity file

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

namespace Oxide.Plugins
{
    [Info("Door Close Logger", "RustPluginGenerator", "1.0.0")]
    [Description("Logs which doors players close, with timestamps, to a data file.")]
    public class DoorCloseLogger : RustPlugin
    {
        private const string PermAdmin = "doorcloselogger.admin";

        private class Entry
        {
            [JsonProperty("Door entity id")] public ulong DoorId;
            [JsonProperty("Door prefab")] public string Prefab;
            [JsonProperty("Position")] public string Position;
            [JsonProperty("Closed at (UTC)")] public string ClosedAt;
        }

        private class PluginConfig
        {
            [JsonProperty("Max entries per player")] public int MaxEntriesPerPlayer = 50;
        }

        private PluginConfig config;
        private Dictionary<string, List<Entry>> log;

        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>
            {
                ["Log"] = "You closed {0} at {1}.",
                ["Usage"] = "Usage: /doorlog dump <playerId>",
                ["NoData"] = "No door closes recorded for {0}.",
                ["Header"] = "Recent door closes for {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(PermAdmin, this);
        }

        private void OnServerInitialized()
        {
            log = Interface.Oxide.DataFileSystem.ReadObject<Dictionary<string, List<Entry>>>(Name)
                  ?? new Dictionary<string, List<Entry>>();
        }

        private void OnDoorClosed(Door door, BasePlayer player)
        {
            if (door == null || player == null || player.IsNpc) return;
            if (log == null) log = new Dictionary<string, List<Entry>>();

            List<Entry> list;
            if (!log.TryGetValue(player.UserIDString, out list))
            {
                list = new List<Entry>();
                log[player.UserIDString] = list;
            }

            var pos = door.transform.position;
            list.Add(new Entry
            {
                DoorId = door.net.ID.Value,
                Prefab = door.ShortPrefabName,
                Position = string.Format("{0:F0} {1:F0} {2:F0}", pos.x, pos.y, pos.z),
                ClosedAt = DateTime.UtcNow.ToString("u")
            });

            int cap = Mathf.Max(1, config.MaxEntriesPerPlayer);
            while (list.Count > cap) list.RemoveAt(0);
        }

        [ChatCommand("doorlog")]
        private void CmdDoorLog(BasePlayer player, string command, string[] args)
        {
            if (player == null) return;
            if (!permission.UserHasPermission(player.UserIDString, PermAdmin))
            {
                SendReply(player, Msg("Usage", player.UserIDString));
                return;
            }
            if (args.Length < 2 || args[0] != "dump")
            {
                SendReply(player, Msg("Usage", player.UserIDString));
                return;
            }

            string target = args[1];
            List<Entry> list;
            if (log == null || !log.TryGetValue(target, out list) || list.Count == 0)
            {
                SendReply(player, Msg("NoData", player.UserIDString, target));
                return;
            }

            SendReply(player, Msg("Header", player.UserIDString, target));
            int shown = 0;
            for (int i = list.Count - 1; i >= 0 && shown < 10; i--, shown++)
            {
                var e = list[i];
                SendReply(player, string.Format("{0} #{1} {2} @ {3}", e.ClosedAt, e.DoorId, e.Prefab, e.Position));
            }
        }

        private void Unload()
        {
            if (log != null) Interface.Oxide.DataFileSystem.WriteObject(Name, log);
        }
    }
}

The plugin records who closes which door and when. On each OnDoorClosed call it null-checks both arguments and ignores NPC players, then appends an entry to a data structure stored in oxide/data/DoorCloseLogger. Entries are keyed by the player's UserIDString and hold the door entity id, prefab short name, world position, and UTC timestamp. The data is capped per player to avoid unbounded growth. A console command doorlog.dump lets an admin read back a player's recent closes in chat. Everything is flushed in Unload through Interface.Oxide.DataFileSystem, and messages are routed through the language file so server owners can localize the output.

Common use cases

  • Detect after-hours base access

    Track door closes during off-hours and warn online admins when a player without permission closes doors on another player's base.

  • Automatic lockdown

    When a raidable base's doors are closed during a raid timer, send a notification or apply a cooldown on re-opening.

  • Security audit log

    Persist a rolling log of door closes per base so admins can replay activity when investigating theft or griefing reports.

Pitfalls

  • Fires after the door is already closing

    You cannot stop the close here; use CanUseLockedEntity or OnDoorOpened-side logic if you need to block interaction.

  • Player can be null

    The player argument comes from the RPC and may be null in rare desync cases, so always null-check before reading UserIDString or calling ChatMessage.

  • High volume on busy servers

    Doors close frequently; keep the handler cheap and never call Puts/PrintWarning inside it.

Build a plugin with OnDoorClosed

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

Prompt: Detect after-hours base access using the OnDoorClosed 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 OnDoorClosed

Generate