Skip to content
RustPluginGenerator

OnHelicopterRetire

OnHelicopterRetire is called when a patrol helicopter's AI decides to leave the map and retire itself, just before the game schedules its destruction and teleports it away.

Signature
object OnHelicopterRetire(PatrolHelicopterAI patrolHelicopterAI)

Parameters

Hook parameters
ParameterTypeDescription
patrolHelicopterAIPatrolHelicopterAIThe PatrolHelicopterAI component of the patrol helicopter that is being retired. Use its transform, its base entity via patrolHelicopterAI.helicopterBase, or its net ID to identify the specific helicopter.

Return value

Returning null lets the game's retirement proceed: it schedules DestroyMe after 240 seconds and teleports the helicopter to a random position at 200 height. Returning any non-null value skips that entire original body, so the helicopter is not scheduled for destruction and is not repositioned, and OnHelicopterRetire will be able to fire again for the same helicopter later.

How it works

The hook fires inside PatrolHelicopterAI.Retire, which the game calls when the patrol helicopter gives up its patrol route and heads off to be removed. The native code guards the whole retirement body behind this hook, so the call happens once per helicopter and only when it is not already retiring. Typical triggers are the helicopter spending its configured time on the map or the AI reaching the end of its route logic.

The only parameter is the PatrolHelicopterAI itself. From it you can read position and identity, but the useful base entity lives on patrolHelicopterAI.helicopterBase, which you can cast to PatrolHelicopter or BaseHelicopter. If you need the entity in an event or message, capture that reference immediately and null-check it, because the helicopter is about to be destroyed by the game's own timer.

There is no narrower overload to declare here: the parameter type is exactly the AI component, so Oxide will only call your method while the argument is a live PatrolHelicopterAI. Do not assume the base entity is still spawned or valid; treat this as the last moment you can react before removal and keep the work short, since the hook can fire while other entity operations are in flight.

Example plugin: Announce and log every retiring patrol helicopter

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

namespace Oxide.Plugins
{
    [Info("Helicopter Retire Announcer", "RustPluginGenerator", "1.0.0")]
    [Description("Announces and records every patrol helicopter retirement")]
    public class HelicopterRetireAnnouncer : RustPlugin
    {
        private const string PermStats = "helicopterretireannouncer.stats";
        private const string DataFile = "HelicopterRetireAnnouncer/retirements";

        private PluginConfig config;
        private List<RetireRecord> records = new List<RetireRecord>();

        private class PluginConfig
        {
            [JsonProperty("Announce to chat")]
            public bool Announce { get; set; } = true;

            [JsonProperty("Include grid position in message")]
            public bool IncludeGrid { get; set; } = true;
        }

        private class RetireRecord
        {
            [JsonProperty("Utc time")]
            public string Utc { get; set; }

            [JsonProperty("Grid")]
            public string Grid { get; set; }

            [JsonProperty("Position")]
            public string Position { get; set; }
        }

        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>
            {
                ["Retired"] = "The patrol helicopter is leaving the map at grid {0}.",
                ["Stats"] = "{0} helicopter retirements recorded. Last one at {1} ({2}).",
                ["NoRecords"] = "No helicopter retirements have been recorded yet.",
                ["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(PermStats, this);
        }

        private void OnServerInitialized()
        {
            records = Interface.Oxide.DataFileSystem.ReadObject<List<RetireRecord>>(DataFile)
                      ?? new List<RetireRecord>();
        }

        private object OnHelicopterRetire(PatrolHelicopterAI patrolHelicopterAI)
        {
            if (patrolHelicopterAI == null) return null;

            var helicopter = patrolHelicopterAI.helicopterBase;
            Vector3 position = helicopter != null ? helicopter.transform.position : patrolHelicopterAI.transform.position;
            string grid = FormatGrid(position);

            records.Add(new RetireRecord
            {
                Utc = DateTime.UtcNow.ToString("u"),
                Grid = grid,
                Position = string.Format("{0:F0} {1:F0} {2:F0}", position.x, position.y, position.z)
            });

            if (records.Count > 200) records.RemoveAt(0);
            Interface.Oxide.DataFileSystem.WriteObject(DataFile, records);

            if (config.Announce)
            {
                Server.Broadcast(config.IncludeGrid ? Msg("Retired", null, grid) : Msg("Retired", null, "unknown"));
            }

            return null;
        }

        private string FormatGrid(Vector3 position)
        {
            int x = Mathf.FloorToInt((position.x + 1024f) / 146.3f);
            int z = Mathf.FloorToInt((1024f - position.z) / 146.3f);
            char letter = (char)('A' + Mathf.Clamp(x, 0, 25));
            return letter + Mathf.Clamp(z, 0, 99).ToString();
        }

        [ChatCommand("heliretire")]
        private void CmdHeliRetire(BasePlayer player, string command, string[] args)
        {
            if (player == null) return;
            if (!permission.UserHasPermission(player.UserIDString, PermStats))
            {
                player.ChatMessage(Msg("NoPermission", player.UserIDString));
                return;
            }

            if (records.Count == 0)
            {
                player.ChatMessage(Msg("NoRecords", player.UserIDString));
                return;
            }

            var last = records[records.Count - 1];
            player.ChatMessage(Msg("Stats", player.UserIDString, records.Count, last.Utc, last.Grid));
        }
    }
}

The plugin listens for OnHelicopterRetire and announces to the server that the patrol helicopter is leaving. It reads the base entity from patrolHelicopterAI.helicopterBase, checks it is still valid, and broadcasts a localized message that includes the helicopter's map grid position. The same information is written to a stored retire list (position, grid and the UTC timestamp) using Interface.Oxide.DataFileSystem, so administrators have a persistent record of how often the helicopter cycles. A chat command lets permission holders print the count of recorded retirements and the last occurrence time. The hook always returns null so vanilla retirement timing and cleanup stay intact.

Common use cases

  • Announce helicopter departures

    Broadcast a chat or console notice when the patrol helicopter leaves so players know the threat is over and new crates are not coming from it. Include the grid position where it retired for context.

  • Track helicopter cycle statistics

    Persist a timestamp and grid per retirement to see how often the helicopter spawns and how long it lives on your server. Useful when tuning spawn and patrol settings in the vanilla config.

  • Slow down automatic removal

    Returning a non-null value keeps the retirement body from running, which prevents the game from scheduling destruction and relocating the helicopter. Pair this with your own timer that calls the destruction later so the helicopter stays on the map longer.

Pitfalls

  • Base entity may be null or invalid

    patrolHelicopterAI.helicopterBase can be null or already destroyed in edge cases. Always null-check it and use IsDestroyed before reading position or sending network updates.

  • Returning a value blocks removal permanently

    A non-null return skips the whole retirement body, including the 240 second DestroyMe timer and the relocation. If you never destroy the helicopter yourself, it stays on the map and OnHelicopterRetire can be called on it again.

  • Do not assume crates were dropped

    The crates the helicopter carries are governed by other hooks such as CanHelicopterDropCrate and OnHelicopterOutOfCrates. Retirement does not guarantee the player-facing reward sequence finished, so do not gate economy payouts on this hook alone.

Build a plugin with OnHelicopterRetire

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

Prompt: Announce helicopter departures using the OnHelicopterRetire 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 OnHelicopterRetire

Generate