Skip to content
RustPluginGenerator

OnTimedExplosiveExplode

OnTimedExplosiveExplode is called by TimedExplosive.Explode just before the explosive resolves its damage list, after the visual effect has been played. Returning a non-null value skips the damage step entirely.

Signature
object OnTimedExplosiveExplode(TimedExplosive timedExplosive, UnityEngine.Vector3 explosionFxPos)

Parameters

Hook parameters
ParameterTypeDescription
timedExplosiveTimedExplosiveThe TimedExplosive entity that is about to apply its damage (a thrown C4, satchel, rocket or similar explosive). Use it to read ownership, position, flags and the current damage list.
explosionFxPosUnityEngine.Vector3World position where the explosion effect is spawned. This is where the visual blast and the damage/raycast origin are centered, so it is the right reference for radius checks.

Return value

Return null to let the explosive continue into its normal damage resolution. Return any non-null value to make Explode return immediately, so the explosion effect plays but no damage, splash or entity destruction from the damage list is applied.

How it works

The hook fires inside TimedExplosive.Explode, after the water/ground explosion effect is spawned and after the code checks that GetDamageList(null) is not empty. That means it runs once per detonation of a timed explosive that actually has damage to deal, and never runs for explosives whose damage list is empty. Both parameters are already resolved at that point: timedExplosive is the entity (so OwnerID, transform.position, GetParentEntity() and flags are available), and explosionFxPos is the effect position, which can differ slightly from the entity origin when the surface is water.

Declaring timedExplosive as TimedExplosive is correct; narrowing further (for example to a subclass) makes Oxide skip calls for other explosive types. Because the method returns void, Oxide only checks whether your return value is non-null: any object stops the original code path after the particles have already been played.

This is a good spot for raid-protection logic, per-explosive damage scaling you implement yourself, or auditing who is blowing up what. It is not a damage hook: it fires once per explosion, not per damaged entity, and it does not give you the targets.

Example plugin: Stop raid explosives inside no-raid zones

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

namespace Oxide.Plugins
{
    [Info("No Raid Zone Explosives", "RustPluginGenerator", "1.0.0")]
    [Description("Blocks timed explosive damage inside configured circular no-raid zones")]
    public class NoRaidZoneExplosives : RustPlugin
    {
        private const string PermManage = "noraidzoneexplosives.manage";

        private PluginConfig config;

        private class PluginConfig
        {
            [JsonProperty("Block explosives in zones")]
            public bool BlockInZones = true;

            [JsonProperty("Zones (X, Z, Radius)")]
            public List<Zone> Zones = new List<Zone>
            {
                new Zone { X = 0f, Z = 0f, Radius = 50f }
            };
        }

        private class Zone
        {
            [JsonProperty("X")]
            public float X;

            [JsonProperty("Z")]
            public float Z;

            [JsonProperty("Radius")]
            public float Radius;
        }

        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>
            {
                ["Added"] = "Zone added at {0}, {1} with radius {2}. Total zones: {3}",
                ["List"] = "Zones: {0}",
                ["NoZones"] = "No zones configured.",
                ["Denied"] = "You do not have permission to manage zones."
            }, 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(PermManage, this);
        }

        private object OnTimedExplosiveExplode(TimedExplosive timedExplosive, Vector3 explosionFxPos)
        {
            if (timedExplosive == null || !config.BlockInZones) return null;

            for (int i = 0; i < config.Zones.Count; i++)
            {
                Zone zone = config.Zones[i];
                float dx = explosionFxPos.x - zone.X;
                float dz = explosionFxPos.z - zone.Z;
                if (dx * dx + dz * dz <= zone.Radius * zone.Radius)
                {
                    return false;
                }
            }

            return null;
        }

        [ChatCommand("nrz")]
        private void CmdZone(BasePlayer player, string command, string[] args)
        {
            if (player == null) return;
            if (!player.IsAdmin && !permission.UserHasPermission(player.UserIDString, PermManage))
            {
                SendReply(player, Msg("Denied", player.UserIDString));
                return;
            }

            if (args == null || args.Length == 0 || args[0] == "list")
            {
                if (config.Zones.Count == 0)
                {
                    SendReply(player, Msg("NoZones", player.UserIDString));
                    return;
                }

                string text = "";
                for (int i = 0; i < config.Zones.Count; i++)
                {
                    Zone zone = config.Zones[i];
                    if (i > 0) text += "; ";
                    text += $"#{i + 1} ({zone.X:0}, {zone.Z:0}) r={zone.Radius:0}";
                }
                SendReply(player, Msg("List", player.UserIDString, text));
                return;
            }

            if (args[0] == "add")
            {
                float radius = 50f;
                if (args.Length > 1) float.TryParse(args[1], out radius);
                if (radius < 5f || radius > 500f) radius = 50f;

                Vector3 pos = player.transform.position;
                config.Zones.Add(new Zone { X = pos.x, Z = pos.z, Radius = radius });
                SaveConfig();
                SendReply(player, Msg("Added", player.UserIDString, pos.x.ToString("0"), pos.z.ToString("0"), radius.ToString("0"), config.Zones.Count));
            }
        }
    }
}

The plugin defines circular zones in the config with a center, a radius and a flag for whether the blast may still be shown. OnTimedExplosiveExplode checks explosionFxPos against every zone; when the point is inside, it optionally records the detonation for admins, then returns a non-null value so the explosive skips its damage list and nothing in the zone is destroyed. Because the hook runs after the effect is spawned, players still see the blast unless the zone disables it, which we cannot suppress from this hook. Admins use /nrz list to see configured zones and /nrz add to create one at their current position; the ChatCommand validates the radius and saves to the config file.

Common use cases

  • Raid protection around spawn areas

    Return non-null when explosionFxPos falls inside a protected radius so C4 and satchels cannot level a spawn base, while normal combat damage elsewhere is untouched.

  • Custom explosive damage scaling

    Return non-null and run your own radius scan from explosionFxPos to apply reduced damage, letting you tune raid costs without changing the prefab damage list.

  • Explosive audit logging

    Record the owner, position and time of each detonation into oxide/data, then expose it to admins so raid activity on the map can be reviewed.

Pitfalls

  • The explosion effect already played

    The visual effect is spawned before the hook is called, so blocking damage still shows the blast and makes a sound. Do not promise fully silent blocks.

  • Empty damage lists never reach the hook

    The call sits behind a GetDamageList(null).Count > 0 check, so explosives configured with no damage will not trigger the hook at all.

  • This is not a per-entity damage hook

    Returning null does nothing to individual targets. For per-structure rules use OnEntityTakeDamage; this hook only lets you cancel or allow the whole detonation.

Build a plugin with OnTimedExplosiveExplode

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

Prompt: Raid protection around spawn areas using the OnTimedExplosiveExplode 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 OnTimedExplosiveExplode

Generate