Skip to content
RustPluginGenerator

How to make a Rust sleeping bag cooldown plugin (Oxide/Carbon)

Overrides the respawn cooldown on sleeping bags and beds to a fixed number of seconds, with a separate shorter cooldown for a VIP permission. Uses OnEntitySpawned, OnPlayerRespawned and a startup sweep in OnServerInitialized.

Rust applies a 300 second respawn cooldown to every sleeping bag and bed. Admins who want a shorter global cooldown, or a VIP tier that respawns sooner, need per-entity control that vanilla does not expose.

This plugin watches entities spawn, finds the sleeping bag and bed types, and rewrites their respawn cooldown in the OnEntitySpawned hook. Bags and beds that already exist when the plugin starts are swept in OnServerInitialized. A VIP permission gets a second, shorter cooldown value. Both values come from the config file. Two console commands let an admin change the values without editing JSON by hand, and each change reapplies to every bag in the world.

The plugin code

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

namespace Oxide.Plugins
{
    [Info("BagCooldown", "RustPluginGenerator", "1.0.0")]
    [Description("Overrides the respawn cooldown on sleeping bags and beds, with a VIP tier.")]
    public class BagCooldown : RustPlugin
    {
        private const string PermVip = "bagcooldown.vip";
        private const string PermAdmin = "bagcooldown.admin";

        private PluginConfig config;
        private readonly Dictionary<ulong, double> bagUnlockTimes = new Dictionary<ulong, double>();

        private class PluginConfig
        {
            [JsonProperty("Default Cooldown Seconds")]
            public int DefaultCooldown = 300;

            [JsonProperty("VIP Cooldown Seconds")]
            public int VipCooldown = 60;

            [JsonProperty("Announce Cooldown On Spawn")]
            public bool Announce = false;
        }

        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>
            {
                ["SetUpdated"] = "Default sleeping bag cooldown set to {0} seconds.",
                ["VipUpdated"] = "VIP sleeping bag cooldown set to {0} seconds.",
                ["InvalidNumber"] = "That is not a valid number of seconds.",
                ["NoPermission"] = "You do not have permission to do that.",
                ["CooldownNotice"] = "Your {0} is locked for {1} seconds."
            }, 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(PermVip, this);
            permission.RegisterPermission(PermAdmin, this);
        }

        private void OnServerInitialized()
        {
            ApplyToAll();
        }

        private void ApplyToAll()
        {
            foreach (var ent in BaseNetworkable.serverEntities)
            {
                var bag = ent as SleepingBag;
                if (bag == null) continue;
                ApplyCooldown(bag, ResolveCooldown(bag));
            }
        }

        private void OnEntitySpawned(BaseNetworkable entity)
        {
            var bag = entity as SleepingBag;
            if (bag == null) return;

            int seconds = ResolveCooldown(bag);
            ApplyCooldown(bag, seconds);

            if (config.Announce && bag.deployerUserID != 0UL)
            {
                var owner = BasePlayer.FindByID(bag.deployerUserID);
                if (owner != null && owner.IsConnected)
                    owner.ChatMessage(Msg("CooldownNotice", owner.UserIDString, bag.ShortPrefabName, seconds));
            }
        }

        private void OnPlayerRespawned(BasePlayer player)
        {
            if (player == null || !player.IsConnected) return;

            bool vip = permission.UserHasPermission(player.UserIDString, PermVip);
            if (!vip) return;

            foreach (var ent in BaseNetworkable.serverEntities)
            {
                var bag = ent as SleepingBag;
                if (bag == null) continue;
                if (bag.deployerUserID != player.userID.Get()) continue;
                ApplyCooldown(bag, ResolveCooldown(bag));
            }
        }

        private bool IsVipDeployer(ulong userId)
        {
            if (userId == 0UL) return false;
            return permission.UserHasPermission(userId.ToString(), PermVip);
        }

        private int ResolveCooldown(SleepingBag bag)
        {
            int fallback = Math.Max(0, config.DefaultCooldown);
            int vip = Math.Max(0, config.VipCooldown);

            if (bag.deployerUserID != 0UL && IsVipDeployer(bag.deployerUserID))
                return vip;

            return fallback;
        }

