Oxide OnEntityTakeDamage: Handling Damage in Rust Plugins
OnEntityTakeDamage is the Oxide hook that fires before a Rust entity takes damage, giving a plugin the target and a HitInfo object. You can scale damage with HitInfo.damageTypes, cancel it by returning any non-null value, and classify hits as PvP, PvE, fall or decay by inspecting the target, the initiator and the damage types. Keep the handler cheap, because it runs for almost every hit on the server.
7 min readUpdated Oct 26, 2026
OnEntityTakeDamage(BaseCombatEntity entity, HitInfo info) is the Oxide hook that runs before a Rust entity loses health. Inside it you can read who or what caused the hit, change the damage numbers, or cancel the hit by returning a non-null value. This guide covers the hook's overloads, the useful HitInfo fields, how to classify damage, and how to keep the handler fast. The hook reference is at /hooks/OnEntityTakeDamage.
How the hook fires
The hook is called from the game's damage code on the server, before health is reduced. Two arguments are passed:
entity: theBaseCombatEntitybeing hurt. Players, NPCs, animals, building blocks, deployables, vehicles and helicopters are allBaseCombatEntity.info: theHitInfodescribing the hit.
It is patched so that any non-null return value cancels the damage. That includes false. Returning true does not mean "allow", it means "cancel". To let the hit continue, return null (a plain return null; at the end of the method). If you are unfamiliar with how return values are interpreted, read Oxide hooks explained.
private object OnEntityTakeDamage(BaseCombatEntity entity, HitInfo info)
{
if (entity == null || info == null) return null;
return null;
}Overloads and type filtering
Oxide chooses which of your methods to call by checking that every argument is an instance of the declared parameter type. This gives you free filtering:
// Called for every target.
private object OnEntityTakeDamage(BaseCombatEntity entity, HitInfo info) { return null; }
// Called only when the target is a player.
private object OnEntityTakeDamage(BasePlayer player, HitInfo info) { return null; }
// Called only when the target is a building block.
private object OnEntityTakeDamage(BuildingBlock block, HitInfo info) { return null; }Be careful with two things:
- If you declare only
BasePlayer, hits on any other entity never reach your plugin. That is usually what you want for player-only logic, and it is cheaper than checking inside the method. - Declaring several overloads in one plugin can cause a single hit to run more than one of them, because a
BasePlayeris also aBaseCombatEntity. Either use one general method, or make sure the overloads do not overlap in effect.
Methods may also declare fewer parameters than the hook passes, but for damage you almost always need HitInfo.
HitInfo fields you will use
| Field | Meaning |
|---|---|
Initiator | The BaseEntity that caused the hit. May be null (fall, decay, some environmental damage). |
InitiatorPlayer | The attacking BasePlayer. Null when the source is not a player. |
HitEntity | The entity that was hit. Usually the same as the hook's entity, but read the hook argument as your primary target. |
damageTypes | A DamageTypeList holding the damage amount for each Rust.DamageType. |
isHeadshot | True when the hit counted as a headshot. |
Weapon | The AttackEntity that dealt the hit, if any. |
WeaponPrefab | The prefab of the weapon, useful to identify what was used (for example a rocket launcher versus a rifle). |
ProjectileDistance | Distance the projectile traveled. |
HitPositionWorld | World position of the impact. |
IsProjectile() | True for projectile hits. |
Each of these can be null or default, so check before use: info.InitiatorPlayer, info.Initiator, info.Weapon and info.WeaponPrefab in particular. A common source of NullReferenceException in hooks is reading info.InitiatorPlayer.UserIDString without a check.
Reading and scaling damage
info.damageTypes stores damage per type. Useful members:
Total()returns the sum across all types.GetMajorityDamageType()returns the type that contributes most.ScaleAll(float)multiplies every type.Scale(Rust.DamageType, float)multiplies one type.
Scaling edits the hit in place, so return null afterwards:
private object OnEntityTakeDamage(BasePlayer victim, HitInfo info)
{
if (victim == null || info == null) return null;
var attacker = info.InitiatorPlayer;
if (attacker == null || attacker == victim) return null;
info.damageTypes.ScaleAll(0.5f);
return null;
}To change only explosions, for example when building an explosive damage multiplier:
info.damageTypes.Scale(Rust.DamageType.Explosion, 2f);Scaling by 0 removes the damage while still letting the hit play out, which can feel different from a cancellation (for instance, hit markers and effects may still occur). Choose based on the behavior you want.
If several plugins scale the same hit, the multipliers stack in load order. Document your plugin's multiplier so admins can predict the combined effect.
Cancelling damage
Return any non-null value to stop the damage:
private object OnEntityTakeDamage(BasePlayer victim, HitInfo info)
{
if (victim == null || info == null) return null;
if (victim.IsSleeping()) return true; // cancels the hit
return null;
}Cancelling is the right tool for sleeper protection, god mode and new player protection. Because false cancels as well, do not return false thinking it means "not handled". Always end with return null.
If two plugins return different non-null values, Oxide logs a conflict and uses one of them. That is another reason to keep protection rules in as few plugins as practical.
Classifying damage: PvP, PvE, decay, fall
There is no single flag for the category of a hit. You derive it from the target, the initiator and the damage types.
PvP
The target is a BasePlayer, info.InitiatorPlayer is not null, and it is not the same player. Also exclude NPC players:
private bool IsPvP(BaseCombatEntity entity, HitInfo info)
{
var victim = entity as BasePlayer;
var attacker = info?.InitiatorPlayer;
if (victim == null || attacker == null) return false;
if (victim == attacker) return false;
if (victim.IsNpc || attacker.IsNpc) return false;
return true;
}Scientists and other NPC humans are BasePlayer subclasses, so IsNpc matters. See PvP damage multiplier and friendly fire protection for complete uses; for team checks compare currentTeam values, remembering 0 means no team.
PvE
A player attacked by an NPC, animal, helicopter or Bradley has a null InitiatorPlayer and an Initiator of type BaseAnimalNPC, ScientistNPC, BradleyAPC or PatrolHelicopter. Check with info.Initiator is ScientistNPC and similar type tests.
Decay
Decay damage has the Rust.DamageType.Decay type and no player initiator. Detect it with info.damageTypes.GetMajorityDamageType() == Rust.DamageType.Decay, or by checking info.damageTypes.Get(Rust.DamageType.Decay) > 0f. Cancelling it is the core of a no decay plugin; vehicles have their own variant in no vehicle decay.
Fall damage
Fall damage uses Rust.DamageType.Fall and has no player attacker. A player-only overload with a majority-type check implements no fall damage.
private object OnEntityTakeDamage(BasePlayer player, HitInfo info)
{
if (player == null || info == null) return null;
if (info.damageTypes.GetMajorityDamageType() == Rust.DamageType.Fall) return true;
return null;
}Other types such as Bleeding, Hunger, Thirst, Cold, Radiation and Heat come from the environment and player status, and they also lack a player initiator.
Building damage
Structures are hurt through the same hook. Use a typed overload or a cast:
private object OnEntityTakeDamage(BuildingBlock block, HitInfo info)
{
if (block == null || info == null) return null;
var attacker = info.InitiatorPlayer;
if (attacker == null) return null; // leave decay and environment alone
if (block.OwnerID == 0UL) return null;
if (attacker.userID.Get() == block.OwnerID) return null;
return true; // block raiding of other players' blocks
}This only handles BuildingBlock. Doors, storage boxes, turrets and other deployables are not building blocks, so add checks for Door, StorageContainer or BaseCombatEntity with OwnerID if you need to protect them. For a rule-driven version see building damage protection and raid block. Using block.grade you can apply different rules per tier.
For raid detection without cancelling, read info.damageTypes.Get(Rust.DamageType.Explosion) and log through your own data file, as in raid logger.
NPC damage
NPC-related logic falls into two directions:
- Damage dealt to NPCs: use a typed overload such as
ScientistNPCorBaseAnimalNPC, or testentity is ScientistNPC. A NPC health multiplier can be built by scaling incoming damage by the inverse of the health factor. - Damage dealt by NPCs: the victim is a
BasePlayerandinfo.Initiatoris the NPC. Scaling here yields an NPC damage multiplier.
To keep NPCs from targeting players in the first place, use OnNpcTarget rather than cancelling damage after the fact.
Performance
OnEntityTakeDamage is one of the busiest hooks on a server: every bullet, melee hit, explosion tick and decay pulse calls it. Follow these rules:
- Return early. Check null and type first, then do the cheap comparisons, then the expensive ones.
- Prefer typed overloads over inner
ischecks when only one target type matters. - Do not allocate lists, strings or LINQ queries on each call.
- Do not call
PutsorPrintWarninginside the hook. Log only on startup or rare conditions. - Do not scan
BaseNetworkable.serverEntitiesorBasePlayer.activePlayerListhere. - Cache permission results if you check them often, and invalidate on OnUserPermissionGranted if needed.
Oxide prints a warning when a hook takes more than 100ms, as explained in the hook performance warning. More general advice is in Rust plugin performance.
Follow-up hooks: death
Damage ends in death through two hooks:
- OnPlayerDeath receives
BasePlayer player, HitInfo infowhen a player dies.infomay be null. - OnEntityDeath receives
BaseCombatEntity entity, HitInfo infofor any entity, including players, NPCs and buildings.
Use them for rewards, messages and statistics, since the damage hook does not tell you whether a hit was lethal. A typical pattern is to compute the cause in the death hook:
private void OnEntityDeath(BaseCombatEntity entity, HitInfo info)
{
if (entity == null || info == null) return;
var killer = info.InitiatorPlayer;
if (killer == null) return;
if (entity is ScientistNPC)
{
// reward the killer, update counters
}
}See NPC kill rewards, kill log, death messages and death cause stats for full examples. Remember that a player can die without a hit, so both parameters need null handling, and that a hit canceled in OnEntityTakeDamage never reaches the death hooks.
Common mistakes
- Returning
falseto "allow". Any non-null value cancels. - Using
BasePlayeroverloads and wondering why buildings are ignored. - Reading
InitiatorPlayerwithout a null check. - Treating NPC scientists as ordinary players. Check
IsNpc. - Scaling inside several overlapping overloads and doubling the effect.
- Logging inside the hook, which floods the console and slows the server.
When a damage plugin starts failing after a game update, check whether the hook signature changed using fix Rust plugins after update and the monthly /updates reports. To prototype a damage rule quickly, the plugin generator can produce a starting file you can refine.
FAQ
Can OnEntityTakeDamage change damage and also cancel it?
Yes, but you normally pick one. Scaling edits the values inside HitInfo.damageTypes and returns null so the hit proceeds with the new numbers. Cancelling returns any non-null value, which stops the damage entirely. Scaling by 0 is an alternative that leaves the hit in place but with no effect.
Why does my OnEntityTakeDamage(BasePlayer ...) overload never run for buildings?
Oxide only calls a method when every argument is an instance of the declared parameter type. A BasePlayer parameter makes Oxide skip hits where the target is a BuildingBlock, an NPC or any other entity. Declare BaseCombatEntity to receive all targets, or add a second overload for each type.
Is HitInfo.InitiatorPlayer always the attacker?
No. It is null for fall damage, decay, animals, helicopters, traps and many other sources, and it can also be a non-player-controlled NPC case. Always null-check it, and fall back to Initiator or the damage type when you need to know what actually caused the hit.
How do I find out who killed a player after the damage hook?
Use OnPlayerDeath, which receives the BasePlayer and the final HitInfo. HitInfo may be null there, for example when death comes from a script rather than a hit, so null-check it before reading Initiator or damageTypes.
Does a slow OnEntityTakeDamage handler affect the whole server?
Yes. The hook runs on the main thread for nearly every hit, so expensive work delays the game tick. Oxide logs a warning when a hook takes over 100ms. Avoid allocations, entity scans and logging in the handler, and exit early for targets you do not care about.
More guides
- CovalencePlugin vs RustPlugin: Differences and Which to Use4 min read
- How to Debug a Rust Oxide Plugin: Compile Errors, Exceptions and Slow Hooks10 min read
- How to Give Items to Players in a Rust Plugin (Oxide/Carbon)5 min read
- How to Install Oxide on a Rust Dedicated Server (Windows and Linux)7 min read
Skip the C#: describe your plugin
Open generator