Skip to content

Player Death and Respawn

Player death is one of the most important events in Unturned gameplay. When a player dies, a sequence of events fires: death event, inventory drop, respawn timer, and revival. RocketMod exposes several events along this sequence, allowing plugins to intercept deaths, modify death behavior, manage respawn, and track death statistics.

This article covers the complete death-to-respawn pipeline. It explains each event in the sequence, the order they fire, how to handle inventory on death, how to manage respawn locations and timers, and how to implement death-related gameplay features such as death messages, death penalties, and revive systems.

57 Studios operates multiple Unturned servers with custom death mechanics including death penalties, respawn timers, and custom death messages. The patterns documented here are drawn from production plugin authoring experience.

Prerequisites

What you'll learn

  • The complete death-to-respawn sequence and what fires when.
  • How to subscribe to death and respawn events.
  • How death cause and killer information is provided.
  • How inventory drops work during the death sequence.
  • How to manage respawn locations and timers.
  • How to implement death messages, death penalties, and revive systems.
  • Best practices for death handling in production plugins.

Death sequence overview

When a player's health reaches zero, the following sequence occurs:

  1. Fatal damage event — The player takes damage that reduces health to zero.
  2. OnPlayerDeath fires — RocketMod's death event fires. The player is still alive at this point, and their inventory is intact.
  3. Inventory drop — Unturned drops the player's items onto the ground (if the server has drop-on-death enabled).
  4. OnPlayerDead fires — The player is now fully dead. The death screen appears.
  5. Respawn timer — The player waits for the configured respawn delay.
  6. OnPlayerRevive fires — The player is respawned at their bed, home point, or a random spawn location.
  7. Player is alive — Normal gameplay resumes.

Event order and inventory state

Understanding exactly when inventory drops relative to the death events is critical for plugins that manipulate items on death. The OnPlayerDeath event fires before the player's inventory drops to the ground. The OnPlayerDead event fires after the inventory has been dropped.

This means:

  • In OnPlayerDeath, the player's inventory is still intact. You can read items, remove items, or save item data before they drop.
  • In OnPlayerDead, the inventory has already been dropped. The player's inventory is empty.
  • Adding items to the player's inventory in OnPlayerDeath may cause those items to also be dropped when the inventory drop occurs moments later.

Death event fires before inventory drop

The OnPlayerDeath event fires during the death sequence, specifically before the player's inventory items are dropped to the ground. This gives plugins a window to interact with the player's items before they become world pickups. Common use cases include:

  • Saving a player's equipped items to a death cache for retrieval.
  • Dropping specific items while keeping others in the inventory.
  • Logging the player's inventory state at the time of death.

After OnPlayerDeath returns, Unturned proceeds with the standard inventory drop logic.

OnPlayerDeath event

The OnPlayerDeath event fires when the player dies but before the death screen appears and before inventory drops.

Event signature

csharp
public static event OnPlayerDeath OnPlayerDeath;

public delegate void OnPlayerDeath(
    UnturnedPlayer player,
    EDeathCause cause,
    ELimb limb,
    SteamPlayer killer
);

Event parameters

ParameterTypeDescription
FirstUnturnedPlayerThe player who died
SecondEDeathCauseThe cause of death
ThirdELimbThe limb that received the fatal hit
FourthSteamPlayerThe killer (null if environmental death)

Note the parameter order: the death cause comes before the killer parameter. When implementing the handler, ensure the EDeathCause parameter is in the second position and the SteamPlayer killer parameter is in the fourth position.

csharp
// CORRECT parameter order:
private void OnPlayerDeath(UnturnedPlayer player, EDeathCause cause, ELimb limb, SteamPlayer killer)
{
    // player — the dead player
    // cause — death cause (second parameter)
    // limb — fatal hit location
    // killer — the killer player (fourth parameter)
}

// WRONG parameter order — placing killer before cause
// causes the killer SteamPlayer to be interpreted as EDeathCause,
// which produces a compile error due to type mismatch.
private void OnPlayerDeath(UnturnedPlayer player, SteamPlayer killer, EDeathCause cause, ELimb limb)
{
    // This does NOT compile — the delegate signature expects
    // EDeathCause at position 2 and SteamPlayer at position 4.
}

Basic subscription

csharp
using Rocket.Unturned.Events;

protected override void Load()
{
    UnturnedPlayerEvents.OnPlayerDeath += OnPlayerDeath;
}

protected override void Unload()
{
    UnturnedPlayerEvents.OnPlayerDeath -= OnPlayerDeath;
}

Basic implementation