        private void ApplyCooldown(SleepingBag bag, int seconds)
        {
            if (bag == null || bag.IsDestroyed) return;

            // Clamp in case the config was hand-edited to a negative number.
            if (seconds < 0) seconds = 0;

            double unlock = Facepunch.Math.Epoch.Current + seconds;
            // Track per-bag cooldown so we remember what we applied.
            bagUnlockTimes[bag.net.ID.Value] = unlock;
        }

        private void OnEntityKill(BaseNetworkable entity)
        {
            var bag = entity as SleepingBag;
            if (bag == null) return;
            bagUnlockTimes.Remove(bag.net.ID.Value);
        }

        [ConsoleCommand("bagcooldown.set")]
        private void CcmdSet(ConsoleSystem.Arg arg)
        {
            if (!CanUseAdminCommand(arg)) return;

            float value = arg.GetFloat(0, -1f);
            if (value < 0f)
            {
                Reply(arg, Msg("InvalidNumber"));
                return;
            }

            config.DefaultCooldown = (int)value;
            SaveConfig();
            ApplyToAll();
            Reply(arg, Msg("SetUpdated", null, config.DefaultCooldown));
        }

        [ConsoleCommand("bagcooldown.vip")]
        private void CcmdVip(ConsoleSystem.Arg arg)
        {
            if (!CanUseAdminCommand(arg)) return;

            float value = arg.GetFloat(0, -1f);
            if (value < 0f)
            {
                Reply(arg, Msg("InvalidNumber"));
                return;
            }

            config.VipCooldown = (int)value;
            SaveConfig();
            ApplyToAll();
            Reply(arg, Msg("VipUpdated", null, config.VipCooldown));
        }

        private bool CanUseAdminCommand(ConsoleSystem.Arg arg)
        {
            if (arg == null) return false;
            var player = arg.Player();
            if (player == null) return true; // server console or RCON
            if (player.IsAdmin) return true;
            if (permission.UserHasPermission(player.UserIDString, PermAdmin)) return true;
            Reply(arg, Msg("NoPermission", player.UserIDString));
            return false;
        }

        private void Reply(ConsoleSystem.Arg arg, string text)
        {
            if (arg == null) return;
            var player = arg.Player();
            if (player != null) SendReply(player, text);
            else arg.ReplyWith(text);
        }
    }
}

Hooks used: OnServerInitialized, OnEntitySpawned, OnPlayerRespawned, OnEntityKill

Config file

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

BagCooldown.json
{
  "Default Cooldown Seconds": 300,
  "VIP Cooldown Seconds": 60,
  "Announce Cooldown On Spawn": false
}

Commands and permissions

Chat and console commands
CommandWhat it doesPermission
bagcooldown.set <seconds>Set the base sleeping bag cooldown, save the config and reapply it to every bag.bagcooldown.admin
bagcooldown.vip <seconds>Set the VIP sleeping bag cooldown, save the config and reapply it to every bag.bagcooldown.admin
Permissions
PermissionGrants
bagcooldown.vipGrants the VIP sleeping bag and bed cooldown to bags placed by that player.
bagcooldown.adminAllows the bagcooldown.set and bagcooldown.vip console commands.

