Skip to content
RustPluginGenerator

How to make a Rust respawn loadout plugin (save belt and clothing, restore on respawn)

Lets players save their belt and clothing items with /loadout save, then automatically hands that gear back when they respawn. Uses the OnPlayerRespawned hook plus a per-player permission and cooldown.

Every wipe players rebuild the same kit: weapon, ammo, meds, armour. Forcing them to re-craft or run back to a stash after each death slows the server and pushes people to competitors. A saved loadout keeps the fun part of dying (losing your gear) while removing the chore of rebuilding it from scratch. This plugin stores only the shortnames and amounts of items in the belt and clothing slots, so conditions, skins and mods are not cloned. A configurable whitelist limits which shortnames can be saved, the save command has a cooldown, and players need the savedrespawnloadout.use permission to save or be restored. On OnPlayerRespawned the plugin checks the permission, looks up the saved entry and gives each item back with ItemManager.CreateByName, dropping anything that will not fit. Everything persists through the Oxide data file, so saved loadouts survive restarts and are wiped with the file.

The plugin code

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

namespace Oxide.Plugins
{
    [Info("Saved Respawn Loadout", "RustPluginGenerator", "1.0.0")]
    [Description("Saves a player's belt and clothing items and restores them on respawn")]
    public class SavedRespawnLoadout : RustPlugin
    {
        private const string PermUse = "savedrespawnloadout.use";

        private PluginConfig config;
        private StoredData data;
        private readonly Dictionary<ulong, DateTime> lastSave = new Dictionary<ulong, DateTime>();

        private class PluginConfig
        {
            [JsonProperty("Whitelisted shortnames")]
            public List<string> WhitelistedShortnames = new List<string>
            {
                "rifle.ak", "rifle.bolt", "smg.mp5", "pistol.semiauto",
                "ammo.rifle", "ammo.pistol", "syringe.medical", "bandage",
                "hoodie", "pants", "shoes.boots", "metal.facemask"
            };

            [JsonProperty("Save cooldown (seconds)")]
            public double SaveCooldownSeconds = 60.0;

            [JsonProperty("Remove loadout after respawn")]
            public bool RemoveAfterRespawn = false;
        }

        private class LoadoutEntry
        {
            [JsonProperty("Shortname")]
            public string Shortname;

            [JsonProperty("Amount")]
            public int Amount;
        }

