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.
object OnCorpsePopulate(NPCPlayer npcPlayer, NPCPlayerCorpse npcCorpse)Parameters
| Parameter | Type | Description |
|---|---|---|
npcPlayer | NPCPlayer | The 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. |
npcCorpse | NPCPlayerCorpse | The 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
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
ApplyLootis skipped and only your loot remains.Tag or name PvE corpses for kill logging
Record the corpse's
net.IDand the NPC type so a laterCanLootEntityorOnLootEntityhandler 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.CreateCorpsecall passesNPCPlayer, the Gen2 ragdoll call passesBaseEntity. Declaring onlyNPCPlayersilently skips Gen2 corpses; declare both overloads if you need full coverage.Returning the corpse skips default loot
If you return the
BaseCorpse, the game'sApplyLootcall 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.inventoryexpecting 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.
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