Skip to content
RustPluginGenerator

OnCargoShipSpawnCrate

OnCargoShipSpawnCrate is called at the start of CargoShip.RespawnLoot, right before the ship spawns its locked and elite crates (and the following loot loop). Returning a non-null value skips the vanilla loot respawn for that ship.

Signature
object OnCargoShipSpawnCrate(CargoShip cargoShip)

Parameters

Hook parameters
ParameterTypeDescription
cargoShipCargoShipThe CargoShip entity that is repopulating its loot. Use it to read the ship's position, net ID or owner, or to attach your own state to that specific ship.

Return value

Return null to let the game run its normal respawn: horn repeats, locked crate, elite crate and the four additional crates. Return any non-null object to stop the vanilla method entirely for that ship; nothing below the hook call is executed, so no crates are spawned and the horn is not scheduled.

How it works

The hook fires inside CargoShip.RespawnLoot, which the game calls when a cargo ship populates (or repopulates) its hull with loot. The vanilla method starts the horn InvokeRepeating, spawns the locked crate, the elite crate and then a four-iteration loop of additional crates. OnCargoShipSpawnCrate runs before any of that.

The single parameter is the CargoShip entity being filled. Use it to inspect position, net.ID.Value, OwnerID or nearby players, and to decide whether the vanilla crate set should be replaced. Because the call happens once per respawn, it is a cheap place to run logic; it is not a per-crate hook.

Declaring the parameter as CargoShip is required here: no other type is passed, so a broader BaseEntity parameter would still match, but you would need to cast to reach ship members.

A common pattern is to block vanilla loot and spawn your own crates at custom offsets so a server can control the reward table. The hook does not fire for the crates themselves. Use OnCrateSpawned or OnEntitySpawned if you need a callback after each individual crate exists.

Example plugin: Replace vanilla cargo loot with a custom crate set

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

namespace Oxide.Plugins
{
    [Info("Cargo Ship Custom Loot", "RustPluginGenerator", "1.0.0")]
    [Description("Replaces the vanilla cargo ship crate spawn with a configurable crate set.")]
    public class CargoShipCustomLoot : RustPlugin
    {
        private class PluginConfig
        {
            [JsonProperty("Crate prefabs (path, amount)")]
            public List<CrateEntry> Crates = new List<CrateEntry>
            {
                new CrateEntry { Prefab = "assets/prefabs/misc/crate_grey.prefab", Amount = 1 },
                new CrateEntry { Prefab = "assets/prefabs/misc/crate_blue.prefab", Amount = 1 },
                new CrateEntry { Prefab = "assets/prefabs/misc/crate_green.prefab", Amount = 2 }
            };

            [JsonProperty("Delay before crates spawn (seconds)")]
            public float SpawnDelay = 1f;

            [JsonProperty("Offset between crates (meters)")]
            public float Offset = 1.5f;
        }

        public class CrateEntry
        {
            [JsonProperty("Prefab path")]
            public string Prefab;

            [JsonProperty("Amount")]
            public int Amount;
        }

        private PluginConfig config;
        private readonly HashSet<ulong> handledShips = new HashSet<ulong>();
        private Timer spawnTimer;

        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);

        private object OnCargoShipSpawnCrate(CargoShip cargoShip)
        {
            if (cargoShip == null || cargoShip.IsDestroyed) return null;
            if (handledShips.Contains(cargoShip.net.ID.Value)) return null;

            handledShips.Add(cargoShip.net.ID.Value);
            Vector3 origin = cargoShip.transform.position;

            spawnTimer = timer.Once(config.SpawnDelay, () =>
            {
                if (cargoShip == null || cargoShip.IsDestroyed) return;
                int index = 0;
                foreach (CrateEntry entry in config.Crates)
                {
                    if (entry == null || string.IsNullOrEmpty(entry.Prefab)) continue;
                    int count = Mathf.Max(1, entry.Amount);
                    for (int i = 0; i < count; i++)
                    {
                        Vector3 pos = origin + new Vector3(0f, 1f, index * config.Offset);
                        BaseEntity crate = GameManager.server.CreateEntity(entry.Prefab, pos, Quaternion.identity);
                        if (crate == null) continue;
                        crate.Spawn();
                        index++;
                    }
                }
            });

            return true;
        }

        private void OnEntityKill(BaseNetworkable entity)
        {
            CargoShip ship = entity as CargoShip;
            if (ship != null) handledShips.Remove(ship.net.ID.Value);
        }

        private void Unload()
        {
            spawnTimer?.Destroy();
            spawnTimer = null;
            handledShips.Clear();
        }
    }
}

The plugin reads a config that lists crate prefab paths and how many of each to spawn. In OnCargoShipSpawnCrate it checks whether the ship already has an active loot cycle tracked in a dictionary; if so it returns null so the vanilla path proceeds. Otherwise it records the ship and returns a non-null object to cancel vanilla loot, then schedules a short timer that creates each configured crate at the ship's position using GameManager.server.CreateEntity, calls Spawn() and adds a small offset per index. A safeguard removes stale entries when the ship is killed via OnEntityKill on a CargoShip. Config values default to the standard locked and elite crates so the plugin is safe to install with no edits.

Common use cases

  • Server economy balance

    Swap vanilla crates for a reduced or expanded set to match the server's loot economy, or to route loot through a custom tier system.

  • Event-driven cargo

    Use the hook as a trigger to spawn a scripted encounter, alert a team, or teleport a marker to the ship's location before any crate exists.

  • Logging and audit

    Record the ship position, time and online player count to a data file so admins can review when cargo cycles happen on their server.

Pitfalls

  • Cancelling blocks all vanilla crates

    Returning non-null stops the entire method, including the horn and both locked and elite spawns. If you want the vanilla set plus extra crates, spawn them from a timer or from OnCrateSpawned instead.

  • No player list in the hook

    The hook only receives the ship entity. To inspect nearby players, read BasePlayer.activePlayerList yourself and compare positions; the ship reference is enough for that but no loot or crate list is passed.

  • Timers must be destroyed

    Crates spawned one tick later rely on a Timer. Reference it and call Destroy() in Unload, otherwise a plugin reload leaves a pending timer to fire on a destroyed ship.

Build a plugin with OnCargoShipSpawnCrate

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

Prompt: Server economy balance using the OnCargoShipSpawnCrate 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 OnCargoShipSpawnCrate

Generate