Skip to content
RustPluginGenerator

OnCollectiblePickup

OnCollectiblePickup is called when a player harvests a CollectibleEntity such as hemp, corn, berries or mushrooms, before the server calculates yields and grants items.

Signature
object OnCollectiblePickup(CollectibleEntity collectibleEntity, BasePlayer reciever, bool eat)

Parameters

Hook parameters
ParameterTypeDescription
collectibleEntityCollectibleEntityThe CollectibleEntity being picked up (a hemp bush, corn plant, pumpkin, berry bush, mushroom, etc.). Use its itemList or ShortPrefabName to decide what is being harvested.
recieverBasePlayerThe BasePlayer performing the pickup. Can be null in edge cases, so null-check before touching UserIDString, inventory or position.
eatboolTrue when the pickup is the 'eat' variant of the collectible (used for edible plants), false for a normal harvest.

Return value

Returning null (or nothing) lets vanilla continue: the game computes the collectible's yield, applies modifiers and gives the items to the player. Returning any non-null value stops DoPickup immediately, so no items are granted and the plant is not consumed through that call.

How it works

The hook fires inside CollectibleEntity.DoPickup, which is the method invoked whenever a player picks a wild collectible. That covers every plant-style harvestable in the world: hemp bushes, corn, pumpkin, berries, mushrooms and similar spawns. Because the call happens at the top of the method, nothing has been added to the player's inventory yet when your hook runs.

collectibleEntity lets you inspect what is being gathered (prefab name, item list, position). reciever is the harvesting player and eat distinguishes the eating interaction from a normal pickup. Note the parameter is spelled reciever in the game's own signature; parameter names do not matter to hook dispatch, but types do.

Declaring collectibleEntity as CollectibleEntity matches the game's argument exactly. If you declared a narrower type that the argument is not, Oxide would skip your method for that call. Keep both parameters null-checked; a null reciever would otherwise throw inside your code.

Practical uses are multiplier events, block lists, bonus item grants and per-plant statistics. The hook returns before vanilla yield math runs, so returning a non-null value cancels the pickup entirely.

Example plugin: Boost hemp harvests during an event window

CollectibleEventBonus.cs
using System.Linq;
using UnityEngine;
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;

namespace Oxide.Plugins
{
    [Info("Collectible Event Bonus", "RustPluginGenerator", "1.0.0")]
    [Description("Grants bonus items on collectible pickups while a timed event is active")]
    public class CollectibleEventBonus : RustPlugin
    {
        private const string PermUse = "collectibleeventbonus.use";

        private PluginConfig config;
        private Timer eventTimer;
        private bool eventActive;

        private class PluginConfig
        {
            [JsonProperty("Bonus item shortname")]
            public string BonusShortname = "cloth";

            [JsonProperty("Bonus amount per pickup")]
            public int BonusAmount = 3;

            [JsonProperty("Boosted collectible short prefabs")]
            public List<string> BoostedPrefabs = new List<string> { "hemp-collectable" };
        }

        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>
            {
                ["Bonus"] = "Event bonus: +{0} {1}",
                ["Started"] = "Collectible event started for {0} seconds.",
                ["Stopped"] = "Collectible event stopped.",
                ["NoPermission"] = "You do not have permission to use this command."
            }, 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(PermUse, this);

        private void Unload()
        {
            eventTimer?.Destroy();
            eventTimer = null;
        }

        [ChatCommand("collectibleevent")]
        private void CmdEvent(BasePlayer player, string command, string[] args)
        {
            if (player != null && !player.IsAdmin && !permission.UserHasPermission(player.UserIDString, PermUse))
            {
                SendReply(player, Msg("NoPermission", player.UserIDString));
                return;
            }

            if (args == null || args.Length == 0) return;

            if (args[0] == "stop")
            {
                eventTimer?.Destroy();
                eventTimer = null;
                eventActive = false;
                Server.Broadcast(Msg("Stopped"));
                return;
            }

            if (args[0] == "start" && args.Length > 1)
            {
                int seconds;
                if (!int.TryParse(args[1], out seconds) || seconds <= 0) return;

                eventTimer?.Destroy();
                eventActive = true;
                eventTimer = timer.Once(seconds, () => { eventActive = false; });
                Server.Broadcast(Msg("Started", null, seconds));
            }
        }

        private object OnCollectiblePickup(CollectibleEntity collectibleEntity, BasePlayer reciever, bool eat)
        {
            if (!eventActive || collectibleEntity == null || reciever == null || reciever.IsNpc) return null;
            if (collectibleEntity.ShortPrefabName == null || !config.BoostedPrefabs.Contains(collectibleEntity.ShortPrefabName)) return null;

            var bonus = ItemManager.CreateByName(config.BonusShortname, config.BonusAmount);
            if (bonus == null) return null;

            if (!reciever.inventory.GiveItem(bonus))
                bonus.Drop(reciever.transform.position + UnityEngine.Random.insideUnitSphere, Vector3.zero);

            SendReply(reciever, Msg("Bonus", reciever.UserIDString, config.BonusAmount, config.BonusShortname));
            return null;
        }
    }
}

The plugin runs a timed DoubleHemp event that admins start with /collectibleevent start <seconds>. While the event is active, OnCollectiblePickup checks the collectible's short prefab name and, when it is hemp, adds an extra amount of cloth to the picker's inventory with GiveItem. A config option sets the bonus amount and the message that the player receives is pulled from the language file. Unload destroys the event timer and clears the CUI-free state; there is no CUI here, so only the timer needs cleanup. Every hook parameter is null-checked, and the code reads the player id through UserIDString so the chat message is localized per player.

Common use cases

  • Multiplier weekends

    Grant extra items on every wild plant pickup while an admin toggled event is running, without touching the game's own yield math.

  • Blocked resources in protected zones

    Return a non-null value when the collectible sits inside a zone you consider off-limits, so the pickup is cancelled.

  • Harvest statistics

    Count pickups per player or per plant type and persist totals to oxide/data for leaderboards.

Pitfalls

  • Returning a value cancels the pickup

    Any non-null return stops DoPickup before items are granted. If you only want to observe, return null explicitly and never return true/int by accident.

  • reciever can be null

    Do not dereference the player without a check. A null receiver inside your hook throws and the pickup fails for the player.

  • Distinguish this from OnDispenserGather

    CollectibleEntity covers plants and wild spawns; stone, wood and ore nodes use the dispenser hooks. Using the wrong one means your multiplier never fires.

Plugin recipes that use OnCollectiblePickup

Build a plugin with OnCollectiblePickup

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

Prompt: Multiplier weekends using the OnCollectiblePickup 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 OnCollectiblePickup

Generate