Skip to content
RustPluginGenerator

OnTrapSnapped

OnTrapSnapped is called from BaseTrapTrigger.OnObjectAdded the moment an object enters a trap's trigger volume, before the trap entity receives ObjectEntered and before the snap check completes.

Signature
void OnTrapSnapped(BaseTrapTrigger trapTrigger, UnityEngine.GameObject obj, UnityEngine.Collider col)

Parameters

Hook parameters
ParameterTypeDescription
trapTriggerBaseTrapTriggerThe BaseTrapTrigger whose trigger volume has just flagged an object. Use it to reach the parent trap entity with trapTrigger.trap, get its OwnerID, OwnerID, position and ShortPrefabName.
objUnityEngine.GameObjectThe GameObject that entered the trigger volume, typically the root object of a player, NPC or dropped item. Check for a BasePlayer with obj.ToBaseEntity() as BasePlayer.
colUnityEngine.ColliderThe Collider that produced the overlap, useful when you need the exact body part or a component on the hit object via col.GetComponent<T>() or col.gameObject.

Return value

The return value is ignored. The original OnObjectAdded body runs regardless of what you return, so the trap proceeds to notify its entity and evaluate the object. Return nothing from this hook; use it only for observation or side effects.

How it works

The hook fires whenever a BaseTrapTrigger (the trigger component on bear traps, landmines, snap traps and similar BaseTrap prefabs) registers a new overlapping object. Because the call sits at the very start of BaseTrapTrigger.OnObjectAdded, it runs before the trap entity's own ObjectEntered handler and before base.OnObjectAdded, so at this point the trap has not yet been armed at the object or reacted to it.

trapTrigger is the trigger component itself; trapTrigger.trap gives you the owning BaseTrap entity for ownership, prefab and position checks. obj is the entering GameObject and col is the producing collider, so you can distinguish a player body from an NPC, an animal or a dropped item and read the body part when needed.

Declaring trapTrigger as a more specific type is not helpful here, but you can safely take obj/col as derived types only if you are sure every overlap is that type — Oxide skips calls where the argument is not an instance of your parameter type, so the hook would silently miss other entries. Most plugins use the base types and cast manually.

The hook is informational only: it cannot cancel the entry, and the trap still processes the object afterwards.

Example plugin: Notify owners when someone steps on their trap

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

namespace Oxide.Plugins
{
    [Info("Trap Trigger Notifier", "RustPluginGenerator", "1.0.0")]
    [Description("Alerts trap owners when a player steps on one of their traps")]
    public class TrapTriggerNotifier : RustPlugin
    {
        private const string PermAlerts = "traptriggernotifier.alerts";

        private PluginConfig config;
        private readonly Dictionary<string, double> cooldowns = new Dictionary<string, double>();

        private class PluginConfig
        {
            [JsonProperty("Notify trap owners")]
            public bool NotifyOwners = true;

            [JsonProperty("Seconds between alerts per trap")]
            public float CooldownSeconds = 20f;
        }

        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>
            {
                ["Alert"] = "{0} stepped on your {1} at {2}.",
                ["NoPermission"] = "You do not have permission to receive trap alerts.",
                ["Enabled"] = "Trap alerts are enabled for you."
            }, 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(PermAlerts, this);
            cooldowns.Clear();
        }

        private void OnTrapSnapped(BaseTrapTrigger trapTrigger, GameObject obj, Collider col)
        {
            if (!config.NotifyOwners || trapTrigger == null || obj == null) return;

            // BaseTrapTrigger is not a BaseEntity; the hosting trap is found in its parents.
            var trap = trapTrigger.GetComponentInParent<BaseEntity>();
            if (trap == null || trap.IsDestroyed || trap.OwnerID == 0) return;

            var player = obj.ToBaseEntity() as BasePlayer;
            if (player == null || player.IsNpc || !player.IsConnected || player.IsDead()) return;

            var owner = BasePlayer.FindByID(trap.OwnerID);
            if (owner == null || !owner.IsConnected || owner == player) return;
            if (!permission.UserHasPermission(owner.UserIDString, PermAlerts)) return;

            string cooldownKey = $"{owner.UserIDString}:{trap.net.ID.Value}";
            double now = Time.realtimeSinceStartup;
            double last;
            if (cooldowns.TryGetValue(cooldownKey, out last) && now - last < config.CooldownSeconds) return;
            cooldowns[cooldownKey] = now;

            string trapName = trap.ShortPrefabName ?? "trap";
            Vector3 pos = trap.transform.position;
            owner.ChatMessage(Msg("Alert", owner.UserIDString, player.displayName, trapName, $"({Mathf.RoundToInt(pos.x)}, {Mathf.RoundToInt(pos.z)})"));
        }

        [ChatCommand("trapalerts")]
        private void CmdTrapAlerts(BasePlayer player, string command, string[] args)
        {
            if (player == null) return;
            player.ChatMessage(permission.UserHasPermission(player.UserIDString, PermAlerts)
                ? Msg("Enabled", player.UserIDString)
                : Msg("NoPermission", player.UserIDString));
        }

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

The plugin listens on OnTrapSnapped, checks that the entering collider belongs to a real, connected, non-NPC player, and then resolves the trap's owner through trapTrigger.trap.OwnerID. If the owner is online and not the victim, they get a chat message with the victim's name and the trap prefab so they know which of their traps was stepped on. Each player is rate-limited per trap with a short cooldown stored in a dictionary, which keeps mine fields from spamming chat when several people walk through at once. A config toggle turns the notifications off entirely, and language keys keep the text localizable.

Common use cases

  • Owner alerts for landmines and bear traps

    Message the placing player when any of their traps is triggered, including the victim name and trap type, without needing to poll trap state.

  • Anti-team-kill warnings

    Compare the entering player's currentTeam with the trap owner's team and warn them that a teammate is standing on a friendly trap.

  • Audit logging for trap events

    Record a timestamped line in a data file each time a trap snaps, so admins can review disputes about mines placed around bases.

Pitfalls

  • Fires for every overlap, not just players

    Animals, NPCs, dropped items and even other entities can enter the trigger volume. Always cast obj.ToBaseEntity() and bail out when it is not a BasePlayer.

  • Trap may already be spent or owned by nobody

    trapTrigger.trap can be null on a destroyed or unspawned trap, and OwnerID is 0 for traps placed by admin commands or spawned by plugins. Null-check both before messaging anyone.

  • It is not a dead-player hook

    The hook reports entry into the trigger, not the resulting death. Pair it with OnEntityDeath or OnPlayerDeath if you need to know whether the trap actually killed the victim.

Build a plugin with OnTrapSnapped

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

Prompt: Owner alerts for landmines and bear traps using the OnTrapSnapped 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 OnTrapSnapped

Generate