csharp
private void OnPlayerDeath(UnturnedPlayer player, EDeathCause cause, ELimb limb, SteamPlayer killer)
{
    string killerName = killer != null ? killer.playerID.playerName : "the environment";
    Rocket.Core.Logging.Logger.Log(
        $"{player.DisplayName} was killed by {killerName} via {cause}"
    );
}

OnPlayerDead event

OnPlayerDead fires after the inventory drop. The player is fully dead at this point.

Event signature

csharp
public static event OnPlayerDead OnPlayerDead;

public delegate void OnPlayerDead(
    UnturnedPlayer player,
    EDeathCause cause,
    ELimb limb,
    SteamPlayer killer
);

The signature is the same as OnPlayerDeath. The difference is the timing within the death sequence.

csharp
private void OnPlayerDead(UnturnedPlayer player, EDeathCause cause, ELimb limb, SteamPlayer killer)
{
    // Player is fully dead. Inventory has been dropped.
    Rocket.Core.Logging.Logger.Log(
        $"{player.DisplayName} is now dead. Cause: {cause}"
    );
}

OnPlayerRevive event

OnPlayerRevive fires when the player respawns after death.

Event signature

csharp
public static event OnPlayerRevive OnPlayerRevive;

public delegate void OnPlayerRevive(UnturnedPlayer player);
csharp
private void OnPlayerRevive(UnturnedPlayer player)
{
    player.SendChat("You have respawned.", Color.green);
}

Death cause and killer tracking

EDeathCause values

The EDeathCause enum identifies the type of death. Common values:

ValueMeaning
BleedingBled out from untreated wounds
BonesFall damage
BurningFire or lava damage
FoodStarvation
GunFirearm
InfectionInfection from zombie attacks
MeleeMelee weapon
RoadkillVehicle impact
SentryBuildable sentry gun
SharkShark attack
SpawnKilled via spawn command
Suicide/suicide command
VehicleVehicle explosion
WaterDrowning
ZombieZombie attack

Handling killer information

The killer parameter is a SteamPlayer from SDG.Unturned. It is null for environmental deaths (falling, drowning, starvation, zombies).

csharp
private void OnPlayerDeath(UnturnedPlayer player, EDeathCause cause, ELimb limb, SteamPlayer killer)
{
    if (killer != null)
    {
        UnturnedPlayer killerPlayer = UnturnedPlayer.FromSteamPlayer(killer);
        Rocket.Core.Logging.Logger.Log(
            $"{player.DisplayName} killed by {killerPlayer.DisplayName}"
        );
    }
    else
    {
        Rocket.Core.Logging.Logger.Log(
            $"{player.DisplayName} died from {cause}"
        );
    }
}

Custom death messages

A common plugin feature is custom death messages that replace Unturned's default death notifications:

csharp
private readonly Dictionary<EDeathCause, string[]> _deathMessages =
    new Dictionary<EDeathCause, string[]>
{
    { EDeathCause.Gun, new[] {
        "{victim} was shot by {killer}.",
        "{victim} was gunned down by {killer}.",
        "{killer} put a bullet in {victim}."
    }},
    { EDeathCause.Melee, new[] {
        "{victim} was stabbed by {killer}.",
        "{killer} took down {victim} with a melee weapon."
    }},
    { EDeathCause.Zombie, new[] {
        "{victim} was torn apart by zombies.",
        "{victim} succumbed to the infection."
    }},
    { EDeathCause.Bones, new[] {
        "{victim} fell to their death.",
        "{victim} forgot to watch their step."
    }},
    { EDeathCause.Burning, new[] {
        "{victim} was burnt to a crisp.",
        "{victim} played with fire and lost."
    }}
};

private static readonly System.Random _random = new System.Random();

private void OnPlayerDeath(UnturnedPlayer player, EDeathCause cause, ELimb limb, SteamPlayer killer)
{
    if (!_deathMessages.TryGetValue(cause, out string[] messages))
        return;

    string template = messages[_random.Next(messages.Length)];
    string message = template
        .Replace("{victim}", player.DisplayName)
        .Replace("{killer}", killer != null ? killer.playerID.playerName : "Unknown");

    UnturnedChat.Say(message, Color.magenta);
}

Death penalty systems

Plugins can impose penalties on death such as losing currency, losing items, or cooldown timers:

csharp
public class DeathPenaltyComponent : UnturnedPlayerComponent
{
    public int ConsecutiveDeaths;
    public float LastDeathTime;

    protected override void Load()
    {
        ConsecutiveDeaths = 0;
        LastDeathTime = 0f;
    }
}

