Skip to content
RustPluginGenerator

How to make a Rust death penalty plugin (scrap transfer on PvP death)

Moves a configurable percentage of a dead player's carried scrap to their killer using OnPlayerDeath, with min/max clamps and separate handling for team kills and NPC kills.

On most Rust servers dying is cheap: you lose the loot you were carrying, but the scrap in your inventory just sits in the bag for whoever finds it. A death penalty makes PvP losses matter by taking a slice of the victim's scrap at the moment of death and handing it directly to the killer, instead of leaving it on the corpse.

The tricky parts are the edge cases. Suicide and fall damage should not pay anyone. Team kills should not let groups farm each other. NPC kills (scientists, animals) have no player killer, so there is nothing to transfer. And a flat percentage is unfair at both ends: 10% of 40 scrap is nothing, 10% of 8000 is brutal.

The plugin below handles all of that through OnPlayerDeath. It counts the scrap in the victim's main inventory, applies the percentage, clamps the result between a minimum and maximum, removes it from the victim and gives it to the killer. Team kills and NPC kills are configurable: either ignore the transfer or apply a flat penalty that is destroyed rather than paid out.

The plugin code

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

namespace Oxide.Plugins
{
    [Info("Death Penalty", "RustPluginGenerator", "1.0.0")]
    [Description("Moves a percentage of the victim's carried scrap to the killer on PvP death.")]
    public class DeathPenalty : RustPlugin
    {
        private const string PermUse = "deathpenalty.use";

        private class PluginConfig
        {
            [JsonProperty("Percent of victim's scrap transferred")]
            public float PercentOfScrap { get; set; } = 0.15f;

            [JsonProperty("Minimum scrap moved per kill")]
            public int MinimumScrap { get; set; } = 10;

            [JsonProperty("Maximum scrap moved per kill")]
            public int MaximumScrap { get; set; } = 200;

            [JsonProperty("Ignore team kills")]
            public bool IgnoreTeamKills { get; set; } = true;

            [JsonProperty("Ignore NPC kills")]
            public bool IgnoreNpcKills { get; set; } = true;

            [JsonProperty("Notify victim and killer in chat")]
            public bool NotifyPlayers { get; set; } = true;
        }

        private PluginConfig config;
        private int scrapItemId;

        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>
            {
                ["PenaltyCharged"] = "You lost {0} scrap to {1}.",
                ["PenaltyReceived"] = "You received {0} scrap from {1}.",
                ["PenaltyDestroyed"] = "You lost {0} scrap.",
                ["CommandReply"] = "You carry {0} scrap. Death transfers {1}% between {2} and {3} scrap."
            }, 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 OnServerInitialized()
        {
            var definition = ItemManager.FindItemDefinition("scrap");
            if (definition != null) scrapItemId = definition.itemid;
        }

        [ChatCommand("deathpenalty")]
        private void CmdDeathPenalty(BasePlayer player, string command, string[] args)
        {
            if (player == null || !permission.UserHasPermission(player.UserIDString, PermUse)) return;
            var amount = scrapItemId == 0 ? 0 : player.inventory.GetAmount(scrapItemId);
            player.ChatMessage(Msg("CommandReply", player.UserIDString, amount,
                Mathf.RoundToInt(config.PercentOfScrap * 100f), config.MinimumScrap, config.MaximumScrap));
        }

        private object OnPlayerDeath(BasePlayer player, HitInfo info)
        {
            if (player == null || info == null || scrapItemId == 0) return null;
            if (player.IsNpc) return null;

            var killer = info.InitiatorPlayer;
            var isSelfKill = killer != null && killer.userID == player.userID;
            var isNpcKill = killer == null || killer.IsNpc;
            var isTeamKill = killer != null && !isSelfKill && player.currentTeam != 0 && player.currentTeam == killer.currentTeam;

            if (isNpcKill)
            {
                if (config.IgnoreNpcKills) return null;
                ChargeWithoutPayout(player);
                return null;
            }

            if (isSelfKill) return null;

            if (isTeamKill)
            {
                if (config.IgnoreTeamKills) return null;
                ChargeWithoutPayout(player);
                return null;
            }

            var held = player.inventory.GetAmount(scrapItemId);
            if (held <= 0) return null;

            var amount = Mathf.RoundToInt(held * config.PercentOfScrap);
            amount = Mathf.Clamp(amount, config.MinimumScrap, config.MaximumScrap);
            if (amount <= 0) return null;
            if (amount > held) amount = held;

            var taken = player.inventory.Take(new List<Item>(), scrapItemId, amount);
            if (taken <= 0) return null;

            var item = ItemManager.CreateByItemID(scrapItemId, taken);
            if (item == null)
            {
                player.inventory.GiveItem(ItemManager.CreateByItemID(scrapItemId, taken));
                return null;
            }

            killer.GiveItem(item);

            if (config.NotifyPlayers)
            {
                player.ChatMessage(Msg("PenaltyCharged", player.UserIDString, taken, killer.displayName));
                killer.ChatMessage(Msg("PenaltyReceived", killer.UserIDString, taken, player.displayName));
            }

            return null;
        }

        private void ChargeWithoutPayout(BasePlayer player)
        {
            var held = player.inventory.GetAmount(scrapItemId);
            if (held <= 0) return;

            var amount = Mathf.RoundToInt(held * config.PercentOfScrap);
            amount = Mathf.Clamp(amount, config.MinimumScrap, config.MaximumScrap);
            if (amount <= 0) return;
            if (amount > held) amount = held;

            var taken = player.inventory.Take(new List<Item>(), scrapItemId, amount);
            if (taken > 0 && config.NotifyPlayers)
                player.ChatMessage(Msg("PenaltyDestroyed", player.UserIDString, taken));
        }
    }
}

