# esx_death (/docs/addons/esx_death)

esx_death is the global death system for ESX Legacy. It shows a branded death screen when a player dies, persists the dead state in the database, runs the early-respawn and bleedout timers, applies respawn penalties and exposes exports to revive players. [esx_ambulancejob](/docs/addons/esx_ambulancejob) builds on it for EMS revives and distress calls.

<Callout type="info">
esx_death has no page in the official ESX documentation. This reference is written from the resource source (version 1.0.0).
</Callout>

## Overview [#overview]

| Fact | Value |
| --- | --- |
| Resource | `esx_death` |
| Side | Client + server + NUI |
| Dependencies | `es_extended`, `esx_lib`, `oxmysql`, OneSync (`/onesync`) |
| Database | `users.is_dead`, `users.death_time` |
| Commands | `/revive`, `/reviveall` (group `admin`) |
| Required by | `esx_ambulancejob` |

### How it works [#how-it-works]

1. es_extended detects the death on the client and triggers `esx:onPlayerDeath` (client and server) with killer data.
2. esx_death stores `is_dead = 1` and `death_time` (unix seconds), sets the `isDead` state bag and opens the death screen.
3. After `Config.EarlyRespawnTimer` the player can hold **E** for `Config.HoldDuration` ms to respawn at the closest `Config.RespawnPoints` entry. Respawning before the bleedout ends costs `Config.EarlyRespawnFineAmount` from the bank when `Config.EarlyRespawnFine` is enabled.
4. When the bleedout timer (`EarlyRespawnTimer + BleedoutTimer`) runs out, the client respawns automatically without a fine.
5. On respawn the configured penalties remove items, cash, black money and weapons. A revive (EMS, admin, txAdmin) keeps everything.

### Death screen controls [#death-screen-controls]

| Key | Action |
| --- | --- |
| **E** (hold) | Respawn when allowed. |
| **G** | Send a distress signal (cooldown `Config.DistressCooldown`). |
| **F2** | Toggle the mouse cursor to click the NUI buttons. |

Chat (**T**) and push-to-talk stay enabled. All other controls are disabled while dead.

## Installation [#installation]

<Steps>
<Step>

### Database [#database]

With `Config.AutoMigrate = true` (default) the server adds the two `users` columns on start. To migrate manually, run `esx_death.sql`, which adds them only if missing:

```sql
ALTER TABLE `users` ADD COLUMN `is_dead` TINYINT(1) NOT NULL DEFAULT 0;
ALTER TABLE `users` ADD COLUMN `death_time` BIGINT NULL DEFAULT NULL;
```

The resource runs `SELECT is_dead, death_time FROM users LIMIT 0` at start, so it fails loudly if the columns are missing.

</Step>
<Step>

### server.cfg [#servercfg]

OneSync must be enabled. Start esx_death before esx_ambulancejob:

```bash title="server.cfg"
set onesync on
ensure esx_lib
ensure es_extended
ensure esx_death
ensure esx_ambulancejob
```

</Step>
</Steps>

## Configuration [#configuration]

