Skip to content
RustPluginGenerator

How to make a Rust NPC damage multiplier plugin (per-type overrides)

Scales damage NPCs deal to players and damage players deal to NPCs independently, with per-NPC-type overrides, using Oxide's OnEntityTakeDamage(BaseCombatEntity, HitInfo) hook.

NPCs in Rust hit hard and die quickly, and the same multiplier rarely suits every mob. A global buff makes scientists trivial to farm, while a player-damage nerf makes bears and heavies feel unkillable. The fix is two multipliers that move independently: one for damage coming out of NPCs, one for damage going into them.

This plugin hooks OnEntityTakeDamage, resolves the attacker with hitInfo.InitiatorPlayer and the hit entity, and classifies each side as NPC or player through BaseNpc casts. Damage NPC→player is scaled by NpcDamageToPlayers, player→NPC by PlayerDamageToNpcs. A per-type override table matches on ShortPrefabName so each mob can have its own value. NPC-vs-NPC hits, decay, fall and suicide damage are skipped, and ScaleAll is applied to the damage type list before returning null so the rest of the damage pipeline runs unchanged.

The plugin code

NpcDamageMultiplier.cs
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;

namespace Oxide.Plugins
{
    [Info("NpcDamageMultiplier", "RustPluginGenerator", "1.0.0")]
    [Description("Scales damage NPCs deal to players and damage players deal to NPCs, with per-NPC-type overrides.")]
    public class NpcDamageMultiplier : RustPlugin
    {
        private const string PermUse = "npcdamagemultiplier.use";
        private const string PermAdmin = "npcdamagemultiplier.admin";

        private PluginConfig config;

        private class NpcTypeOverride
        {
            [JsonProperty("Npc deals damage multiplier")]
            public float Outgoing = 1f;

            [JsonProperty("Npc takes damage multiplier")]
            public float Incoming = 1f;
        }

        private class PluginConfig
        {
            [JsonProperty("Npc damage to players")]
            public float NpcDamageToPlayers = 1f;

            [JsonProperty("Player damage to NPCs")]
            public float PlayerDamageToNpcs = 1f;

