How to make a Rust NPC ignore permission plugin (Oxide/Carbon)
Adds a permission and a toggle command that make hostile NPCs and animals skip a player when they pick targets. It uses the Oxide OnNpcTarget hook to cancel targeting before the NPC commits.
On most servers hostile scientists and animals pick targets purely on proximity and line of sight, which makes quiet building, admin work and test sessions annoying because every animal and scientist in the area turns to look at you. There is no vanilla permission or toggle for this, so the only reliable place to intervene is the moment an NPC chooses what to attack. Oxide exposes that moment through the OnNpcTarget hook, which fires with the NPC as the first argument and the candidate target as the second. Returning a non-null object cancels the selection, so a plugin can look at the candidate, decide whether that player is immune, and stop the target from being set. The plugin below registers npcignore.use for the opt-in command and npcignore.immune for permanent immunity, answers to both the HumanNPC and BaseNpc variants of the hook, and prints nothing during normal play.
The plugin code
using System.Linq;
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;
using UnityEngine;
namespace Oxide.Plugins
{
[Info("NpcIgnore", "RustPluginGenerator", "1.0.0")]
[Description("Lets players and admins opt out of hostile NPC and animal targeting.")]
public class NpcIgnore : RustPlugin
{
private const string PermImmune = "npcignore.immune";
private const string PermUse = "npcignore.use";
private const string DataFileName = "NpcIgnore";
private PluginConfig config;
private HashSet<ulong> ignored = new HashSet<ulong>();
private class PluginConfig
{
[JsonProperty("Use permission immunity")]
public bool UsePermission { get; set; } = true;
[JsonProperty("Allow /npcignore toggle")]
public bool AllowToggle { get; set; } = true;
[JsonProperty("Grant immunity to admin group on load")]
public bool GrantAdmins { get; set; } = true;
[JsonProperty("Warn player when NPC ignores them")]
public bool NotifyOnToggle { get; set; } = 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>
{
["Ignored"] = "NPCs and animals will now ignore you.",
["NotIgnored"] = "NPCs and animals will now target you again.",
["NoPermission"] = "You do not have permission to use this command.",
["ToggleDisabled"] = "The /npcignore toggle is disabled on this server."
}, 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(PermImmune, this);
permission.RegisterPermission(PermUse, this);
}
private void OnServerInitialized()
{
ignored = Interface.Oxide.DataFileSystem.ReadObject<HashSet<ulong>>(DataFileName)
?? new HashSet<ulong>();
if (config.GrantAdmins && !permission.GroupHasPermission("admin", PermImmune))
permission.GrantGroupPermission("admin", PermImmune, this);
}
private void Unload()
{
Interface.Oxide.DataFileSystem.WriteObject(DataFileName, ignored);
}
private void SaveData()
{
Interface.Oxide.DataFileSystem.WriteObject(DataFileName, ignored);
}
private bool IsIgnored(BasePlayer player)
{
if (player == null) return false;
if (config.UsePermission && permission.UserHasPermission(player.UserIDString, PermImmune))
return true;
return ignored.Contains(player.userID.Get());
}
private object HandleTarget(BaseEntity candidate)
{
if (!config.UsePermission && !config.AllowToggle) return null;
var player = candidate as BasePlayer;
if (player == null || player.IsNpc) return null;
if (player.IsDead() || player.IsSleeping()) return null;
return IsIgnored(player) ? (object)true : null;
}
private object OnNpcTarget(BaseEntity owner, BaseEntity entity)
{
if (owner == null || entity == null) return null;
return HandleTarget(entity);
}
private object OnNpcTarget(HumanNPC humanNPC, BaseEntity entity)
{
if (humanNPC == null || entity == null) return null;
return HandleTarget(entity);
}
private object OnNpcTarget(BaseNpc npc, BaseEntity entity)
{
if (npc == null || entity == null) return null;
return HandleTarget(entity);
}
[ChatCommand("npcignore")]
private void CmdNpcIgnore(BasePlayer player, string command, string[] args)
{
if (player == null) return;
if (!config.AllowToggle)
{
SendReply(player, Msg("ToggleDisabled", player.UserIDString));
return;
}
if (!permission.UserHasPermission(player.UserIDString, PermUse) && !player.IsAdmin)
{
SendReply(player, Msg("NoPermission", player.UserIDString));
return;
}
ulong id = player.userID.Get();
bool nowIgnored;
if (ignored.Contains(id))
{
ignored.Remove(id);
nowIgnored = false;
}
else
{
ignored.Add(id);
nowIgnored = true;
}
SaveData();
if (config.NotifyOnToggle)
SendReply(player, Msg(nowIgnored ? "Ignored" : "NotIgnored", player.UserIDString));
}
}
}Hooks used: OnServerInitialized, OnNpcTarget
Config file
Created automatically on first load at oxide/config/NpcIgnore.json.
{
"Use permission immunity": true,
"Allow /npcignore toggle": true,
"Grant immunity to admin group on load": true,
"Warn player when NPC ignores them": true
}Commands and permissions
| Command | What it does | Permission |
|---|---|---|
/npcignore | Toggles whether hostile NPCs and animals ignore you. Prints the new state. | npcignore.use |
| Permission | Grants |
|---|---|
npcignore.immune | NPCs and animals always ignore this player, no command needed |
npcignore.use | Allows using /npcignore to toggle personal immunity |
How the code works
Cancel targeting through the NPC hooks
Oxide calls
OnNpcTargetwith the NPC asownerand the candidate asentity. Returning a non-null object aborts the selection, so the method returnstruewhen the target is a player who should be skipped andnullotherwise. Two more overloads cover the same event with narrower first parameters,HumanNPCandBaseNpc. Because Oxide dispatches by argument type, each call reaches only the methods whose declared parameter types match, so all three methods can live side by side without double-handling.Reading immunity from permission and stored data
The permanent case is a permission check:
permission.UserHasPermission(player.UserIDString, PermImmune). The opt-in case reads aHashSet<ulong>of player ids persisted throughInterface.Oxide.DataFileSystem. Both are cheap lookups, which matters because the hook fires whenever an NPC scans for a target. Admins are covered by granting the immune permission to theadmingroup inOnServerInitialized, so freshly installed servers behave sensibly without extra setup.The /npcignore toggle
[ChatCommand("npcignore")]registers the command. The method checksPermUsefirst, then adds or removes the caller's id from the set and saves the data file immediately so a restart does not lose the choice. Feedback goes through the language file withMsg. There is no cooldown on purpose: the command touches one set entry and one disk write, and players toggle it rarely.Keeping the toggle quiet on NPCs
Both hook variants null-check
ownerandentitybefore anything else, then bail out whenentityis not aBasePlayeror when the player is dead, sleeping or an NPC flag holder. The player's id is fetched withplayer.userID.Get()and compared against the set. If you later add a notification for toggling, route it throughSendReplyand never throughPuts/PrintWarning; those flood the console when NPCs scan constantly.Persisting the set and cleaning up
LoadDefaultConfiggives the plugin a config object with a few switches, while player state lives inoxide/data/NpcIgnore.json.Unloadwrites the set one last time. If you add timers later, hold references and destroy them inUnload; this plugin uses none, so the unloading block is only the save. The config lets you turn the permission path or the command path off without editing code.
Install it on your server
Save NpcIgnore.cs
Keep the file name exactly as the class name. Oxide matches them.Upload it to the plugins folder
Oxide:oxide/plugins/. Carbon:carbon/plugins/. The server compiles and loads it within a few seconds, no restart needed.Edit the config if you want
On first load the default config is written tooxide/config/NpcIgnore.json(Carbon:carbon/configs/NpcIgnore.json). After editing, reload it:oxide.reload NpcIgnore c.reload NpcIgnore # CarbonGrant the permission
Give it to every player, or swapdefaultfor a group such asvip:oxide.grant group default npcignore.immune c.grant group default npcignore.immune # Carbon
Variations
Each one opens the generator with a ready prompt you can tweak.
Team-wide immunity
When one member has the permission, the whole team is skipped.
Generate this versionZone-limited immunity
Only ignore players inside a designated safe area.
Generate this versionTimed toggle
The toggle expires after N minutes so it cannot be abused.
Generate this version
FAQ
Which NPCs does OnNpcTarget cover?
Oxide calls the hook for hostile human NPCs (scientists, tunnel dwellers) and for animal NPCs when they choose a target. It is not a damage hook, so it does not stop explosions, friendly fire or a player shooting an NPC that then fights back through a different path; it stops the NPC from picking that player as a target in the first place.
Do sleeping players and players inside a base count?
The hook fires with the candidate entity; the plugin skips the check when the candidate is sleeping or dead so the data set only holds ids that matter. Building interiors are not treated specially, because NPC targeting is not line-of-sight aware at the hook level. Use a zone variation if you need interior-only immunity.
How do I install this on Oxide or Carbon?
Drop NpcIgnore.cs into oxide/plugins, then run oxide.reload NpcIgnore from the server console. Carbon loads the same file from its plugins folder. Grant npcignore.immune to a group or player with oxide.grant, or let players use /npcignore themselves once npcignore.use is granted.
Does it work with Bradley APC or the patrol helicopter?
No. Those vehicles use separate targeting paths and appear in the hook list under their own names. This plugin only handles the two NPC hook overloads. If you need to ignore a Bradley, that is a different recipe and a different set of hooks.
Customize it for your server
Change the reward, add a cooldown, restrict it to VIPs. Start from this prompt and edit anything.
Edit the prompt on the next screen. 2 free plugins a month, no card.
More npcs plugins
- Animal spawn commandAdds a permission-gated /animal <species> [count] chat command that spawns vanilla animals in front of the player, with a per-command cap, aโฆ
- Custom NPC lootRewrites the items inside corpses left by configured NPCs, using a weighted per-prefab loot table.OnServerInitializedOnCorpsePopulate
- NPC free zonesDefines named world zones where newly spawned scientists and animals are deleted immediately.OnEntitySpawned
Last updated Oct 26, 2026.
Customize npcs ignore players
Customize