Skip to content
RustPluginGenerator

OnCargoShipHarborLeave

OnCargoShipHarborLeave is called when a CargoShip finishes its harbor stop and resumes its route, right after its approach node counter advances and its reserved flags are cleared.

Signature
void OnCargoShipHarborLeave(CargoShip cargoShip)

Parameters

Hook parameters
ParameterTypeDescription
cargoShipCargoShipThe CargoShip entity that has just left the harbor node it was stopped at. Use its position, net.ID or OwnerID to identify the event; it is still a live spawned entity at this point.

Return value

The return value is ignored. The patched call site is the last statement in the void method CargoShip.LeaveHarbor, and Oxide calls the hook without acting on what your method returns, so returning null or any other value changes nothing about the ship's route.

How it works

The hook fires inside CargoShip.LeaveHarbor, immediately after the ship clears Flags.Reserved1 and sets Flags.Reserved2, and after its currentHarborApproachNode counter is incremented. That method is what moves the ship off the harbor stop it has been holding at, so this is the moment the crate run continues rather than the moment it arrived. The single argument is the ship itself, so you can read cargoShip.transform.position, cargoShip.net.ID.Value or cargoShip.OwnerID to identify it, and you can inspect flags or start a timer relative to its departure. There is only one parameter and its type is CargoShip, so no type narrowing is possible here; declaring a base type such as BaseEntity would still match but gains nothing. Keep the handler cheap: it fires once per harbor stop per ship, not per tick, but several ships can be routed at once on busy servers, so avoid spawning entities or writing data inside it.

Example plugin: Announce cargo ship harbor departures to admins

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

namespace Oxide.Plugins
{
    [Info("Cargo Ship Departure Alert", "RustPluginGenerator", "1.0.0")]
    [Description("Reports cargo ship harbor departures to staff and optionally broadcasts a notice")]
    public class CargoShipDepartureAlert : RustPlugin
    {
        private const string PermNotify = "cargoshipdeparturealert.notify";

        private PluginConfig config;
        private readonly Dictionary<ulong, float> spawnTimes = new Dictionary<ulong, float>();

        private class PluginConfig
        {
            [JsonProperty("Broadcast departures to all players")]
            public bool BroadcastToAll { get; set; } = false;

            [JsonProperty("Notify players with permission")]
            public bool NotifyStaff { get; set; } = true;
        }

        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>
            {
                ["Departure"] = "Cargo ship {0} left the harbor at {1} after {2} min at sea.",
                ["Broadcast"] = "A cargo ship has left port at {0}. Crates are on the move.",
                ["NoTracked"] = "No spawn time recorded for this ship."
            }, 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 OnEntitySpawned(BaseNetworkable entity)
        {
            var ship = entity as CargoShip;
            if (ship != null)
            {
                spawnTimes[ship.net.ID.Value] = Time.realtimeSinceStartup;
            }
        }

        private void OnEntityKill(BaseNetworkable entity)
        {
            if (entity == null) return;
            spawnTimes.Remove(entity.net.ID.Value);
        }

        private void OnCargoShipHarborLeave(CargoShip cargoShip)
        {
            if (cargoShip == null || cargoShip.net == null) return;
            if (cargoShip.IsDestroyed) return;

            var netId = cargoShip.net.ID.Value;
            var position = cargoShip.transform.position;
            var grid = PositionToGrid(position);

            float spawnedAt;
            string elapsed = spawnTimes.TryGetValue(netId, out spawnedAt)
                ? ((Time.realtimeSinceStartup - spawnedAt) / 60f).ToString("0.0")
                : Msg("NoTracked");

            var report = Msg("Departure", null, netId, grid, elapsed);

            if (config.NotifyStaff)
            {
                foreach (var player in BasePlayer.activePlayerList)
                {
                    if (player == null || player.IsNpc) continue;
                    if (!permission.UserHasPermission(player.UserIDString, PermNotify)) continue;
                    player.ChatMessage(report);
                }
            }

            if (config.BroadcastToAll)
            {
                Server.Broadcast(Msg("Broadcast", null, grid));
            }
        }

        private static string PositionToGrid(Vector3 position)
        {
            const float cellSize = 146.3f;
            var x = Mathf.FloorToInt((position.x + 2048f) / cellSize);
            var z = Mathf.FloorToInt((position.z + 2048f) / cellSize);
            var letter = (char)('A' + Mathf.Clamp(x, 0, 25));
            return letter + Mathf.Max(z, 0).ToString();
        }

        private void Unload()
        {
            spawnTimes.Clear();
        }
    }
}

The plugin listens for OnCargoShipHarborLeave and builds a short report: the ship's network ID, its grid position and how many seconds it has been tracked since spawn. It sends that report to players with the cargoshipdeparturealert.notify permission and, if the config option is enabled, broadcasts a chat notice to everyone. A Dictionary<ulong, float> records each ship's spawn time using Time.realtimeSinceStartup so the report can show elapsed time; entries are removed in OnEntityKill when the ship is killed. Config toggles the broadcast, and messages go through the language file so admins can translate them.

Common use cases

  • Admin route monitoring

    Report each harbor departure to staff with the ship's grid position so they can tell whether a crate run is progressing or stuck at a stop.

  • Custom event styling

    Play a horn effect or send a server-wide notice when a ship leaves a harbor, marking the second half of a cargo run for players hunting the crates.

  • Timing and statistics

    Record departure timestamps per ship to measure how long ships sit at each harbor and how quickly full routes complete on your map.

Pitfalls

  • This is departure, not arrival

    The hook runs after the ship resumes its route. Use OnCargoShipHarborArrived or OnCargoShipHarborApproach if you need the ship while it is holding at a harbor.

  • The ship may be short-lived

    Only keep references to the CargoShip if you clean them up. Store the network ID rather than the entity object, and clear state in OnEntityKill to avoid stale entries.

  • Return value does nothing

    Returning a value from this hook will not cancel the departure or redirect the ship; the call site ignores the result entirely.

Build a plugin with OnCargoShipHarborLeave

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

Prompt: Admin route monitoring using the OnCargoShipHarborLeave 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 OnCargoShipHarborLeave

Generate