Skip to content
RustPluginGenerator

OnNpcAttack

OnNpcAttack is called from BaseNpc.StartAttack right before an NPC commits to an attack on its current target. Returning a non-null value cancels that attack for this cycle, so the NPC deals no damage and does not reset its attack timer.

Signature
object OnNpcAttack(BaseNpc npc, BaseEntity AttackTarget)

Parameters

Hook parameters
ParameterTypeDescription
npcBaseNpcThe BaseNpc instance that is about to start an attack. Use it to read health, position, OwnerID or to compare it with tracked entity IDs. It is never null when the hook fires.
AttackTargetBaseEntityThe BaseEntity the NPC wants to hit this attack cycle. It is usually a BasePlayer or another combat entity; check its type before reading player-specific members.

Return value

Return null to let the NPC perform the attack. Return any non-null value (a common choice is true) to stop the method before the attack timer is set and the damage code runs; the method returns void, so the value itself is not stored anywhere. The check is repeated on the next attack opportunity, so a cancelled attack is not a permanent disable.

How it works

The hook fires inside BaseNpc.StartAttack, and only after the game has already verified that the NPC has a live AttackTarget, that AttackReady() is true and that the target is within AttackRange. This means the call happens immediately before the attack timer is refreshed and the damage logic runs. If you do nothing, the attack proceeds normally.

npc is the attacker. NPCs spawned by the server (scientists, animals, custom NPCs deriving from BaseNpc) all pass through this method. AttackTarget is whatever entity the AI brain currently holds as its combat target, so it is frequently a player but can also be a deployable, a vehicle or another NPC.

Because the parameter is typed BaseNpc, declaring BasePlayer would make Oxide skip every call, and the game's BaseNpc subclasses are not BasePlayer, so always keep the declared types as given. Filter on npc.ShortPrefabName, npc.OwnerID or the target type instead of narrowing the signature.

Typical uses are region-based NPC safety zones, damage-free tutorial areas, and NPCs that should refuse to hit sleeping or AFK players. The hook fires once per attack attempt, which for fast NPCs can be several times per second, so keep the body cheap and avoid logging.

Example plugin: Stop NPCs attacking players inside safe zones

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

namespace Oxide.Plugins
{
    [Info("Npc Safe Zone", "RustPluginGenerator", "1.0.0")]
    [Description("Prevents BaseNpc attacks on players standing inside configured safe zones")]
    public class NpcSafeZone : RustPlugin
    {
        private const float WarnCooldown = 20f;

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

            [JsonProperty("Y")]
            public float Y { get; set; }

            [JsonProperty("Z")]
            public float Z { get; set; }

            [JsonProperty("Radius")]
            public float Radius { get; set; }

            [JsonProperty("Check target position (false = attacker position)")]
            public bool CheckTarget { get; set; }
        }

        private class PluginConfig
        {
            [JsonProperty("Safe zones")]
            public List<Zone> Zones { get; set; } = new List<Zone>
            {
                new Zone { X = 0f, Y = 0f, Z = 0f, Radius = 50f, CheckTarget = true }
            };

            [JsonProperty("Warn messages (seconds between warnings per player)")]
            public float WarnInterval { get; set; } = 20f;
        }

        private PluginConfig config;
        private readonly Dictionary<ulong, float> lastWarn = new Dictionary<ulong, float>();

        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>
            {
                ["SafeZone"] = "NPCs cannot attack you here. You are inside a safe zone."
            }, this);
        }

        private string Msg(string key, string userId = null) => lang.GetMessage(key, this, userId);

        private object OnNpcAttack(BaseNpc npc, BaseEntity AttackTarget)
        {
            if (npc == null || AttackTarget == null || config.Zones == null) return null;

            var player = AttackTarget as BasePlayer;

            foreach (var zone in config.Zones)
            {
                if (zone == null || zone.Radius <= 0f) continue;

                Vector3 center = new Vector3(zone.X, zone.Y, zone.Z);
                Vector3 checkPos = zone.CheckTarget && player != null
                    ? player.transform.position
                    : npc.transform.position;

                if (Vector3.Distance(center, checkPos) > zone.Radius) continue;

                if (player != null && player.IsConnected) NotifyPlayer(player);
                return true;
            }

            return null;
        }

        private void NotifyPlayer(BasePlayer player)
        {
            float now = Time.realtimeSinceStartup;
            float last;
            if (lastWarn.TryGetValue(player.userID, out last) && now - last < config.WarnInterval) return;

            lastWarn[player.userID] = now;
            player.ChatMessage(Msg("SafeZone", player.UserIDString));
        }
    }
}

The plugin stores a list of spherical safe zones in its config. Each entry has a centre, a radius and a flag controlling whether the attacker's position or the victim's position is tested. OnNpcAttack checks the target first: if it is not a player, the attack is allowed. For player targets it exits early unless the player is inside a configured zone, depending on the mode. If the player is inside, the hook returns true, the game's attack is skipped, and a short warning is sent on a per-player cooldown so the chat is not flooded. Zones are read from config on load, so admins edit them in oxide/config/NpcSafeZone.json and reload the plugin.

Common use cases

  • Safe zones around spawn

    Prevent animals and scientists from landing hits on players while they stand inside a radius around the spawn point, giving new players a protected area without disabling NPCs entirely.

  • Disable NPCs during events

    Temporarily return true for all attacks while a PvE event or maintenance window is active, then restore normal behaviour by clearing a flag instead of removing NPCs.

  • Protect AFK players

    If the target is a player who has not moved for several minutes, cancel the attack so idle players are not killed by wandering bears or scientists.

Pitfalls

  • The NPC keeps trying

    Returning a value only cancels the current attempt. The NPC stays in its attack state, so the hook fires again on the next attack tick. Use a cooldown or a state flag if you need a longer suppression.

  • Target is often not a player

    AttackTarget can be a deployable or another NPC. Casting it to BasePlayer without an as check throws and breaks the NPC's attack loop.

  • Hook runs very frequently

    StartAttack can fire several times per second per NPC. Avoid expensive work such as iterating all players or sending unbuffered chat messages inside the hook.

Build a plugin with OnNpcAttack

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

Prompt: Safe zones around spawn using the OnNpcAttack 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 OnNpcAttack

Generate