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
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.
{
"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
| Command | What it does | Permission |
|---|---|---|
/npddmg status | Prints the active global multipliers and the configured override count to chat. | npcdamagemultiplier.use |
/npddmg reload | Reloads the config file and applies new multipliers without a server restart. | npcdamagemultiplier.admin |
| Permission | Grants |
|---|---|
npcdamagemultiplier.use | Allows use of /npddmg status. |
npcdamagemultiplier.admin | Allows use of /npddmg reload. |
How the code works
Scale the damage in OnEntityTakeDamage
The hook receives the entity that will take damage and the
HitInfo. Null-check both, plushitInfo.damageTypes, then decide which direction the hit goes. Scaling is done withhitInfo.damageTypes.ScaleAll(mult), and the method returnsnullso Rust's own armor, health and death code still runs. The plugin only rewrites the damage numbers before that point.Classify each side as NPC or player
The private helper
GetNpc(BaseEntity)returnsentity as BaseNpcafter a destroyed check, and is called on bothentityandhitInfo.Initiator. Turrets, traps and vehicle weapons have aBaseEntityinitiator and noInitiatorPlayer, so those simply fall through to the default. WhenvictimNpc != null && attackerNpc != nullthe 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.Apply the per-type override table
NpcTypeOverridesmaps a key to anNpcTypeOverridewithOutgoingandIncomingfloats.TryGetOverridematches on the NPC'sShortPrefabNamebecause the full prefab path can change between patches.GetOutgoingMultiplierandGetIncomingMultiplieruse the override when found, otherwise the globalNpcDamageToPlayersorPlayerDamageToNpcs. Adding a key for a prefab whose short name is unknown just does nothing, so a typo fails safe.Config defaults and validation
PluginConfigholds the two globals and the override dictionary, with readable[JsonProperty]names so admins editing JSON seeNpc damage to playersrather than camel-case.ClampConfigclamps 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.LoadConfigcatches a broken file, callsLoadDefaultConfig, clamps and saves.Skip decay, fall and suicide damage
ShouldSkip(HitInfo)readshitInfo.damageTypes.GetMajorityDamageType()and returns true when it isDecay,FallorSuicide. 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.Permissions and the admin command
Initregistersnpcdamagemultiplier.useandnpcdamagemultiplier.admin. The/npddmgchat command dispatches onargs[0]:statusprints the two globals and the override count,reloadcallsLoadConfig()and confirms.statusaccepts either permission,reloadrequires admin. Every reply goes through theMsghelper andlang.GetMessage, so an empty arg list returns theUsagestring.
Install it on your server
Save NpcDamageMultiplier.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/NpcDamageMultiplier.json(Carbon:carbon/configs/NpcDamageMultiplier.json). After editing, reload it:oxide.reload NpcDamageMultiplier c.reload NpcDamageMultiplier # CarbonGrant the permission
Give it to every player, or swapdefaultfor a group such asvip: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 versionTeam-scaled NPC damage
Make grouping up against NPCs reduce the damage each player takes.
Generate this versionScrap 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.
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 npc damage multiplier
Customize