Skip to content

Player Damage

Handling player damage events is essential for plugins that implement custom combat systems, invulnerability zones, damage tracking, or gameplay modifiers. RocketMod provides the UnturnedEvents.OnPlayerDamaged event for intercepting damage before it is applied to a player. Understanding how damage flows through Unturned's systems and how RocketMod exposes it is necessary for any gameplay-related plugin.

This article covers the damage event system, how to subscribe and respond to damage events, how damage cancellation works, and practical patterns for damage modification and tracking.

57 Studios operates multiple Unturned servers with custom PvP and PvE mechanics. The damage patterns documented here are drawn from production plugin authoring experience where damage modification is used for zone-based invulnerability, weapon balancing, and combat logging.

Prerequisites

  • A working RocketMod installation. See RocketMod and OpenMod Plugin Basics.
  • Visual Studio with C# development workload.
  • Familiarity with RocketMod event subscription patterns.
  • Basic understanding of Unturned's damage system (melee, gun, explosion, environmental).

What you'll learn

  • How to subscribe to the UnturnedEvents.OnPlayerDamaged event.
  • What damage sources exist and how to identify them.
  • How to modify or cancel damage before it is applied.
  • How to implement safe zones and invulnerability.
  • How to track damage statistics per player.
  • Best practices for damage-related plugins.

Damage event overview

RocketMod exposes player damage through the UnturnedEvents static class. The primary event is OnPlayerDamaged, which fires when a player is about to take damage from any source. The event fires before the damage is applied, allowing plugins to modify or cancel it.

Event location

The OnPlayerDamaged event lives on Rocket.Unturned.Events.UnturnedEvents:

csharp
using Rocket.Unturned.Events;

Subscribing to damage events

csharp
using Rocket.Unturned.Events;

protected override void Load()
{
    UnturnedEvents.OnPlayerDamaged += OnPlayerDamaged;
}

protected override void Unload()
{
    UnturnedEvents.OnPlayerDamaged -= OnPlayerDamaged;
}

Damage event parameters

The OnPlayerDamaged event passes parameters that describe the damage being dealt, including the player receiving the damage, the amount of damage, the damage source, and contextual information about how the damage occurred.

csharp
private void OnPlayerDamaged(UnturnedPlayer victim, ref ushort damageAmount, ref bool cancel, /* additional parameters */)
{
    // victim — the player receiving the damage
    // damageAmount — the amount of damage (modifiable via ref)
    // cancel — whether to cancel the damage (modifiable via ref)
}

Setting ref bool cancel to true prevents the damage from being applied:

csharp
private void OnPlayerDamaged(UnturnedPlayer victim, ref ushort damageAmount, ref bool cancel, EDeathCause cause, ELimb limb, SteamPlayer killer)
{
    // Cancel all damage for testing
    cancel = true;
}

::: caution Cancellation behavior varies RocketMod's OnPlayerDamaged event supports cancellation through event arguments. Setting ref bool cancel = true in the handler should prevent the damage from being applied. However, the cancellation behavior depends on the specific damage source and the version of Unturned the server is running.

For some damage sources — particularly environmental damage (falling, drowning, starvation) and legacy damage types — the cancellation may not prevent the damage from being applied. The event fires, the cancel flag is set, but the damage is still processed by Unturned's internal systems.

Always test your damage cancellation logic on the specific Unturned version your server runs. Do not assume that setting cancel = true works uniformly across all damage sources.

For reliable invulnerability, consider combining event cancellation with post-damage health restoration:

csharp
private void OnPlayerDamaged(UnturnedPlayer victim, ref ushort damageAmount, ref bool cancel, EDeathCause cause, ELimb limb, SteamPlayer killer)
{
    if (victim.Player.life.health > 0)
    {
        cancel = true;
        // Post-damage health reset as a safety net
        // for cases where cancellation does not fully work
    }
}

:::

Damage source identification

The event provides an EDeathCause enum that identifies the type of damage:

