Skip to content
RustPluginGenerator

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: the BaseCombatEntity being hurt. Players, NPCs, animals, building blocks, deployables, vehicles and helicopters are all BaseCombatEntity.
  • info: the HitInfo describing 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.

C#
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:

C#
// 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 BasePlayer is also a BaseCombatEntity. 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

FieldMeaning
InitiatorThe BaseEntity that caused the hit. May be null (fall, decay, some environmental damage).
InitiatorPlayerThe attacking BasePlayer. Null when the source is not a player.
HitEntityThe entity that was hit. Usually the same as the hook's entity, but read the hook argument as your primary target.
damageTypesA DamageTypeList holding the damage amount for each Rust.DamageType.
isHeadshotTrue when the hit counted as a headshot.
WeaponThe AttackEntity that dealt the hit, if any.
WeaponPrefabThe prefab of the weapon, useful to identify what was used (for example a rocket launcher versus a rifle).
ProjectileDistanceDistance the projectile traveled.
HitPositionWorldWorld 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:

C#
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:

C#
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:

C#
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:

C#
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.

C#
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:

C#
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 ScientistNPC or BaseAnimalNPC, or test entity 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 BasePlayer and info.Initiator is 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 is checks when only one target type matters.
  • Do not allocate lists, strings or LINQ queries on each call.
  • Do not call Puts or PrintWarning inside the hook. Log only on startup or rare conditions.
  • Do not scan BaseNetworkable.serverEntities or BasePlayer.activePlayerList here.
  • 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 info when a player dies. info may be null.
  • OnEntityDeath receives BaseCombatEntity entity, HitInfo info for 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:

C#
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 false to "allow". Any non-null value cancels.
  • Using BasePlayer overloads and wondering why buildings are ignored.
  • Reading InitiatorPlayer without 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.

Skip the C#: describe your plugin

Open generator