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
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.
{
"Block Seconds": 120,
"Blocked Commands": [
"home",
"tpr",
"kit"
],
"Block Admins": false
}Commands and permissions
| Command | What it does | Permission |
|---|---|---|
raidblock.status | Prints 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 |
| Permission | Grants |
|---|---|
raidblock.use | Allows a player to run raidblock.status on themselves. |
raidblock.admin | Grants raidblock.clear to remove another player's raid block. |
raidblock.immune | Player is never raid blocked, neither as attacker nor as base owner. |
How the code works
Start the block from explosion damage
OnEntityTakeDamage(BaseCombatEntity entity, HitInfo hitInfo)checkshitInfo?.damageTypes?.GetMajorityDamageType() == Rust.DamageType.Explosion. If the victim is aBuildingBlock, the attacker (hitInfo.InitiatorPlayer) is blocked. The block is applied throughAddBlock, which storesTime.realtimeSinceStartupin a dictionary keyed byUserIDString. Nested members are null-checked before use, and the hook always returnsnullso damage is unchanged.Block the building owners
When the damaged entity is a
BuildingBlock,entity.GetBuildingPrivilege()returns the cupboard. Every entry inBuildingPrivlidge.authorizedPlayersis aProtoBuf.PlayerNameIDwith auseridfield, so online players can be found withBasePlayer.FindByIDand blocked. This catches defenders who are not the direct target of the rockets but are still part of the raid.Enforce the block on commands
OnPlayerCommand(BasePlayer player, string cmd, string[] args)checkspermission.UserHasPermission(player.UserIDString, PermImmune)and returnsnullfor immune players. OtherwiseIsBlockedcompares the command againstBlockedCommandsfrom config and, when the timer is still running, sends theRaidBlockedlang message with the remaining seconds and returnstrueto cancel the command.Remaining time and expiry
Remainingsubtracts the stored start time fromTime.realtimeSinceStartupand compares againstconfig.BlockSeconds. Expired entries are removed from the dictionary on read, so no cleanup timer is needed.OnPlayerDisconnectedalso drops the entry so reconnecting players start fresh, andUnloadclears the dictionary.Config and messages
PluginConfigholdsBlock Seconds,Blocked Commands(shortnames without the leading slash) andBlock Admins.LoadDefaultConfigfills them, andLoadConfigfalls back to defaults if the file is invalid. Player-facing text lives inLoadDefaultMessagesand is read throughMsg, with{0}for the command and{1}for the seconds left.Admin console tools
[ConsoleCommand("raidblock.clear")]accepts a player name or steam id, resolves it withBasePlayer.FindAwakeOrSleeping, and removes the entry.[ConsoleCommand("raidblock.status")]reports the caller's remaining time viaarg.ReplyWith. Both check permissions withpermission.UserHasPermission, andarg.Player()is null when run from RCON, so the status command guards against that.
Install it on your server
Save RaidBlock.cs
Keep the file name exactly as the class name. Oxide matches them.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.Edit the config if you want
On first load the default config is written tooxide/config/RaidBlock.json(Carbon:carbon/configs/RaidBlock.json). After editing, reload it:oxide.reload RaidBlock c.reload RaidBlock # CarbonGrant the permission
Give it to every player, or swapdefaultfor a group such asvip: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 versionBlock kit and home only
Shrink the blocked command list to the two commands most raids depend on.
Generate this versionScale 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.
Edit the prompt on the next screen. 2 free plugins a month, no card.
More raiding plugins
- Explosive damage multiplierScales explosion damage against building blocks, deployables and other structures using per-weapon multipliers keyed by the attacker's weapon prefabโฆOnEntityTakeDamage
- New player protectionBlocks damage to buildings owned by players under a configurable playtime threshold, tracks playtime per player in oxide/data, and removesโฆOnServerInitializedOnNewSaveOnEntityTakeDamage
- Offline raid protectionScales damage to building blocks and deployables when every player authorized on the covering tool cupboard has been offline for a configurable delay.OnServerInitializedOnCupboardAuthorizeOnCupboardDeauthorize
Last updated Oct 26, 2026.
Customize raid block
Customize