Hooks used: OnServerInitialized, OnPlayerDeath

Config file

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

DeathPenalty.json
{
  "Percent of victim's scrap transferred": 0.15,
  "Minimum scrap moved per kill": 10,
  "Maximum scrap moved per kill": 200,
  "Ignore team kills": true,
  "Ignore NPC kills": true,
  "Notify victim and killer in chat": true
}

Commands and permissions

Chat and console commands
CommandWhat it doesPermission
/deathpenaltyShows your current scrap balance and the configured penalty percentage and clamps.deathpenalty.use
Permissions
PermissionGrants
deathpenalty.useAllows use of the /deathpenalty chat command.

How the code works

  1. Decide if the death counts

    OnPlayerDeath(BasePlayer player, HitInfo info) fires before the corpse is populated. Return null and let the game continue. The hook reads info.InitiatorPlayer into killer. If it is null or a bot (killer.IsNpc) the death is an NPC kill; if killer.userID == player.userID it is a self-kill; if both players share a non-zero currentTeam it is a team kill. Self-kills always return early. NPC and team kills return early when the matching config flag is true, otherwise they fall through to ChargeWithoutPayout.

  2. Count and clamp the penalty

    player.inventory.GetAmount(scrapItemId) gives the carried scrap. scrapItemId is resolved once in OnServerInitialized from ItemManager.FindItemDefinition("scrap"), so nothing is hard-coded. The plugin multiplies by PercentOfScrap, rounds with Mathf.RoundToInt, then clamps with Mathf.Clamp between MinimumScrap and MaximumScrap. If the result is zero it bails out; if it exceeds the carried amount it is lowered to that amount, so Take never runs short.

  3. Move the scrap to the killer

    player.inventory.Take(new List<Item>(), scrapItemId, amount) removes the scrap and returns the amount actually taken. The plugin builds a fresh stack with ItemManager.CreateByItemID(scrapItemId, taken) and hands it over with killer.GiveItem(item). If GiveItem has no space the game drops the item at the killer's feet. Both players get a chat line through the language file with the Msg helper, gated by the NotifyPlayers config flag.

  4. Team kills and NPC deaths

    Two config flags control the awkward cases. IgnoreTeamKills (default true) means a teammate killing you does not pay out. IgnoreNpcKills (default true) means death by scientist, animal, Bradley or helicopter costs nothing. If you set either to false the plugin applies the penalty anyway but destroys the scrap instead of paying a killer, so groups cannot farm teammates for scrap. That path is the private ChargeWithoutPayout method, which takes the scrap and shows the PenaltyDestroyed message.

  5. Minimum and maximum per kill

    MinimumScrap and MaximumScrap in the config bound every transfer. MinimumScrap works as a floor: if the percentage result is below it and the victim has at least that much scrap, the floor is charged. MaximumScrap caps the payout so a single death cannot move an entire wipe's worth of scrap. A common setup is 15%, min 10, max 200. The clamp runs after the percentage is applied and before the item is taken.

  6. Command and permission

    /deathpenalty is registered with [ChatCommand("deathpenalty")] and requires the deathpenalty.use permission, registered in Init(). It replies with the player's current scrap from player.inventory.GetAmount(scrapItemId) plus the configured percentage and clamp values, all formatted into the CommandReply language entry. Text is pulled from the language file with the Msg helper, so admins can edit oxide/lang/en/DeathPenalty.json without touching code.

Install it on your server

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

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

Variations

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

  • Transfer a random item instead of scrap

    On death, move one random stack from the victim's belt to the killer instead of a scrap percentage.

    Generate this version
  • Wallet-based penalty

    Charge a scrap amount from the victim's stored balance rather than their inventory and pay the killer.

    Generate this version
  • Notify and log the payout

    Extend the recipe so every transfer is printed to the server console for admin review.

    Generate this version

FAQ

Does the penalty come out of the corpse or the inventory?

The plugin runs in OnPlayerDeath before the corpse is populated, so the scrap is removed from the player's inventory first. The killer receives it directly and the corpse contains whatever was left. The killer does not need to loot the body to get the penalty.

Will this double-charge if multiple plugins modify death?

No, but if another plugin also removes items in OnPlayerDeath the order between them depends on load order. If you run a loot plugin that changes death loot, test both together on a staging server. If both move scrap, you will see less than expected per death.

How do I install this on Oxide or Carbon?

Drop DeathPenalty.cs in oxide/plugins/, reload or restart, then edit oxide/config/DeathPenalty.json and run oxide.reload DeathPenalty. Carbon loads the same file. The plugin needs no other dependencies and registers one permission.

What happens when the killer's inventory is full?

BasePlayer.GiveItem drops the item on the ground at the player's feet when there is no space. The victim still loses the scrap, so the penalty is applied and the killer can pick it up. No extra code is needed for this case.

Can players opt out of the penalty?

There is no opt-out. If you want one, add a permission such as deathpenalty.exempt and check permission.UserHasPermission(player.UserIDString, PermExempt) at the top of OnPlayerDeath before any further logic. That would mean the victim keeps their scrap and the killer gets nothing.

Customize it for your server

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

Prompt: Write an Oxide Rust plugin that on player death moves a configured percentage of the victim's carried scrap to the killer, clamped between minimum and maximum amounts, ignoring team kills and NPC kills by default.
Customize for my server

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

Last updated Oct 26, 2026.

Customize death penalty

Customize