Skip to content
RustPluginGenerator

OnExplosiveThrown

OnExplosiveThrown is called right after ThrownWeapon.DoThrow creates the thrown entity, for explosives and thrown weapons alike. It fires before the throw result is sent back to the client and before the server continues with its spawn handling.

Signature
void OnExplosiveThrown(BasePlayer player, BaseEntity thrownEntity, ThrownWeapon thrownWeapon)

Parameters

Hook parameters
ParameterTypeDescription
playerBasePlayerThe BasePlayer who threw the explosive. Null-check it before reading inventory or identity fields.
thrownEntityBaseEntityThe BaseEntity created by the throw (the spawned explosive or thrown weapon object). It can be null when the throw failed to produce an entity, so always guard it.
thrownWeaponThrownWeaponThe ThrownWeapon component that handled the throw, such as the item in hand that triggered DoThrow.

Return value

The return value is ignored at this injection site, so returning null or any object has no effect on the throw. The original DoThrow code continues normally and still sends the throw result to the player.

How it works

The hook fires inside ThrownWeapon.DoThrow, immediately after the internal throw implementation returns the spawned thrownEntity and before the server sends the throw result to the client. Any item driven by ThrownWeapon that goes through DoThrow triggers it, which in practice means grenades, beancan grenades, satchels and similar thrown explosives, plus thrown melee items. player is the thrower, thrownEntity is the newly created world entity (it may be null when creation failed), and thrownWeapon is the ThrownWeapon component that performed the throw. Use the hook to inspect or react to the entity while it exists: track it, attach metadata, or run checks against the thrower. Because the call happens before the client result is sent, thrownEntity is already spawned. Declaring a narrower second parameter such as TimedExplosive makes Oxide run your method only when the created entity is that type; other throws skip it. Declaring BasePlayer as the first parameter is already the exact type, so narrowing there is not useful. Keep the method lightweight, since it runs on every throw.

Example plugin: Track thrown explosives per player

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

namespace Oxide.Plugins
{
    [Info("Thrown Explosive Tracker", "RustPluginGenerator", "1.0.0")]
    [Description("Counts thrown explosives per player and reports totals through a chat command")]
    public class ThrownExplosiveTracker : RustPlugin
    {
        private const string PermReport = "thrownexplosivetracker.report";

        private PluginConfig config;
        private Dictionary<string, int> counts = new Dictionary<string, int>();

        private class PluginConfig
        {
            [JsonProperty("Short prefab names to count")]
            public List<string> TrackedPrefabs = new List<string> { "explosive.timed.deployed", "grenade.beancan.deployed", "grenade.f1.deployed" };

            [JsonProperty("Notify thrower in chat")]
            public bool NotifyThrower = false;
        }

        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>
            {
                ["Thrown"] = "You have thrown {0} tracked explosive(s) this session.",
                ["Report"] = "{0} has thrown {1} tracked explosive(s) this session.",
                ["Usage"] = "Usage: /throwcount <player name or id>"
            }, 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(PermReport, this);
        }

        private void OnServerInitialized()
        {
            counts = Interface.Oxide.DataFileSystem.ReadObject<Dictionary<string, int>>(Name) ?? new Dictionary<string, int>();
        }

        private void OnExplosiveThrown(BasePlayer player, BaseEntity thrownEntity, ThrownWeapon thrownWeapon)
        {
            if (player == null || thrownEntity == null) return;
            string prefab = thrownEntity.ShortPrefabName;
            if (!config.TrackedPrefabs.Contains(prefab)) return;

            int current;
            counts.TryGetValue(player.UserIDString, out current);
            current++;
            counts[player.UserIDString] = current;

            if (config.NotifyThrower && player.IsConnected)
                player.ChatMessage(Msg("Thrown", player.UserIDString, current));
        }

        [ChatCommand("throwcount")]
        private void CmdThrowCount(BasePlayer player, string command, string[] args)
        {
            if (player == null) return;
            if (!permission.UserHasPermission(player.UserIDString, PermReport))
            {
                player.ChatMessage(Msg("Usage", player.UserIDString));
                return;
            }
            if (args == null || args.Length < 1)
            {
                player.ChatMessage(Msg("Usage", player.UserIDString));
                return;
            }

            BasePlayer target = BasePlayer.FindAwakeOrSleeping(args[0]);
            if (target == null)
            {
                player.ChatMessage(Msg("Usage", player.UserIDString));
                return;
            }

            int total;
            counts.TryGetValue(target.UserIDString, out total);
            player.ChatMessage(Msg("Report", player.UserIDString, target.displayName, total));
        }

        private void Unload()
        {
            Interface.Oxide.DataFileSystem.WriteObject(Name, counts);
        }
    }
}

The plugin keeps a running count of explosives each player has thrown since the last server restart. In OnExplosiveThrown it null-checks the player and the thrown entity, resolves a display name from the entity's short prefab name against a configurable allow list, increments the player's counter in a dictionary and stores the current session figure in the same record. A chat command reports the totals, and the counters reset on plugin unload through the stored timer cycle. The config controls which short prefab names are counted and whether a chat notice is sent to the thrower after each throw.

Common use cases

  • Throw statistics and leaderboards

    Count grenades and satchels thrown per player and expose totals through a chat command or an external plugin call.

  • Type-specific handling

    Declare the second parameter as TimedExplosive to react only to explosive throws and ignore thrown melee items.

  • Throw restrictions with feedback

    Check the thrower's permission or zone and message them when a throw is not allowed in the current context.

Pitfalls

  • thrownEntity can be null

    When entity creation fails, the hook still fires with a null entity, so guard before touching its prefab name, position or type.

  • You cannot cancel the throw here

    The return value is ignored and the throw has already been resolved, so blocking must happen in an earlier hook such as OnPlayerInput or OnItemUse.

  • Fires for non-explosive throws too

    Thrown melee weapons also route through DoThrow, so filter by prefab name or declare the specific entity type.

Build a plugin with OnExplosiveThrown

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

Prompt: Throw statistics and leaderboards using the OnExplosiveThrown 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 OnExplosiveThrown

Generate