            [JsonProperty("Npc type overrides")]
            public Dictionary<string, NpcTypeOverride> NpcTypeOverrides = new Dictionary<string, NpcTypeOverride>
            {
                ["scientistnpc_peacekeeper"] = new NpcTypeOverride { Outgoing = 1.25f, Incoming = 1f },
                ["scientistnpc_heavy"] = new NpcTypeOverride { Outgoing = 1.5f, Incoming = 1f },
                ["scientistnpc_junkpile_a"] = new NpcTypeOverride { Outgoing = 1f, Incoming = 2f },
                ["bear"] = new NpcTypeOverride { Outgoing = 1.2f, Incoming = 1f }
            };
        }

        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();
            }
            ClampConfig();
            SaveConfig();
        }

        protected override void SaveConfig() => Config.WriteObject(config);

        protected override void LoadDefaultMessages()
        {
            lang.RegisterMessages(new Dictionary<string, string>
            {
                ["Status"] = "NPC multiplier: NPC->player {0}x, player->NPC {1}x, {2} override(s).",
                ["Reloaded"] = "Configuration reloaded.",
                ["NoPermission"] = "You do not have permission to use this command.",
                ["Usage"] = "Usage: /npddmg <status|reload>"
            }, this);
        }

        private void Init()
        {
            permission.RegisterPermission(PermUse, this);
            permission.RegisterPermission(PermAdmin, this);
        }

        private void ClampConfig()
        {
            config.NpcDamageToPlayers = Clamp(config.NpcDamageToPlayers);
            config.PlayerDamageToNpcs = Clamp(config.PlayerDamageToNpcs);

            if (config.NpcTypeOverrides == null)
                config.NpcTypeOverrides = new Dictionary<string, NpcTypeOverride>();

            foreach (var kvp in config.NpcTypeOverrides)
            {
                if (kvp.Value == null) continue;
                kvp.Value.Outgoing = Clamp(kvp.Value.Outgoing);
                kvp.Value.Incoming = Clamp(kvp.Value.Incoming);
            }
        }

        private static float Clamp(float value)
        {
            if (value < 0.05f) return 0.05f;
            if (value > 10f) return 10f;
            return value;
        }

        private object OnEntityTakeDamage(BaseCombatEntity entity, HitInfo hitInfo)
        {
            if (entity == null || hitInfo == null || hitInfo.damageTypes == null) return null;

            var victimNpc = GetNpc(entity);
            var attackerNpc = GetNpc(hitInfo.Initiator);
            var attackerPlayer = hitInfo.InitiatorPlayer;

            // NPC hits NPC -> leave vanilla.
            if (victimNpc != null && attackerNpc != null) return null;

            if (attackerNpc != null && attackerPlayer == null && entity is BasePlayer)
            {
                if (ShouldSkip(hitInfo)) return null;
                var mult = GetOutgoingMultiplier(attackerNpc);
                if (mult != 1f) hitInfo.damageTypes.ScaleAll(mult);
                return null;
            }

            if (victimNpc != null && attackerPlayer != null)
            {
                if (ShouldSkip(hitInfo)) return null;
                var mult = GetIncomingMultiplier(victimNpc);
                if (mult != 1f) hitInfo.damageTypes.ScaleAll(mult);
                return null;
            }

            return null;
        }

        private static BaseNpc GetNpc(BaseEntity entity)
        {
            if (entity == null || entity.IsDestroyed) return null;
            return entity as BaseNpc;
        }

        private float GetOutgoingMultiplier(BaseNpc npc)
        {
            NpcTypeOverride over;
            if (TryGetOverride(npc, out over)) return over.Outgoing;
            return config.NpcDamageToPlayers;
        }

        private float GetIncomingMultiplier(BaseNpc npc)
        {
            NpcTypeOverride over;
            if (TryGetOverride(npc, out over)) return over.Incoming;
            return config.PlayerDamageToNpcs;
        }

        private bool TryGetOverride(BaseNpc npc, out NpcTypeOverride over)
        {
            over = null;
            if (npc == null || config.NpcTypeOverrides == null) return false;
            var key = npc.ShortPrefabName;
            if (string.IsNullOrEmpty(key)) return false;
            return config.NpcTypeOverrides.TryGetValue(key, out over) && over != null;
        }

        private static bool ShouldSkip(HitInfo hitInfo)
        {
            var type = hitInfo.damageTypes.GetMajorityDamageType();
            return type == Rust.DamageType.Decay || type == Rust.DamageType.Fall || type == Rust.DamageType.Suicide;
        }

        [ChatCommand("npddmg")]
        private void CmdNpDmg(BasePlayer player, string command, string[] args)
        {
            if (player == null) return;

            if (args == null || args.Length == 0)
            {
                SendReply(player, Msg("Usage", player.UserIDString));
                return;
            }

            switch (args[0].ToLowerInvariant())
            {
                case "status":
                    if (!permission.UserHasPermission(player.UserIDString, PermUse) &&
                        !permission.UserHasPermission(player.UserIDString, PermAdmin))
                    {
                        SendReply(player, Msg("NoPermission", player.UserIDString));
                        return;
                    }
                    SendReply(player, Msg("Status", player.UserIDString,
                        config.NpcDamageToPlayers, config.PlayerDamageToNpcs, config.NpcTypeOverrides.Count));
                    break;
                case "reload":
                    if (!permission.UserHasPermission(player.UserIDString, PermAdmin))
                    {
                        SendReply(player, Msg("NoPermission", player.UserIDString));
                        return;
                    }
                    LoadConfig();
                    SendReply(player, Msg("Reloaded", player.UserIDString));
                    break;
                default:
                    SendReply(player, Msg("Usage", player.UserIDString));
                    break;
            }
        }

        private string Msg(string key, string userId = null, params object[] args) =>
            string.Format(lang.GetMessage(key, this, userId), args);
    }
}

Hooks used: OnEntityTakeDamage

Config file

Created automatically on first load at oxide/config/NpcDamageMultiplier.json.

NpcDamageMultiplier.json
{
  "Npc damage to players": 1,
  "Player damage to NPCs": 1,
  "Npc type overrides": {
    "scientistnpc_peacekeeper": {
      "Npc deals damage multiplier": 1.25,
      "Npc takes damage multiplier": 1
    },
    "scientistnpc_heavy": {
      "Npc deals damage multiplier": 1.5,
      "Npc takes damage multiplier": 1
    },
    "scientistnpc_junkpile_a": {
      "Npc deals damage multiplier": 1,
      "Npc takes damage multiplier": 2
    },
    "bear": {
      "Npc deals damage multiplier": 1.2,
      "Npc takes damage multiplier": 1
    }
  }
}

Commands and permissions

Chat and console commands
CommandWhat it doesPermission
/npddmg statusPrints the active global multipliers and the configured override count to chat.npcdamagemultiplier.use
/npddmg reloadReloads the config file and applies new multipliers without a server restart.npcdamagemultiplier.admin
Permissions
PermissionGrants
npcdamagemultiplier.useAllows use of /npddmg status.
npcdamagemultiplier.adminAllows use of /npddmg reload.