| Key | Default | Description |
| --- | --- | --- |
| `Config.Debug` | `false` | Print detailed client and server debug logs. |
| `Config.Locale` | `GetConvar('esx:locale', 'en')` | Locale for the death screen and commands. |
| `Config.DeathAnim` | `{ enabled = true, dict = 'misslamar1dead_body', name = 'dead_idle', fadeIn = 10.0, fadeOut = 10.0, flags = 1 \| 2 \| 8, playbackRate = 1.0 }` | Looping animation while dead. |
| `Config.zoom` | `{ min = 1, max = 6, step = 0.5 }` | Zoom limits for the death camera. |
| `Config.MedalClip` | `true` | Trigger a Medal.tv clip on death through `xLib.medal.triggerClip()`. |
| `Config.EarlyRespawnTimer` | `60000 * 1` | Time in ms before respawn becomes available. |
| `Config.BleedoutTimer` | `60000 * 10` | Additional time in ms until the player bleeds out and respawns automatically. |
| `Config.RemoveWeaponsAfterRPDeath` | `true` | Remove all weapons on respawn (ESX inventory only). |
| `Config.RemoveCashAfterRPDeath` | `true` | Remove all cash and black money on respawn. |
| `Config.RemoveItemsAfterRPDeath` | `true` | Clear the inventory on respawn (`ox_inventory:ClearInventory` with ox_inventory). |
| `Config.EarlyRespawnFine` | `false` | Charge a bank fine when respawning before the bleedout ends. |
| `Config.EarlyRespawnFineAmount` | `5000` | Amount of that fine. Respawn is refused if the bank balance is too low. |
| `Config.OxInventory` | `ESX.GetConfig('CustomInventory') == 'ox' or GetResourceState('ox_inventory') == 'started'` | Detected automatically. Switches item and weapon handling to ox_inventory. |
| `Config.RespawnPoints` | 2 points | `{ coords = vector3, heading = number }` list. The closest point is used. |
| `Config.HoldDuration` | `1500` | Ms to hold **E** for respawn. |
| `Config.DistressCooldown` | `60000` | Ms between distress signals. |
| `Config.ShowDeathReason` | `true` | Show "killed by a player" under the buttons for player kills. |
| `Config.ShowKillerName` | `false` | Show the killer's name instead (requires `ShowDeathReason`). |
| `Config.CameraEnabled` | `false` | Use the orbit death camera instead of the normal camera. |
| `Config.AutoMigrate` | `true` | Add missing `users.is_dead` / `users.death_time` columns on start. |

Default respawn points:

```lua title="config.lua"
Config.RespawnPoints = {
    { coords = vector3(341.0, -1397.3, 32.5), heading = 48.5 },     -- Central Los Santos
    { coords = vector3(1836.03, 3670.99, 34.28), heading = 296.06 } -- Sandy Shores
}
```

## Server exports [#server-exports]

### IsDead [#isdead]

```lua
exports.esx_death:IsDead(playerId) --> boolean
```

Returns `true` if the cached state of the player is dead. Returns `false` for players whose state is not loaded yet.

### GetDeathState [#getdeathstate]

```lua
exports.esx_death:GetDeathState(playerId) --> table | nil
```

Returns a snapshot or `nil` when no state is cached:

| Field | Type | Description |
| --- | --- | --- |
| `dead` | `boolean` | Current state. |
| `elapsed` | `number` | Seconds since death. |
| `earlyRemaining` | `number` | Seconds until respawn is allowed. |
| `remaining` | `number` | Seconds until bleedout. |
| `total` | `number` | Total bleedout length in seconds. |
| `distressRemaining` | `number` | Seconds until the next distress signal. |
| `distressSent` | `boolean` | A distress signal was sent during this death. |
| `fine` | `number` | Early respawn fine, `0` when disabled. |
| `killedByPlayer` | `boolean` | Death was caused by another player. |
| `killerServerId` | `number?` | Validated killer server id. |
| `deathCause` | `number\|string?` | Weapon hash or cause name. |

### GetDeadPlayers [#getdeadplayers]

```lua
exports.esx_death:GetDeadPlayers() --> { [playerId] = 'dead' | 'distress' }
```

### GetDeadPlayerLocations [#getdeadplayerlocations]

```lua
exports.esx_death:GetDeadPlayerLocations() --> { [playerId] = { x, y, z } }
```

### Revive [#revive]

```lua
exports.esx_death:Revive(playerId, reason?) --> ok, wasDead
```

| Name | Type | Description |
| --- | --- | --- |
| `playerId` | `number` | Server id of the player. |
| `reason` | `string?` | Passed to `esx_death:stateChanged`. Defaults to `'revive'`. |

Revives in place, keeps the inventory, sets hunger and thirst to 500000 through `esx_status:set` and clears the dead flag in the database. Returns `false, false` if the player was not dead.

```lua title="server/main.lua"
local ok = exports.esx_death:Revive(targetId, 'mymedic')

if ok then
    TriggerClientEvent('esx:showNotification', targetId, 'You have been revived.')
end
```

### SetDead [#setdead]

```lua
exports.esx_death:SetDead(playerId) --> boolean
```

Marks a player as dead in the database and syncs the death screen to that client. This does not kill the ped; use it together with your own logic.

