Skip to content
RustPluginGenerator

OnGrowableStateChange

OnGrowableStateChange is called when a GrowableEntity is about to move to a new plant growth state, such as Seed to Sprout or Mature to Fruiting. Returning a non-null value cancels the transition.

Signature
object OnGrowableStateChange(GrowableEntity growableEntity, PlantProperties.State state)

Parameters

Hook parameters
ParameterTypeDescription
growableEntityGrowableEntityThe plant or growable whose state is about to change. Use it to read position, OwnerID, prefab and find the planting entity or player that owns the crop.
statePlantProperties.StateThe PlantProperties.State the growable is transitioning to (for example Seed, Sprout, Sapling, Mature, Fruiting, Dying, Dead). Compare it to growableEntity.State to detect real growth events.

Return value

Returning null lets the state change proceed normally. Returning any non-null value aborts the transition: the method returns before State is updated, so the growable keeps its current state, age is not reset and no growth effects or network updates are applied.

How it works

The hook fires inside GrowableEntity.ChangeState, before the growable's State field is assigned and before any server side effects (age reset, network update, effects) run. It fires whenever the game advances a plant's growth stage: a seed genome maturing on a planter, a crop reaching fruiting, or a plant dying from neglect. It is not tied to player interaction, so one hook call per state transition per growable.

The state argument is the destination state, not the current one. Compare it against growableEntity.State to know what the plant is moving from. Useful fields on growableEntity are transform.position, OwnerID, ShortPrefabName and the parent planter entity, which let you attribute the change to a player or zone.

Because every argument is typed GrowableEntity, declaring a more specific subclass parameter would filter out plain GrowableEntity instances; keep the declared type as given unless you know you only care about a custom prefab class. The hook also fires while loading a saved plant, so avoid heavy work or player notifications for transitions that arrive during a load.

Example plugin: Freeze crop growth for a permission

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

namespace Oxide.Plugins
{
    [Info("GrowableGrowthFreezer", "RustPluginGenerator", "1.0.0")]
    [Description("Blocks plant growth state transitions for players with a permission")]
    public class GrowableGrowthFreezer : RustPlugin
    {
        private const string PermUse = "growablegrowthfreezer.freeze";

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

        private class PluginConfig
        {
            [JsonProperty("Only block after server is ready")]
            public bool OnlyAfterReady { get; set; } = 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>
            {
                ["FreezeOn"] = "Crop growth freezing is now enabled for you ({0} tracked plants).",
                ["FreezeOff"] = "Crop growth freezing is now disabled for you.",
                ["NoPermission"] = "You do not have permission to use this command.",
                ["Loaded"] = "GrowableGrowthFreezer loaded. {0} plants were restored."
            }, 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 OnServerInitialized()
        {
            Puts(Msg("Loaded", null, frozenPlants.Count));
        }

        [ConsoleCommand("growable.freeze")]
        private void CmdFreeze(ConsoleSystem.Arg arg)
        {
            var player = arg.Player();
            if (player == null) return;
            if (!player.IsAdmin && !permission.UserHasPermission(player.UserIDString, PermUse))
            {
                SendReply(player, Msg("NoPermission", player.UserIDString));
                return;
            }

            bool enable = arg.GetBool(0, true);
            if (enable)
            {
                if (!permission.UserHasPermission(player.UserIDString, PermUse))
                    permission.GrantUserPermission(player.UserIDString, PermUse, this);
                SendReply(player, Msg("FreezeOn", player.UserIDString, CountOwned(player.userID.Get())));
            }
            else
            {
                permission.RevokeUserPermission(player.UserIDString, PermUse);
                SendReply(player, Msg("FreezeOff", player.UserIDString));
            }
        }

        private int CountOwned(ulong ownerId)
        {
            int count = 0;
            foreach (var kvp in frozenPlants)
            {
                if (kvp.Value == ownerId) count++;
            }
            return count;
        }

        private object OnGrowableStateChange(GrowableEntity growableEntity, PlantProperties.State state)
        {
            if (growableEntity == null) return null;
            if (state == PlantProperties.State.Dying) return null;
            if (config.OnlyAfterReady && !ServerMgr.Instance) return null;

            ulong ownerId = growableEntity.OwnerID;
            if (ownerId == 0) return null;
            if (!permission.UserHasPermission(ownerId.ToString(), PermUse)) return null;

            if (growableEntity.net == null) return null;
            frozenPlants[growableEntity.net.ID.Value] = ownerId;
            return false;
        }

        private void Unload()
        {
            frozenPlants.Clear();
        }
    }
}

The plugin registers the growablegrowthfreezer.freeze permission and tracks frozen crops in an in-memory dictionary keyed by the growable's network id. In OnGrowableStateChange, it ignores dying plants, returns null before the server is ready when the config option Only block after server is ready is enabled, and otherwise blocks the transition with false only when the growable has a non-zero OwnerID whose user has the permission. The growable.freeze console command takes an optional boolean (default true); admins and players who already hold the permission can use it to grant or revoke the permission for themselves, and enabling reports how many already-tracked plants that player owns. The tracked set is not persisted: it is cleared on unload.

Common use cases

  • Pause growth for donators

    Return a non-null value for plants owned by players with a permission so their crops stay at the current stage while they are away.

  • Announce rare mutations

    When the destination state is Mature, check the plant's genome or owner team and send a one-time chat message to the planting player.

  • Prevent dying stage on owned farms

    Block the transition to Dying for plants inside a protected zone so crops cannot wither during water or light shortages.

Pitfalls

  • Fires during load

    ChangeState is also used when restoring saved plants, so blocking every transition can leave loaded plants stuck or inconsistent. Check whether the server has finished initializing before enforcing rules.

  • Returning true is not a value

    This hook uses the return only as a cancel signal. Returning false or true both abort the change; only null lets it continue.

  • No player context

    There is no BasePlayer parameter. Resolve ownership via growableEntity.OwnerID or the parent planter, and treat OwnerID 0 as unowned ground plants.

Build a plugin with OnGrowableStateChange

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

Prompt: Pause growth for donators using the OnGrowableStateChange 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 OnGrowableStateChange

Generate