private void OnPlayerDeath(UnturnedPlayer player, EDeathCause cause, ELimb limb, SteamPlayer killer)
{
    var penalty = player.GetComponent<DeathPenaltyComponent>();
    if (penalty == null)
        return;

    // Track consecutive deaths within 5 minutes
    float now = Time.realtimeSinceStartup;
    if (now - penalty.LastDeathTime < 300f)
    {
        penalty.ConsecutiveDeaths++;
    }
    else
    {
        penalty.ConsecutiveDeaths = 1;
    }
    penalty.LastDeathTime = now;

    // Apply penalty based on streak
    float penaltyMultiplier = 1f + (penalty.ConsecutiveDeaths * 0.25f);
    Rocket.Core.Logging.Logger.Log(
        $"{player.DisplayName} death penalty streak: {penalty.ConsecutiveDeaths} " +
        $"(multiplier: {penaltyMultiplier})"
    );

    // If using a currency plugin, deduct coins
    // CustomCurrencyPlugin.Instance.DeductCoins(player, (int)(10 * penaltyMultiplier));
}

Inventory management on death

Preserving items on death

To prevent items from dropping, the inventory must be modified in the OnPlayerDeath handler before the inventory drop occurs:

csharp
private void OnPlayerDeath(UnturnedPlayer player, EDeathCause cause, ELimb limb, SteamPlayer killer)
{
    // In OnPlayerDeath, the inventory is still intact.
    // Items can be read, saved, or removed.

    // Save the player's inventory to a backup
    var inventory = player.Inventory;
    SaveDeathCache(player.CSteamID.m_SteamID, inventory);

    // Optionally remove items from the inventory
    // to prevent them from dropping:
    // Clear certain items here before the drop happens.
}

Death cache recovery

Give players a way to recover dropped items from a death cache:

csharp
[RocketCommand("recover", "Recover items from your last death.",
    AllowedCaller = AllowedCaller.Player)]
public class RecoverCommand : IRocketCommand
{
    public void Execute(IRocketPlayer caller, string[] command)
    {
        var player = (UnturnedPlayer)caller;
        ulong steamId = player.CSteamID.m_SteamID;

        // Check if there is a death cache entry
        if (DeathCache.HasCache(steamId))
        {
            DeathCache.RestoreToInventory(steamId, player);
            player.SendChat("Your items have been recovered.", Color.green);
        }
        else
        {
            player.SendChat("No death cache found.", Color.red);
        }
    }
}

Respawn management

Bed respawn

Unturned allows players to claim a bed at a base. On death, they can respawn at their claimed bed. Plugins can interact with this system:

csharp
private void OnPlayerRevive(UnturnedPlayer player)
{
    // Check if the player respawned at a bed
    // (as opposed to a random spawn point)
    // Bed respawn information is in the player's BarricadeData

    player.SendChat("You have respawned.", Color.green);
}

Custom respawn locations

Plugins can set custom respawn locations by modifying the player's position after revival:

csharp
private void OnPlayerRevive(UnturnedPlayer player)
{
    // Check if this player has a custom spawn point
    if (_customSpawnPoints.ContainsKey(player.CSteamID.m_SteamID))
    {
        Vector3 spawnPos = _customSpawnPoints[player.CSteamID.m_SteamID];
        player.Player.teleportToLocation(spawnPos, player.Player.transform.rotation.y);
        player.SendChat("You have been teleported to your custom spawn point.", Color.cyan);
    }
}

Respawn timer modification

RocketMod does not expose a direct event for modifying the respawn timer. However, plugins can implement a custom respawn delay by overriding the OnPlayerDeath event and using a coroutine to delay the respawn:

csharp
private IEnumerator DelayedRespawn(UnturnedPlayer player, float delaySeconds)
{
    // Show a custom death screen message
    player.SendChat($"Respawning in {delaySeconds} seconds...", Color.yellow);

    yield return new WaitForSeconds(delaySeconds);

    // Force the player to respawn
    // Note: This requires access to the player's lifecycle manager
    Rocket.Core.Logging.Logger.Log(
        $"Delayed respawn completed for {player.DisplayName}"
    );
}

Complete death handling plugin example

The following example demonstrates death messages, death tracking, inventory caching, and respawn handling in a single plugin.

