Skip to content
RustPluginGenerator

OnNpcTargetSense

OnNpcTargetSense is called from Rust.Ai.SimpleAIMemory.SetKnown before an NPC records an entity in its known-targets memory. Returning a non-null value skips the original registration.

Signature
object OnNpcTargetSense(BaseEntity owner, BaseEntity ent, AIBrainSenses brainSenses)

Parameters

Hook parameters
ParameterTypeDescription
ownerBaseEntityThe BaseEntity that owns the AI sensing memory, in practice the NPC (Scientist, animal, APC crewman) whose senses are being updated.
entBaseEntityThe BaseEntity being recorded as seen or known by the NPC's SimpleAIMemory. Often a BasePlayer, but can be any entity the senses resolve.
brainSensesAIBrainSensesThe AIBrainSenses component belonging to the owner. Useful to read the NPC's sensing configuration or ignore flags.

Return value

Returning null lets SetKnown run normally and the entity is registered in the NPC's memory. Returning any non-null value makes the injected code return early from SetKnown, so the NPC does not record that entity as known at this moment; the method's own return type is void, so the value you return is not otherwise used.

How it works

The hook fires inside Rust.Ai.SimpleAIMemory.SetKnown(BaseEntity ent, BaseEntity owner, AIBrainSenses brainSenses), which is the point where an NPC's senses commit a perceived entity into its memory so the AI can chase, attack or react to it. owner is the sensing NPC, ent is the candidate target, and brainSenses is the senses component that produced the observation. Because the call sits at the top of SetKnown, it runs whenever the AI resolves something as known — typically a player or deployable inside the NPC's vision cone or sensing range.

Both owner and ent are BaseEntity, so declaring more specific types narrows the hook: using ScientistNPC owner or BasePlayer ent makes Oxide skip calls where the argument is a different type. That is the cleanest way to scope logic to one NPC kind or to player targets only.

This hook is a pure filter: it does not alter the AI's damage, loot or loot tables, only whether the entity enters the memory. Combine it with damage hooks if you want a target both invisible and invulnerable, or return null and let vanilla sensing continue. Avoid heavy work here — the hook fires frequently during any AI engagement, so keep the check cheap.

Example plugin: Make NPCs ignore players inside safe zones

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

namespace Oxide.Plugins
{
    [Info("Npc Safe Zone Sense", "RustPluginGenerator", "1.0.0")]
    [Description("Prevents NPC AI from registering players inside configured safe zones as known targets")]
    public class NpcSafeZoneSense : RustPlugin
    {
        private const string PermUse = "npcsafezonesense.admin";

        private PluginConfig config;
        private List<Zone> zones = new List<Zone>();

        private class Zone
        {
            [JsonProperty("X")] public float X;
            [JsonProperty("Y")] public float Y;
            [JsonProperty("Z")] public float Z;
            [JsonProperty("Radius")] public float Radius;
        }

        private class PluginConfig
        {
            [JsonProperty("Safe zones")]
            public List<Zone> SafeZones = new List<Zone>
            {
                new Zone { X = 0f, Y = 0f, Z = 0f, Radius = 30f }
            };
        }

        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>
            {
                ["ZoneAdded"] = "Safe zone added at your position with radius {0}.",
                ["ZoneList"] = "Configured safe zones: {0}.",
                ["Usage"] = "Usage: /npczone add <radius> | /npczone list",
                ["NoPermission"] = "You do not have permission to use this command."
            }, 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);
            zones = config.SafeZones ?? new List<Zone>();
        }

        private void Unload()
        {
            zones?.Clear();
            zones = null;
        }

        [ChatCommand("npczone")]
        private void CmdNpcZone(BasePlayer player, string command, string[] args)
        {
            if (player == null) return;
            if (!permission.UserHasPermission(player.UserIDString, PermUse))
            {
                player.ChatMessage(Msg("NoPermission", player.UserIDString));
                return;
            }
            if (args == null || args.Length == 0)
            {
                player.ChatMessage(Msg("Usage", player.UserIDString));
                return;
            }
            if (args[0].Equals("add", StringComparison.OrdinalIgnoreCase))
            {
                float radius = 30f;
                if (args.Length > 1) float.TryParse(args[1], out radius);
                if (radius < 1f) radius = 1f;
                var pos = player.transform.position;
                config.SafeZones.Add(new Zone { X = pos.x, Y = pos.y, Z = pos.z, Radius = radius });
                zones = config.SafeZones;
                SaveConfig();
                player.ChatMessage(Msg("ZoneAdded", player.UserIDString, radius));
                return;
            }
            if (args[0].Equals("list", StringComparison.OrdinalIgnoreCase))
            {
                player.ChatMessage(Msg("ZoneList", player.UserIDString, config.SafeZones.Count));
                return;
            }
            player.ChatMessage(Msg("Usage", player.UserIDString));
        }

        private object OnNpcTargetSense(BaseEntity owner, BaseEntity ent, AIBrainSenses brainSenses)
        {
            if (owner == null || ent == null || brainSenses == null) return null;

            var player = ent as BasePlayer;
            if (player == null || player.IsNpc || player.IsDead() || player.IsSleeping()) return null;

            var position = player.transform.position;
            for (int i = 0; i < zones.Count; i++)
            {
                var zone = zones[i];
                var center = new Vector3(zone.X, zone.Y, zone.Z);
                if (Vector3.Distance(position, center) <= zone.Radius) return true;
            }
            return null;
        }
    }
}

The plugin keeps a small list of safe-zone centers and radii in its config. OnNpcTargetSense checks that both the owner and the target are not null, resolves the target as a BasePlayer, and skips players who are dead, sleeping or NPCs themselves. It then compares the player's position against every configured zone with Vector3.Distance and returns a non-null value (a true boxed as object) when the player is inside, which makes SimpleAIMemory.SetKnown bail out before registering the player. Use /npczone add <radius> to record your current position as a zone; the data is saved to oxide/data on unload. The npcsafezone.admin permission gates the command.

Common use cases

  • Protect safe zones from NPC aggro

    Return a value for players standing inside designated spawn, trade or event areas so scientists and animals never add them to their known-targets memory and stop chasing them.

  • Hide staff in vanish mode from NPCs

    Track players toggled into an invisible admin mode and block sensing for those players only, letting staff observe events without being attacked by wildlife or scientists.

  • Restrict PvE NPC targeting to a whitelist

    Return a value for all players except those holding a permission, so only opted-in players are recognized as targets by NPC AI on a PvE server.

Pitfalls

  • Sensing is re-evaluated constantly

    SetKnown runs repeatedly while an NPC is alert, so keep the callback cheap. Avoid iterating large lists or calling data files inside the hook; cache values on load and on command changes instead.

  • Blocking memory does not stop damage already in flight

    A projectile or melee swing that lands after the sense was skipped still deals damage through normal damage handling. Pair the hook with OnEntityTakeDamage if you need the target fully immune.

  • Returning a value inside the owner check can silently kill AI

    A broad early return for every entity disables the NPC's ability to track anything, including player pets or deployables. Always narrow by target type and position rather than returning a value unconditionally.

Build a plugin with OnNpcTargetSense

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

Prompt: Protect safe zones from NPC aggro using the OnNpcTargetSense 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 OnNpcTargetSense

Generate