Skip to content
RustPluginGenerator

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.

Signature
void OnLootEntityEnd(BasePlayer player, LootableCorpse lootableCorpse)

Parameters

Hook parameters
ParameterTypeDescription
playerBasePlayerThe BasePlayer who closed the loot panel. This is the looter, not necessarily the owner of the container or corpse.
lootableCorpseLootableCorpseThe 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

CorpseLootAlert.cs
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 OnLootEntity to 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

    OnLootEntityEnd is also called for StorageContainer, DroppedItemContainer, ContainerIOEntity, and ItemBasedFlowRestrictor. Declaring LootableCorpse receives 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 NextTick or 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.

Prompt: Raid logging for corpses using the OnLootEntityEnd 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 OnLootEntityEnd

Generate