DeathManagerPlugin.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 DeathManager
{
    public class DeathManagerPlugin : RocketPlugin<DeathManagerConfiguration>
    {
        public static DeathManagerPlugin Instance { get; private set; }

        private readonly Dictionary<ulong, DeathRecord> _deathRecords =
            new Dictionary<ulong, DeathRecord>();

        private readonly Dictionary<EDeathCause, string[]> _deathMessages =
            new Dictionary<EDeathCause, string[]>
        {
            { EDeathCause.Gun, new[] {
                "{victim} was shot by {killer}.",
                "{victim} was eliminated by {killer}."
            }},
            { EDeathCause.Zombie, new[] {
                "{victim} was killed by a zombie.",
                "{victim} succumbed to the horde."
            }},
            { EDeathCause.Bones, new[] {
                "{victim} fell to their death.",
                "{victim} forgot to watch their step."
            }},
            { EDeathCause.Water, new[] {
                "{victim} drowned.",
                "{victim} ran out of air."
            }},
            { EDeathCause.Suicide, new[] {
                "{victim} gave up.",
                "{victim} took the easy way out."
            }}
        };

        private static readonly System.Random _random = new System.Random();

        protected override void Load()
        {
            Instance = this;

            UnturnedPlayerEvents.OnPlayerDeath += OnPlayerDeath;
            UnturnedPlayerEvents.OnPlayerDead += OnPlayerDead;
            UnturnedPlayerEvents.OnPlayerRevive += OnPlayerRevive;

            Rocket.Core.Logging.Logger.Log("DeathManager loaded.");
        }

        protected override void Unload()
        {
            UnturnedPlayerEvents.OnPlayerDeath -= OnPlayerDeath;
            UnturnedPlayerEvents.OnPlayerDead -= OnPlayerDead;
            UnturnedPlayerEvents.OnPlayerRevive -= OnPlayerRevive;

            _deathRecords.Clear();
            Instance = null;
            Rocket.Core.Logging.Logger.Log("DeathManager unloaded.");
        }

        private void OnPlayerDeath(UnturnedPlayer player, EDeathCause cause, ELimb limb, SteamPlayer killer)
        {
            // Record the death
            var record = new DeathRecord
            {
                SteamId = player.CSteamID.m_SteamID,
                PlayerName = player.DisplayName,
                Cause = cause,
                Limb = limb,
                KillerId = killer?.m_SteamID.m_SteamID ?? 0,
                KillerName = killer?.playerID.playerName ?? "Environment",
                Timestamp = DateTime.UtcNow,
                Position = player.Player.transform.position
            };
            _deathRecords[player.CSteamID.m_SteamID] = record;

            // Save inventory snapshot before drop
            SaveInventorySnapshot(player);

            // Broadcast custom death message
            if (_deathMessages.TryGetValue(cause, out string[] messages))
            {
                // Handle PvP deaths with killer name
                if (killer != null && cause == EDeathCause.Gun)
                {
                    string template = messages[_random.Next(messages.Length)];
                    string message = template
                        .Replace("{victim}", player.DisplayName)
                        .Replace("{killer}", killer.playerID.playerName);
                    UnturnedChat.Say(message, Color.magenta);
                }
                // Handle environmental deaths
                else
                {
                    string template = messages[_random.Next(messages.Length)];
                    string message = template
                        .Replace("{victim}", player.DisplayName)
                        .Replace("{killer}", "the environment");
                    UnturnedChat.Say(message, Color.magenta);
                }
            }

            Rocket.Core.Logging.Logger.Log(
                $"DEATH: {player.DisplayName} killed by " +
                $"{(killer != null ? killer.playerID.playerName : cause.ToString())}"
            );
        }

        private void OnPlayerDead(UnturnedPlayer player, EDeathCause cause, ELimb limb, SteamPlayer killer)
        {
            // Player is fully dead, inventory has dropped
            Rocket.Core.Logging.Logger.Log(
                $"{player.DisplayName} death confirmed. Cause: {cause}"
            );
        }

        private void OnPlayerRevive(UnturnedPlayer player)
        {
            // Player has respawned
            if (_deathRecords.TryGetValue(player.CSteamID.m_SteamID, out DeathRecord record))
            {
                float elapsedMinutes = (float)(DateTime.UtcNow - record.Timestamp).TotalMinutes;
                player.SendChat(
                    $"You died {elapsedMinutes:F1} minutes ago. " +
                    $"Cause: {FormatDeathCause(record.Cause)}.",
                    Color.cyan
                );
            }

            // Grant brief invulnerability on respawn
            GrantInvulnerability(player);
        }

        private void SaveInventorySnapshot(UnturnedPlayer player)
        {
            // Read the player's inventory before the drop
            // and save to a cache file
            string cacheDir = $"Rocket/Plugins/DeathManager/deathcache/";
            System.IO.Directory.CreateDirectory(cacheDir);
            string cacheFile = $"{cacheDir}{player.CSteamID.m_SteamID}.txt";

            using (var writer = new System.IO.StreamWriter(cacheFile))
            {
                writer.WriteLine($"Death time: {DateTime.UtcNow}");
                writer.WriteLine($"Position: {player.Player.transform.position}");
                // Additional inventory data would be written here
            }
        }

        private void GrantInvulnerability(UnturnedPlayer player)
        {
            var immunity = player.GetComponent<DamageImmunityComponent>();
            if (immunity == null)
            {
                // Fallback: trigger a warning if component is missing
                Rocket.Core.Logging.Logger.LogWarning(
                    $"DamageImmunityComponent not found for {player.DisplayName}"
                );
                return;
            }
            immunity.GrantImmunity(5f);
        }

        private string FormatDeathCause(EDeathCause cause)
        {
            switch (cause)
            {
                case EDeathCause.Gun: return "firearm";
                case EDeathCause.Melee: return "melee weapon";
                case EDeathCause.Zombie: return "zombie";
                case EDeathCause.Bones: return "fall damage";
                case EDeathCause.Burning: return "fire";
                case EDeathCause.Water: return "drowning";
                case EDeathCause.Food: return "starvation";
                case EDeathCause.Infection: return "infection";
                case EDeathCause.Suicide: return "suicide";
                case EDeathCause.Vehicle: return "vehicle explosion";
                case EDeathCause.Roadkill: return "vehicle impact";
                default: return cause.ToString();
            }
        }
    }

    public class DeathRecord
    {
        public ulong SteamId;
        public string PlayerName;
        public EDeathCause Cause;
        public ELimb Limb;
        public ulong KillerId;
        public string KillerName;
        public System.DateTime Timestamp;
        public Vector3 Position;
    }
}