EDeathCause valueDamage typeExample
BleedingBleed-out damagePlayer ignored a bleed effect
BonesFall damagePlayer fell from a height
BrainHeadshot damageSniper rifle headshot
BurningFire damageStanding in fire
FoodStarvation damagePlayer's food meter reached zero
GunFirearm damageShot by a gun
InfectionInfection damagePlayer ignored an infection
MeleeMelee weapon damageHit with a melee weapon
RoadkillVehicle impactRun over by a vehicle
SentrySentry gun damageHit by a buildable sentry
SharkShark attackAttacked by a shark in water
SpawnKill command/kill or suicide command
SuicideSuicidePlayer killed themselves
VehicleVehicle explosionVehicle exploded near the player
WaterDrowning damagePlayer's oxygen meter depleted
ZombieZombie attackAttacked by a zombie

Damage amount

The damageAmount parameter is a ushort (0–65535). It is the raw damage value before player armor and resistance calculations. Modifying this value changes the damage the player receives:

csharp
private void OnPlayerDamaged(UnturnedPlayer victim, ref ushort damageAmount, ref bool cancel, EDeathCause cause, ELimb limb, SteamPlayer killer)
{
    // Halve all gun damage
    if (cause == EDeathCause.Gun)
    {
        damageAmount = (ushort)(damageAmount / 2);
    }
}

Safe zone implementation

A common use case for damage events is implementing safe zones — areas where players cannot take or deal damage:

csharp
using System.Collections.Generic;
using UnityEngine;

public class SafeZoneComponent : UnturnedPlayerComponent
{
    public bool IsInSafeZone { get; set; }
}

// In the plugin:
private void OnPlayerDamaged(UnturnedPlayer victim, ref ushort damageAmount, ref bool cancel, EDeathCause cause, ELimb limb, SteamPlayer killer)
{
    var component = victim.GetComponent<SafeZoneComponent>();
    if (component != null && component.IsInSafeZone)
    {
        cancel = true;
    }

    // Also prevent players in safe zones from dealing damage
    if (killer != null)
    {
        var killerPlayer = UnturnedPlayer.FromSteamPlayer(killer);
        var killerComponent = killerPlayer.GetComponent<SafeZoneComponent>();
        if (killerComponent != null && killerComponent.IsInSafeZone)
        {
            cancel = true;
        }
    }
}

Zone checking with distance

A more practical approach checks distance from predefined safe zone centers:

csharp
public class SafeZone
{
    public Vector3 Center;
    public float Radius;
}

private readonly List<SafeZone> _safeZones = new List<SafeZone>
{
    new SafeZone { Center = new Vector3(100f, 0f, 200f), Radius = 50f }
};

private void OnPlayerDamaged(UnturnedPlayer victim, ref ushort damageAmount, ref bool cancel, EDeathCause cause, ELimb limb, SteamPlayer killer)
{
    Vector3 victimPos = victim.Player.transform.position;

    foreach (var zone in _safeZones)
    {
        if (Vector3.Distance(victimPos, zone.Center) <= zone.Radius)
        {
            cancel = true;
            return;
        }
    }
}

Damage tracking and statistics

Plugins that track damage statistics for leaderboards or gameplay analysis can aggregate damage data in the event handler:

csharp
using System.Collections.Generic;

public class DamageTrackerComponent : UnturnedPlayerComponent
{
    public Dictionary<EDeathCause, int> DamageTaken;
    public Dictionary<string, int> DamageDealtByPlayer;
    public int TotalDamageTaken;
    public int TotalDamageDealt;

    protected override void Load()
    {
        DamageTaken = new Dictionary<EDeathCause, int>();
        DamageDealtByPlayer = new Dictionary<string, int>();
        TotalDamageTaken = 0;
        TotalDamageDealt = 0;
    }
}

