Skip to content
RustPluginGenerator

OnCoalingTowerStart

OnCoalingTowerStart is called when a player invokes the CoalingTower unload RPC, giving plugins a chance to block the tower from unloading its active wagon.

Signature
object OnCoalingTowerStart(CoalingTower coalingTower, BasePlayer player)

Parameters

Hook parameters
ParameterTypeDescription
coalingTowerCoalingTowerThe CoalingTower entity whose unload RPC was invoked. Use it for its position, net.ID or to read or change the tower's state.
playerBasePlayerThe BasePlayer who triggered the unload RPC. Can be null when the RPC arrives without an attached connection, so null-check it before use.

Return value

Return null to let the vanilla unload attempt run as normal. Return any non-null value (a bool works) to skip the original unload path entirely, so no wagon is released and no ActionFailed RPC is sent to the player.

How it works

The hook fires inside CoalingTower.RPC_Unload, the server-side RPC handler for a player requesting that a coaling tower release its active wagon. The RPC carries a player, so player is normally the client that sent the request, but the handler does not guarantee it is non-null, so null-check it. coalingTower is the tower entity that received the RPC; its position, net.ID and ownership are the usual things you need when deciding whether to allow the unload.

Returning a non-null value stops the original unload path, so the tower keeps its wagon and the player is not sent the ActionFailed client RPC. Returning null lets the unload continue and the vanilla error feedback is still sent when the unload attempt fails. Because the injection happens before the actual unload logic, this is a gate, not a notification: you cannot use it to react to a completed unload.

Declaring the player parameter as BasePlayer matches the RPC caller. If you only care about the tower, drop the parameter or replace it with a more specific type only when you can guarantee the argument instance matches, otherwise Oxide will skip your method on calls where the argument is a different type.

Example plugin: Rate-limit coaling tower unloads and log them

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

namespace Oxide.Plugins
{
    [Info("Coaling Tower Guard", "RustPluginGenerator", "1.0.0")]
    [Description("Rate-limits coaling tower unload requests and notifies players on cooldown.")]
    public class CoalingTowerGuard : RustPlugin
    {
        private const string PermBypass = "coalingtowerguard.bypass";

        private PluginConfig config;
        private readonly Dictionary<string, float> lastRequest = new Dictionary<string, float>();

        private class PluginConfig
        {
            [JsonProperty("Cooldown seconds")]
            public float CooldownSeconds { get; set; } = 10f;

            [JsonProperty("Notify player on cooldown")]
            public bool NotifyOnCooldown { 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>
            {
                ["Cooldown"] = "The coaling tower is still busy. Try again in {0}s."
            }, 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(PermBypass, this);
        }

        private object OnCoalingTowerStart(CoalingTower coalingTower, BasePlayer player)
        {
            if (coalingTower == null || player == null) return null;
            if (permission.UserHasPermission(player.UserIDString, PermBypass)) return null;

            string userId = player.UserIDString;
            float now = UnityEngine.Time.realtimeSinceStartup;
            float last;
            if (lastRequest.TryGetValue(userId, out last))
            {
                float elapsed = now - last;
                if (elapsed < config.CooldownSeconds)
                {
                    if (config.NotifyOnCooldown)
                    {
                        float remaining = Mathf.Ceil(config.CooldownSeconds - elapsed);
                        player.ChatMessage(Msg("Cooldown", userId, remaining));
                    }
                    return true;
                }
            }

            lastRequest[userId] = now;
            return null;
        }

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

The plugin adds a cooldown per player on coaling tower unload requests and stops the vanilla unload when a player spams the RPC. A configurable CooldownSeconds value is compared against a dictionary of last-request times keyed by user ID string. Players with the coalingtowerguard.bypass permission skip the check. On a block, OnCoalingTowerStart sends a localized chat message and returns true, which stops the original unload and suppresses the ActionFailed RPC. Otherwise it records the timestamp, returns null and lets the tower unload normally. The dictionary is cleared on unload so reloads start fresh.

Common use cases

  • Cooldown on unload requests

    Stop players from hammering the unload RPC by returning true while a per-player timestamp is still fresh.

  • Restrict who can unload a tower

    Block the RPC unless the player is authorised on the tower's building privilege or holds a permission, then return true to deny silently.

  • Audit tower usage

    Record tower position and player ID for each unload attempt, since the hook fires on every request the client sends.

Pitfalls

  • Cancelling hides the failure message

    Returning non-null skips the whole original branch, so the player never receives the ActionFailed RPC. Send your own feedback or they will see nothing happen.

  • The player can be null

    The RPC handler only checks the player after the hook. Guard against a null player before reading UserIDString or sending chat.

  • Fires per request, not per unload

    The hook runs before TryUnloadActiveWagon, so it fires even when the unload would have failed. Do not treat it as proof the wagon was released.

Build a plugin with OnCoalingTowerStart

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

Prompt: Cooldown on unload requests using the OnCoalingTowerStart 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 OnCoalingTowerStart

Generate