Skip to content
RustPluginGenerator

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

NpcKillRewards.cs
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.

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

Chat and console commands
CommandWhat it doesPermission
/npcresetClears your own kill cooldown so the next kill pays out immediately.npckillrewards.reset
npckillrewards.reloadReloads the config file from disk.npckillrewards.admin
Permissions
PermissionGrants
npckillrewards.resetAllows a player to clear their own kill cooldown with /npcreset.
npckillrewards.adminAllows use of the npckillrewards.reload console command.

How the code works

  1. Detect the kill and the killer

    OnEntityDeath(BaseCombatEntity entity, HitInfo info) fires for every combat entity death. The plugin returns early when entity is null, info is null or entity.IsDestroyed is true. It then reads info.InitiatorPlayer into a local called player and 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.

  2. Filter to configured NPCs

    The reward table is a dictionary inside the nested PluginConfig class, serialized with [JsonProperty("Rewards")]. The plugin stores entity.ShortPrefabName in a prefab variable and looks it up with config.Rewards.TryGetValue. When the key is missing or the entry has a zero amount or empty shortname, the hook returns. Values such as scientistnpc_roam, boar, bradleyapc and patrolhelicopter are the keys you edit in the config file.

  3. Guard against farming

    Each player's last paid kill time is kept in a Dictionary<ulong, double> named lastPaid, keyed by player.userID and stamped with Time.realtimeSinceStartup. Before paying, the plugin compares the stored value against config.CooldownSeconds. If the elapsed time is below that value the kill is skipped. OnPlayerDisconnected removes the player's entry so the dictionary does not grow forever.

  4. Pay the reward

    PayReward receives the player and the matched RewardEntry (fields Shortname and Amount). When UseEconomics is true and the [PluginReference] Plugin Economics is loaded, it calls Economics.Call("Deposit", ...) and returns true if the result is not null. Otherwise it builds an item with ItemManager.CreateByName(reward.Shortname, reward.Amount, 0UL) and delivers it with player.GiveItem. A failed deposit logs a warning and falls back to the item.

  5. 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 key Reward through SendReply, formatted as +{1} {0} for killing {2} with the item name, the amount and the prefab name. The lang file lives in oxide/lang/en/NpcKillRewards.json and can be translated.

  6. Reload and reset commands

    [ConsoleCommand("npckillrewards.reload")] checks npckillrewards.admin when invoked by a player, then calls LoadConfig() so admins can edit the reward table without a restart. [ChatCommand("npcreset")] removes the caller's entry from lastPaid, gated by npckillrewards.reset. Both permissions are registered in Init with permission.RegisterPermission.

Install it on your server

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

    Give it to every player, or swap default for a group such as vip: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 version
  • Bonus for headshots

    Multiply the payout when HitInfo.isHeadshot is true.

    Generate this version
  • Daily 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.

Prompt: Create a Rust Oxide plugin called NpcKillRewards that pays scrap for killing scientists, animals, Bradley APC and patrol helicopter, keyed by short prefab name in config with a per-player cooldown.
Customize for my server

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

Last updated Oct 26, 2026.

Customize npc and boss kill rewards

Customize