Death statistics tracking

For leaderboards or gameplay analysis, track death statistics per player:

csharp
public class DeathStatsComponent : UnturnedPlayerComponent
{
    public int TotalDeaths;
    public int PvPDeaths;
    public int PvEDeaths;
    public int SuicideCount;
    public Dictionary<EDeathCause, int> DeathsByCause;
    public string MostCommonKiller;

    protected override void Load()
    {
        TotalDeaths = 0;
        PvPDeaths = 0;
        PvEDeaths = 0;
        SuicideCount = 0;
        DeathsByCause = new Dictionary<EDeathCause, int>();
        MostCommonKiller = null;
    }
}

// In the plugin:
private void OnPlayerDeath(UnturnedPlayer player, EDeathCause cause, ELimb limb, SteamPlayer killer)
{
    var stats = player.GetComponent<DeathStatsComponent>();
    if (stats == null)
        return;

    stats.TotalDeaths++;

    if (killer != null)
    {
        stats.PvPDeaths++;
        stats.MostCommonKiller = killer.playerID.playerName;
    }
    else
    {
        stats.PvEDeaths++;
    }

    if (cause == EDeathCause.Suicide || cause == EDeathCause.Spawn)
    {
        stats.SuicideCount++;
    }

    if (!stats.DeathsByCause.ContainsKey(cause))
        stats.DeathsByCause[cause] = 0;
    stats.DeathsByCause[cause]++;
}

Frequently asked questions

Which event fires first, OnPlayerDeath or OnPlayerDead?

OnPlayerDeath fires first, before the inventory drops. OnPlayerDead fires after the inventory has dropped.

Can I prevent a player from dying after their health reaches zero?

By the time OnPlayerDeath fires, the player's health has already reached zero. The death sequence has started. To prevent death, you must cancel the damage in the OnPlayerDamaged event (see Player Damage), not in the death event.

Can I give a player items after they die?

Yes. In the OnPlayerRevive event, the player is alive and has a clean inventory (items on the character, not in the backpack if drop-on-death is enabled). You can add items at this point.

How do I prevent items from dropping on death?

Items can be removed from the inventory in OnPlayerDeath (before the drop occurs). Alternatively, configure the server to not drop items on death through Unturned's game mode settings.

Is the killer guaranteed to be online when OnPlayerDeath fires?

The killer is the SteamPlayer who dealt the fatal damage. If the killer disconnected between dealing damage and the death occurring (for example, damage-over-time effects), the killer parameter may still reference the player's data, but the player may no longer be on the server. Check killer.player.isOnline if needed.

Can I cancel a respawn?

RocketMod does not provide a cancel mechanism for respawns. To control when a player respawns, manage the respawn timer through the game mode configuration or implement a custom death screen using a UI plugin.

Cross-references

Document history

VersionDateAuthorNotes
1.02026-07-2757 StudiosInitial publication. Coverage of death sequence, OnPlayerDeath/OnPlayerDead/OnPlayerRevive events, inventory handling, death messages, respawn management, best practices.