Skip to content
RustPluginGenerator

CanLootEntity

CanLootEntity is called when a player tries to open a lootable entity such as a storage box, and returning a non-null value blocks the loot panel from opening.

Signature
object CanLootEntity(BasePlayer player, StorageContainer storageContainer)

Parameters

Hook parameters
ParameterTypeDescription
playerBasePlayerThe BasePlayer attempting to open the container. Already validated with CanInteract by the caller in most injection sites, but check IsConnected before sending UI or chat.
storageContainerStorageContainerThe StorageContainer being opened. Use its inventory for item checks, OwnerID for ownership, and transform.position for distance or zone logic.

Return value

Return null to let the container open as normal. Return any non-null value (a string or true is conventional) to stop the original open-loot code, so the player never receives the loot panel.

How it works

This hook fires from the open-loot path of each lootable type: StorageContainer.PlayerOpenLoot for boxes, LootableCorpse.RPC_LootCorpse for player and NPC corpses, ResourceContainer.StartLootingContainer for hemp and other gatherables, DroppedItemContainer.PlayerOpenLoot for dropped backpacks, ContainerIOEntity.PlayerOpenLoot for hoppers and conveyors, plus horses, industrial crafters and world items. It runs after the game's own checks (player exists, CanInteract(), corpse CanLoot) so a block here is final.

The player argument is the looter. The container argument gives you inventory for item inspection, OwnerID to compare against team or privilege data, and transform.position for distance checks. Declaring a more specific second parameter type, for example DroppedItemContainer or LootableCorpse, makes Oxide call your method only for that source; other loot types skip your hook entirely, which is how you scope a rule to a single container class.

Because the hook fires on every open attempt, keep the body cheap: no prints, no repeated data file reads, cache what you need at load time.

Other signatures

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

  • object CanLootEntity(BasePlayer player, LootableCorpse lootableCorpse)
  • object CanLootEntity(BasePlayer player, ResourceContainer resourceContainer)
  • object CanLootEntity(BasePlayer player, DroppedItemContainer droppedItemContainer)
  • object CanLootEntity(BasePlayer player, RidableHorse ridableHorse)
  • object CanLootEntity(BasePlayer player, ContainerIOEntity containerIOEntity)
  • object CanLootEntity(BasePlayer player, IndustrialCrafter industrialCrafter)
  • object CanLootEntity(BasePlayer player, WorldItem worldItem)

Example plugin: Lock player corpses for the owning team

CorpseTeamLootGate.cs
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;

namespace Oxide.Plugins
{
    [Info("Corpse Team Loot Gate", "RustPluginGenerator", "1.0.0")]
    [Description("Restricts corpse looting to the dead player's team and the corpse owner.")]
    public class CorpseTeamLootGate : RustPlugin
    {
        private const string PermBypass = "corpseteamlootgate.bypass";

        private PluginConfig config;
        private StoredData data;

        private class PluginConfig
        {
            [JsonProperty("Enable team-only corpse looting")]
            public bool Enabled = true;

            [JsonProperty("Notify blocked players in chat")]
            public bool NotifyBlocked = true;
        }

        private class StoredData
        {
            [JsonProperty("Corpse teams by net id")]
            public Dictionary<ulong, ulong> CorpseTeams = new Dictionary<ulong, ulong>();
        }

        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>
            {
                ["Blocked"] = "This corpse belongs to another team. Only the owner's team can loot it."
            }, 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(PermBypass, this);
            data = Interface.Oxide.DataFileSystem.ReadObject<StoredData>(Name) ?? new StoredData();
        }

        private void Unload()
        {
            Interface.Oxide.DataFileSystem.WriteObject(Name, data);
        }

        private void OnEntityDeath(BaseCombatEntity entity, HitInfo info)
        {
            var player = entity as BasePlayer;
            if (player == null || player.currentTeam == 0 || entity.net == null) return;

            data.CorpseTeams[entity.net.ID.Value] = player.currentTeam;
        }

        private object CanLootEntity(BasePlayer player, LootableCorpse corpse)
        {
            if (!config.Enabled || player == null || corpse == null || corpse.net == null) return null;

            ulong corpseId = corpse.net.ID.Value;
            ulong teamId = 0;
            if (data.CorpseTeams.TryGetValue(corpseId, out teamId))
            {
                // teamId was recorded at death
            }

            ulong owner = corpse.playerSteamID;
            if (teamId == 0)
            {
                // Fall back to the corpse owner's current team when nothing was recorded.
                var ownerPlayer = BasePlayer.FindByID(owner);
                if (ownerPlayer != null) teamId = ownerPlayer.currentTeam;
            }

            if (player.userID.Get() == owner) return null;
            if (player.IsAdmin || permission.UserHasPermission(player.UserIDString, PermBypass)) return null;
            if (teamId != 0 && player.currentTeam == teamId) return null;

            if (config.NotifyBlocked && player.IsConnected)
                player.ChatMessage(Msg("Blocked", player.UserIDString));

            return true;
        }
    }
}

The plugin declares CanLootEntity with a LootableCorpse parameter, so it only runs for corpses and ignores boxes. When the config option Enable team-only corpse looting is true, the plugin looks up the team recorded for the corpse at death. If no team was recorded, it falls back to the corpse owner's current team when that player can be found. The corpse owner, admins, and players with corpseteamlootgate.bypass pass through with null, as do members of the recorded team. Anyone else gets a chat message from the lang file (if Notify blocked in chat is enabled) and a non-null return that blocks the panel. On OnEntityDeath, the dead player's team id is written into a data file keyed by net.ID.Value; the file is saved in Unload.

Common use cases

  • Team-only corpse looting

    Compare the corpse owner's team with the looter's currentTeam and block outsiders. This makes PvP deaths riskier without touching regular boxes.

  • Raid windows on player storage

    Return non-null from the StorageContainer overload outside configured raid hours, or when the looter is not building authed on the box's GetBuildingPrivilege().

  • Premium or permission-gated boxes

    Check permission.UserHasPermission on a custom key before opening certain DroppedItemContainers, and point players to the store or a help command when denied.

Pitfalls

  • The hook fires per container type

    Declaring StorageContainer does not cover corpses, dropped backpacks or hoppers. Add one overload per type you want to police, otherwise players bypass the rule through a different loot source.

  • Returning false still blocks

    The check is for non-null, not truthiness. Returning false from a method declared object stops the open just like true; only null allows looting.

  • Corpse data goes stale

    A corpse can be despawned or emptied without any hook you handle. Persist the corpse id on death, remove it when consumed or killed, and periodically prune the data file.

Plugin recipes that use CanLootEntity

Build a plugin with CanLootEntity

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

Prompt: Team-only corpse looting using the CanLootEntity 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 CanLootEntity

Generate