Skip to content
RustPluginGenerator

How to make a Rust raid block plugin (block tpr/home after explosives)

Places a temporary raid block on attackers and on the owner of a building that takes explosive damage, blocking configured chat commands such as /home and /tpr and printing the remaining time. Uses OnEntityTakeDamage to start the block and OnPlayerCommand to enforce it.

Teams that raid can hop out with /home or /tpr the moment they are spotted, and defenders who are losing a base can teleport away with their loot. This plugin adds a raid block timer: any player who deals explosion damage to a building block gets blocked, and every authorised player on the attacked cupboard gets blocked too.

The timer reads from config (default 120 seconds). Blocked players still see the blocked command name and the exact seconds remaining, so the rule is obvious. The plugin never touches damage values: OnEntityTakeDamage only observes, returns null, and lets the game apply its normal damage. Enforcement happens in OnPlayerCommand, which compares the command against a config list and cancels it while the timer is active.

The plugin code

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

namespace Oxide.Plugins
{
    [Info("Raid Block", "RustPluginGenerator", "1.0.0")]
    [Description("Temporarily blocks teleport and kit commands for raiders and the owners of a raided building.")]
    public class RaidBlock : RustPlugin
    {
        private const string PermUse = "raidblock.use";
        private const string PermAdmin = "raidblock.admin";
        private const string PermImmune = "raidblock.immune";

        private class PluginConfig
        {
            [JsonProperty("Block Seconds")]
            public float BlockSeconds = 120f;

            [JsonProperty("Blocked Commands")]
            public List<string> BlockedCommands = new List<string> { "home", "tpr", "kit" };

            [JsonProperty("Block Admins")]
            public bool BlockAdmins = false;
        }

        private PluginConfig config;
        private readonly Dictionary<string, float> blocks = new Dictionary<string, float>();

        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>
            {
                ["RaidBlocked"] = "<color=#ce422b>Raid Block:</color> /{0} is blocked for another {1} seconds.",
                ["NoBlock"] = "You are not raid blocked.",
                ["Status"] = "Raid block remaining: {0} seconds.",
                ["Cleared"] = "Raid block removed from {0}.",
                ["NotFound"] = "Player '{0}' was not found.",
                ["NoPermission"] = "You do not have permission to use this command."
            }, 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(PermUse, this);
            permission.RegisterPermission(PermAdmin, this);
            permission.RegisterPermission(PermImmune, this);
        }

        private void Unload()
        {
            blocks.Clear();
        }

        private void OnServerInitialized()
        {
            bool changed = false;
            for (int i = 0; i < config.BlockedCommands.Count; i++)
            {
                string cmd = config.BlockedCommands[i]?.Trim().TrimStart('/');
                if (!string.IsNullOrEmpty(cmd) && cmd == config.BlockedCommands[i]) continue;
                config.BlockedCommands[i] = cmd;
                changed = true;
            }
            if (changed) SaveConfig();
        }

        private object OnEntityTakeDamage(BaseCombatEntity entity, HitInfo hitInfo)
        {
            if (entity == null || hitInfo == null || hitInfo.damageTypes == null) return null;
            if (hitInfo.damageTypes.GetMajorityDamageType() != Rust.DamageType.Explosion) return null;

            BasePlayer attacker = hitInfo.InitiatorPlayer;
            if (attacker != null && !attacker.IsNpc)
            {
                TryAddBlock(attacker);
            }

            BuildingBlock block = entity as BuildingBlock;
            if (block == null) return null;

            BuildingPrivlidge privilege = block.GetBuildingPrivilege();
            if (privilege == null) return null;

            foreach (ulong userId in privilege.authorizedPlayers)
            {
                BasePlayer owner = BasePlayer.FindByID(userId);
                if (owner == null || owner == attacker) continue;
                TryAddBlock(owner);
            }

            return null;
        }

        private void TryAddBlock(BasePlayer player)
        {
            if (player == null || player.IsNpc) return;
            if (!config.BlockAdmins && player.IsAdmin) return;
            if (permission.UserHasPermission(player.UserIDString, PermImmune)) return;

            if (blocks.ContainsKey(player.UserIDString)) return;
            AddBlock(player.UserIDString);
            player.ChatMessage(Msg("RaidBlocked", player.UserIDString, CurrentCommandName(), Mathf.CeilToInt(config.BlockSeconds)));
        }

        private string CurrentCommandName()
        {
            if (config.BlockedCommands.Count == 0) return "command";
            return config.BlockedCommands[0];
        }

