How to make a Rust building protection plugin (PvE owner-only damage)
Blocks and deployables only take damage from their owner or players authorized on the covering tool cupboard. Uses OnEntityTakeDamage with a BaseCombatEntity signature, ignoring decay and NPC sources.
On a PvE server you still want structures to be destructible by the people who built them so bases can be reshaped, but you do not want a random raider, a stray grenade or a griefer's pickaxe to chip away at somebody else's base. The usual shortcut is to switch off player damage entirely, which also stops owners from demolishing or upgrading their own work.
This plugin answers a narrower question every time damage is applied to a building block or a deployable: is the attacker the owner, or is the attacker authorized on the tool cupboard that covers this entity? If neither is true, the damage is cancelled. Decay is left alone when the majority damage type is Decay and IgnoreDecayDamage is on, and any source that has no player initiator is allowed through, which keeps NPC, heli and trap damage working. Owners and cupboard-authorised players still damage their own base normally.
The check runs in OnEntityTakeDamage with a BaseCombatEntity signature, so it sees blocks, doors, deployables and anything else damageable that is not a player.
The plugin code
using System.Linq;
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;
using UnityEngine;
namespace Oxide.Plugins
{
[Info("PveBuildingProtection", "RustPluginGenerator", "1.0.0")]
[Description("Restricts building and deployable damage to the owner or players authorised on the covering tool cupboard.")]
public class PveBuildingProtection : RustPlugin
{
private const string PermAdmin = "pvebuildingprotection.admin";
private PluginConfig config;
private class PluginConfig
{
[JsonProperty("Enabled")]
public bool Enabled = true;
[JsonProperty("ProtectDeployables")]
public bool ProtectDeployables = true;
[JsonProperty("AllowNpcDamage")]
public bool AllowNpcDamage = true;
[JsonProperty("IgnoreDecayDamage")]
public bool IgnoreDecayDamage = true;
[JsonProperty("ProtectedPrefabs")]
public List<string> ProtectedPrefabs = new List<string>
{
"foundation",
"foundation.triangle",
"wall",
"wall.half",
"wall.low",
"wall.window",
"wall.doorway",
"floor",
"floor.triangle",
"floor.frame",
"roof",
"block.stair.lshape",
"block.stair.spiral",
"block.stair.ushape",
"wall.external.high",
"wall.external.high.stone",
"gates.external.high.wood",
"gates.external.high.stone",
"door.hinged.wood",
"door.hinged.metal",
"door.double.hinged.wood",
"door.double.hinged.metal",
"door.hinged.toptier",
"door.double.hinged.toptier"
};
}
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>
{
["Report"] = "Building protection: {0} | Decay bypass: {1} | Deployables: {2}",
["On"] = "enabled",
["Off"] = "disabled",
["Toggled"] = "Building protection is now {0}.",
["NoPermission"] = "You do not have permission to use this command."
}, this);
}
private void Init()
{
permission.RegisterPermission(PermAdmin, this);
}
private string Msg(string key, string userId = null, params object[] args)
{
return string.Format(lang.GetMessage(key, this, userId), args);
}
private object OnEntityTakeDamage(BaseCombatEntity entity, HitInfo hitInfo)
{
if (!config.Enabled || entity == null || hitInfo == null) return null;
if (entity is BasePlayer || entity is BaseNpc) return null;
var attacker = hitInfo.InitiatorPlayer;
if (attacker == null || attacker.IsNpc) return null;
var damageType = hitInfo.damageTypes.GetMajorityDamageType();
if (config.IgnoreDecayDamage && damageType == Rust.DamageType.Decay) return null;
if (!(entity is BuildingBlock) && !(entity is Door) && !(entity is DecayEntity) && !config.ProtectDeployables) return null;
if (!IsProtected(entity)) return null;
if (attacker.IsAdmin) return null;
if (attacker.userID == GetOwner(entity)) return null;
var privilege = entity.GetBuildingPrivilege();
if (privilege != null && privilege.IsAuthed(attacker)) return null;
hitInfo.damageTypes.ScaleAll(0f);
return true;
}
private bool IsProtected(BaseCombatEntity entity)
{
var block = entity as BuildingBlock;
if (block != null)
{
var building = block.GetBuilding();
if (building == null) return config.ProtectDeployables;
return true;
}
if (entity is Door) return config.ProtectDeployables;
if (entity.ShortPrefabName != null && config.ProtectedPrefabs.Contains(entity.ShortPrefabName))
return true;
return config.ProtectDeployables && entity is DecayEntity;
}
private ulong GetOwner(BaseCombatEntity entity)
{
return entity.OwnerID;
}
[ChatCommand("buildingprotect")]
private void CmdReport(BasePlayer player, string command, string[] args)
{
if (player == null) return;
if (!permission.UserHasPermission(player.UserIDString, PermAdmin))
{
player.ChatMessage(Msg("NoPermission", player.UserIDString));
return;
}
player.ChatMessage(Msg("Report", player.UserIDString,
config.Enabled ? Msg("On", player.UserIDString) : Msg("Off", player.UserIDString),
config.IgnoreDecayDamage ? "yes" : "no",
config.ProtectDeployables ? "protected" : "ignored"));
}
[ConsoleCommand("pvebuildingprotection.toggle")]
private void CcmdToggle(ConsoleSystem.Arg arg)
{
if (arg == null) return;
var player = arg.Player();
if (player != null && !permission.UserHasPermission(player.UserIDString, PermAdmin)) return;
if (player == null && !arg.IsAdmin) return;
config.Enabled = !config.Enabled;
SaveConfig();
var state = config.Enabled ? Msg("On") : Msg("Off");
arg.ReplyWith(string.Format(lang.GetMessage("Toggled", this), state));
}
private void Unload()
{
config = null;
}
}
}Hooks used: OnEntityTakeDamage
Config file
Created automatically on first load at oxide/config/PveBuildingProtection.json.
{
"Enabled": true,
"ProtectDeployables": true,
"AllowNpcDamage": true,
"IgnoreDecayDamage": true,
"ProtectedPrefabs": [
"foundation",
"foundation.triangle",
"wall",
"wall.half",
"wall.low",
"wall.window",
"wall.doorway",
"floor",
"floor.triangle",
"floor.frame",
"roof",
"block.stair.lshape",
"block.stair.spiral",
"block.stair.ushape",
"wall.external.high",
"wall.external.high.stone",
"gates.external.high.wood",
"gates.external.high.stone",
"door.hinged.wood",
"door.hinged.metal",
"door.double.hinged.wood",
"door.double.hinged.metal",
"door.hinged.toptier",
"door.double.hinged.toptier"
]
}Commands and permissions
| Command | What it does | Permission |
|---|---|---|
/buildingprotect | Shows the plugin status (enabled, decay bypass, deployable coverage) to the player who runs it. | pvebuildingprotection.admin |
pvebuildingprotection.toggle | Turns protection on or off at runtime without reloading the plugin. | pvebuildingprotection.admin |
| Permission | Grants |
|---|---|
pvebuildingprotection.admin | Grants access to the /buildingprotect report and the toggle console command. |
How the code works
Config and the prefab list
PluginConfigholdsEnabled,ProtectDeployables,AllowNpcDamage,IgnoreDecayDamageand aProtectedPrefabslist of short prefab names.LoadConfigcatches deserialization failures and falls back to defaults so a hand-edited file cannot break the plugin. The prefab list is matched againstentity.ShortPrefabName, which is the cheap way to recognise walls, foundations and gates without hard-coding a C# type check for every one of them. Note thatAllowNpcDamageis present in the config but is not read by the damage code; NPC damage is allowed becauseInitiatorPlayeris null for NPC attackers.Which entities the hook rejects
OnEntityTakeDamage(BaseCombatEntity entity, HitInfo hitInfo)returnsnullimmediately when the plugin is disabled, whenentityorhitInfois null, when the entity is aBasePlayerorBaseNpc, and whenhitInfo.InitiatorPlayeris null or an NPC. Returningnullleaves the hit untouched. This guard keeps the method out of every damage event that is not about a structure and out of normal player-versus-player combat.Filtering decay and non-player sources
The majority damage type is read once with
hitInfo.damageTypes.GetMajorityDamageType(). WhenIgnoreDecayDamageis on and that type isRust.DamageType.Decay, the hit is allowed through. Sources with no player initiator (helicopter rockets, turrets, traps, scientists) never reach the ownership checks becauseInitiatorPlayeris null there, which is why NPC damage is allowed regardless of theAllowNpcDamagesetting.Deciding what counts as protected
IsProtecteddecides whether the entity is in scope. ABuildingBlockis in scope whenGetBuilding()returns a non-null building. ADooris always in scope. OtherDecayEntityinstances are in scope whenProtectDeployablesis on. Anything else is in scope when itsShortPrefabNameappears inProtectedPrefabs. Entities that fail this check returnnulland take damage normally, so unrelated props on the map are never frozen.Owner and cupboard checks
GetOwnerreadsentity.OwnerIDfor every protected entity; it does not consult the building. Server admins skip the checks entirely. A player whoseuserIDequals the owner is allowed to damage the entity. Otherwise the code callsentity.GetBuildingPrivilege()andIsAuthed(attacker); a cupboard match also allows the damage. If neither check matches,hitInfo.damageTypes.ScaleAll(0f)zeroes the damage and the method returnstrue, which cancels the rest of the hit.Commands, permissions and messages
Initregisterspvebuildingprotection.admin./buildingprotectreplies with the current enabled state, whether decay is being ignored and whether deployables are protected; the console commandpvebuildingprotection.toggleflipsconfig.Enabled, callsSaveConfig()and replies througharg.ReplyWith. Every player-facing string is loaded byLoadDefaultMessagesand formatted with the smallMsghelper, so the report can be translated without editing the plugin.
Install it on your server
Save PveBuildingProtection.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/PveBuildingProtection.json(Carbon:carbon/configs/PveBuildingProtection.json). After editing, reload it:oxide.reload PveBuildingProtection c.reload PveBuildingProtection # CarbonGrant the permission
Give it to every player, or swapdefaultfor a group such asvip:oxide.grant group default pvebuildingprotection.admin c.grant group default pvebuildingprotection.admin # Carbon
Variations
Each one opens the generator with a ready prompt you can tweak.
Team-wide protection without a cupboard
Players in the same Rust team may damage each other's structures even when no tool cupboard is present.
Generate this versionRaid window schedule
Protection only applies outside configured raid hours, where raiding is allowed.
Generate this versionOwner-only damage in PvP
A stricter mode where even cupboard-authorised players cannot damage blocks they did not build.
Generate this version
FAQ
Why does decay still damage my base with the plugin loaded?
Decay is deliberately skipped when IgnoreDecayDamage is true. Decay damage reaches the hook with a Decay majority type and the plugin lets it through, so idle bases still rot as intended. Set the option to false if you want decay blocked as well.
Do NPCs and helicopters still damage structures?
Yes. Damage from scientists, Bradley, the patrol helicopter and traps has no player initiator, so InitiatorPlayer is null and the ownership check is skipped. Only hits with a real non-admin player attacker are ever evaluated against ownership or cupboard authorisation.
How do I make sure doors and boxes are covered?
Leave ProtectDeployables enabled. Door instances are always protected, and other DecayEntity types are protected when ProtectDeployables is on, using entity.OwnerID and GetBuildingPrivilege() for the authorisation check. Set it to false if you only want walls, foundations and prefabs listed in ProtectedPrefabs covered, plus doors.
How do I install it on Oxide or Carbon?
Place the .cs file in oxide/plugins and reload the server or run oxide.reload PveBuildingProtection. Carbon loads the same file from its plugins folder. The config is written to oxide/config/PveBuildingProtection.json on first load; edit and reload to apply changes.
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 pve plugins
- Player corpse loot protectionLocks a dead player's corpse and dropped backpack to the owner and their current team for a configurable number of minutes.OnServerInitializedOnEntitySpawnedCanLootEntity
- Heli and Bradley loot lockTracks damage dealt to the Patrol Helicopter and Bradley APC per player and locks the crates they drop for a configurable time, allowing only theโฆOnServerInitializedOnServerSaveOnEntityTakeDamage
- NPC corpse loot lockLocks NPC corpses to the killer and their team for a configurable number of seconds after death, then opens them to everyone.OnServerInitializedOnEntityDeathCanLootEntity
Last updated Oct 26, 2026.
Customize building damage protection
Customize