// In the plugin:
private void OnPlayerDamaged(UnturnedPlayer victim, ref ushort damageAmount, ref bool cancel, EDeathCause cause, ELimb limb, SteamPlayer killer)
{
    var victimComp = victim.GetComponent<DamageTrackerComponent>();
    if (victimComp != null)
    {
        if (!victimComp.DamageTaken.ContainsKey(cause))
            victimComp.DamageTaken[cause] = 0;
        victimComp.DamageTaken[cause] += damageAmount;
        victimComp.TotalDamageTaken += damageAmount;
    }

    if (killer != null)
    {
        var killerPlayer = UnturnedPlayer.FromSteamPlayer(killer);
        var killerComp = killerPlayer.GetComponent<DamageTrackerComponent>();
        if (killerComp != null)
        {
            killerComp.TotalDamageDealt += damageAmount;
        }
    }
}

Damage modification by weapon type

For gameplay balancing, plugins can modify damage based on the equipped weapon:

csharp
private void OnPlayerDamaged(UnturnedPlayer victim, ref ushort damageAmount, ref bool cancel, EDeathCause cause, ELimb limb, SteamPlayer killer)
{
    if (cause != EDeathCause.Gun && cause != EDeathCause.Melee)
        return;

    if (killer == null)
        return;

    // Get the killer's active item
    Items kItems = killer.player.equipped;
    if (kItems == null)
        return;

    ushort itemId = kItems.id;

    // Apply damage modifiers based on weapon ID
    switch (itemId)
    {
        case 101: // Maple Rifle
            damageAmount = (ushort)(damageAmount * 0.8); // 20% damage reduction
            break;
        case 102: // Sport Shot
            damageAmount = (ushort)(damageAmount * 1.2); // 20% damage increase
            break;
        case 103: // Snayperskya
            damageAmount = (ushort)(damageAmount * 1.5); // 50% damage increase
            break;
    }
}

Combat logging and invulnerability

A common pattern in RP servers is combat logging prevention — players who have taken recent damage cannot disconnect without consequences:

csharp
public class CombatLogComponent : UnturnedPlayerComponent
{
    public bool IsInCombat { get; private set; }
    public UnturnedPlayer LastAttacker;
    private float _combatEndTime;

    public void Tag(UnturnedPlayer attacker)
    {
        IsInCombat = true;
        LastAttacker = attacker;
        _combatEndTime = Time.realtimeSinceStartup + 30f; // 30-second combat timer
        Player.SendChat("You are now in combat! Do not disconnect.", Color.red);
    }

    private void Update()
    {
        if (IsInCombat && Time.realtimeSinceStartup >= _combatEndTime)
        {
            IsInCombat = false;
            LastAttacker = null;
            Player.SendChat("You are no longer in combat.", Color.green);
        }
    }
}

// In the plugin:
private void OnPlayerDamaged(UnturnedPlayer victim, ref ushort damageAmount, ref bool cancel, EDeathCause cause, ELimb limb, SteamPlayer killer)
{
    var victimCombat = victim.GetComponent<CombatLogComponent>();
    if (victimCombat != null && victimCombat.IsInCombat)
    {
        // Extend combat timer on each hit
        victimCombat.Tag(killer != null ? UnturnedPlayer.FromSteamPlayer(killer) : null);
    }

    if (killer != null)
    {
        var killerPlayer = UnturnedPlayer.FromSteamPlayer(killer);
        var killerCombat = killerPlayer.GetComponent<CombatLogComponent>();
        if (killerCombat != null)
        {
            killerCombat.Tag(victim);
        }
    }
}

Damage immunity periods

Some gameplay mechanics require temporary damage immunity — after respawn, teleportation, or entering a protected area:

csharp
public class DamageImmunityComponent : UnturnedPlayerComponent
{
    private float _immunityEndTime;

    public void GrantImmunity(float seconds)
    {
        _immunityEndTime = Time.realtimeSinceStartup + seconds;
        Player.SendChat($"You are immune to damage for {seconds} seconds.", Color.cyan);
    }

    public bool IsImmune
    {
        get { return Time.realtimeSinceStartup < _immunityEndTime; }
    }
}

// In the plugin:
private void OnPlayerDamaged(UnturnedPlayer victim, ref ushort damageAmount, ref bool cancel, EDeathCause cause, ELimb limb, SteamPlayer killer)
{
    var immunity = victim.GetComponent<DamageImmunityComponent>();
    if (immunity != null && immunity.IsImmune)
    {
        cancel = true;
    }
}

