Skip to content
RustPluginGenerator

How to make a Rust wipe raid cooldown plugin (block building damage after wipe)

Stores the save timestamp on each new map save and blocks all damage dealt to building blocks, deployables and their building privilege range until N hours have passed. Uses OnNewSave to record the wipe and OnEntityTakeDamage to block the damage.

After a forced wipe everybody rebuilds from scratch, and the first groups to get a stone base up spend the next hours farming rockets against neighbours who still have a wooden 2x1. A short no-raid window evens that out: nobody can damage structures for the first few hours, everyone gets their footprint down, then raiding opens normally.

The plugin records the save time the first time it sees a fresh map. It hooks OnNewSave to reset that timestamp and OnEntityTakeDamage to cancel any incoming damage to a building block, deployable or turret that sits inside a building privilege zone while the cooldown is still running. Damage from NPCs, animals and the environment is left alone, and so are players who are not part of a raid (fall damage, hunger, radiation). /raidcooldown prints the remaining time to chat.

The plugin code

WipeRaidCooldown.cs
using System.Linq;
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using UnityEngine;

namespace Oxide.Plugins
{
    [Info("Wipe Raid Cooldown", "RustPluginGenerator", "1.0.0")]
    [Description("Blocks player damage to structures for the configured number of hours after each new save.")]
    public class WipeRaidCooldown : RustPlugin
    {
        private class PluginConfig
        {
            [JsonProperty("Cooldown Hours")]
            public float CooldownHours = 12f;

            [JsonProperty("Notify Attacker")]
            public bool NotifyAttacker = true;

            [JsonProperty("Notify Interval Seconds")]
            public float NotifyIntervalSeconds = 60f;

            [JsonProperty("Save Timestamp")]
            public DateTime SaveTimestamp = DateTime.MinValue;
        }

        private const string PermUse = "wiperaidcooldown.use";

        private PluginConfig config;
        private readonly Dictionary<ulong, float> lastNotified = new Dictionary<ulong, 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>
            {
                ["CooldownActive"] = "Raiding is disabled for another {0}.",
                ["RaidingOpen"] = "Raiding is currently enabled.",
                ["NoPermission"] = "You do not have permission to use this command.",
                ["BlockedNotice"] = "Raiding is disabled for another {0} after the wipe."
            }, 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);
        }

        private void OnNewSave(string strFilename)
        {
            config.SaveTimestamp = DateTime.UtcNow;
            SaveConfig();
        }

        private void Unload()
        {
            SaveConfig();
        }

        private bool IsCooldownActive()
        {
            if (config == null || config.SaveTimestamp == DateTime.MinValue) return false;
            return DateTime.UtcNow < config.SaveTimestamp.AddHours(config.CooldownHours);
        }

        private TimeSpan Remaining()
        {
            if (config == null || config.SaveTimestamp == DateTime.MinValue) return TimeSpan.Zero;
            TimeSpan span = config.SaveTimestamp.AddHours(config.CooldownHours) - DateTime.UtcNow;
            return span < TimeSpan.Zero ? TimeSpan.Zero : span;
        }

        private string FormatSpan(TimeSpan span)
        {
            int hours = (int)span.TotalHours;
            int minutes = span.Minutes;
            if (hours > 0) return string.Format("{0}h {1}m", hours, minutes);
            if (minutes > 0) return string.Format("{0}m", minutes);
            return string.Format("{0}s", Mathf.Max(1, span.Seconds));
        }

        private bool IsProtected(BaseEntity entity)
        {
            if (entity == null || entity.IsDestroyed) return false;
            if (entity is BuildingBlock) return true;
            if (entity is Door) return true;
            if (entity is StorageContainer) return true;
            if (entity is BuildingPrivlidge) return true;
            if (entity.GetBuildingPrivilege() != null) return true;
            return false;
        }

        private object HandleDamage(BaseCombatEntity entity, HitInfo hitInfo)
        {
            if (!IsCooldownActive()) return null;
            if (entity == null || hitInfo == null) return null;
            if (hitInfo.InitiatorPlayer == null) return null;
            if (!IsProtected(entity)) return null;

            ulong userId = hitInfo.InitiatorPlayer.userID.Get();
            if (config.NotifyAttacker)
            {
                float now = Time.realtimeSinceStartup;
                float last;
                if (!lastNotified.TryGetValue(userId, out last) || now - last >= config.NotifyIntervalSeconds)
                {
                    lastNotified[userId] = now;
                    string msg = Msg("BlockedNotice", hitInfo.InitiatorPlayer.UserIDString, FormatSpan(Remaining()));
                    hitInfo.InitiatorPlayer.ChatMessage(msg);
                }
            }

            hitInfo.damageTypes.ScaleAll(0f);
            return false;
        }

        private object OnEntityTakeDamage(BaseCombatEntity entity, HitInfo hitInfo)
        {
            return HandleDamage(entity, hitInfo);
        }

        private object OnEntityTakeDamage(BasePlayer player, HitInfo hitInfo)
        {
            return null;
        }

        [ChatCommand("raidcooldown")]
        private void CmdRaidCooldown(BasePlayer player, string command, string[] args)
        {
            if (player == null) return;
            if (!permission.UserHasPermission(player.UserIDString, PermUse))
            {
                player.ChatMessage(Msg("NoPermission", player.UserIDString));
                return;
            }

            if (IsCooldownActive())
                player.ChatMessage(Msg("CooldownActive", player.UserIDString, FormatSpan(Remaining())));
            else
                player.ChatMessage(Msg("RaidingOpen", player.UserIDString));
        }
    }
}

