Skip to content
RustPluginGenerator

OnExplosiveDropped

OnExplosiveDropped is called by ThrownWeapon.DoDrop after the thrown entity has been spawned and set up, but before one unit of the item is consumed. It fires for every thrown weapon entity created through the throw-message code path.

Signature
void OnExplosiveDropped(BasePlayer player, BaseEntity entity, ThrownWeapon thrownWeapon)

Parameters

Hook parameters
ParameterTypeDescription
playerBasePlayerThe BasePlayer whose throw was released. This is msg.player from the game's throw message, so it is always a connected player at the moment the hook runs.
entityBaseEntityThe BaseEntity created for the thrown item (the live projectile/crate object). It has already been spawned and configured with creator, skin and thrown-weapon setup before the hook fires.
thrownWeaponThrownWeaponThe ThrownWeapon component that performed the drop, i.e. the held world entity of the thrown item (grenade, bean can, satchel, etc.). Its item is consumed right after the hook.

Return value

The return value is ignored; the hook is patched with a continue behavior in ThrownWeapon.DoDrop. It cannot be used to cancel the throw or prevent the item from being consumed.

How it works

The hook fires inside ThrownWeapon.DoDrop, once per throw message processed by the server. By the time it runs, the game has already created the entity from the prefab, set creatorEntity to the throwing player, applied the item skin, spawned it, run SetUpThrownWeapon (fuse times, velocity, etc.) and started the attack cooldown. Only then does the game call the hook, and immediately afterwards it calls UseItemAmount(1) to consume the item.

Use it to react to the moment of release: announce grenade throws, adjust the spawned entity (for example flags or skin) before anyone interacts with it, track throw statistics, or apply cooldowns. The entity parameter is the freshly created object, so you can inspect its prefab name (ShortPrefabName) to tell a grenade.f1 from a grenade.smoke or a explosive.satchel. thrownWeapon gives access to the weapon component itself, and player is the thrower.

You can narrow parameter types: declaring BaseCombatEntity or TimedExplosive for entity makes Oxide skip calls where the spawned entity is not that type. Rarely worth it here because the hook only fires for thrown weapons, but it is an option for filtering.

The hook fires on every valid throw that reaches DoDrop, so keep the body cheap and avoid logging to console there.

Example plugin: Announce and log grenade throws

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

namespace Oxide.Plugins
{
    [Info("Explosive Drop Notifier", "RustPluginGenerator", "1.0.0")]
    [Description("Announces and counts thrown explosives per player.")]
    public class ExplosiveDropNotifier : RustPlugin
    {
        private const string PermAnnounce = "explosivedropnotifier.announce";

        private PluginConfig config;
        private StoredData data;

        private class PluginConfig
        {
            [JsonProperty("Announce To All")]
            public bool AnnounceToAll = true;

            [JsonProperty("Tracked shortnames")]
            public List<string> TrackedShortnames = new List<string>
            {
                "grenade.f1",
                "grenade.beancan",
                "grenade.smoke"
            };
        }

        private class StoredData
        {
            [JsonProperty("Throws by user id")]
            public Dictionary<string, int> ThrowsByUser = new Dictionary<string, int>();
        }

        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 threw a {0}.",
                ["Broadcast"] = "{0} threw a {1}.",
                ["Count"] = "{0} has thrown {1} tracked explosive(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(PermAnnounce, this);
            data = Interface.Oxide.DataFileSystem.ReadObject<StoredData>(Name) ?? new StoredData();
        }

        private void OnExplosiveDropped(BasePlayer player, BaseEntity entity, ThrownWeapon thrownWeapon)
        {
            if (player == null || entity == null) return;

            string shortName = entity.ShortPrefabName;
            if (shortName == null || !config.TrackedShortnames.Contains(shortName)) return;

            string id = player.UserIDString;
            int count;
            data.ThrowsByUser.TryGetValue(id, out count);
            data.ThrowsByUser[id] = count + 1;

            string display = DisplayName(shortName);
            player.ChatMessage(Msg("Thrown", id, display));

            if (config.AnnounceToAll && permission.UserHasPermission(id, PermAnnounce))
            {
                Server.Broadcast(Msg("Broadcast", null, player.displayName, display));
            }
        }

        private static string DisplayName(string shortName)
        {
            ItemDefinition def = ItemManager.FindItemDefinition(shortName);
            return def != null && def.displayName != null ? def.displayName.english : shortName;
        }

        [ChatCommand("throws")]
        private void CmdThrows(BasePlayer player, string command, string[] args)
        {
            if (player == null) return;

            BasePlayer target = player;
            if (args != null && args.Length > 0)
            {
                if (!player.IsAdmin) return;
                target = BasePlayer.FindAwakeOrSleeping(args[0]);
                if (target == null)
                {
                    SendReply(player, "Player not found.");
                    return;
                }
            }

            int count;
            data.ThrowsByUser.TryGetValue(target.UserIDString, out count);
            SendReply(player, Msg("Count", player.UserIDString, target.displayName, count));
        }

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

The plugin listens to OnExplosiveDropped and checks the spawned entity's short prefab name against a configurable list of thrown shortnames (default: grenade.f1, grenade.beancan, grenade.smoke). For matching throws it prints a short chat message to the thrower and broadcasts an optional server-wide notice to all players when Announce To All is enabled in the config. It also increments a per-player counter so admins can quickly see who is throwing the most explosives; counts are persisted under oxide/data/ExplosiveDropNotifier and written on unload. The hook body stays short and avoids console logging. All player-facing text goes through the language file, and permission explosivedropnotifier.announce gates the server-wide broadcast.

Common use cases

  • Grenade throw announcements

    Broadcast a short notice to the server when a player throws an F1 grenade into a raid, so defenders get a situational cue without reading combat logs.

  • Throw statistics

    Increment per-player counters in a data file each time an explosive is released, then expose them through a chat command for admins reviewing raid activity.

  • Skin or flag adjustments

    Inspect the freshly spawned entity and apply a skin or flag before it interacts with the world, since the hook runs after spawn but before the item is consumed.

Pitfalls

  • Cannot block the throw

    The return value is ignored. If you need to prevent the throw entirely, use a throw-side hook such as OnExplosiveThrown and check whether the returned value cancels the action.

  • Runs before ammo is consumed

    The item is used after the hook returns. Do not attempt to remove or move the held item inside the hook; the game will still call UseItemAmount(1) afterwards.

  • Fires for every thrown entity

    DoDrop is shared by all thrown weapons, including smoke and non-lethal ones. Filter by entity.ShortPrefabName or narrow the parameter type if you only want explosive grenades.

Build a plugin with OnExplosiveDropped

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

Prompt: Grenade throw announcements using the OnExplosiveDropped 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 OnExplosiveDropped

Generate