OnLootEntityEnd
OnLootEntityEnd is called when a player closes the loot panel of an entity, for this signature specifically a LootableCorpse. It fires from LootableCorpse.PlayerStoppedLooting before the corpse's Open flag is cleared and its removal timer is reset.
void OnLootEntityEnd(BasePlayer player, LootableCorpse lootableCorpse)Parameters
| Parameter | Type | Description |
|---|---|---|
player | BasePlayer | The BasePlayer who closed the loot panel. This is the looter, not necessarily the owner of the container or corpse. |
lootableCorpse | LootableCorpse | The LootableCorpse that was being looted. Use it to read the corpse's identity, position, and inventory state after the player walks away from it. |
Return value
The return value is ignored at every injection site: PlayerStoppedLooting returns void and the injected call does not branch on the result. Return null or nothing; returning a value has no effect and will not cancel the flag reset or network update.
How it works
The hook fires when a player stops looting an entity. For the LootableCorpse overload it is injected at the top of LootableCorpse.PlayerStoppedLooting, which runs when the looter closes the corpse loot panel, presses the exit key, dies while looting, or otherwise leaves the corpse UI. The same hook name is also called for storage containers, dropped item containers, container IO entities, and flow restrictors, so a single method name can receive several entity types if you declare a common base type.
player is the BasePlayer closing the panel; lootableCorpse is the corpse itself. At call time the corpse still has its Open flag set and the loot UI may not yet be torn down. The hook runs before ResetRemovalTime, SetFlagLocal(Flags.Open, false) and SendNetworkUpdate.
Declare LootableCorpse to only receive corpse events; declare StorageContainer to catch boxes and other storage; declare a wider base (e.g. BaseEntity) if you want every stop-looting call to hit one method. The hook is not called when you simply cannot open the entity — that is CanLootEntity.
Other signatures
Oxide also calls OnLootEntityEnd from other places in the game with these parameter types. Declare the overload you need, or several:
void OnLootEntityEnd(BasePlayer player, StorageContainer storageContainer)void OnLootEntityEnd(BasePlayer player, ContainerIOEntity containerIOEntity)void OnLootEntityEnd(BasePlayer player, DroppedItemContainer droppedItemContainer)void OnLootEntityEnd(BasePlayer player, ItemBasedFlowRestrictor itemBasedFlowRestrictor)
Example plugin: Log corpse looting and notify the owner
using UnityEngine;
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;
namespace Oxide.Plugins
{
[Info("Corpse Loot Alert", "RustPluginGenerator", "1.0.0")]
[Description("Notifies corpse owners and admins when someone closes the loot panel of a corpse.")]
public class CorpseLootAlert : RustPlugin
{
private const string PermAdmin = "corpselootalert.admin";
private readonly Dictionary<ulong, float> lastNotify = new Dictionary<ulong, float>();
private class PluginConfig
{
[JsonProperty("Notify online owners")]
public bool NotifyOwner { get; set; } = true;
[JsonProperty("Notify admins with permission")]
public bool NotifyAdmins { get; set; } = true;
[JsonProperty("Minimum seconds between notifications per looter")]
public float CooldownSeconds { get; set; } = 3f;
}
private PluginConfig config;
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>
{
["OwnerNotice"] = "{0} was seen leaving your corpse at {1}",
["AdminNotice"] = "{0} looted corpse of {1} at {2}"
}, 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(PermAdmin, this);
}
private void OnLootEntityEnd(BasePlayer player, LootableCorpse lootableCorpse)
{
if (player == null || lootableCorpse == null) return;
ulong ownerId = lootableCorpse.OwnerID;
if (ownerId == 0UL) return;
float now = UnityEngine.Time.realtimeSinceStartup;
float last;
if (lastNotify.TryGetValue(player.userID.Get(), out last) && now - last < config.CooldownSeconds)
return;
lastNotify[player.userID.Get()] = now;
string pos = FormatPosition(lootableCorpse.transform.position);
string looterName = player.displayName ?? player.UserIDString;
string ownerName = ownerId.ToString();
if (config.NotifyOwner && ownerId != player.userID.Get())
{
BasePlayer owner = BasePlayer.FindByID(ownerId);
if (owner != null && owner.IsConnected)
{
owner.ChatMessage(Msg("OwnerNotice", owner.UserIDString, looterName, pos));
ownerName = owner.displayName;
}
}
if (config.NotifyAdmins)
{
foreach (BasePlayer admin in BasePlayer.activePlayerList)
{
if (admin == null || admin == player) continue;
if (!permission.UserHasPermission(admin.UserIDString, PermAdmin)) continue;
if (ownerId == admin.userID.Get()) continue;
admin.ChatMessage(Msg("AdminNotice", admin.UserIDString, looterName, ownerName, pos));
}
}
}
private static string FormatPosition(UnityEngine.Vector3 position)
=> string.Format("{0:0},{1:0}", position.x, position.z);
private void Unload()
{
lastNotify.Clear();
}
}
}The plugin listens on OnLootEntityEnd(BasePlayer, LootableCorpse) and, when a player closes a corpse loot panel, looks up who owns the corpse via lootableCorpse.OwnerID. Config holds an opt-in chat notification list and an admin-log toggle. If the looter is not the corpse owner and the owner is online, the owner gets a localized chat message showing the looter's name and the corpse's world position; admins with the configured permission also receive the same line. Corpse events for the same player are rate-limited through a last-seen timestamp dictionary to avoid spam from repeated quick open/close cycles. All text goes through lang.RegisterMessages; a single short warning is printed at startup only when the admin log option is enabled without a permission configured.
Common use cases
Raid logging for corpses
Record who looted which corpse and when to a data file so admins can audit contested fights or offline raids. Pair with
OnLootEntityto correlate the open and close of the same corpse.Anti-loot spamming
Detect players who repeatedly open and close a corpse to trip timers or observers, and apply a cooldown, message, or moderation note when the frequency exceeds a threshold.
Personalized loot protection
If your server blocks enemies from taking a corpse's items, use this hook to restore or return items taken within a grace window after the corpse was dropped, then notify the owner and the looter.
Pitfalls
Same hook, different entity overloads
OnLootEntityEndis also called for StorageContainer, DroppedItemContainer, ContainerIOEntity, and ItemBasedFlowRestrictor. DeclaringLootableCorpsereceives only corpses; using a broader type (e.g.BaseEntity) receives every variant and needs its own type checks.Fires before the corpse is fully closed
At call time the corpse still has its Open flag set and its removal timer has not been reset yet. Delay logic with
NextTickor a short timer if you need to inspect the post-close state.Rate-limit notifications
Players and scripts can flicker the loot panel open and closed; naive notifications will spam owners and admins. Gate messages with per-player timestamps or a minimum interval.
Build a plugin with OnLootEntityEnd
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 OnLootEntityEnd
Generate