## Client exports [#client-exports]

```lua
exports.esx_death:IsDead() --> boolean
exports.esx_death:GetDeathState() --> { dead, earlyRemaining, remaining, distressRemaining }
```

The server also sets the replicated state bag `isDead`, so `LocalPlayer.state.isDead` (client) and `Player(src).state.isDead` (server) work without exports.

## Events [#events]

### Server events (local) [#server-events-local]

| Event | Arguments | When |
| --- | --- | --- |
| `esx_death:stateChanged` | `playerId`, `dead`, `reason` | State flips. `reason` is `'death'`, `'respawn'`, `'revive'`, `'admin'`, `'txadmin'`, `'restore'`, `'logout'` or a custom revive reason. |
| `esx_death:distress` | `playerId`, `coords` | A dead player sent a distress signal. esx_ambulancejob uses it to alert medics. |

```lua title="server/main.lua"
AddEventHandler('esx_death:stateChanged', function(playerId, dead, reason)
    if dead then
        print(('Player %s died'):format(playerId))
    elseif reason == 'respawn' then
        print(('Player %s respawned at a hospital'):format(playerId))
    end
end)
```

### Client events (local) [#client-events-local]

| Event | Arguments | When |
| --- | --- | --- |
| `esx_death:entered` | none | The death screen opened. |
| `esx_death:recovered` | `'respawn'` or `'revive'` | The player was respawned or revived. |
| `esx_death:requestDistress` | none | Trigger it to send a distress signal (same as pressing **G**). |

On recovery esx_death also fires `esx:onPlayerSpawn` (client and server), `playerSpawned` and `esx_basicneeds:resetStatus`.

### Net events and callbacks (security) [#net-events-and-callbacks-security]

| Name | Type | Notes |
| --- | --- | --- |
| `esx:onPlayerDeath` | net event | Client-triggerable. A client can only mark **itself** dead. Rate limited (3 per 5 s), killer id and cause are sanitized, timestamps and penalties are always set by the server. |
| `esx_ambulancejob:onPlayerDistress` | net event | Client-triggerable. Only works while the sender is dead and respects `Config.DistressCooldown`. |
| `esx_death:respawn` | callback | Refused if not dead, too early or the fine cannot be paid. |
| `esx_death:distress`, `esx_death:getState` | callbacks | Only affect the calling player. |
| `esx_death:sync`, `esx_death:recover`, `esx_ambulancejob:clsearch` | client net events | The client ignores them unless they come from the server. |

## Commands [#commands]

| Command | Group | Arguments | Description |
| --- | --- | --- | --- |
| `/revive` | `admin` | `playerId` | Revive one player. Also works from the server console. |
| `/reviveall` | `admin` | none | Revive every online player. |

The txAdmin "Heal" action (`txAdmin:events:healedPlayer`) revives the player, or everyone when txAdmin heals all.

## Integration tips [#integration-tips]

- Block actions while dead (shops, phones, inventories) with `LocalPlayer.state.isDead` on the client and `exports.esx_death:IsDead(source)` on the server.
- Use `Revive` instead of calling `NetworkResurrectLocalPlayer` yourself; otherwise the database still says dead and the player is restored as dead on the next login.
- To change who gets alerted on distress, listen to `esx_death:distress` in your own dispatch resource.

## Common issues [#common-issues]

- **SQL error about `is_dead` on start**: the `users` table lacks `is_dead`/`death_time` and `Config.AutoMigrate` is off. Run `esx_death.sql`.
- **Two death screens or instant respawn**: another death or revive script is still running. Only esx_death should handle death.
- **Weapons not removed with ox_inventory**: with ox_inventory, weapons are items. Use `Config.RemoveItemsAfterRPDeath` instead of the weapons option.
- **Player respawns dead after relog**: expected when they logged out while dead. Revive them or let the timer run out.

## Related [#related]

- [esx_ambulancejob](/docs/addons/esx_ambulancejob)
- [esx_basicneeds](/docs/addons/esx_basicneeds)
- [Client events](/docs/es_extended/events/client)
- [Commands](/docs/es_extended/commands)
- [OneSync functions](/docs/es_extended/server/onesync)
