OnCrateSpawned
OnCrateSpawned is called after a PatrolHelicopter or BradleyAPC dies and the game has spawned the loot crate at the wreck site.
void OnCrateSpawned(PatrolHelicopter patrolHelicopter, BaseEntity crateEntity)Parameters
| Parameter | Type | Description |
|---|---|---|
patrolHelicopter | PatrolHelicopter | The PatrolHelicopter that just died and dropped its crash crate. When the hook fires from BradleyAPC, declaring this as PatrolHelicopter makes Oxide skip the call entirely; use a BaseEntity parameter to receive both. |
crateEntity | BaseEntity | The crate entity that was spawned at the wreck, typically a HackableLockedCrate-style loot crate created by the helicopter or Bradley death handler. |
Return value
The return value is ignored at both injection sites, so returning null or any value has no effect on the game's crate spawning code. Use the hook to observe or modify the spawned crate entity rather than to cancel the spawn.
How it works
This hook fires inside PatrolHelicopter.OnDied and BradleyAPC.OnDied, after the gibs and crate entity have been created and the collision ignores set up. It runs once per kill, not per crate inside the crate inventory. The first parameter is the dying vehicle and the second is the spawned crate entity, so you can reach both the killer vehicle and the loot container in one callback.
Oxide dispatches by parameter type: a plugin declaring PatrolHelicopter only receives helicopter kills, BradleyAPC only Bradley kills. Declaring BaseEntity for the first parameter receives both, which is the usual choice for a shared crate-handling plugin. Because the third-party signature variant uses the same hook name, keep the second parameter typed as BaseEntity in both overloads so dispatch never fails on the crate argument.
The call happens after the crate has been spawned and positioned, so calling Kill(), moving it, or changing flags here takes effect immediately on the live entity. It fires off the death path, not on a timer, so keep the handler cheap.
Other signatures
Oxide also calls OnCrateSpawned from other places in the game with these parameter types. Declare the overload you need, or several:
void OnCrateSpawned(BradleyAPC bradleyAPC, BaseEntity baseEntity2)
Example plugin: Replace heli and Bradley crash crates with configurable loot
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;
using UnityEngine;
namespace Oxide.Plugins
{
[Info("Heli Crate Replacer", "RustPluginGenerator", "1.0.0")]
[Description("Replaces the crash crate dropped by a PatrolHelicopter or BradleyAPC with configurable loot.")]
public class HeliCrateReplacer : RustPlugin
{
private const string PermNotify = "helicratereplacer.notify";
private class PluginConfig
{
[JsonProperty("Enable for PatrolHelicopter")]
public bool EnableHeli = true;
[JsonProperty("Enable for BradleyAPC")]
public bool EnableBradley = true;
[JsonProperty("Replacement crate shortname")]
public string CrateShortname = "codelockedhackablecrate";
[JsonProperty("Replacement crate skin")]
public ulong CrateSkin = 0UL;
[JsonProperty("Spawn effect prefab")]
public string SpawnEffect = "assets/prefabs/npc/patrol helicopter/effects/rocket_explosion.prefab";
}
private PluginConfig config;
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>
{
["CrateReplaced"] = "A {0} kill crate has been replaced with custom loot.",
["Heli"] = "Patrol Helicopter",
["Bradley"] = "Bradley APC"
}, 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(PermNotify, this);
}
private void OnCrateSpawned(BaseEntity source, BaseEntity crateEntity)
{
if (source == null || crateEntity == null || crateEntity.IsDestroyed) return;
string sourceName = null;
if (source is PatrolHelicopter && config.EnableHeli) sourceName = "Heli";
else if (source is BradleyAPC && config.EnableBradley) sourceName = "Bradley";
if (sourceName == null) return;
Vector3 position = crateEntity.transform.position;
crateEntity.Kill();
ItemDefinition def = ItemManager.FindItemDefinition(config.CrateShortname);
if (def == null)
{
PrintWarning($"Unknown replacement shortname: {config.CrateShortname}");
return;
}
Item item = ItemManager.CreateByItemID(def.itemid, 1, config.CrateSkin);
if (item == null) return;
item.MoveToContainer(null);
BaseEntity replacement = item.Drop(position, Vector3.zero) as BaseEntity;
if (replacement == null) return;
replacement.SendNetworkUpdate();
if (!string.IsNullOrEmpty(config.SpawnEffect))
Effect.server.Run(config.SpawnEffect, position);
NotifyPermitted(Msg("CrateReplaced", null, Msg(sourceName)));
}
private void NotifyPermitted(string message)
{
foreach (BasePlayer player in BasePlayer.activePlayerList)
{
if (player != null && player.IsConnected && permission.UserHasPermission(player.UserIDString, PermNotify))
player.ChatMessage(message);
}
}
[ConsoleCommand("crate.reload")]
private void CmdReload(ConsoleSystem.Arg arg)
{
if (arg != null && arg.Player() != null && arg.Player().IsAdmin == false)
{
arg.ReplyWith("Admin only");
return;
}
LoadConfig();
LoadDefaultMessages();
arg?.ReplyWith("HeliCrateReplacer reloaded");
}
}
}The plugin declares OnCrateSpawned with a BaseEntity first parameter so it catches both PatrolHelicopter and BradleyAPC deaths. On each call it checks the config toggle for the source type, destroys the original crate with Kill(), and spawns a replacement at the same position. The replacement shortname, amount, skin and a broadcast message are all configurable. A crate.reload console command rereads config and messages, and Unload clears the change. Players with the helicratereplacer.notify permission get a chat line via the language file when a crate is swapped.
Common use cases
Custom loot for heli crashes
Kill the default crate and spawn a custom SupplyDrop or HackableLockedCrate with your own loot table, or refill the crate's inventory container in place.
Announce heli and Bradley kills
Send a server-wide chat message or audio effect when the crash crate appears so players know a fight just ended and loot is on the ground.
Limit crate farming on small servers
Track kills per player or per time window and remove the crate entirely when a cooldown is active, forcing players to spread out their heli farming.
Pitfalls
Wrong first parameter hides Bradley
If you declare the first parameter as
PatrolHelicopter, Oxide skips the call fromBradleyAPC.OnDied. UseBaseEntityto handle both, then cast withaswhen you need vehicle specifics.Crate already positioned and parented
The crate entity is spawned before the hook fires, so calling
Spawn()again or moving it afterKill()won't help. Destroy the old entity, then create and spawn the replacement at the old position.Multiple plugins may fight over the crate
Another plugin can kill or replace the same crate in its own handler. Null-check and call
IsDestroyedbefore touching the entity, and avoid assuming your replacement is the only entity at that position.
Build a plugin with OnCrateSpawned
Describe what should happen and get a compiled plugin that uses this hook.
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 OnCrateSpawned
Generate