OnWeaponModChange
OnWeaponModChange is called from BaseProjectile.DelayedModsChanged, which runs when the weapon's mod list has been altered and the game is about to recompute magazine capacity and other mod-derived stats.
object OnWeaponModChange(BaseProjectile projectile, BasePlayer ownerPlayer)Parameters
| Parameter | Type | Description |
|---|---|---|
projectile | BaseProjectile | The BaseProjectile whose attached weapon mods were just recalculated, typically the weapon held by the player who swapped, attached or removed a mod. |
ownerPlayer | BasePlayer | The BasePlayer that owns the weapon, taken from GetOwnerPlayer(). This is null when the projectile has no player holder, for example when it sits in a container or is held by an NPC without a player owner. |
Return value
Returning null lets the original DelayedModsChanged continue, so the game recalculates magazine capacity and applies the mod effects normally. Returning any non-null object stops that method immediately and the recalculation is skipped, which leaves the previous capacity/stat state in place; do not return a value unless you intend to block the change handling.
How it works
The hook fires inside BaseProjectile.DelayedModsChanged, the deferred pass the game runs after a weapon's mod list changes. In practice this happens when a player attaches, swaps or removes a mod (scope, barrel, magazine, muzzle) on the held weapon, and whenever the game schedules that delayed recalculation after a mod item moves in or out of the weapon. Because the call happens before the capacity math, it is the right place to inspect or gate the mod layout rather than reacting after stats are applied.
projectile gives you the weapon instance: read projectile.AttachedEntity for the Item to inspect contents and each mod's info.shortname. ownerPlayer is convenient for permission checks and chat feedback but can be null for stored or NPC-held weapons, so always null-check it before using player APIs.
Declaring BaseProjectile (as in the signature) is already the specific type; do not widen the first parameter to BaseEntity unless you deliberately want calls for other held entities, which this hook does not produce.
Example plugin: Limit weapon mods by permission
using UnityEngine;
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;
namespace Oxide.Plugins
{
[Info("Weapon Mod Gate", "RustPluginGenerator", "1.0.0")]
[Description("Limits how many weapon mods non-permitted players can keep attached")]
public class WeaponModGate : RustPlugin
{
private const string PermUse = "weaponmodgate.use";
private PluginConfig config;
private readonly Dictionary<ulong, float> lastMessage = new Dictionary<ulong, float>();
private class PluginConfig
{
[JsonProperty("Enable restriction")]
public bool Enabled = true;
[JsonProperty("Max mods without permission")]
public int MaxMods = 0;
}
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>
{
["TooManyMods"] = "You cannot attach more than {0} weapon mod(s) without the required permission."
}, 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(PermUse, this);
}
private object OnWeaponModChange(BaseProjectile projectile, BasePlayer ownerPlayer)
{
if (!config.Enabled || projectile == null || ownerPlayer == null) return null;
if (permission.UserHasPermission(ownerPlayer.UserIDString, PermUse)) return null;
var weapon = projectile.GetItem();
if (weapon?.contents?.itemList == null) return null;
if (weapon.contents.itemList.Count <= config.MaxMods) return null;
var player = ownerPlayer;
NextTick(() =>
{
if (player == null || !player.IsConnected || weapon == null || weapon.contents == null) return;
StripExtraMods(player, weapon);
});
return null;
}
private void StripExtraMods(BasePlayer player, Item weapon)
{
if (weapon.contents.itemList.Count <= config.MaxMods) return;
var removed = new List<Item>();
for (int i = weapon.contents.itemList.Count - 1; i >= config.MaxMods && i >= 0; i--)
{
removed.Add(weapon.contents.itemList[i]);
}
foreach (var mod in removed)
{
mod.RemoveFromContainer();
if (!player.inventory.GiveItem(mod))
{
mod.Drop(player.transform.position + UnityEngine.Vector3.up, UnityEngine.Vector3.zero);
}
}
Notify(player);
}
private void Notify(BasePlayer player)
{
float now = UnityEngine.Time.realtimeSinceStartup;
float last;
if (lastMessage.TryGetValue(player.userID, out last) && now - last < 3f) return;
lastMessage[player.userID] = now;
player.ChatMessage(Msg("TooManyMods", player.UserIDString, config.MaxMods));
}
private void Unload()
{
lastMessage.Clear();
}
}
}The plugin defines a permission, weaponmodgate.use, and a config flag controlling whether the restriction is active. Inside OnWeaponModChange it reads projectile.AttachedEntity as an Item, walks item.contents.itemList and counts how many mods are attached. If the owner lacks the permission and the count exceeds MaxMods, the plugin reverts by scheduling a NextTick that pulls mods off the weapon and returns them to the player inventory, then messages the player through the language file. It returns null so normal capacity handling still runs. A short cooldown map prevents the revert message from spamming if several mod moves happen in quick succession.
Common use cases
Restrict mod count for non-VIP players
Check the mod contents of the weapon and strip or reject extra attachments when the owner lacks a permission, keeping fully kitted weapons behind a donor tier.
Log suspicious mod layouts
Record the shortnames of all attached mods for weapons that appear with combinations the vanilla item set should not allow, useful for catching item-spawning exploits.
Apply custom stats on mod change
Store your own modifiers keyed by mod shortname and apply them in a separate timer pass so your balance changes are not overwritten by the game's recalculation.
Pitfalls
Owner is often null
GetOwnerPlayer() returns null for weapons in boxes, on the ground or held by NPCs. Guard every player call, otherwise the hook throws and the game's mod recalculation is interrupted.
It fires on every delayed change, not just player actions
The deferred pass can run more than once per edit and during load. Keep the body cheap and avoid PrintWarning/Puts inside it; queue any messaging through a timer or chat message only when state actually changed.
Modifying inventory inside the hook is unsafe
Moving or removing mod items directly mid-call can leave the weapon item contents in an inconsistent state. Defer changes with NextTick and re-validate the item still exists before touching it.
Build a plugin with OnWeaponModChange
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 OnWeaponModChange
Generate