Skip to content
RustPluginGenerator

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

NpcIgnore.cs
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.

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

Chat and console commands
CommandWhat it doesPermission
/npcignoreToggles whether hostile NPCs and animals ignore you. Prints the new state.npcignore.use
Permissions
PermissionGrants
npcignore.immuneNPCs and animals always ignore this player, no command needed
npcignore.useAllows using /npcignore to toggle personal immunity

How the code works

  1. Cancel targeting through the NPC hooks

    Oxide calls OnNpcTarget with the NPC as owner and the candidate as entity. Returning a non-null object aborts the selection, so the method returns true when the target is a player who should be skipped and null otherwise. Two more overloads cover the same event with narrower first parameters, HumanNPC and BaseNpc. 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.

  2. Reading immunity from permission and stored data

    The permanent case is a permission check: permission.UserHasPermission(player.UserIDString, PermImmune). The opt-in case reads a HashSet<ulong> of player ids persisted through Interface.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 the admin group in OnServerInitialized, so freshly installed servers behave sensibly without extra setup.

  3. The /npcignore toggle

    [ChatCommand("npcignore")] registers the command. The method checks PermUse first, 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 with Msg. There is no cooldown on purpose: the command touches one set entry and one disk write, and players toggle it rarely.

  4. Keeping the toggle quiet on NPCs

    Both hook variants null-check owner and entity before anything else, then bail out when entity is not a BasePlayer or when the player is dead, sleeping or an NPC flag holder. The player's id is fetched with player.userID.Get() and compared against the set. If you later add a notification for toggling, route it through SendReply and never through Puts/PrintWarning; those flood the console when NPCs scan constantly.

  5. Persisting the set and cleaning up

    LoadDefaultConfig gives the plugin a config object with a few switches, while player state lives in oxide/data/NpcIgnore.json. Unload writes the set one last time. If you add timers later, hold references and destroy them in Unload; 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

  1. Save NpcIgnore.cs

    Keep the file name exactly as the class name. Oxide matches them.
  2. 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.
  3. Edit the config if you want

    On first load the default config is written to oxide/config/NpcIgnore.json (Carbon: carbon/configs/NpcIgnore.json). After editing, reload it:oxide.reload NpcIgnore c.reload NpcIgnore # Carbon
  4. Grant the permission

    Give it to every player, or swap default for a group such as vip: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.

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.

Prompt: Build an Oxide plugin NpcIgnore with npcignore.immune and npcignore.use permissions, a /npcignore toggle stored in oxide/data, config switches to enable the permission path and command path, and OnNpcTarget returning true for immune players.
Customize for my server

Edit the prompt on the next screen. 2 free plugins a month, no card.

Last updated Oct 26, 2026.

Customize npcs ignore players

Customize