Hooks used: OnNewSave, OnEntityTakeDamage

Config file

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

WipeRaidCooldown.json
{
  "Cooldown Hours": 12,
  "Notify Attacker": true,
  "Notify Interval Seconds": 60,
  "Save Timestamp": "0001-01-01T00:00:00"
}

Commands and permissions

Chat and console commands
CommandWhat it doesPermission
/raidcooldownShows how much time is left before raiding is enabled, or confirms raiding is already open.wiperaidcooldown.use
Permissions
PermissionGrants
wiperaidcooldown.useAllows the player to run /raidcooldown.

How the code works

  1. Store the wipe timestamp

    The wipe timestamp is a config field (Save Timestamp) written through the normal config file, so it survives reloads and restarts. When OnNewSave fires with the new save filename, the plugin sets the field to DateTime.UtcNow and calls SaveConfig(). A fresh install with no timestamp yet falls back to treating the cooldown as inactive until the next save, so you never block a running server by accident.

  2. Decide whether raiding is still closed

    A single helper IsCooldownActive() compares the stored timestamp plus the configured Cooldown Hours against DateTime.UtcNow. If the timestamp is missing or older than that, it returns false and the damage hook costs almost nothing. The helper is used by both the damage filter and the chat command, so the number shown by /raidcooldown is always the number the hook uses.

  3. Filter damage in OnEntityTakeDamage

    The hook returns null to keep vanilla damage, or a non-null object to cancel it. Two OnEntityTakeDamage overloads are declared for BaseCombatEntity and BasePlayer so Oxide dispatches on the entity type. Only structure damage is cancelled: the entity must be a BuildingBlock, a Door, a StorageContainer, or any deployable with a GetBuildingPrivilege() result. Player-vs-player damage, animal damage and fall damage are never touched.

  4. Require a player attacker

    Raiding means a player breaching a base. If hitInfo.InitiatorPlayer is null (a bear biting a door, decay, a supply drop landing on a box) the damage passes through untouched. This keeps PvE and environmental damage working during the cooldown window and avoids unexpected side effects on server events like airdrops or NPC raids.

  5. Give feedback without spamming

    Blocked hits are silent by default; a chat line on every bullet would flood the attacker. The config flag Notify Attacker sends a single localised message per player per Notify Interval Seconds using a dictionary of last-notify times. That is enough for players to learn the rule without the plugin becoming a chat nuisance.

  6. The /raidcooldown command

    The chat command checks the wiperaidcooldown.use permission, then either reports the remaining time formatted as hours and minutes or states that raiding is open. Every string comes from the language file, so admins can rename the wording in oxide/lang/en/WipeRaidCooldown.json without touching code.

Install it on your server

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

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

Variations

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

  • Notify both sides with a UI timer

    Add a small CUI bar with the remaining cooldown for every connected player.

    Generate this version
  • Restrict to specific structure types

    Only block damage to building blocks and doors, letting players loot boxes and destroy barricades.

    Generate this version
  • Manual override for early opens

    Add a console command that lifts the cooldown immediately.

    Generate this version

FAQ

How does the plugin know when a wipe happened?

Oxide calls OnNewSave when the server creates a new save file, which happens after a forced wipe. The plugin writes DateTime.UtcNow into the Save Timestamp config key at that moment. If you restore an old save the hook does not fire, so the previous timestamp is kept.

Does this block all damage to bases?

It blocks damage that comes from a player to a building block, door, storage container or deployable inside a privilege zone. Decay, animal attacks and environmental damage continue as normal. PvP damage is never blocked by this plugin.

Will it work with Carbon?

Yes. Carbon loads the same Oxide plugin file and dispatches the same hooks, so the compiled DLL runs unchanged. Install it through your Carbon plugins folder like any other Oxide plugin and the config file is written to oxide/config regardless.

What is the typical cooldown length?

Most servers run 6 to 12 hours. Set Cooldown Hours in the config; the value is a float so 1.5 gives a 90 minute window. /raidcooldown reports the remainder in hours and minutes.

Can I change the message language?

Yes. Edit oxide/lang/en/WipeRaidCooldown.json after the first load. Keys are CooldownActive, RaidingOpen, BlockedNotice and NoPermission. Run oxide.reload WipeRaidCooldown after saving changes.

Customize it for your server

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

Prompt: Build a Rust Oxide plugin that blocks player damage to buildings and deployables for 12 hours after each new save, with a /raidcooldown command and attacker notices.
Customize for my server

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

Last updated Oct 26, 2026.

Customize no raiding after wipe

Customize