        private class StoredData
        {
            [JsonProperty("Loadouts")]
            public Dictionary<string, List<LoadoutEntry>> Loadouts = new Dictionary<string, List<LoadoutEntry>>();
        }

        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>
            {
                ["NoPermission"] = "You do not have permission to use loadouts.",
                ["Saved"] = "Saved {0} item(s) to your respawn loadout.",
                ["SaveEmpty"] = "Nothing in your belt or clothing could be saved.",
                ["Cleared"] = "Your saved respawn loadout was deleted.",
                ["Cooldown"] = "You can save again in {0} second(s).",
                ["Restored"] = "Your saved loadout was returned to you.",
                ["Usage"] = "Usage: /loadout save or /loadout clear"
            }, 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);
            data = Interface.Oxide.DataFileSystem.ReadObject<StoredData>(Name) ?? new StoredData();
        }

        private void Unload()
        {
            Interface.Oxide.DataFileSystem.WriteObject(Name, data);
        }

        private bool IsWhitelisted(string shortname)
        {
            return config.WhitelistedShortnames != null && config.WhitelistedShortnames.Contains(shortname);
        }

        private void AddFromContainer(ItemContainer container, List<LoadoutEntry> result)
        {
            if (container == null) return;
            foreach (var item in container.itemList)
            {
                if (item?.info == null) continue;
                if (!IsWhitelisted(item.info.shortname)) continue;
                if (ItemManager.FindItemDefinition(item.info.shortname) == null) continue;
                result.Add(new LoadoutEntry { Shortname = item.info.shortname, Amount = item.amount });
            }
        }

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

            if (args == null || args.Length == 0)
            {
                player.ChatMessage(Msg("Usage", player.UserIDString));
                return;
            }

            switch (args[0].ToLower())
            {
                case "save":
                    SaveLoadout(player);
                    break;
                case "clear":
                    ClearLoadout(player);
                    break;
                default:
                    player.ChatMessage(Msg("Usage", player.UserIDString));
                    break;
            }
        }

        private void SaveLoadout(BasePlayer player)
        {
            var now = DateTime.UtcNow;
            DateTime previous;
            if (lastSave.TryGetValue(player.userID, out previous))
            {
                var remaining = config.SaveCooldownSeconds - (now - previous).TotalSeconds;
                if (remaining > 0)
                {
                    player.ChatMessage(Msg("Cooldown", player.UserIDString, Math.Ceiling(remaining)));
                    return;
                }
            }

            var entries = new List<LoadoutEntry>();
            AddFromContainer(player.inventory?.containerBelt, entries);
            AddFromContainer(player.inventory?.containerWear, entries);

            if (entries.Count == 0)
            {
                player.ChatMessage(Msg("SaveEmpty", player.UserIDString));
                return;
            }

            data.Loadouts[player.UserIDString] = entries;
            lastSave[player.userID] = now;
            Interface.Oxide.DataFileSystem.WriteObject(Name, data);
            player.ChatMessage(Msg("Saved", player.UserIDString, entries.Count));
        }

        private void ClearLoadout(BasePlayer player)
        {
            if (data != null && data.Loadouts.Remove(player.UserIDString))
            {
                Interface.Oxide.DataFileSystem.WriteObject(Name, data);
            }
            player.ChatMessage(Msg("Cleared", player.UserIDString));
        }

        private void OnPlayerRespawned(BasePlayer player)
        {
            if (player == null || data == null) return;
            if (!permission.UserHasPermission(player.UserIDString, PermUse)) return;

            List<LoadoutEntry> entries;
            if (!data.Loadouts.TryGetValue(player.UserIDString, out entries) || entries == null || entries.Count == 0) return;

            foreach (var entry in entries)
            {
                if (entry == null || string.IsNullOrEmpty(entry.Shortname)) continue;
                var item = ItemManager.CreateByName(entry.Shortname, Mathf.Max(1, entry.Amount));
                if (item == null) continue;
                player.GiveItem(item);
            }

            player.ChatMessage(Msg("Restored", player.UserIDString));

            if (config.RemoveAfterRespawn)
            {
                data.Loadouts.Remove(player.UserIDString);
                Interface.Oxide.DataFileSystem.WriteObject(Name, data);
            }
        }
    }
}

Hooks used: OnPlayerRespawned

Config file

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

SavedRespawnLoadout.json
{
  "Whitelisted shortnames": [
    "rifle.ak",
    "rifle.bolt",
    "smg.mp5",
    "pistol.semiauto",
    "ammo.rifle",
    "ammo.pistol",
    "syringe.medical",
    "bandage",
    "hoodie",
    "pants",
    "shoes.boots",
    "metal.facemask"
  ],
  "Save cooldown (seconds)": 60,
  "Remove loadout after respawn": false,
  "Shortname": null,
  "Amount": 0,
  "Loadouts": {}
}

Commands and permissions

Chat and console commands
CommandWhat it doesPermission
/loadout saveSaves the shortnames and amounts of your belt and clothing slots. Respects the whitelist and cooldown.savedrespawnloadout.use
/loadout clearDeletes your saved loadout so you respawn with nothing restored.savedrespawnloadout.use
Permissions
PermissionGrants
savedrespawnloadout.useAllows saving a loadout with /loadout save and having it restored on respawn