How the code works

  1. Scale the damage in OnEntityTakeDamage

    The hook receives the entity that will take damage and the HitInfo. Null-check both, plus hitInfo.damageTypes, then decide which direction the hit goes. Scaling is done with hitInfo.damageTypes.ScaleAll(mult), and the method returns null so Rust's own armor, health and death code still runs. The plugin only rewrites the damage numbers before that point.

  2. Classify each side as NPC or player

    The private helper GetNpc(BaseEntity) returns entity as BaseNpc after a destroyed check, and is called on both entity and hitInfo.Initiator. Turrets, traps and vehicle weapons have a BaseEntity initiator and no InitiatorPlayer, so those simply fall through to the default. When victimNpc != null && attackerNpc != null the method returns early, and hits where neither side is an NPC also return early, so normal PvP and NPC-vs-NPC fights keep vanilla damage.

  3. Apply the per-type override table

    NpcTypeOverrides maps a key to an NpcTypeOverride with Outgoing and Incoming floats. TryGetOverride matches on the NPC's ShortPrefabName because the full prefab path can change between patches. GetOutgoingMultiplier and GetIncomingMultiplier use the override when found, otherwise the global NpcDamageToPlayers or PlayerDamageToNpcs. Adding a key for a prefab whose short name is unknown just does nothing, so a typo fails safe.

  4. Config defaults and validation

    PluginConfig holds the two globals and the override dictionary, with readable [JsonProperty] names so admins editing JSON see Npc damage to players rather than camel-case. ClampConfig clamps every value to between 0.05 and 10, so a typo cannot make NPCs harmless or immortal, and rebuilds the dictionary if it deserialized as null. LoadConfig catches a broken file, calls LoadDefaultConfig, clamps and saves.

  5. Skip decay, fall and suicide damage

    ShouldSkip(HitInfo) reads hitInfo.damageTypes.GetMajorityDamageType() and returns true when it is Decay, Fall or Suicide. That guard prevents a player getting amplified fall damage just because an NPC happened to be under them, and it keeps NPC decay ticking normally. The check runs before any multiplier is calculated, so it costs nothing when the plugin is idle.

  6. Permissions and the admin command

    Init registers npcdamagemultiplier.use and npcdamagemultiplier.admin. The /npddmg chat command dispatches on args[0]: status prints the two globals and the override count, reload calls LoadConfig() and confirms. status accepts either permission, reload requires admin. Every reply goes through the Msg helper and lang.GetMessage, so an empty arg list returns the Usage string.

Install it on your server

  1. Save NpcDamageMultiplier.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/NpcDamageMultiplier.json (Carbon: carbon/configs/NpcDamageMultiplier.json). After editing, reload it:oxide.reload NpcDamageMultiplier c.reload NpcDamageMultiplier # Carbon
  4. Grant the permission

    Give it to every player, or swap default for a group such as vip:oxide.grant group default npcdamagemultiplier.use c.grant group default npcdamagemultiplier.use # Carbon

Variations

Each one opens the generator with a ready prompt you can tweak.

  • Distance-scaled NPC damage

    Hit harder at close range to discourage melee against scientists.

    Generate this version
  • Team-scaled NPC damage

    Make grouping up against NPCs reduce the damage each player takes.

    Generate this version
  • Scrap rewards for NPC kills

    Pay players scrap for killing specific NPC prefabs.

    Generate this version

FAQ

Which NPCs does this affect?

Any entity Rust represents as BaseNpc: scientists, animals and similar. Bradley APC and the patrol helicopter are not BaseNpc entities, so the plugin does not touch damage involving them. If a particular mob is not affected, check its ShortPrefabName and whether the entity type derives from BaseNpc at all.

Will this break armor or loot?

No. The plugin only rewrites the numbers on HitInfo.damageTypes via ScaleAll. Armor mitigation, health loss and loot tables run afterwards in Rust's normal pipeline, so a 2.0 incoming multiplier simply makes the NPC lose twice the post-armor health.

How do I find a prefab short name?

Spawn the NPC and inspect it, or add a temporary PrintToChat in TryGetOverride during testing to log npc.ShortPrefabName. Use that exact lowercase string as the key under Npc type overrides. Additional keys are ignored until the matching NPC actually takes or deals damage.

How do I install it on Oxide or Carbon?

Drop the .cs file into oxide/plugins/ on either framework and run oxide.reload NpcDamageMultiplier (or c.reload NpcDamageMultiplier on Carbon). The plugin creates oxide/config/NpcDamageMultiplier.json on first load; edit it and run /npddmg reload as an admin.

Why is NPC-vs-NPC damage untouched?

Scripted battles like Bradley vs scientists should keep their vanilla outcomes, otherwise testing becomes chaotic. The method returns early when both victimNpc and attackerNpc are non-null. Only player→NPC and NPC→player hits are multiplied, so an NPC attacking another NPC or a turret hitting an NPC is ignored.

Customize it for your server

Change the reward, add a cooldown, restrict it to VIPs. Start from this prompt and edit anything.

Prompt: Rust Oxide plugin that scales NPC damage to players and player damage to NPCs independently, with a per-NPC-type override table keyed by ShortPrefabName, using OnEntityTakeDamage; includes /npddmg status and /npddmg reload with permissions.
Customize for my server

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

Last updated Oct 26, 2026.

Customize npc damage multiplier

Customize