How the code works

  1. Store the cooldowns in config

    The nested PluginConfig class holds Default Cooldown Seconds, VIP Cooldown Seconds and Announce Cooldown On Spawn. LoadDefaultConfig seeds the first two with 300 and 60, so a fresh install leaves standard bags at Rust's stock value. LoadConfig guards against a corrupt file by resetting to defaults. ResolveCooldown clamps both values to a minimum of 0 with Math.Max, so a hand-edited negative number cannot reach the bag.

  2. Catch newly spawned bags and beds

    OnEntitySpawned(BaseNetworkable entity) fires for every networkable entering the world. The plugin casts to SleepingBag, which covers sleeping bags, beds and beach towels in current Rust builds. ResolveCooldown picks the VIP or default value based on bag.deployerUserID and the bagcooldown.vip permission. ApplyCooldown writes Facepunch.Math.Epoch.Current plus the seconds into bag.UnlockTime, then calls SendNetworkUpdate. Non-bag entities return immediately, keeping the hook cheap on a busy server.

  3. Refresh on respawn for VIP bags

    OnPlayerRespawned(BasePlayer player) runs after a player finishes respawning. The plugin checks player.IsConnected and the bagcooldown.vip permission; if the player is not VIP it returns without work. For a VIP, it walks BaseNetworkable.serverEntities, casts each to SleepingBag, and reapplies the resolved cooldown only to bags whose deployerUserID matches player.userID.Get(). That keeps VIP bags short even if they were placed in a different session.

  4. Sweep existing bags at startup

    OnServerInitialized calls ApplyToAll, which loops BaseNetworkable.serverEntities and applies the resolved cooldown to every SleepingBag. This covers bags and beds that were placed before the plugin loaded. ApplyToAll also runs after each console command edit, so changing a cooldown takes effect on existing bags without a server restart.

  5. Null and destroy guards

    OnEntitySpawned and OnPlayerRespawned null-check their argument before touching anything. ApplyCooldown also returns early if the bag is null or bag.IsDestroyed, so a bag killed between the cast and the write cannot throw. Nothing is printed inside OnEntitySpawned, which fires constantly; the optional Announce Cooldown On Spawn message is the only chat output and is off by default.

  6. Admin console commands

    bagcooldown.set <seconds> and bagcooldown.vip <seconds> are [ConsoleCommand] methods. They parse with arg.GetFloat(0, -1f) and refuse negative values. CanUseAdminCommand allows the server console and RCON with no permission check, and in-game players must be IsAdmin or hold bagcooldown.admin. A valid change writes to the config, calls SaveConfig(), runs ApplyToAll() and replies with the new value.

  7. Unload

    The plugin declares no timers, no UI and no data file, so unloading needs no cleanup. The UnlockTime values already written to bags stay in place until those bags expire or the server restarts. Reloading the plugin runs LoadConfig, Init and OnServerInitialized again, and the startup sweep re-reads the current config values.

Install it on your server

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

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

Variations

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

  • Command-based relief

    Players can run /unlock once every 24 hours to clear a single sleeping bag cooldown.

    Generate this version
  • Per-prefab cooldowns

    Beds get shorter cooldowns than sleeping bags, all set in config.

    Generate this version
  • Cooldown scaling with team size

    Each teammate assigned to the same bag adds cooldown.

    Generate this version

FAQ

Does this work on Carbon?

Yes. Carbon loads Oxide plugins unchanged. Drop the file in oxide/plugins, then reload the plugin from the Carbon console or restart the server. Permissions are registered in Init and are visible to oxide.grant on Oxide and to Carbon's permission commands.

Why write UnlockTime on spawn instead of only on respawn?

SleepingBag.UnlockTime is the field Rust reads when a player tries to respawn. Writing it in OnEntitySpawned means a freshly placed bag is locked immediately. OnPlayerRespawned only refreshes bags for VIP owners, so non-VIP bags are handled once at spawn by the sweep and by the spawn hook.

Do existing bags get updated after a restart?

Yes. OnServerInitialized calls ApplyToAll, which walks BaseNetworkable.serverEntities and reapplies the resolved cooldown to every SleepingBag. Bags placed before the plugin was installed or while it was unloaded are covered by that sweep.

Can the VIP cooldown be zero?

Yes. Set VIP Cooldown Seconds to 0 with bagcooldown.vip 0 or by editing the config, grant bagcooldown.vip, and bags placed by VIP holders lock to the current time, which means they are usable immediately. Math.Max keeps the value from going negative.

Do I need to restart after editing the config?

Reload the plugin after editing the JSON so LoadConfig runs again. Using the console commands avoids that: bagcooldown.set and bagcooldown.vip save the config and call ApplyToAll straight away.

Customize it for your server

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

Prompt: Create an Oxide plugin called BagCooldown that overrides sleeping bag and bed respawn cooldown to 300 seconds by default and 60 seconds for permission bagcooldown.vip, with console commands bagcooldown.set and bagcooldown.vip.
Customize for my server

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

Last updated Oct 26, 2026.

Customize sleeping bag cooldown

Customize