CanPickupEntity
CanPickupEntity is called when a player attempts to pick up a combat entity such as a deployed item, corpse, or sleeping bag, letting you block or allow the action.
object CanPickupEntity(BasePlayer player, BaseCombatEntity entity)Parameters
| Parameter | Type | Description |
|---|---|---|
player | BasePlayer | The BasePlayer attempting to pick up the entity. May be null in rare call paths, so null-check before using it. |
entity | BaseCombatEntity | The BaseCombatEntity (corpse, sleeping bag, box, deployable) being picked up. When the hook fires from the DoorCloser path this is a DoorCloser instead. |
Return value
Return null (or don't return) to keep the default behavior, which is to allow pickup. Returning a bool sets the result of CanCompletePickup: false blocks the pickup, true explicitly allows it. When called for a DoorCloser, returning false blocks the pickup; any other return value is ignored.
How it works
Oxide calls CanPickupEntity from BaseCombatEntity.CanCompletePickup, which runs when a player tries to pick up a deployable combat entity (sleeping bags, small stash-like objects, and other items that return the entity to inventory). It also fires from the RustCore ICanPickupEntity path with a DoorCloser as the second argument, which is used when a player picks up a door closer. Use player to check permissions, cooldowns, or ownership, and entity to inspect the item being picked up.
Both parameters follow Oxide's normal dispatch: declaring BasePlayer is required to match the signature, but if you declare the entity parameter as DoorCloser the hook will only run for the door-closer call and be skipped for BaseCombatEntity pickups. Keep it as BaseCombatEntity and branch internally with entity is DoorCloser when you need door-closer-specific behavior.
The hook fires on the interaction tick, so avoid heavy work. It is a good place to enforce pickup rules such as ownership, a minimum placement age, or permission gates.
Other signatures
Oxide also calls CanPickupEntity from other places in the game with these parameter types. Declare the overload you need, or several:
object CanPickupEntity(BasePlayer player, DoorCloser entity)
Example plugin: Block pickup of recently placed deployables
using System.Linq;
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;
using UnityEngine;
namespace Oxide.Plugins
{
[Info("Pickup Cooldown", "RustPluginGenerator", "1.0.0")]
[Description("Blocks players from picking up deployables they placed within a configured cooldown window.")]
public class PickupCooldown : RustPlugin
{
private const string PermBypass = "pickupcooldown.bypass";
private class PluginConfig
{
[JsonProperty("Cooldown Seconds")]
public float CooldownSeconds = 300f;
[JsonProperty("Prune Interval Seconds")]
public float PruneInterval = 60f;
}
private PluginConfig config;
private readonly Dictionary<ulong, float> placedAt = new Dictionary<ulong, float>();
private Timer pruneTimer;
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>
{
["Cooldown"] = "You must wait {0} more seconds before picking that up."
}, 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);
}
private void OnServerInitialized()
{
pruneTimer = timer.Every(Mathf.Max(10f, config.PruneInterval), Prune);
}
private void Unload()
{
pruneTimer?.Destroy();
placedAt.Clear();
}
private object CanPickupEntity(BasePlayer player, BaseCombatEntity entity)
{
if (player == null || entity == null || entity.IsDestroyed) return null;
if (entity is DoorCloser) return null;
if (player.IsAdmin || permission.UserHasPermission(player.UserIDString, PermBypass)) return null;
if (entity.OwnerID == 0 || entity.OwnerID != player.userID.Get()) return null;
ulong id = entity.net.ID.Value;
if (!placedAt.TryGetValue(id, out float firstSeen))
{
placedAt[id] = UnityEngine.Time.realtimeSinceStartup;
return null;
}
float elapsed = UnityEngine.Time.realtimeSinceStartup - firstSeen;
if (elapsed >= config.CooldownSeconds) return null;
int remaining = Mathf.CeilToInt(config.CooldownSeconds - elapsed);
player.ChatMessage(Msg("Cooldown", player.UserIDString, remaining));
return false;
}
private void Prune()
{
if (placedAt.Count == 0) return;
float now = UnityEngine.Time.realtimeSinceStartup;
List<ulong> stale = null;
foreach (var entry in placedAt)
{
if (now - entry.Value < config.CooldownSeconds) continue;
stale ??= new List<ulong>();
stale.Add(entry.Key);
}
if (stale == null) return;
for (int i = 0; i < stale.Count; i++) placedAt.Remove(stale[i]);
}
}
}The plugin tracks when each deployable was placed by listening to OnEntityBuilt (structures) is not the right source for deployables, so instead it stamps a placement time on the first CanPickupEntity call it sees per entity and enforces a configurable cooldown window afterwards. To make the feature work without extra hook surface, it compares each entity's OwnerID against the picker and only applies the cooldown to the owner. A Dictionary<ulong, float> keyed by network ID stores the first-seen time. If the elapsed time is under the configured Cooldown Seconds, the hook returns false and the player gets a localized chat message. Door closers are exempt. A periodic timer prunes stale entries so the dictionary does not grow forever.
Common use cases
Enforce ownership
Return false when
entity.OwnerIDdiffers from the picker's user ID, so players cannot steal deployed items placed by others.Rate limit repeated pickups
Track pickups per player and block rapid pick-up-and-replace cycles used to farm placement or to grief with sleeping bags.
Prevent combat pickups
Deny pickups when the player is wounded, mounted, or has a held weapon, matching No Pickup style rulesets.
Pitfalls
DoorCloser signature mismatch
If you type the second parameter as
DoorCloser, Oxide will skip the BaseCombatEntity call entirely and you will stop blocking normal pickups. KeepBaseCombatEntityand branch withis DoorCloser.Returning true does not force a pickup
Returning
trueonly says the pickup is allowed; other checks and container space still apply. It does not bypass the game's own validation.Not all pickups fire this hook
Lootable world items and dropped item containers use different paths. If you need to block those, look at
CanLootEntityorOnItemPickupas well.
Build a plugin with CanPickupEntity
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 CanPickupEntity
Generate