Skip to content
RustPluginGenerator

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

PveBuildingProtection.cs
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.

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

Chat and console commands
CommandWhat it doesPermission
/buildingprotectShows the plugin status (enabled, decay bypass, deployable coverage) to the player who runs it.pvebuildingprotection.admin
pvebuildingprotection.toggleTurns protection on or off at runtime without reloading the plugin.pvebuildingprotection.admin
Permissions
PermissionGrants
pvebuildingprotection.adminGrants access to the /buildingprotect report and the toggle console command.

How the code works

  1. Config and the prefab list

    PluginConfig holds Enabled, ProtectDeployables, AllowNpcDamage, IgnoreDecayDamage and a ProtectedPrefabs list of short prefab names. LoadConfig catches deserialization failures and falls back to defaults so a hand-edited file cannot break the plugin. The prefab list is matched against entity.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 that AllowNpcDamage is present in the config but is not read by the damage code; NPC damage is allowed because InitiatorPlayer is null for NPC attackers.

  2. Which entities the hook rejects

    OnEntityTakeDamage(BaseCombatEntity entity, HitInfo hitInfo) returns null immediately when the plugin is disabled, when entity or hitInfo is null, when the entity is a BasePlayer or BaseNpc, and when hitInfo.InitiatorPlayer is null or an NPC. Returning null leaves 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.

  3. Filtering decay and non-player sources

    The majority damage type is read once with hitInfo.damageTypes.GetMajorityDamageType(). When IgnoreDecayDamage is on and that type is Rust.DamageType.Decay, the hit is allowed through. Sources with no player initiator (helicopter rockets, turrets, traps, scientists) never reach the ownership checks because InitiatorPlayer is null there, which is why NPC damage is allowed regardless of the AllowNpcDamage setting.

  4. Deciding what counts as protected

    IsProtected decides whether the entity is in scope. A BuildingBlock is in scope when GetBuilding() returns a non-null building. A Door is always in scope. Other DecayEntity instances are in scope when ProtectDeployables is on. Anything else is in scope when its ShortPrefabName appears in ProtectedPrefabs. Entities that fail this check return null and take damage normally, so unrelated props on the map are never frozen.

  5. Owner and cupboard checks

    GetOwner reads entity.OwnerID for every protected entity; it does not consult the building. Server admins skip the checks entirely. A player whose userID equals the owner is allowed to damage the entity. Otherwise the code calls entity.GetBuildingPrivilege() and IsAuthed(attacker); a cupboard match also allows the damage. If neither check matches, hitInfo.damageTypes.ScaleAll(0f) zeroes the damage and the method returns true, which cancels the rest of the hit.

  6. Commands, permissions and messages

    Init registers pvebuildingprotection.admin. /buildingprotect replies with the current enabled state, whether decay is being ignored and whether deployables are protected; the console command pvebuildingprotection.toggle flips config.Enabled, calls SaveConfig() and replies through arg.ReplyWith. Every player-facing string is loaded by LoadDefaultMessages and formatted with the small Msg helper, so the report can be translated without editing the plugin.

Install it on your server

  1. Save PveBuildingProtection.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/PveBuildingProtection.json (Carbon: carbon/configs/PveBuildingProtection.json). After editing, reload it:oxide.reload PveBuildingProtection c.reload PveBuildingProtection # Carbon
  4. Grant the permission

    Give it to every player, or swap default for a group such as vip: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 version
  • Raid window schedule

    Protection only applies outside configured raid hours, where raiding is allowed.

    Generate this version
  • Owner-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.

Prompt: Make a Rust Oxide plugin that protects building blocks and deployables so only the owner or players authorised on the covering tool cupboard can damage them, ignoring decay and NPC damage.
Customize for my server

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

Last updated Oct 26, 2026.

Customize building damage protection

Customize