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
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.
{
"Default Cooldown Seconds": 300,
"VIP Cooldown Seconds": 60,
"Announce Cooldown On Spawn": false
}Commands and permissions
| Command | What it does | Permission |
|---|---|---|
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 |
| Permission | Grants |
|---|---|
bagcooldown.vip | Grants the VIP sleeping bag and bed cooldown to bags placed by that player. |
bagcooldown.admin | Allows the bagcooldown.set and bagcooldown.vip console commands. |
How the code works
Store the cooldowns in config
The nested
PluginConfigclass holdsDefault Cooldown Seconds,VIP Cooldown SecondsandAnnounce Cooldown On Spawn.LoadDefaultConfigseeds the first two with 300 and 60, so a fresh install leaves standard bags at Rust's stock value.LoadConfigguards against a corrupt file by resetting to defaults.ResolveCooldownclamps both values to a minimum of 0 withMath.Max, so a hand-edited negative number cannot reach the bag.Catch newly spawned bags and beds
OnEntitySpawned(BaseNetworkable entity)fires for every networkable entering the world. The plugin casts toSleepingBag, which covers sleeping bags, beds and beach towels in current Rust builds.ResolveCooldownpicks the VIP or default value based onbag.deployerUserIDand thebagcooldown.vippermission.ApplyCooldownwritesFacepunch.Math.Epoch.Currentplus the seconds intobag.UnlockTime, then callsSendNetworkUpdate. Non-bag entities return immediately, keeping the hook cheap on a busy server.Refresh on respawn for VIP bags
OnPlayerRespawned(BasePlayer player)runs after a player finishes respawning. The plugin checksplayer.IsConnectedand thebagcooldown.vippermission; if the player is not VIP it returns without work. For a VIP, it walksBaseNetworkable.serverEntities, casts each toSleepingBag, and reapplies the resolved cooldown only to bags whosedeployerUserIDmatchesplayer.userID.Get(). That keeps VIP bags short even if they were placed in a different session.Sweep existing bags at startup
OnServerInitializedcallsApplyToAll, which loopsBaseNetworkable.serverEntitiesand applies the resolved cooldown to everySleepingBag. This covers bags and beds that were placed before the plugin loaded.ApplyToAllalso runs after each console command edit, so changing a cooldown takes effect on existing bags without a server restart.Null and destroy guards
OnEntitySpawnedandOnPlayerRespawnednull-check their argument before touching anything.ApplyCooldownalso returns early if the bag is null orbag.IsDestroyed, so a bag killed between the cast and the write cannot throw. Nothing is printed insideOnEntitySpawned, which fires constantly; the optionalAnnounce Cooldown On Spawnmessage is the only chat output and is off by default.Admin console commands
bagcooldown.set <seconds>andbagcooldown.vip <seconds>are[ConsoleCommand]methods. They parse witharg.GetFloat(0, -1f)and refuse negative values.CanUseAdminCommandallows the server console and RCON with no permission check, and in-game players must beIsAdminor holdbagcooldown.admin. A valid change writes to the config, callsSaveConfig(), runsApplyToAll()and replies with the new value.Unload
The plugin declares no timers, no UI and no data file, so unloading needs no cleanup. The
UnlockTimevalues already written to bags stay in place until those bags expire or the server restarts. Reloading the plugin runsLoadConfig,InitandOnServerInitializedagain, and the startup sweep re-reads the current config values.
Install it on your server
Save BagCooldown.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/BagCooldown.json(Carbon:carbon/configs/BagCooldown.json). After editing, reload it:oxide.reload BagCooldown c.reload BagCooldown # CarbonGrant the permission
Give it to every player, or swapdefaultfor a group such asvip: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 versionPer-prefab cooldowns
Beds get shorter cooldowns than sleeping bags, all set in config.
Generate this versionCooldown 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.
Edit the prompt on the next screen. 2 free plugins a month, no card.
More quality of life plugins
- BackpacksAdds a /backpack command that opens a per-player StorageContainer sized by permission, with contents persisted to oxide/data and optionally droppedโฆOnPlayerLootEndOnPlayerDisconnectedOnPlayerDeath
- Crafting speedA plugin that speeds up crafting by scaling the duration of ItemCraftTask instances when a craft starts, using a global multiplier plus optionalโฆOnItemCraft
- Day and night controlSkips or shortens night by advancing the in-game clock to a configurable morning time, and adds /day and /night admin commands.OnServerInitialized
Last updated Oct 26, 2026.
Customize sleeping bag cooldown
Customize