        private void AddBlock(string userId)
        {
            if (string.IsNullOrEmpty(userId)) return;
            blocks[userId] = Time.realtimeSinceStartup;
        }

        private float Remaining(string userId)
        {
            float start;
            if (!blocks.TryGetValue(userId, out start)) return 0f;
            float left = config.BlockSeconds - (Time.realtimeSinceStartup - start);
            if (left <= 0f)
            {
                blocks.Remove(userId);
                return 0f;
            }
            return left;
        }

        private bool IsBlocked(string userId, string cmd)
        {
            if (string.IsNullOrEmpty(cmd)) return false;
            string clean = cmd.Trim().TrimStart('/').ToLowerInvariant();
            if (!config.BlockedCommands.Contains(clean)) return false;
            return Remaining(userId) > 0f;
        }

        private object OnPlayerCommand(BasePlayer player, string cmd, string[] args)
        {
            if (player == null || string.IsNullOrEmpty(cmd)) return null;
            if (permission.UserHasPermission(player.UserIDString, PermImmune)) return null;

            if (!IsBlocked(player.UserIDString, cmd)) return null;

            if (config.BlockAdmins || !player.IsAdmin)
            {
                SendBlockedMessage(player, cmd);
                return true;
            }

            return null;
        }

        private void SendBlockedMessage(BasePlayer player, string cmd)
        {
            string clean = cmd.Trim().TrimStart('/');
            int seconds = Mathf.CeilToInt(Remaining(player.UserIDString));
            player.ChatMessage(Msg("RaidBlocked", player.UserIDString, clean, seconds));
        }

        private void OnPlayerDisconnected(BasePlayer player, string reason)
        {
            if (player == null) return;
            blocks.Remove(player.UserIDString);
        }

        [ConsoleCommand("raidblock.status")]
        private void CmdStatus(ConsoleSystem.Arg arg)
        {
            BasePlayer player = arg.Player();
            if (player == null)
            {
                arg.ReplyWith("This command must be run in-game.");
                return;
            }
            if (!permission.UserHasPermission(player.UserIDString, PermUse))
            {
                arg.ReplyWith(Msg("NoPermission", player.UserIDString));
                return;
            }

            float left = Remaining(player.UserIDString);
            if (left <= 0f)
            {
                arg.ReplyWith(Msg("NoBlock", player.UserIDString));
                return;
            }
            arg.ReplyWith(Msg("Status", player.UserIDString, Mathf.CeilToInt(left)));
        }

        [ConsoleCommand("raidblock.clear")]
        private void CmdClear(ConsoleSystem.Arg arg)
        {
            BasePlayer caller = arg.Player();
            if (caller != null && !permission.UserHasPermission(caller.UserIDString, PermAdmin))
            {
                arg.ReplyWith(Msg("NoPermission", caller.UserIDString));
                return;
            }

            string target = arg.GetString(0, string.Empty);
            if (string.IsNullOrEmpty(target))
            {
                arg.ReplyWith("Usage: raidblock.clear <player name or steamid>");
                return;
            }

            BasePlayer player = BasePlayer.FindAwakeOrSleeping(target);
            if (player == null)
            {
                arg.ReplyWith(Msg("NotFound", caller?.UserIDString, target));
                return;
            }

            blocks.Remove(player.UserIDString);
            arg.ReplyWith(Msg("Cleared", caller?.UserIDString, player.displayName));
        }
    }
}

Hooks used: OnServerInitialized, OnEntityTakeDamage, OnPlayerCommand, OnPlayerDisconnected

Config file

Created automatically on first load at oxide/config/RaidBlock.json.

RaidBlock.json
{
  "Block Seconds": 120,
  "Blocked Commands": [
    "home",
    "tpr",
    "kit"
  ],
  "Block Admins": false
}

Commands and permissions

Chat and console commands
CommandWhat it doesPermission
raidblock.statusPrints your remaining raid block time in the server console.raidblock.use
raidblock.clear <player name or steamid>Removes the raid block from a player.raidblock.admin
Permissions
PermissionGrants
raidblock.useAllows a player to run raidblock.status on themselves.
raidblock.adminGrants raidblock.clear to remove another player's raid block.
raidblock.immunePlayer is never raid blocked, neither as attacker nor as base owner.