Limb-specific damage

The ELimb parameter identifies which body part was hit:

ELimb valueBody part
LeftArmLeft arm
RightArmRight arm
LeftLegLeft leg
RightLegRight leg
SpineTorso
SkullHead
csharp
private void OnPlayerDamaged(UnturnedPlayer victim, ref ushort damageAmount, ref bool cancel, EDeathCause cause, ELimb limb, SteamPlayer killer)
{
    // Headshots deal 3x damage
    if (limb == ELimb.Skull)
    {
        damageAmount = (ushort)(damageAmount * 3);
    }

    // Leg shots deal 0.5x damage
    if (limb == ELimb.LeftLeg || limb == ELimb.RightLeg)
    {
        damageAmount = (ushort)(damageAmount * 0.5);
    }
}

Complete damage plugin example

The following plugin demonstrates damage modification, safe zones, combat logging, and limb-specific damage multipliers integrated into a single plugin.

CombatPlugin.cs:

csharp
using Rocket.API;
using Rocket.Core.Plugins;
using Rocket.Unturned.Chat;
using Rocket.Unturned.Events;
using Rocket.Unturned.Player;
using SDG.Unturned;
using System.Collections.Generic;
using UnityEngine;

namespace CombatPlugin
{
    public class CombatPlugin : RocketPlugin<CombatPluginConfiguration>
    {
        public static CombatPlugin Instance { get; private set; }

        private readonly List<Vector3> _safeZoneCenters = new List<Vector3>
        {
            new Vector3(0f, 0f, 0f),    // Example safe zone 1
            new Vector3(200f, 0f, 300f) // Example safe zone 2
        };
        private const float SAFE_ZONE_RADIUS = 30f;

        private readonly Dictionary<EDeathCause, float> _damageMultipliers =
            new Dictionary<EDeathCause, float>
        {
            { EDeathCause.Gun, 1.0f },
            { EDeathCause.Melee, 1.0f },
            { EDeathCause.Zombie, 0.5f },
            { EDeathCause.Burning, 1.5f },
            { EDeathCause.Bones, 1.0f }
        };

        protected override void Load()
        {
            Instance = this;
            UnturnedEvents.OnPlayerDamaged += OnPlayerDamaged;
            Rocket.Core.Logging.Logger.Log("CombatPlugin loaded.");
        }

        protected override void Unload()
        {
            UnturnedEvents.OnPlayerDamaged -= OnPlayerDamaged;
            Instance = null;
            Rocket.Core.Logging.Logger.Log("CombatPlugin unloaded.");
        }

        private void OnPlayerDamaged(UnturnedPlayer victim, ref ushort damageAmount, ref bool cancel, EDeathCause cause, ELimb limb, SteamPlayer killer)
        {
            // 1. Safe zone check
            foreach (Vector3 zoneCenter in _safeZoneCenters)
            {
                float dist = Vector3.Distance(victim.Player.transform.position, zoneCenter);
                if (dist <= SAFE_ZONE_RADIUS)
                {
                    cancel = true;
                    return;
                }
            }

            // 2. Combat logging tag
            var combatLog = victim.GetComponent<CombatLogComponent>();
            if (combatLog != null)
            {
                if (killer != null)
                {
                    combatLog.Tag(UnturnedPlayer.FromSteamPlayer(killer));
                }
                else
                {
                    combatLog.Tag(null);
                }
            }

            // 3. Apply damage multiplier by cause
            if (_damageMultipliers.TryGetValue(cause, out float multiplier))
            {
                damageAmount = (ushort)Mathf.RoundToInt(damageAmount * multiplier);
            }

            // 4. Limb multiplier
            if (limb == ELimb.Skull)
            {
                damageAmount = (ushort)Mathf.RoundToInt(damageAmount * 2.5f);
            }
            else if (limb == ELimb.LeftLeg || limb == ELimb.RightLeg)
            {
                damageAmount = (ushort)Mathf.RoundToInt(damageAmount * 0.6f);
            }

            // 5. Log significant damage events
            if (damageAmount > 50)
            {
                string killerName = killer != null ? killer.playerID.playerName : "environment";
                Rocket.Core.Logging.Logger.Log(
                    $"HIGH DAMAGE: {victim.DisplayName} took {damageAmount} damage " +
                    $"from {killerName} ({cause})"
                );
            }
        }
    }
}