How the code works

  1. Saving belt and clothing slots

    AddFromContainer walks player.inventory.containerBelt and player.inventory.containerWear, iterating container.itemList. For each entry it skips items whose info.shortname is not listed in Whitelisted shortnames, and skips shortnames that ItemManager.FindItemDefinition cannot resolve. Surviving items are copied into a LoadoutEntry holding only Shortname and Amount, so skins, conditions and attachments are never stored.

  2. Restoring on respawn

    OnPlayerRespawned(BasePlayer player) fires after the player is alive again. The plugin confirms permission.UserHasPermission against PermUse, then looks up the UserIDString key in data.Loadouts. For each stored entry it calls ItemManager.CreateByName(entry.Shortname, Mathf.Max(1, entry.Amount)); null results are skipped. player.GiveItem(item) places the item in the inventory or drops it at the player's feet when slots are full.

  3. Whitelist and cooldown in config

    PluginConfig exposes Whitelisted shortnames, Save cooldown (seconds) and Remove loadout after respawn. SaveLoadout compares DateTime.UtcNow against the timestamp kept in the lastSave dictionary, and when the remaining time is positive it replies with the Cooldown message. No timers are involved, so nothing needs tearing down in Unload.

  4. Permission and command handling

    Init registers PermUse (savedrespawnloadout.use) with permission.RegisterPermission. The single [ChatCommand("loadout")] method checks the permission first, then switches on args[0] to reach SaveLoadout or ClearLoadout. Every reply goes through Msg, which pulls text from LoadDefaultMessages, so no chat string is hard-coded.

  5. Persistence through the data file

    StoredData holds a Loadouts dictionary keyed by UserIDString with LoadoutEntry lists as values. Init reads it with Interface.Oxide.DataFileSystem.ReadObject<StoredData>(Name), falling back to a new instance when the file is missing. Saving and clearing both call WriteObject immediately, and Unload writes once more so the state survives a restart.

  6. No duplication across respawns

    /loadout clear removes the player's key from Loadouts and writes the file. When Remove loadout after respawn is true, OnPlayerRespawned deletes the entry right after restoring it, so a player receives the gear exactly once. This makes the feature safe for one-life events and stops loadouts from stacking up over repeated deaths.

Install it on your server

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

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

Variations

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

  • Restore only for specific teams

    Limit restoration to players on a team so solo players cannot use the feature.

    Generate this version
  • Charge a scrap cost per respawn

    Require scrap in the inventory before the saved loadout is handed back.

    Generate this version
  • Save condition and skin

    Restore the exact weapon condition and skin rather than a fresh item.

    Generate this version

FAQ

Are weapon attachments and skins saved?

No. Only the shortname and amount of each item in the belt and clothing slots is stored. ItemManager.CreateByName gives a fresh item with default skin and condition, which keeps the feature simple and avoids copying a broken modded weapon back into play.

How do I restrict which items can be saved?

Edit Whitelisted shortnames in oxide/config/SavedRespawnLoadout.json. Only shortnames in that list are recorded by /loadout save, and the same list is checked when creating items on respawn. Shortnames that do not resolve through ItemManager.FindItemDefinition are ignored.

Why did my loadout not come back after dying?

Check that you have saved with /loadout save, that you still hold savedrespawnloadout.use, and that your belt and clothing slots were not full when you respawned. Anything that will not fit is dropped at your position by GiveItem.

Does this work on Carbon?

Yes. Carbon loads the same Oxide plugin file. Drop SavedRespawnLoadout.cs into oxide/plugins, run oxide.reload SavedRespawnLoadout, and the config and data files are created on first load. No separate Carbon build or patcher is needed.

Where is the loadout data stored?

In oxide/data/SavedRespawnLoadout.json, written with Interface.Oxide.DataFileSystem. Deleting that file wipes every saved loadout. The file is saved on plugin unload, so give the server a moment before shutting down hard.

Customize it for your server

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

Prompt: Create a Rust Oxide plugin named SavedRespawnLoadout that saves belt and clothing items with /loadout save, respects a config whitelist and cooldown, and restores them in OnPlayerRespawned using the savedrespawnloadout.use permission.
Customize for my server

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

Last updated Oct 26, 2026.

Customize saved respawn loadout

Customize