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
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.
{
"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
| Command | What it does | Permission |
|---|---|---|
/deathpenalty | Shows your current scrap balance and the configured penalty percentage and clamps. | deathpenalty.use |
| Permission | Grants |
|---|---|
deathpenalty.use | Allows use of the /deathpenalty chat command. |
How the code works
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 readsinfo.InitiatorPlayerintokiller. If it is null or a bot (killer.IsNpc) the death is an NPC kill; ifkiller.userID == player.userIDit is a self-kill; if both players share a non-zerocurrentTeamit 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 toChargeWithoutPayout.Count and clamp the penalty
player.inventory.GetAmount(scrapItemId)gives the carried scrap.scrapItemIdis resolved once inOnServerInitializedfromItemManager.FindItemDefinition("scrap"), so nothing is hard-coded. The plugin multiplies byPercentOfScrap, rounds withMathf.RoundToInt, then clamps withMathf.ClampbetweenMinimumScrapandMaximumScrap. If the result is zero it bails out; if it exceeds the carried amount it is lowered to that amount, soTakenever runs short.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 withItemManager.CreateByItemID(scrapItemId, taken)and hands it over withkiller.GiveItem(item). IfGiveItemhas no space the game drops the item at the killer's feet. Both players get a chat line through the language file with theMsghelper, gated by theNotifyPlayersconfig flag.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 privateChargeWithoutPayoutmethod, which takes the scrap and shows thePenaltyDestroyedmessage.Minimum and maximum per kill
MinimumScrapandMaximumScrapin the config bound every transfer.MinimumScrapworks as a floor: if the percentage result is below it and the victim has at least that much scrap, the floor is charged.MaximumScrapcaps 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.Command and permission
/deathpenaltyis registered with[ChatCommand("deathpenalty")]and requires thedeathpenalty.usepermission, registered inInit(). It replies with the player's current scrap fromplayer.inventory.GetAmount(scrapItemId)plus the configured percentage and clamp values, all formatted into theCommandReplylanguage entry. Text is pulled from the language file with theMsghelper, so admins can editoxide/lang/en/DeathPenalty.jsonwithout touching code.
Install it on your server
Save DeathPenalty.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/DeathPenalty.json(Carbon:carbon/configs/DeathPenalty.json). After editing, reload it:oxide.reload DeathPenalty c.reload DeathPenalty # CarbonGrant the permission
Give it to every player, or swapdefaultfor a group such asvip: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 versionWallet-based penalty
Charge a scrap amount from the victim's stored balance rather than their inventory and pay the killer.
Generate this versionNotify 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.
Edit the prompt on the next screen. 2 free plugins a month, no card.
More economy plugins
- BountiesA scrap bounty system where players fund a pool on a target and the killer collects it, using the OnEntityDeath hook plus a data file forโฆOnServerInitializedOnEntityDeath
- Chat shopA chat-driven shop where players spend scrap from their inventory to buy configured items.
- Daily rewardsAdds a /daily command that lets each player claim one reward from a configurable weighted list every 24 hours, with a streak bonus that scales whileโฆOnServerInitializedOnServerSave
Last updated Oct 26, 2026.
Customize death penalty
Customize