Debugging damage events

Logging all damage

During development, log every damage event to understand the event flow:

csharp
private void OnPlayerDamaged(UnturnedPlayer victim, ref ushort damageAmount, ref bool cancel, EDeathCause cause, ELimb limb, SteamPlayer killer)
{
    string killerName = killer != null ? killer.playerID.playerName : "environment";
    Rocket.Core.Logging.Logger.Log(
        $"DAMAGE: {victim.DisplayName} | Amount: {damageAmount} | " +
        $"Cause: {cause} | Limb: {limb} | " +
        $"Killer: {killerName} | Cancel: {cancel}"
    );
}

Common damage event issues

SymptomLikely causeFix
Cancellation does not prevent damageDamage source does not support cancellationAdd post-damage health restoration as fallback
Damage modification does not applyHandler subscribed after damage appliedVerify subscription in Load()
NullReferenceException in handlerkiller or victim is nullNull-check all parameters before accessing
Damage values appear incorrectRaw damage vs modified damage confusionLog both raw and modified values

Frequently asked questions

Can I prevent all damage to a player?

Yes, by setting cancel = true in the damage event handler. However, as noted in the caution above, some damage sources may bypass the cancellation mechanism. For complete invulnerability, combine cancellation with periodic health restoration.

How do I identify the weapon used?

The weapon item ID can be obtained from the killer's equipped item context through SteamPlayer and the Items class. Note that in some damage events, the weapon information may not be directly available.

Does OnPlayerDamaged fire for environmental damage?

Yes. Environmental damage (falling, drowning, burning, starving) fires the OnPlayerDamaged event with the corresponding EDeathCause value.

Can I make a player take more damage from specific sources?

Yes. Modify the damageAmount parameter in the event handler based on the EDeathCause value. Apply a multiplier greater than 1.0 to increase damage.

Is there an event for when a player heals?

RocketMod does not expose a built-in healing event. To track healing, you would need to poll the player's health periodically or subscribe to Unturned's lower-level healing events.

How do I make a player invulnerable for a specific duration?

Use a component with a timer. In the OnPlayerDamaged handler, check whether the component's immunity flag is active and set cancel = true if so. Reset the flag after the duration expires using Update() or a coroutine.

Can I log all damage dealt and received?

Yes. Subscribe to OnPlayerDamaged and log the victim, damage amount, cause, limb, and killer to a file. Use a Dictionary<ulong, DamageLog> structure keyed by player Steam ID to aggregate per-player statistics.

What happens if I set damageAmount to 0?

Setting damageAmount to 0 effectively cancels the damage for most sources, but the event still fires and the player may still receive a hit marker or sound effect. For complete cancellation, set cancel = true instead of reducing damage to zero.

Can I reflect damage back to the attacker?

Yes. In the OnPlayerDamaged handler, when killer is not null, apply damage back to the killer using the same damage amount. Be careful to add a reflection cap or counter to prevent infinite reflection loops.

Does OnPlayerDamaged fire for self-inflicted damage?

Yes. Self-inflicted damage (such as from a explosive placed by the player themselves or fall damage from a self-built structure) fires the event with the player as both the victim and the killer.

How do I test damage events during development?

Use a debug command that deals a fixed amount of damage to yourself. Subscribe a test handler that logs the full event payload: victim, damage amount, cause, limb, and killer. Compare the logged output against your expected behavior.

Can I make certain items deal no damage?

Yes. Check the killer's equipped item ID in the handler and set cancel = true for specific items. This is useful for disabling damage from admin-only weapons or event items during special game modes.

Cross-references

Document history

VersionDateAuthorNotes
1.02026-07-2757 StudiosInitial publication. Coverage of damage events, damage modification, safe zones, combat logging, damage tracking, best practices.