OnNpcTarget
OnNpcTarget is called when an NPC's AI is evaluating a candidate entity as a possible target. Returning a non-null value rejects that candidate so the NPC ignores it.
object OnNpcTarget(BaseEntity owner, BaseEntity entity)Parameters
| Parameter | Type | Description |
|---|---|---|
owner | BaseEntity | The entity whose AI is choosing a target. Cast it to BaseNpc, HumanNPC, ScientistNPC or another AI type to scope your logic to specific NPCs; it may be any entity that runs senses. |
entity | BaseEntity | The candidate target being evaluated. Return a non-null value to veto this candidate. Check its type (BasePlayer, BaseAnimalNPC, etc.) and its owner/team to decide. |
Return value
Returning null lets the candidate stay in consideration. Returning any non-null value (a bool, an empty string, zero, anything) removes that candidate from the selection: in AIBrainSenses.GetNearest and HumanNPC.GetBestTarget the loop skips it, and in RustCore.IOnNpcTarget the NPC's enemy facts are reset so it drops the target. The actual value you return is not used as a target, only non-null matters.
How it works
The hook fires inside AIBrainSenses.GetNearest for every entity the brain considers while it searches for the closest valid target, and inside RustCore.IOnNpcTarget when the NPC combat core selects an enemy. Both call sites pass the NPC as the first argument and the candidate as the second. Each candidate is filtered: entities already dead are skipped before the hook is reached, but the hook still fires per candidate, so it can run very often.
You can narrow the hook by declaring a more specific first parameter type. Declaring BaseNpc npc or HumanNPC humanNPC makes Oxide only call your method when the owner is actually that type, so a scientist-only rule won't run for every animal brain. The second parameter can be narrowed to BasePlayer when you only care about players.
Practically this is the way to make NPCs ignore players or teams. Because the first site walks all nearby entities each sense tick, keep your method cheap and avoid allocations, logging, or database access inside it.
Other signatures
Oxide also calls OnNpcTarget from other places in the game with these parameter types. Declare the overload you need, or several:
object OnNpcTarget(HumanNPC humanNPC, BaseEntity player)object OnNpcTarget(BaseNpc npc, BaseEntity target)
Example plugin: Make scientists ignore sleeping players and teammates
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;
using UnityEngine;
namespace Oxide.Plugins
{
[Info("Npc Ignore Sleepers", "RustPluginGenerator", "1.0.0")]
[Description("Lets server owners stop NPCs from targeting sleeping, offline or team-owned players.")]
public class NpcIgnoreSleepers : RustPlugin
{
private const string PermAdmin = "npcignoresleepers.admin";
private PluginConfig config;
private class PluginConfig
{
[JsonProperty("Ignore sleeping players")]
public bool IgnoreSleeping = true;
[JsonProperty("Ignore disconnected players")]
public bool IgnoreDisconnected = true;
[JsonProperty("Ignore players whose team owns the NPC")]
public bool RespectTeams = true;
}
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>
{
["Status"] = "NPC target protection: sleepers {0}, offline {1}, teams {2}",
["Toggled"] = "NPC target protection is now {0}",
["Denied"] = "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(PermAdmin, this);
}
[ChatCommand("npctarget")]
private void CmdNpcTarget(BasePlayer player, string command, string[] args)
{
if (player == null) return;
if (!permission.UserHasPermission(player.UserIDString, PermAdmin))
{
SendReply(player, Msg("Denied", player.UserIDString));
return;
}
if (args != null && args.Length > 0 && args[0] == "toggle")
{
config.IgnoreSleeping = !config.IgnoreSleeping;
SaveConfig();
SendReply(player, Msg("Toggled", player.UserIDString, config.IgnoreSleeping ? "on" : "off"));
return;
}
SendReply(player, Msg("Status", player.UserIDString,
config.IgnoreSleeping, config.IgnoreDisconnected, config.RespectTeams));
}
private object OnNpcTarget(BaseNpc npc, BaseEntity entity)
{
if (npc == null || entity == null) return null;
var player = entity as BasePlayer;
if (player == null) return null;
if (config.IgnoreDisconnected && !player.IsConnected) return true;
if (config.IgnoreSleeping && player.IsSleeping()) return true;
if (config.RespectTeams && player.currentTeam != 0 && npc.OwnerID == player.currentTeam)
return true;
return null;
}
}
}The plugin registers a config toggle and a permission. In OnNpcTarget it checks the owner with as BaseNpc; if it is not an NPC it returns null immediately so no other brain is affected. Players that are gone or sleeping are rejected with a non-null return when the config option is on. When RespectTeams is enabled, it compares entity.currentTeam with the NPC's OwnerID so an NPC owned by a team never targets that team's members. The first branch, a simple range shield, shows how a candidate is dropped without any further processing. Every check is a plain field read, which keeps the hook cheap since it runs per candidate per sense tick.
Common use cases
Protect AFK and sleeping players
Reject players who are sleeping or not connected so AFK bases don't get cleared by wandering scientists. Combine with the config toggle to switch protection on per server.
Team-safe NPC guards
If an NPC is owned by a team ID, compare
entity.currentTeamto the NPC's OwnerID and veto the candidate, so hired guards won't shoot their own side.Neutral zone NPCs
Veto any candidate within a fixed distance of a Monument or custom safe-zone position, keeping NPCs passive around spawn areas without editing each spawn.
Pitfalls
It fires per candidate, per sense tick
Do not call PrintWarning, do expensive LINQ queries or iterate large collections here. Each brain walking its senses calls the hook for every nearby entity every tick.
Non-null is the veto, not the target
Returning a BasePlayer from a narrowed signature does not set that player as the target; the value is not used for selection. Only null/non-null matters.
Narrow the owner parameter
If you only want to affect scientists, declare the parameter as
ScientistNPCorHumanNPC. With plainBaseEntityyour method runs for every animal and NPC brain on the server.
Plugin recipes that use OnNpcTarget
Build a plugin with OnNpcTarget
Describe what should happen and get a compiled plugin that uses this hook.
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 OnNpcTarget
Generate