Skip to content
RustPluginGenerator

How to make a Rust timed airdrop plugin (Oxide/Carbon)

A plugin that spawns a cargo plane supply drop on a repeating timer whenever the online player count meets a minimum, announces the drop in chat, and lets admins call one manually with /airdrop. It uses a repeating Oxide timer, a warm-up tick and OnServerInitialized, plus a chat command and a console status command.

Vanilla Rust ties airdrops to supply signals and loot, so servers that want a predictable event schedule can't get one without a plugin. This plugin runs a repeating timer that checks how many players are connected and, when the count is at or above your threshold, creates the vanilla cargo plane prefab so the drop behaves exactly the way the game does it. Every drop is announced in chat, and admins can force one on demand with /airdrop. The config controls the interval, the minimum player count, and the spawn height, so you can tune it to a 30-player wipe or a 5-player test server without touching code. The plugin persists nothing to disk and destroys its timer in Unload, so reloading it never leaves duplicate planes spawning.

The plugin code

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

namespace Oxide.Plugins
{
    [Info("Timed Airdrop", "RustPluginGenerator", "1.0.0")]
    [Description("Spawns a vanilla cargo plane supply drop on a timer when enough players are online.")]
    public class TimedAirdrop : RustPlugin
    {
        private const string PermUse = "timedairdrop.use";

        private class PluginConfig
        {
            [JsonProperty("Interval Minutes")]
            public int IntervalMinutes { get; set; } = 45;

            [JsonProperty("Minimum Players")]
            public int MinimumPlayers { get; set; } = 5;

            [JsonProperty("Spawn Height")]
            public float SpawnHeight { get; set; } = 250f;
        }

        private PluginConfig config;
        private Timer dropTimer;
        private int lastWarnedPlayerCount = -1;

        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>
            {
                ["Incoming"] = "A cargo plane is dropping a supply crate — watch the sky!",
                ["NoPermission"] = "You don't have permission to call an airdrop.",
                ["Called"] = "A supply drop has been called.",
                ["Skipped"] = "Not enough players online ({0}/{1}) — airdrop skipped."
            }, 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()
        {
            StartTimer();

            // Warm-up tick so a live server doesn't wait a whole cycle after reload.
            timer.Once(10f, CheckScheduledDrop);
        }

        private void Unload()
        {
            dropTimer?.Destroy();
            dropTimer = null;
        }

        private void StartTimer()
        {
            dropTimer?.Destroy();
            var seconds = Mathf.Max(1, config.IntervalMinutes) * 60f;
            dropTimer = timer.Every(seconds, CheckScheduledDrop);
        }

        private void CheckScheduledDrop()
        {
            var online = CountOnlinePlayers();
            if (online < config.MinimumPlayers)
            {
                if (online != lastWarnedPlayerCount)
                {
                    lastWarnedPlayerCount = online;
                    Puts($"Skipping airdrop — {online}/{config.MinimumPlayers} players online.");
                }
                return;
            }

            lastWarnedPlayerCount = -1;
            SpawnAirdrop();
        }

        private int CountOnlinePlayers()
        {
            int count = 0;
            foreach (var player in BasePlayer.activePlayerList)
            {
                if (player == null || player.IsNpc || !player.IsConnected || player.IsDead()) continue;
                count++;
            }
            return count;
        }

        private void SpawnAirdrop()
        {
            var position = new Vector3(
                TerrainMeta.Size.x * 0.5f,
                config.SpawnHeight,
                TerrainMeta.Size.z * 0.5f);

            var plane = GameManager.server.CreateEntity(
                "assets/prefabs/npc/cargo plane/cargo_plane.prefab",
                position,
                Quaternion.Euler(0f, UnityEngine.Random.Range(0f, 360f), 0f));

            if (plane == null)
            {
                PrintWarning("Failed to create the cargo plane prefab. Check the prefab path after a game update.");
                return;
            }

            plane.Spawn();
            Interface.Oxide.CallHook("OnAirdropCalled", plane);
            Server.Broadcast(Msg("Incoming"));
            Puts("Airdrop spawned.");
        }

        [ChatCommand("airdrop")]
        private void CmdAirdrop(BasePlayer player, string command, string[] args)
        {
            if (!permission.UserHasPermission(player.UserIDString, PermUse))
            {
                player.ChatMessage(Msg("NoPermission", player.UserIDString));
                return;
            }

            SpawnAirdrop();
            player.ChatMessage(Msg("Called", player.UserIDString));
        }

        [ConsoleCommand("timedairdrop.status")]
        private void CcmdStatus(ConsoleSystem.Arg arg)
        {
            if (arg == null) return;
            var online = CountOnlinePlayers();
            arg.ReplyWith($"Interval: {config.IntervalMinutes} min, Minimum: {config.MinimumPlayers}, Online: {online}");
        }
    }
}

Hooks used: OnServerInitialized

Config file

Created automatically on first load at oxide/config/TimedAirdrop.json.

TimedAirdrop.json
{
  "Interval Minutes": 45,
  "Minimum Players": 5,
  "Spawn Height": 250
}

Commands and permissions

Chat and console commands
CommandWhat it doesPermission
/airdropSpawns a cargo plane supply drop immediately and announces it in chat.timedairdrop.use
timedairdrop.statusPrints the current interval, minimum player count and online player headcount to the caller (RCON or server console).Everyone
Permissions
PermissionGrants
timedairdrop.useAllows using /airdrop to trigger a supply drop manually.

