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.
void OnExplosiveDropped(BasePlayer player, BaseEntity entity, ThrownWeapon thrownWeapon)Parameters
| Parameter | Type | Description |
|---|---|---|
player | BasePlayer | The 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. |
entity | BaseEntity | The 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. |
thrownWeapon | ThrownWeapon | The 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
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
OnExplosiveThrownand 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.ShortPrefabNameor 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.
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