How to make a Rust NPC kill rewards plugin (economy payouts)
Pays a configurable item or Economics reward when a player lands the killing blow on a scientist, animal, Bradley APC or patrol helicopter, using OnEntityDeath with a prefab-name reward table.
Admins who run an economy want PvE to feed into it: killing scientists and animals should be worth something beyond their loot. The tricky part is knowing who actually killed the entity, because OnEntityDeath gives you a HitInfo that is null for many deaths. The plugin below reads info.InitiatorPlayer, checks the entity's ShortPrefabName against a reward table, and pays out only for configured prefabs. Rewards are any item shortname, or an Economics deposit when that plugin is loaded and enabled. A per-player cooldown stops players farming a spawn point. Everything is keyed by prefab name so you can add new NPCs without touching code.
The plugin code
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;
using Oxide.Core.Plugins;
using UnityEngine;
namespace Oxide.Plugins
{
[Info("Npc Kill Rewards", "RustPluginGenerator", "1.0.0")]
[Description("Pays a configurable reward when a player kills a configured NPC prefab.")]
public class NpcKillRewards : RustPlugin
{
private const string PermReset = "npckillrewards.reset";
private const string PermAdmin = "npckillrewards.admin";
private PluginConfig config;
private readonly Dictionary<ulong, double> lastPaid = new Dictionary<ulong, double>();
[PluginReference] private Plugin Economics;
private class PluginConfig
{
[JsonProperty("Cooldown seconds per player")]
public double CooldownSeconds = 15.0;
[JsonProperty("Use Economics deposit")]
public bool UseEconomics = false;
[JsonProperty("Rewards")]
public Dictionary<string, RewardEntry> Rewards = new Dictionary<string, RewardEntry>
{
["scientistnpc_roam"] = new RewardEntry { Shortname = "scrap", Amount = 10 },
["scientistnpc_peacekeeper"] = new RewardEntry { Shortname = "scrap", Amount = 12 },
["scientistnpc_heavy"] = new RewardEntry { Shortname = "scrap", Amount = 25 },
["boar"] = new RewardEntry { Shortname = "scrap", Amount = 2 },
["stag"] = new RewardEntry { Shortname = "scrap", Amount = 3 },
["bear"] = new RewardEntry { Shortname = "scrap", Amount = 6 },
["wolf"] = new RewardEntry { Shortname = "scrap", Amount = 5 },
["bradleyapc"] = new RewardEntry { Shortname = "scrap", Amount = 150 },
["patrolhelicopter"] = new RewardEntry { Shortname = "scrap", Amount = 200 }
};
}
private class RewardEntry
{
[JsonProperty("Item shortname")]
public string Shortname = "scrap";
[JsonProperty("Amount")]
public int Amount = 10;
}
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>
{
["Reward"] = "+{1} {0} for killing {2}",
["Reset.Done"] = "Your kill cooldown has been cleared.",
["NoPermission"] = "You do not have permission to use this command.",
["Reloaded"] = "NpcKillRewards config reloaded."
}, 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(PermReset, this);
permission.RegisterPermission(PermAdmin, this);
}
private void OnEntityDeath(BaseCombatEntity entity, HitInfo info)
{
if (entity == null || info == null || entity.IsDestroyed) return;
var player = info.InitiatorPlayer;
if (player == null || player.IsNpc || player.IsDead()) return;
string prefab = entity.ShortPrefabName;
if (string.IsNullOrEmpty(prefab)) return;
RewardEntry reward;
if (!config.Rewards.TryGetValue(prefab, out reward) || reward == null) return;
if (reward.Amount <= 0 || string.IsNullOrEmpty(reward.Shortname)) return;
double now = Time.realtimeSinceStartup;
double last;
if (lastPaid.TryGetValue(player.userID, out last) && now - last < config.CooldownSeconds) return;
if (!PayReward(player, reward)) return;
lastPaid[player.userID] = now;
string itemName = ItemManager.FindItemDefinition(reward.Shortname)?.displayName?.english ?? reward.Shortname;
SendReply(player, Msg("Reward", player.UserIDString, itemName, reward.Amount, entity.ShortPrefabName));
}
private bool PayReward(BasePlayer player, RewardEntry reward)
{
if (config.UseEconomics && Economics != null)
{
object result = Economics.Call("Deposit", player.UserIDString, (double)reward.Amount);
if (result != null) return true;
PrintWarning("Economics deposit failed, falling back to item reward");
}
var item = ItemManager.CreateByName(reward.Shortname, reward.Amount, 0UL);
if (item == null) return false;
player.GiveItem(item);
return true;
}
private void OnPlayerDisconnected(BasePlayer player, string reason)
{
if (player == null) return;
lastPaid.Remove(player.userID);
}
[ChatCommand("npcreset")]
private void CmdReset(BasePlayer player, string command, string[] args)
{
if (player == null) return;
if (!permission.UserHasPermission(player.UserIDString, PermReset))
{
player.ChatMessage(Msg("NoPermission", player.UserIDString));
return;
}
lastPaid.Remove(player.userID);
player.ChatMessage(Msg("Reset.Done", player.UserIDString));
}
[ConsoleCommand("npckillrewards.reload")]
private void CcmdReload(ConsoleSystem.Arg arg)
{
var player = arg.Player();
if (player != null && !permission.UserHasPermission(player.UserIDString, PermAdmin))
{
arg.ReplyWith(Msg("NoPermission", player.UserIDString));
return;
}
LoadConfig();
arg.ReplyWith(Msg("Reloaded", player?.UserIDString));
}
private void Unload()
{
lastPaid.Clear();
}
}
}Hooks used: OnEntityDeath, OnPlayerDisconnected
Config file
Created automatically on first load at oxide/config/NpcKillRewards.json.
{
"Cooldown seconds per player": 15,
"Use Economics deposit": false,
"Rewards": {
"scientistnpc_roam": {
"Item shortname": "scrap",
"Amount": 10
},
"scientistnpc_peacekeeper": {
"Item shortname": "scrap",
"Amount": 12
},
"scientistnpc_heavy": {
"Item shortname": "scrap",
"Amount": 25
},
"boar": {
"Item shortname": "scrap",
"Amount": 2
},
"stag": {
"Item shortname": "scrap",
"Amount": 3
},
"bear": {
"Item shortname": "scrap",
"Amount": 6
},
"wolf": {
"Item shortname": "scrap",
"Amount": 5
},
"bradleyapc": {
"Item shortname": "scrap",
"Amount": 150
},
"patrolhelicopter": {
"Item shortname": "scrap",
"Amount": 200
}
}
}Commands and permissions
| Command | What it does | Permission |
|---|---|---|
/npcreset | Clears your own kill cooldown so the next kill pays out immediately. | npckillrewards.reset |
npckillrewards.reload | Reloads the config file from disk. | npckillrewards.admin |
| Permission | Grants |
|---|---|
npckillrewards.reset | Allows a player to clear their own kill cooldown with /npcreset. |
npckillrewards.admin | Allows use of the npckillrewards.reload console command. |
How the code works
Detect the kill and the killer
OnEntityDeath(BaseCombatEntity entity, HitInfo info)fires for every combat entity death. The plugin returns early whenentityis null,infois null orentity.IsDestroyedis true. It then readsinfo.InitiatorPlayerinto a local calledplayerand returns when that is null or the player is dead. Only real player kills therefore reach the payout, and deaths without a killing player are ignored.Filter to configured NPCs
The reward table is a dictionary inside the nested
PluginConfigclass, serialized with[JsonProperty("Rewards")]. The plugin storesentity.ShortPrefabNamein aprefabvariable and looks it up withconfig.Rewards.TryGetValue. When the key is missing or the entry has a zero amount or empty shortname, the hook returns. Values such asscientistnpc_roam,boar,bradleyapcandpatrolhelicopterare the keys you edit in the config file.Guard against farming
Each player's last paid kill time is kept in a
Dictionary<ulong, double>namedlastPaid, keyed byplayer.userIDand stamped withTime.realtimeSinceStartup. Before paying, the plugin compares the stored value againstconfig.CooldownSeconds. If the elapsed time is below that value the kill is skipped.OnPlayerDisconnectedremoves the player's entry so the dictionary does not grow forever.Pay the reward
PayRewardreceives the player and the matchedRewardEntry(fieldsShortnameandAmount). WhenUseEconomicsis true and the[PluginReference] Plugin Economicsis loaded, it callsEconomics.Call("Deposit", ...)and returns true if the result is not null. Otherwise it builds an item withItemManager.CreateByName(reward.Shortname, reward.Amount, 0UL)and delivers it withplayer.GiveItem. A failed deposit logs a warning and falls back to the item.Notify the player through lang
After a successful payout the plugin resolves the item's display name with
ItemManager.FindItemDefinition(reward.Shortname)?.displayName?.english, falling back to the raw shortname. It then sends the lang keyRewardthroughSendReply, formatted as+{1} {0} for killing {2}with the item name, the amount and the prefab name. The lang file lives inoxide/lang/en/NpcKillRewards.jsonand can be translated.Reload and reset commands
[ConsoleCommand("npckillrewards.reload")]checksnpckillrewards.adminwhen invoked by a player, then callsLoadConfig()so admins can edit the reward table without a restart.[ChatCommand("npcreset")]removes the caller's entry fromlastPaid, gated bynpckillrewards.reset. Both permissions are registered inInitwithpermission.RegisterPermission.
Install it on your server
Save NpcKillRewards.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/NpcKillRewards.json(Carbon:carbon/configs/NpcKillRewards.json). After editing, reload it:oxide.reload NpcKillRewards c.reload NpcKillRewards # CarbonGrant the permission
Give it to every player, or swapdefaultfor a group such asvip:oxide.grant group default npckillrewards.reset c.grant group default npckillrewards.reset # Carbon
Variations
Each one opens the generator with a ready prompt you can tweak.
Team-shared payouts
Split the reward across every alive member of the killer's team via RelationshipManager.ServerInstance.FindTeam.
Generate this versionBonus for headshots
Multiply the payout when HitInfo.isHeadshot is true.
Generate this versionDaily kill cap
Limit each player to a configurable number of paid kills per day using persisted data.
Generate this version
FAQ
Why do some kills not pay out?
OnEntityDeath passes a HitInfo that is null for many hazard and decay deaths, and InitiatorPlayer is null when the killing blow came from a trap, turret or another NPC. The plugin pays only when a player is resolved and the entity's short prefab name exists in the Rewards table. Add the exact prefab name to the config to cover a new NPC.
How do I find the short prefab name for an NPC?
The value is the prefab file name without the .prefab extension, which you can see in the server files. Examples are scientistnpc_roam, boar, bradleyapc and patrolhelicopter. Keys in the Rewards dictionary are case-sensitive and must match the entity's ShortPrefabName exactly.
Does this work with Economics, ServerRewards or custom currencies?
The plugin ships an Economics hook controlled by the Use Economics deposit config key. For ServerRewards or another currency plugin, change the plugin reference and the Call method name inside PayReward. If the referenced plugin is not loaded the deposit is skipped and the item reward is given instead.
How do I install it on Oxide or Carbon?
Place NpcKillRewards.cs in oxide/plugins and run oxide.reload NpcKillRewards or restart the server. Carbon loads the same file. The config is generated at oxide/config/NpcKillRewards.json on first load and the language file at oxide/lang/en/NpcKillRewards.json.
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 npc and boss kill rewards
Customize