How the code works

  1. Counting online players

    The CountOnlinePlayers helper walks BasePlayer.activePlayerList and skips NPCs, disconnected and dead players so only live players count. It returns an int compared against the Minimum Players config key. The check runs every time the timer fires, so the drop only happens when enough people are actually on the server — a quiet 2am server won't get plane after plane.

  2. Spawning the cargo plane

    SpawnAirdrop puts the plane at the middle of the map using TerrainMeta.Size.x/z * 0.5f at the Spawn Height value, with a random yaw from UnityEngine.Random.Range. It calls GameManager.server.CreateEntity on assets/prefabs/npc/cargo plane/cargo_plane.prefab, null-checks the result, then Spawn(). After spawning it calls the OnAirdropCalled hook on Oxide so other plugins can react, broadcasts the Incoming language message, and logs to console.

  3. The repeating timer

    OnServerInitialized calls StartTimer, which builds timer.Every with the interval in seconds from Interval Minutes * 60 (clamped to at least 1 minute) and stores the Timer reference in dropTimer. The same hook then schedules a one-off timer.Once(10f, ...) warm-up so a freshly loaded server doesn't wait a full cycle. Unload calls dropTimer?.Destroy() so reloading the plugin never stacks timers.

  4. Announcing drops in chat

    Every drop broadcasts the Incoming string from oxide/lang/en/TimedAirdrop.json, so admins can reword it without touching the DLL. Skips are logged rather than broadcast: when the headcount is below the threshold, Puts writes the Skipping airdrop line once per changed headcount so an idle server doesn't fill the log every interval. Players only see the message when a plane is actually flying.

  5. The /airdrop command

    The [ChatCommand("airdrop")] method checks permission.UserHasPermission against timedairdrop.use before doing anything. If the check passes it calls the same SpawnAirdrop path the timer uses, then sends the Called message back to that player only. This means scheduled drops and manual drops use identical spawn logic, spawn height and announcements, so there is no second code path to maintain.

  6. The status console command

    timedairdrop.status is a [ConsoleCommand] that replies with the current interval, minimum player count and live headcount via arg.ReplyWith. It is intended for admins running over RCON or the server console when tuning Interval Minutes and Minimum Players, so they can confirm the plugin is loaded and reading config values without leaving the terminal.

Install it on your server

  1. Save TimedAirdrop.cs

    Keep the file name exactly as the class name. Oxide matches them.
  2. Upload it to the plugins folder

    Oxide: oxide/plugins/. Carbon: carbon/plugins/. The server compiles and loads it within a few seconds, no restart needed.
  3. Edit the config if you want

    On first load the default config is written to oxide/config/TimedAirdrop.json (Carbon: carbon/configs/TimedAirdrop.json). After editing, reload it:oxide.reload TimedAirdrop c.reload TimedAirdrop # Carbon
  4. Grant the permission

    Give it to every player, or swap default for a group such as vip:oxide.grant group default timedairdrop.use c.grant group default timedairdrop.use # Carbon

Variations

Each one opens the generator with a ready prompt you can tweak.

  • Random interval instead of fixed

    Varies the delay between drops so players can't predict when the next plane arrives.

    Generate this version
  • Team-size cap

    Blocks scheduled drops when one giant team dominates the server, so solo players aren't farmed.

    Generate this version
  • Scrap reward for first looter

    Pays scrap to the first player to open the airdropped crate, using a companion reward plugin.

    Generate this version

FAQ

Does this plugin use the vanilla airdrop crate or a custom prefab?

It spawns assets/prefabs/npc/cargo plane/cargo_plane.prefab directly via GameManager.server.CreateEntity, so the plane and the crate it drops come from the game itself. Loot tables remain the standard airdrop pool. If Facepunch renames the prefab the plugin will log a warning and skip that drop instead of throwing.

How does the plugin install on Oxide or Carbon?

Drop TimedAirdrop.cs into oxide/plugins/ on an Oxide server, or the equivalent plugins folder on Carbon (Carbon loads Oxide plugins unchanged). Reload with oxide.reload TimedAirdrop. Config generates on first load under oxide/config/TimedAirdrop.json; messages are in oxide/lang/en/TimedAirdrop.json.

What happens if no players are online when the timer fires?

CheckScheduledDrop compares the headcount against Minimum Players and simply returns when it falls short, so nothing is queued. A quiet server never wakes to a backlog of planes. Use /airdrop if you want a drop with fewer players than the threshold.

Can I make the first drop happen sooner after a restart?

Yes — OnServerInitialized runs a warm-up timer.Once ten seconds after load, so a busy server gets a check almost immediately. If the headcount meets Minimum Players the plane spawns then. Change Interval Minutes in config to adjust the recurring cadence after that.

Why doesn't /airdrop work for a non-admin?

The command checks the timedairdrop.use permission. Grant it with oxide.grant user <name> timedairdrop.use or oxide.grant group admin timedairdrop.use. Players without it get the NoPermission language line. The timedairdrop.status console command has no permission gate — restrict it through your RCON access rules.

Customize it for your server

Change the reward, add a cooldown, restrict it to VIPs. Start from this prompt and edit anything.

Prompt: Create a Rust Oxide/Carbon plugin called TimedAirdrop that spawns a vanilla cargo plane every 45 minutes when at least 5 players are online, announces it in chat, and lets admins (timedairdrop.use) trigger one with /airdrop.
Customize for my server

Edit the prompt on the next screen. 2 free plugins a month, no card.

Last updated Oct 26, 2026.

Customize timed airdrops

Customize