OnCuttingTake
OnCuttingTake is called when a player takes a cutting (clone) from a GrowableEntity, after the clone item is created and its genes are encoded but before the item is actually given to the player.
object OnCuttingTake(GrowableEntity growableEntity, BasePlayer player, Item item)Parameters
| Parameter | Type | Description |
|---|---|---|
growableEntity | GrowableEntity | The GrowableEntity (the planted clone/hemp/berry bush) the player is taking a cutting from. Use it to read the plant's position, prefab and current state. |
player | BasePlayer | The BasePlayer who is taking the cutting. Check permissions, team, inventory space or economy here; it is never null in this hook. |
item | Item | The newly created clone item (an Item built from the plant's CloneItem property, already carrying encoded genes) that is about to be handed to the player. Inspect or modify it before it is given. |
Return value
Return null (or let the method fall through) to allow the take and let the game give the clone item to the player. Returning any non-null object (commonly true) blocks it: the game removes the freshly created item and returns from TakeClones without handing it over or recording the gather.
How it works
This hook fires inside GrowableEntity.TakeClones, the game method that runs when a player interacts with a plant to harvest a cutting instead of the plant itself. By the time the hook is called the game has already created the clone Item, attached ownership with the Cloned phrase and encoded the plant's genes into it, so all three parameters are fully populated: the growable being cut, the player performing the action and the finished item.
The only thing that has not happened yet is the item being handed over and the analytics event being recorded. Returning a non-null value cancels the take: the plugin's item is removed and the method returns early, so the player gets nothing. That makes the hook a clean gate for per-player limits, permission or cooldown checks, or custom drop logic.
The parameters are always the concrete types above, so narrowing them is not useful here. You can safely read or edit item (amount, skin, name via item.name) before letting the take proceed, and you can inspect the plant's OwnerID to know who planted it. Because the hook runs once per cutting and only on player input, it is cheap enough to do permission checks in it, but keep any logging out of it.
Example plugin: Limit cuttings per plant to a daily quota
using System.Linq;
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;
namespace Oxide.Plugins
{
[Info("Cutting Quota", "RustPluginGenerator", "1.0.0")]
[Description("Limits how many plant cuttings a player can take per day.")]
public class CuttingQuota : RustPlugin
{
private const string PermBypass = "cuttingquota.bypass";
private PluginConfig config;
private StoredData data;
private Timer resetTimer;
private class PluginConfig
{
[JsonProperty("Daily cutting limit")]
public int DailyLimit { get; set; } = 20;
[JsonProperty("Notify player in chat when blocked")]
public bool NotifyBlocked { get; set; } = true;
}
private class PlayerRecord
{
[JsonProperty("Date")]
public string Date { get; set; }
[JsonProperty("Taken")]
public int Taken { get; set; }
}
private class StoredData
{
[JsonProperty("Players")]
public Dictionary<string, PlayerRecord> Players { get; set; } = new Dictionary<string, PlayerRecord>();
}
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>
{
["QuotaReached"] = "You have reached your daily limit of {0} cuttings. Try again tomorrow.",
["QuotaLeft"] = "Cuttings left today: {0}"
}, 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()
{
data = Interface.Oxide.DataFileSystem.ReadObject<StoredData>(Name) ?? new StoredData();
ScheduleReset();
}
private void ScheduleReset()
{
var now = DateTime.UtcNow;
var nextMidnight = now.Date.AddDays(1);
var seconds = (float)Math.Max(60, (nextMidnight - now).TotalSeconds);
resetTimer = timer.Once(seconds, () =>
{
data.Players.Clear();
SaveData();
ScheduleReset();
});
}
private object OnCuttingTake(GrowableEntity growableEntity, BasePlayer player, Item item)
{
if (player == null || item == null) return null;
if (permission.UserHasPermission(player.UserIDString, PermBypass)) return null;
string today = DateTime.UtcNow.ToString("yyyy-MM-dd");
PlayerRecord record;
if (!data.Players.TryGetValue(player.UserIDString, out record) || record.Date != today)
{
record = new PlayerRecord { Date = today, Taken = 0 };
data.Players[player.UserIDString] = record;
}
if (record.Taken >= config.DailyLimit)
{
if (config.NotifyBlocked)
player.ChatMessage(Msg("QuotaReached", player.UserIDString, config.DailyLimit));
return true;
}
record.Taken++;
SaveData();
return null;
}
private void SaveData()
{
Interface.Oxide.DataFileSystem.WriteObject(Name, data);
}
private void Unload()
{
resetTimer?.Destroy();
resetTimer = null;
if (data != null) SaveData();
}
}
}The plugin tracks how many cuttings each player has taken in the current day and compares it with a configurable limit. When OnCuttingTake fires, it looks up the player's record, and if the quota is already reached it sends a localized message and returns true, which makes the game remove the clone item so the player never receives it. Otherwise the counter is incremented and saved through the data file system, and the hook returns null so the take goes ahead normally. Data is loaded in OnServerInitialized, keyed by player ID with the date it was last reset, and unloaded cleanly in Unload.
Common use cases
Daily clones cap
Restrict how many cuttings a player may take per day so that large clone farms cannot be farmed endlessly by one account.
VIP-only cutting rights
Check a permission such as
cuttingquota.vipand block takes by players who have not been granted the perk.Custom clone item tweaks
Adjust the item's amount, skin or custom name before the take completes, for example tagging clones that come from a specific planter.
Pitfalls
The item is created, not yet given
The
itempassed in has no container yet. Do not callRemove()on it yourself when blocking; just return non-null and the game removes it. Removing it and returning null will cause an error on the subsequent code path.Return type is object, not bool
Oxide treats any non-null value as a block, so returning
falseblocks the take too. Always returnnullwhen you want to allow it.Blocking hides the plant from the player's perspective
The plant is not consumed when you block the take, but the player may get no visual feedback other than no new item. Send a chat or console reply so they understand why nothing happened.
Build a plugin with OnCuttingTake
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 OnCuttingTake
Generate