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
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.
{
"Interval Minutes": 45,
"Minimum Players": 5,
"Spawn Height": 250
}Commands and permissions
| Command | What it does | Permission |
|---|---|---|
/airdrop | Spawns a cargo plane supply drop immediately and announces it in chat. | timedairdrop.use |
timedairdrop.status | Prints the current interval, minimum player count and online player headcount to the caller (RCON or server console). | Everyone |
| Permission | Grants |
|---|---|
timedairdrop.use | Allows using /airdrop to trigger a supply drop manually. |
How the code works
Counting online players
The
CountOnlinePlayershelper walksBasePlayer.activePlayerListand skips NPCs, disconnected and dead players so only live players count. It returns anintcompared against theMinimum Playersconfig 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.Spawning the cargo plane
SpawnAirdropputs the plane at the middle of the map usingTerrainMeta.Size.x/z * 0.5fat theSpawn Heightvalue, with a random yaw fromUnityEngine.Random.Range. It callsGameManager.server.CreateEntityonassets/prefabs/npc/cargo plane/cargo_plane.prefab, null-checks the result, thenSpawn(). After spawning it calls theOnAirdropCalledhook on Oxide so other plugins can react, broadcasts theIncominglanguage message, and logs to console.The repeating timer
OnServerInitializedcallsStartTimer, which buildstimer.Everywith the interval in seconds fromInterval Minutes * 60(clamped to at least 1 minute) and stores theTimerreference indropTimer. The same hook then schedules a one-offtimer.Once(10f, ...)warm-up so a freshly loaded server doesn't wait a full cycle.UnloadcallsdropTimer?.Destroy()so reloading the plugin never stacks timers.Announcing drops in chat
Every drop broadcasts the
Incomingstring fromoxide/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,Putswrites theSkipping airdropline 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.The /airdrop command
The
[ChatCommand("airdrop")]method checkspermission.UserHasPermissionagainsttimedairdrop.usebefore doing anything. If the check passes it calls the sameSpawnAirdroppath the timer uses, then sends theCalledmessage 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.The status console command
timedairdrop.statusis a[ConsoleCommand]that replies with the current interval, minimum player count and live headcount viaarg.ReplyWith. It is intended for admins running over RCON or the server console when tuningInterval MinutesandMinimum Players, so they can confirm the plugin is loaded and reading config values without leaving the terminal.
Install it on your server
Save TimedAirdrop.cs
Keep the file name exactly as the class name. Oxide matches them.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.Edit the config if you want
On first load the default config is written tooxide/config/TimedAirdrop.json(Carbon:carbon/configs/TimedAirdrop.json). After editing, reload it:oxide.reload TimedAirdrop c.reload TimedAirdrop # CarbonGrant the permission
Give it to every player, or swapdefaultfor a group such asvip: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 versionTeam-size cap
Blocks scheduled drops when one giant team dominates the server, so solo players aren't farmed.
Generate this versionScrap 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.
Edit the prompt on the next screen. 2 free plugins a month, no card.
More events plugins
- Bradley and heli kill announcerTracks damage dealt to the Bradley APC and patrol helicopter, then announces in chat which player did the most damage and the map grid where the…OnEntityTakeDamageOnEntityDeath
- Double gather hourRuns scheduled or admin-triggered double gather windows that multiply dispenser and collectible yields for a fixed duration, with start and end…OnServerInitializedOnDispenserGatherOnCollectiblePickup
- First bloodAnnounces the first player kill after a restart as First Blood and pays the killer a configured reward.OnServerInitializedOnNewSaveOnEntityDeath
Last updated Oct 26, 2026.
Customize timed airdrops
Customize