Skip to content
RustPluginGenerator

OnCuttingTaken

OnCuttingTaken is called after a player takes a cutting from a growable plant, right as the plant is destroyed. It runs after the new cutting item has been created and given out.

Signature
void OnCuttingTaken(GrowableEntity growableEntity, BasePlayer player, Item item)

Parameters

Hook parameters
ParameterTypeDescription
growableEntityGrowableEntityThe GrowableEntity the cutting was taken from. It has already been killed by the time the hook fires, so treat it as the source record rather than a live plant.
playerBasePlayerThe BasePlayer who took the cutting. Use it for permission checks, inventory rewards, chat feedback or per-player cooldown tracking.
itemItemThe Item instance handed to the player for the new cutting. It can be null in unusual paths, so null-check before reading info.shortname or adjusting amount/skin.

Return value

The return value of this hook is ignored. Returning null, true or anything else does not change the cutting that was created, does not restore the source plant, and does not block the action. Do your work through side effects only.

How it works

This hook fires at the end of GrowableEntity.TakeClones, the game method that runs when a player takes a clone/cutting from a planted crop. By the time Oxide calls it, the pick effect has played, the planter has been notified, Die() has killed the source growable, and the cutting item exists. Because the source plant is already dead, you cannot cancel the removal here — use it to record, reward or adjust what just happened.

All three arguments are useful: growableEntity still carries position and prefab data for the now-dead plant, player identifies who took the cutting (null-check player?.userID), and item is the freshly created cutting, so you can inspect item.info.shortname, change item.amount, or attach metadata via a data store keyed on the item. Declaring BasePlayer player is correct here since the injected argument is always a player; narrowing to a specific growable subclass is unnecessary because the injection site always passes a GrowableEntity.

The hook is not throttled — taking cuttings is a manual player action, so per-call logic is fine, but avoid heavy work like full inventory scans.

Example plugin: Limit cuttings per player with a cooldown and chat feedback

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

namespace Oxide.Plugins
{
    [Info("Cutting Cooldown", "RustPluginGenerator", "1.0.0")]
    [Description("Enforces a per-player cooldown between taking plant cuttings.")]
    public class CuttingCooldown : RustPlugin
    {
        private const string PermBypass = "cuttingcooldown.bypass";

        private PluginConfig config;
        private readonly Dictionary<ulong, float> lastCutting = new Dictionary<ulong, float>();

        private class PluginConfig
        {
            [JsonProperty("Cooldown seconds")]
            public float CooldownSeconds = 30f;

            [JsonProperty("Notify on removal")]
            public bool Notify = true;
        }

        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}s before taking another cutting."
            }, 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 Unload()
        {
            lastCutting.Clear();
        }

        private void OnCuttingTaken(GrowableEntity growableEntity, BasePlayer player, Item item)
        {
            if (player == null || player.userID == 0) return;
            if (permission.UserHasPermission(player.UserIDString, PermBypass)) return;

            float now = Time.realtimeSinceStartup;
            ulong id = (ulong)player.userID;
            float cooldown = config.CooldownSeconds;

            if (lastCutting.TryGetValue(id, out var last) && now - last < cooldown)
            {
                if (item != null && item.parent != null)
                {
                    item.Remove();
                    item.parent.MarkDirty();
                }
                if (config.Notify)
                {
                    float remaining = Mathf.Ceil(cooldown - (now - last));
                    player.ChatMessage(Msg("Cooldown", player.UserIDString, remaining));
                }
                return;
            }

            lastCutting[id] = now;
        }

        [ChatCommand("cuttingcd")]
        private void CmdCooldown(BasePlayer player, string command, string[] args)
        {
            if (player == null) return;
            float remaining = 0f;
            if (lastCutting.TryGetValue((ulong)player.userID, out var last))
            {
                remaining = Mathf.Max(0f, config.CooldownSeconds - (Time.realtimeSinceStartup - last));
            }
            player.ChatMessage($"Cutting cooldown: {Mathf.Ceil(remaining)}s remaining.");
        }
    }
}

The plugin tracks a per-player timestamp of their last cutting and enforces a configurable cooldown in OnCuttingTaken. When a player exceeds the limit, the plugin removes the cutting item they just received (calling item.Remove() and marking the container dirty) and sends a localized message. Players with the cuttingcooldown.bypass permission skip the check. State is kept in memory and cleared on Unload. Because the hook fires after the item already exists and the plant is already destroyed, removal is the only realistic way to reverse the action, which is exactly what this plugin demonstrates.

Common use cases

  • Per-player cutting cooldowns

    Enforce a minimum time between cuttings so farms cannot be cloned instantly, removing the item if the limit is exceeded.

  • Reward players for cloning

    Grant a small amount of currency or an item when a cutting is taken, using a referenced economy plugin with a null check.

  • Track clone usage statistics

    Log how many cuttings each player takes per plant type into a data file for admin reporting and anti-cheat review.

Pitfalls

  • The source plant is already dead

    Die() runs before the hook, so you cannot prevent the removal from this hook. If you need to block taking cuttings, use CanTakeCutting instead.

  • Item may be null or already moved

    Null-check item and its parent container before calling Remove(). If the player's inventory was full the item might have been dropped on the ground.

  • Modifying amount changes gameplay

    Changing item.amount here alters what the player received after the fact. Only do this intentionally, and call container.MarkDirty() after mutations so clients see the update.

Build a plugin with OnCuttingTaken

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

Prompt: Per-player cutting cooldowns using the OnCuttingTaken 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 OnCuttingTaken

Generate