Skip to content
RustPluginGenerator

OnWeaponFired

OnWeaponFired is called on the server when a client reports firing a projectile weapon, after the anti-cheat count check and before the attack signal and projectile spawn.

Signature
void OnWeaponFired(BaseProjectile projectile, BasePlayer player, ItemModProjectile component, ProtoBuf.ProjectileShoot projectileShoot)

Parameters

Hook parameters
ParameterTypeDescription
projectileBaseProjectileThe BaseProjectile weapon entity that fired, e.g. a rifle or bow. Use it to read the held weapon, its owner or prefab name.
playerBasePlayerThe BasePlayer who pulled the trigger. Check for null and for IsNpc/IsAdmin before applying player-specific logic.
componentItemModProjectileThe ItemModProjectile for the fired projectile, describing the ammo item being launched. Can be null if the projectile has no item component.
projectileShootProtoBuf.ProjectileShootThe ProtoBuf.ProjectileShoot network message the client sent, containing the projectile count, ammo id and start positions.

Return value

The return value is ignored. The game continues running CLProject normally after the hook, broadcasting the attack signal and spawning the projectiles.

How it works

The hook fires inside BaseProjectile.CLProject, the server method that processes a client's projectile shoot message. It runs after the anti-hack projectile count check passes (a mismatch returns early and the hook never fires) and before the weapon broadcasts its attack signal and spawns the projectiles. Every shot from a projectile-based weapon โ€” guns, bows, crossbows โ€” produced by serverInput passes through here, whether the shot is legitimately aimed at a target or fired in the air.

The player argument tells you who fired; projectile is the weapon entity itself, so projectile.ShortPrefabName or projectile.ownerID are available. component exposes the ammo ItemModProjectile, while projectileShoot carries raw message data like projectile count. Narrowing the projectile parameter to a subclass (e.g. BaseProjectile is already the base) will not help here because the game only calls this hook from BaseProjectile. Declaring player as BasePlayer is correct; a different type skips the call.

Because this fires once per shot at a high rate on busy servers, keep the callback lightweight โ€” no chat prints, no per-shot logging, no config writes.

Example plugin: Rate-limit rapid firing and punish impossible fire rates

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

namespace Oxide.Plugins
{
    [Info("FireRateLimiter", "RustPluginGenerator", "1.0.0")]
    [Description("Rate-limits projectile weapon fire and punishes impossible fire rates.")]
    public class FireRateLimiter : RustPlugin
    {
        private const string PermBypass = "fireratelimiter.bypass";

        private PluginConfig config;

        private class PluginConfig
        {
            [JsonProperty("Minimum seconds between shots by weapon prefab")]
            public Dictionary<string, float> MinInterval = new Dictionary<string, float>
            {
                ["rifle.ak"] = 0.12f,
                ["rifle.lr300"] = 0.12f,
                ["rifle.bolt"] = 0.9f,
                ["pistol.semiauto"] = 0.13f,
                ["pistol.python"] = 0.13f,
                ["smg.2"] = 0.09f,
                ["smg.thompson"] = 0.11f
            };

            [JsonProperty("Hurt offending player instead of kick")]
            public bool HurtInsteadOfKick = true;

            [JsonProperty("Damage applied per violation")]
            public float Damage = 5f;
        }

        private readonly Dictionary<string, Dictionary<string, float>> lastShot = new Dictionary<string, Dictionary<string, float>>();

        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 void Init()
        {
            permission.RegisterPermission(PermBypass, this);
        }

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

        private void OnWeaponFired(BaseProjectile projectile, BasePlayer player, ItemModProjectile component, ProtoBuf.ProjectileShoot projectileShoot)
        {
            if (projectile == null || player == null || !player.IsConnected)
                return;
            if (player.IsNpc || player.IsAdmin)
                return;
            if (permission.UserHasPermission(player.UserIDString, PermBypass))
                return;

            string weapon = projectile.ShortPrefabName;
            float minInterval;
            if (!config.MinInterval.TryGetValue(weapon, out minInterval) || minInterval <= 0f)
                return;

            float now = Time.realtimeSinceStartup;
            string id = player.UserIDString;

            Dictionary<string, float> playerTimes;
            if (!lastShot.TryGetValue(id, out playerTimes))
            {
                playerTimes = new Dictionary<string, float>();
                lastShot[id] = playerTimes;
            }

            float previous;
            if (playerTimes.TryGetValue(weapon, out previous))
            {
                float gap = now - previous;
                if (gap < minInterval)
                {
                    ApplyPenalty(player, gap, minInterval);
                    return;
                }
            }

            playerTimes[weapon] = now;
        }

        private void ApplyPenalty(BasePlayer player, float gap, float minInterval)
        {
            if (config.HurtInsteadOfKick)
            {
                player.Hurt(config.Damage);
                player.ChatMessage($"<color=#ff6666>Fire rate limit exceeded: {gap:F3}s &lt; {minInterval:F3}s.</color>");
                return;
            }

            player.Kick("Fire rate limit exceeded");
        }

        private void OnPlayerDisconnected(BasePlayer player, string reason)
        {
            if (player != null)
                lastShot.Remove(player.UserIDString);
        }
    }
}

The plugin tracks the last fire timestamp for each weapon prefab per player using Time.realtimeSinceStartup. When OnWeaponFired fires, it checks whether the gap between consecutive shots is shorter than the configured minimum interval for that weapon (defaults to values that ordinary weapons cannot beat). If the shot is too fast, the plugin cancels the action by hurting the player and (optionally) kicking them via Kick. Valid shots update the timestamp. State is stored in a dictionary keyed by user ID and weapon prefab, cleaned up on disconnect and on unload. Config lets admins tune the minimum interval and choose between hurting and kicking.

Common use cases

  • Anti-auto-fire tampering

    Detect clients firing faster than the weapon's real fire rate allows and act on the offending player.

  • Ammo accounting and statistics

    Count shots fired per weapon for leaderboards, XP systems or event scoring without touching the item logic.

  • PvP zone restrictions

    Block or mute a specific weapon's fire inside a protected zone by acting on the shooter when the hook fires.

Pitfalls

  • Fires only after the anti-cheat count check

    If the client sends a projectile count that fails AntiHack.Log, CLProject returns before the hook. You cannot catch every invalid message from here.

  • Very high call frequency

    On servers with many shooters this hook can run dozens of times per second. Keep dictionaries small and avoid PrintToChat/Puts inside it.

  • player can be an NPC

    Scientist and animal NPCs with projectile weapons can trigger the hook too. Filter with player.IsNpc before applying player-only behavior.

Build a plugin with OnWeaponFired

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

Prompt: Anti-auto-fire tampering using the OnWeaponFired 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 OnWeaponFired

Generate