Skip to content
RustPluginGenerator

OnHelicopterOutOfCrates

OnHelicopterOutOfCrates is called when the Chinook AI checks whether it has run out of supply crates. Returning a bool overrides the default `numCrates <= 0` result.

Signature
object OnHelicopterOutOfCrates(CH47HelicopterAIController helicopter)

Parameters

Hook parameters
ParameterTypeDescription
helicopterCH47HelicopterAIControllerThe CH47HelicopterAIController (Chinook) whose crate count is being evaluated. Use it to read the entity's position, net.ID, OwnerID or attached crate inventory when deciding the outcome.

Return value

Returning null (or any non-bool value) keeps the vanilla result, numCrates <= 0. Returning a bool replaces that result entirely: true tells the AI the helicopter is out of crates, false tells it crates remain, regardless of the real counter.

How it works

The hook fires inside CH47HelicopterAIController.OutOfCrates(), the method the Chinook AI periodically runs to decide whether its crate-delivery phase is finished. The game normally answers by comparing an internal crate counter to zero, so the hook only gives you a single boolean decision point per check. It is a good place to force a Chinook to behave as if it is empty (or still loaded) without touching its actual inventory.

The only argument is the helicopter controller itself. Null-check it, since the entity may already be destroyed when the check runs. Casting is unnecessary because the declared type is already specific; declaring a broader type such as BaseEntity would also work but gains nothing here, and Oxide would still call the hook with the concrete controller instance.

Keep the method cheap. This runs on AI think cycles for every Chinook on the map, so avoid allocations, PrintWarning spam and expensive entity queries. Because the default is a counter comparison, reading numCrates directly is not possible from a plugin; the hook is purely a decision override.

Example plugin: Delay Chinook extraction until crates are actually delivered

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

namespace Oxide.Plugins
{
    [Info("ChinookCrateGuard", "RustPluginGenerator", "1.0.0")]
    [Description("Lets admins hold or force the Chinook out-of-crates state per helicopter.")]
    public class ChinookCrateGuard : RustPlugin
    {
        private const string PermUse = "chinookcrateguard.use";

        private PluginConfig config;
        private readonly Dictionary<ulong, double> held = new Dictionary<ulong, double>();
        private Timer sweepTimer;

        private class PluginConfig
        {
            [JsonProperty("Default hold seconds")]
            public double DefaultHoldSeconds = 120.0;
        }

        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>
            {
                ["Held"] = "Chinook {0} will report crates for {1} seconds.",
                ["Forced"] = "Chinook {0} will now report out of crates.",
                ["NoHeli"] = "No Chinook found near you.",
                ["Usage"] = "Usage: /chinook <hold|finish> [seconds]"
            }, 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);
            sweepTimer = timer.Every(60f, Sweep);
        }

        private void Unload()
        {
            sweepTimer?.Destroy();
            held.Clear();
        }

        [ChatCommand("chinook")]
        private void CmdChinook(BasePlayer player, string command, string[] args)
        {
            if (player == null || !permission.UserHasPermission(player.UserIDString, PermUse))
            {
                player?.ChatMessage("You do not have permission to use this command.");
                return;
            }

            if (args == null || args.Length == 0)
            {
                player.ChatMessage(Msg("Usage", player.UserIDString));
                return;
            }

            CH47HelicopterAIController heli = FindNearest(player);
            if (heli == null)
            {
                player.ChatMessage(Msg("NoHeli", player.UserIDString));
                return;
            }

            ulong id = heli.net.ID.Value;
            if (args[0].Equals("finish", StringComparison.OrdinalIgnoreCase))
            {
                held.Remove(id);
                player.ChatMessage(Msg("Forced", player.UserIDString, id));
                return;
            }

            double seconds = config.DefaultHoldSeconds;
            if (args.Length > 1) double.TryParse(args[1], out seconds);
            held[id] = Facepunch.Math.Epoch.Current + seconds;
            player.ChatMessage(Msg("Held", player.UserIDString, id, seconds.ToString("0")));
        }

        private object OnHelicopterOutOfCrates(CH47HelicopterAIController helicopter)
        {
            if (helicopter == null || helicopter.IsDestroyed) return null;

            double until;
            if (!held.TryGetValue(helicopter.net.ID.Value, out until)) return null;

            if (Facepunch.Math.Epoch.Current >= until)
            {
                held.Remove(helicopter.net.ID.Value);
                return null;
            }

            return false;
        }

        private CH47HelicopterAIController FindNearest(BasePlayer player)
        {
            CH47HelicopterAIController best = null;
            float bestDist = 200f;
            Vector3 origin = player.transform.position;
            foreach (BaseNetworkable entity in BaseNetworkable.serverEntities)
            {
                CH47HelicopterAIController heli = entity as CH47HelicopterAIController;
                if (heli == null || heli.IsDestroyed) continue;
                float d = Vector3.Distance(origin, heli.transform.position);
                if (d < bestDist)
                {
                    bestDist = d;
                    best = heli;
                }
            }
            return best;
        }

        private void Sweep()
        {
            if (held.Count == 0) return;
            int now = Facepunch.Math.Epoch.Current;
            List<ulong> expired = null;
            foreach (KeyValuePair<ulong, double> pair in held)
            {
                if (now >= pair.Value)
                {
                    if (expired == null) expired = new List<ulong>();
                    expired.Add(pair.Key);
                }
            }
            if (expired == null) return;
            foreach (ulong id in expired) held.Remove(id);
        }
    }
}

The plugin tracks which Chinooks have been marked by an admin chat command and, optionally, a per-helicopter hold time. When OnHelicopterOutOfCrates fires, it stores the helicopter's net.ID and returns false while the hold window is active, so the Chinook keeps its out-of-crates phase from completing and does not leave early. Once the window expires the hook returns null to restore vanilla behavior. A short timer-driven sweep keeps the tracking dictionary from growing forever, and Unload clears state. The chat command is permission-gated under chinookcrateguard.use and messages go through the language file.

Common use cases

  • Keep Chinooks on the map longer

    Return false for a configured duration so the AI keeps treating the helicopter as loaded and stays in its drop/loiter phase longer.

  • End crate events early for specific helicopters

    Return true when a helicopter is outside a designated event zone, forcing it to finish its run without waiting for the real crate counter to reach zero.

  • Event scheduling

    Combine with spawn tracking: only allow a Chinook to complete when a global event flag is set, giving admins control over when the vanilla run terminates.

Pitfalls

  • Returning true does not remove crates

    The hook only changes the boolean answer. Any crates still in the Chinook inventory remain there; if you want them gone you must remove the entity or handle it separately.

  • Runs on AI think cycles

    Keep the method allocation-free and never log inside it. Store the override in a dictionary and do the work elsewhere.

  • Stale entity references

    Always check the controller for null and consider IsDestroyed before using its transform or net ID, as the AI may still run a final check during destruction.

Build a plugin with OnHelicopterOutOfCrates

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

Prompt: Keep Chinooks on the map longer using the OnHelicopterOutOfCrates 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 OnHelicopterOutOfCrates

Generate