How the code works

  1. Start the block from explosion damage

    OnEntityTakeDamage(BaseCombatEntity entity, HitInfo hitInfo) checks hitInfo?.damageTypes?.GetMajorityDamageType() == Rust.DamageType.Explosion. If the victim is a BuildingBlock, the attacker (hitInfo.InitiatorPlayer) is blocked. The block is applied through AddBlock, which stores Time.realtimeSinceStartup in a dictionary keyed by UserIDString. Nested members are null-checked before use, and the hook always returns null so damage is unchanged.

  2. Block the building owners

    When the damaged entity is a BuildingBlock, entity.GetBuildingPrivilege() returns the cupboard. Every entry in BuildingPrivlidge.authorizedPlayers is a ProtoBuf.PlayerNameID with a userid field, so online players can be found with BasePlayer.FindByID and blocked. This catches defenders who are not the direct target of the rockets but are still part of the raid.

  3. Enforce the block on commands

    OnPlayerCommand(BasePlayer player, string cmd, string[] args) checks permission.UserHasPermission(player.UserIDString, PermImmune) and returns null for immune players. Otherwise IsBlocked compares the command against BlockedCommands from config and, when the timer is still running, sends the RaidBlocked lang message with the remaining seconds and returns true to cancel the command.

  4. Remaining time and expiry

    Remaining subtracts the stored start time from Time.realtimeSinceStartup and compares against config.BlockSeconds. Expired entries are removed from the dictionary on read, so no cleanup timer is needed. OnPlayerDisconnected also drops the entry so reconnecting players start fresh, and Unload clears the dictionary.

  5. Config and messages

    PluginConfig holds Block Seconds, Blocked Commands (shortnames without the leading slash) and Block Admins. LoadDefaultConfig fills them, and LoadConfig falls back to defaults if the file is invalid. Player-facing text lives in LoadDefaultMessages and is read through Msg, with {0} for the command and {1} for the seconds left.

  6. Admin console tools

    [ConsoleCommand("raidblock.clear")] accepts a player name or steam id, resolves it with BasePlayer.FindAwakeOrSleeping, and removes the entry. [ConsoleCommand("raidblock.status")] reports the caller's remaining time via arg.ReplyWith. Both check permissions with permission.UserHasPermission, and arg.Player() is null when run from RCON, so the status command guards against that.

Install it on your server

  1. Save RaidBlock.cs

    Keep the file name exactly as the class name. Oxide matches them.
  2. Upload it to the plugins folder

    Oxide: oxide/plugins/. Carbon: carbon/plugins/. The server compiles and loads it within a few seconds, no restart needed.
  3. Edit the config if you want

    On first load the default config is written to oxide/config/RaidBlock.json (Carbon: carbon/configs/RaidBlock.json). After editing, reload it:oxide.reload RaidBlock c.reload RaidBlock # Carbon
  4. Grant the permission

    Give it to every player, or swap default for a group such as vip:oxide.grant group default raidblock.use c.grant group default raidblock.use # Carbon

Variations

Each one opens the generator with a ready prompt you can tweak.

  • Notify the victim team

    Send a chat warning to every authorised cupboard player when the raid block starts.

    Generate this version
  • Block kit and home only

    Shrink the blocked command list to the two commands most raids depend on.

    Generate this version
  • Scale by raid size

    Make the block longer when more explosions hit the same base.

    Generate this version

FAQ

Does raid block apply to defenders or only raiders?

Both by default. The player who deals the explosion damage is blocked, and every authorised player on the attacked building's cupboard is blocked as well. Set Block Admins to false to skip admins, and grant raidblock.immune to anyone who should never be blocked.

Which commands does it block?

Only the ones listed in Blocked Commands in the config, compared by the command name without a leading slash. Teleport, kit and similar plugins register their own chat commands, so add each one you want covered and reload the plugin.

Does it change explosion damage?

No. The damage hook reads the damage type and the attacker, then returns null, which tells Oxide to let the game continue with normal damage handling. Rockets, C4 and satchels deal exactly the damage they would without the plugin.

How do I install it on Oxide or Carbon?

Drop the compiled RaidBlock.cs into oxide/plugins, then run oxide.reload RaidBlock. Carbon loads the same file from its plugins folder. A default config is written to oxide/config/RaidBlock.json on first load.

Is the timer saved across restarts?

No. Blocks are kept in memory and cleared on unload, so a server restart releases everyone. That is intentional, because real-time timestamps do not survive a restart and stale entries would block players without explanation.

Customize it for your server

Change the reward, add a cooldown, restrict it to VIPs. Start from this prompt and edit anything.

Prompt: Create a raid block plugin that blocks home, tpr and kit for 120 seconds after a player deals explosion damage to a building or while their own base is raided, with an immune permission and a console clear command.
Customize for my server

Edit the prompt on the next screen. 2 free plugins a month, no card.

Last updated Oct 26, 2026.

Customize raid block

Customize