Skip to content
RustPluginGenerator

OnReactiveTargetReset

OnReactiveTargetReset is called after a ReactiveTarget (the shootable target used in shooting ranges) finishes its reset routine and is switched back on. It fires once per reset, at the end of ReactiveTarget.ResetTarget.

Signature
void OnReactiveTargetReset(ReactiveTarget reactiveTarget)

Parameters

Hook parameters
ParameterTypeDescription
reactiveTargetReactiveTargetThe ReactiveTarget entity that just finished resetting. It has already been switched back on with full knockdown health, so you can read its position, owner and net ID to react to the reset.

Return value

The return value is ignored. ResetTarget returns void and the hook is called as a plain notification, so returning null or any other value changes nothing about the reset.

How it works

The hook fires inside ReactiveTarget.ResetTarget, after the target has been set back to the On flag, cleared of its knockdown state and had its knockdown health restored to 100. This means the call happens when a knocked-down target goes back up, either through the game's own reset flow on that entity. Because it runs at the end of the method, the entity's flags and health are already in their post-reset state when your plugin sees it.

The single parameter is the target entity itself. Use net.ID.Value as a stable key, transform.position for location-based logic, and OwnerID when targets are placed on player-owned bases. A common narrowing trick is to declare the parameter as ReactiveTarget (as given) — Oxide only dispatches when the argument really is a ReactiveTarget, so there is no need to cast, though a null check is still good practice.

The hook is a notification only; it does not run often, so light bookkeeping such as scoring, counters or effects is fine here. Avoid heavy per-target scans if a server has many targets on a shooting range.

Example plugin: Track shooting range target resets per player

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

namespace Oxide.Plugins
{
    [Info("Reactive Target Reset Tracker", "RustPluginGenerator", "1.0.0")]
    [Description("Tracks how many times a player's reactive targets reset, with a cooldown and chat command.")]
    public class ReactiveTargetResetTracker : RustPlugin
    {
        private const string PermView = "reactivetargetresettracker.view";

        private PluginConfig config;
        private StoredData data;
        private readonly Dictionary<ulong, float> lastCount = new Dictionary<ulong, float>();

        private class PluginConfig
        {
            [JsonProperty("Enable tracking")]
            public bool Enabled = true;

            [JsonProperty("Cooldown per target in seconds")]
            public float CooldownSeconds = 5f;

            [JsonProperty("Broadcast to owner on reset")]
            public bool NotifyOwner = false;
        }

        private class StoredData
        {
            [JsonProperty("Reset counts by player ID")]
            public Dictionary<ulong, int> Counts = new Dictionary<ulong, 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>
            {
                ["ResetCount"] = "{0}'s targets have reset {1} times.",
                ["NoStats"] = "No reset statistics recorded for {0}.",
                ["OwnerNote"] = "One of your targets is back up. Total resets: {0}."
            }, 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(PermView, this);
            data = Interface.Oxide.DataFileSystem.ReadObject<StoredData>(Name) ?? new StoredData();
        }

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

        private void OnReactiveTargetReset(ReactiveTarget reactiveTarget)
        {
            if (!config.Enabled || reactiveTarget == null || reactiveTarget.IsDestroyed) return;

            ulong owner = reactiveTarget.OwnerID;
            if (owner == 0) return;

            if (lastCount.TryGetValue(reactiveTarget.net.ID.Value, out float last) &&
                UnityEngine.Time.realtimeSinceStartup - last < config.CooldownSeconds)
                return;

            lastCount[reactiveTarget.net.ID.Value] = UnityEngine.Time.realtimeSinceStartup;

            int total = GetCount(owner) + 1;
            data.Counts[owner] = total;

            if (config.NotifyOwner)
            {
                BasePlayer player = BasePlayer.FindByID(owner);
                if (player != null && player.IsConnected)
                    player.ChatMessage(Msg("OwnerNote", owner.ToString(), total));
            }
        }

        private int GetCount(ulong userId)
        {
            return data.Counts.TryGetValue(userId, out int count) ? count : 0;
        }

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

            if (!player.IsAdmin && !permission.UserHasPermission(player.UserIDString, PermView))
            {
                player.ChatMessage("You do not have permission to use this command.");
                return;
            }

            if (args == null || args.Length < 1)
            {
                player.ChatMessage($"Your targets have reset {GetCount(player.userID.Get())} times.");
                return;
            }

            BasePlayer target = BasePlayer.FindAwakeOrSleeping(args[0]);
            if (target == null)
            {
                player.ChatMessage($"Player '{args[0]}' not found.");
                return;
            }

            int count = GetCount(target.userID.Get());
            if (count == 0)
                player.ChatMessage(Msg("NoStats", player.UserIDString, target.displayName));
            else
                player.ChatMessage(Msg("ResetCount", player.UserIDString, target.displayName, count));
        }
    }
}

The plugin listens for every target reset and records a per-owner counter of how many times the owner's shooting-range targets have gone back up. It keeps a small in-memory dictionary plus a data file so the count survives restarts, and it applies a configurable cooldown so a single target cannot inflate the total when it is hit repeatedly. Admins can read the current count with the targetstats chat command, which resolves a player by name or Steam ID and prints the stored total. The plugin demonstrates the hook's real value: knowing when a target returns to duty, not just when it is knocked down.

Common use cases

  • Shooting range scoring

    Count how many times targets on a player's range reset to award range points, tickets or a message in chat when a target is back up.

  • Target maintenance monitoring

    Log or count resets per target net ID to detect targets that are being hammered non-stop and may need to be moved or replaced.

  • Trigger effects on reset

    Play a short sound or effect at the target's position when it comes back up, useful for scripted minigames built on ReactiveTarget entities.

Pitfalls

  • The target is already active

    Because the hook fires after the flags and knockdown health are restored, checking HasFlag(Flags.On) to decide whether the target needs a manual reset will always be false. Treat this as a post-reset event.

  • Entity may be destroyed shortly after

    A target can be killed or despawned around a reset, so always null-check the parameter and guard against IsDestroyed before storing references or effects.

  • Not a per-hit hook

    This fires on reset only, not on every shot. Pair it with OnEntityTakeDamage if you need to know when a target was knocked down.

Build a plugin with OnReactiveTargetReset

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

Prompt: Shooting range scoring using the OnReactiveTargetReset 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 OnReactiveTargetReset

Generate