Skip to content
RustPluginGenerator

OnCorpsePopulate

OnCorpsePopulate is called when an NPC is converted into a lootable corpse, right before the game fills the corpse container with default loot. Returning a BaseCorpse replaces the corpse entirely and skips the default loot fill.

Signature
object OnCorpsePopulate(NPCPlayer npcPlayer, NPCPlayerCorpse npcCorpse)

Parameters

Hook parameters
ParameterTypeDescription
npcPlayerNPCPlayerThe NPC whose body is being turned into a corpse. At the NPCPlayer.CreateCorpse site this is the dying NPC itself; at the AI ragdoll site it is typed as BaseEntity, so use `as NPCPlayer` or check the type before casting.
npcCorpseNPCPlayerCorpseThe freshly spawned NPCPlayerCorpse entity. Its inventory is filled from the NPC's loot after the hook returns, so you can inspect or modify the corpse, its position and its owners here.

Return value

At NPCPlayer.CreateCorpse, returning a BaseCorpse (for example the same corpse after you filled it) becomes the method result and the game's ApplyLoot call is skipped, so the default loot is not added. Returning null lets ApplyLoot run normally. At the Gen2 ragdoll site any non-null return stops the loot-slot loop from running, so the corpse keeps only what you put in it.

How it works

Oxide fires OnCorpsePopulate from two places. The first is NPCPlayer.CreateCorpse, which runs whenever a standard NPCPlayer (Scientist, Tunnel Dweller and similar spawner-driven NPCs) dies and drops a body; here the second argument is the NPCPlayerCorpse that was just created and spawned. The second is Rust.Ai.Gen2.State_Dead.StartRagdoll, used by Gen2 AI entities, where the first argument is typed as BaseEntity and the corpse arrives as a LootableCorpse.

At the NPCPlayer site the hook runs after the corpse exists but before ApplyLoot fills its container, so it is the right moment to seed custom loot, strip the body, or move it. At the Gen2 site the hook runs before the loot slot loop runs, and the same early-return rule applies.

Oxide only calls your method when the arguments match your declared parameter types. Declaring NPCPlayer means the Gen2 call (which passes a plain BaseEntity) is skipped; declaring BaseEntity catches both. Many plugins declare both overloads — a specific one and a generic one — to cover each path.

Other signatures

Oxide also calls OnCorpsePopulate from other places in the game with these parameter types. Declare the overload you need, or several:

  • object OnCorpsePopulate(BaseEntity Owner, LootableCorpse lootableCorpse)

Example plugin: Apply custom loot to scientist corpses

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

namespace Oxide.Plugins
{
    [Info("Npc Corpse Loot", "RustPluginGenerator", "1.0.0")]
    [Description("Replaces default NPC corpse loot with configured drops")]
    public class NpcCorpseLoot : RustPlugin
    {
        private const string PermUse = "npccorpseloot.use";

        private PluginConfig config;

        private class PluginConfig
        {
            [JsonProperty("Use custom loot only for players with permission")]
            public bool RequirePermission { get; set; } = false;

            [JsonProperty("Custom loot entries (shortname, min, max)")]
            public List<LootEntry> Loot { get; set; } = new List<LootEntry>
            {
                new LootEntry { Shortname = "scrap", Min = 5, Max = 15 },
                new LootEntry { Shortname = "rifle.ak", Min = 1, Max = 1 },
                new LootEntry { Shortname = "ammo.rifle", Min = 15, Max = 30 }
            };
        }

        private class LootEntry
        {
            [JsonProperty("Shortname")] public string Shortname { get; set; }
            [JsonProperty("Min")] public int Min { get; set; }
            [JsonProperty("Max")] public int Max { get; set; }
        }

        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);

        private void Init()
        {
            permission.RegisterPermission(PermUse, this);
        }

        private object OnCorpsePopulate(NPCPlayer npc, NPCPlayerCorpse corpse)
        {
            if (npc == null || corpse == null) return null;

            var container = corpse.containers != null && corpse.containers.Length > 0 ? corpse.containers[0] : null;
            if (container == null) return null;

            if (config.RequirePermission)
            {
                var killer = corpse.playerSteamID != 0UL ? BasePlayer.FindByID(corpse.playerSteamID) : null;
                if (killer == null || !permission.UserHasPermission(killer.UserIDString, PermUse))
                    return null;
            }

            container.Clear();
            ApplyCustomLoot(container);
            corpse.SendNetworkUpdate();

            return corpse;
        }

        private void ApplyCustomLoot(ItemContainer container)
        {
            if (container == null || config?.Loot == null) return;

            foreach (var entry in config.Loot)
            {
                if (entry == null || string.IsNullOrEmpty(entry.Shortname)) continue;

                var def = ItemManager.FindItemDefinition(entry.Shortname);
                if (def == null) continue;

                int amount = UnityEngine.Random.Range(entry.Min, entry.Max + 1);
                if (amount <= 0) continue;

                var item = ItemManager.Create(def, amount);
                item?.MoveToContainer(container);
            }
        }
    }
}

The plugin listens on OnCorpsePopulate for NPCPlayer deaths. It reads a configurable list of shortnames and stack ranges, clears the corpse's first container, rolls each entry and inserts the resulting items into the corpse inventory, then calls SendNetworkUpdate on the corpse. When Use custom loot only for players with permission is enabled, the custom loot is applied only if the corpse's playerSteamID resolves to a player holding the npccorpseloot.use permission; otherwise the hook returns null and the default pipeline runs untouched. The plugin does not register any lang messages.

Common use cases

  • Replace NPC default loot with server-configured drops

    Clear the corpse container and roll custom items, then return the corpse so ApplyLoot is skipped and only your loot remains.

  • Tag or name PvE corpses for kill logging

    Record the corpse's net.ID and the NPC type so a later CanLootEntity or OnLootEntity handler can attribute the kill to a player.

  • Move or tint the corpse for events

    Adjust the corpse transform or skin before it settles, for example dropping event loot at a marker position instead of the death spot.

Pitfalls

  • Two different signatures, two different argument types

    The NPCPlayer.CreateCorpse call passes NPCPlayer, the Gen2 ragdoll call passes BaseEntity. Declaring only NPCPlayer silently skips Gen2 corpses; declare both overloads if you need full coverage.

  • Returning the corpse skips default loot

    If you return the BaseCorpse, the game's ApplyLoot call is not executed, so any items you expected from the NPC's loot table will not appear. Only return a corpse when you fully control what goes inside.

  • The hook fires before loot is applied

    Do not read corpse.inventory expecting NPC loot to be present yet; the container is typically empty at the time the hook runs. Populate the container yourself if you want items inside.

Plugin recipes that use OnCorpsePopulate

Build a plugin with OnCorpsePopulate

Describe what should happen and get a compiled plugin that uses this hook.

Prompt: Replace NPC default loot with server-configured drops using the OnCorpsePopulate hook, with a config file and a permission
Generate this plugin

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

Last updated Oct 26, 2026. Found a mistake? Email support@rustplugingenerator.com.

Build a plugin with OnCorpsePopulate

Generate