> ## Documentation Index
> Fetch the complete documentation index at: https://docs.shdevelopment.org/llms.txt
> Use this file to discover all available pages before exploring further.

# sh-doctorjob: Doctor & Medical Job System for RedM

> sh-doctorjob adds a full RedM medical job with death handling, treatments, diseases, NPC services, alert dispatch, boss management, medical carts, crafting, storage, payroll, and VORP/RSG support.

The `sh-doctorjob` resource delivers a complete medical profession for RedM servers. It supports VORP and RSG, server-side patient persistence, configurable downed and respawn flows, item-based treatments, disease and injury tracking, doctor dispatch alerts, NPC fallback services, boss management, society ledger integration, clinic storage, medical supply wagons, crafting stations, payroll, and Discord webhook logging.

Version `1.6.1` ships with five clinic locations: Valentine, Rhodes, Strawberry, Blackwater, and Armadillo. The default medical jobs are `doctor`, `doctorR`, `doctorS`, `medic`, and `shaman`.

<Warning>
  If you use the built-in death handler, disable other death/medic systems. For VORP, set `Config.UseDeathHandler = false` in `vorp_core/config/config.lua`. For RSG, remove or stop `rsg_medic` to avoid conflicting downed and revive states.
</Warning>

## Requirements

* RedM server using `fx_version 'cerulean'` and `game 'rdr3'`
* `oxmysql`, started before `sh-doctorjob`
* One supported framework:
  * VORP
  * RSG / RSG Core
* MySQL or MariaDB database access for `sql/install.sql`
* Framework job and item entries for the doctor jobs and medical items you enable
* Optional: `ox_target` for third-eye interactions
* Optional: `sh-society` or `syn_society` for external ledger integration
* Optional: `rsg-inventory` or `vorp_inventory` for framework stash support

The folder/resource should be named `sh-doctorjob`.

## Installation

<Steps>
  <Step title="Install dependencies">
    Install and verify your framework and `oxmysql`. If you use target interactions, install `ox_target`.
  </Step>

  <Step title="Add the resource">
    Copy the `sh-doctorjob` folder into your server resources directory.
  </Step>

  <Step title="Import persistence SQL">
    Run `sql/install.sql` against your database. This creates patient state and internal ledger tables.
  </Step>

  <Step title="Register medical items">
    For VORP, run `sql/items.sql` against the framework `items` table. For RSG, copy the entries from `sql/rsgitems.txt` into your RSG shared item config.
  </Step>

  <Step title="Register jobs">
    Add the medical jobs you want to use, such as `doctor`, `doctorR`, `doctorS`, `medic`, and `shaman`, to your framework job registry with the required grades.
  </Step>

  <Step title="Configure server.cfg">
    Start dependencies before `sh-doctorjob`.

    ```cfg server.cfg theme={null}
    ensure oxmysql
    ensure vorp_core     # or rsg-core
    ensure ox_target     # optional
    ensure sh-society    # optional ledger provider
    ensure sh-doctorjob
    ```
  </Step>

  <Step title="Configure the script">
    Edit the files in `config/` for framework mode, jobs, hospitals, death handling, items, crafting, wagons, storage, webhooks, and integrations.
  </Step>

  <Step title="Test the full flow">
    Assign a test character a doctor job, clock in at a duty station, open `/medui`, down another test character, submit `/alertdoctor`, and confirm treatment, revive, respawn, payroll, and ledger behavior.
  </Step>
</Steps>

## Framework Support

Set framework detection in `config/main.lua`:

```lua config/main.lua theme={null}
Config.Framework = 'auto' -- 'auto', 'vorp', or 'rsg'
```

The framework bridge handles character identity, jobs, grades, inventories, item removal, money, notifications, stashes, and coordinates. Keep `auto` unless your server needs a fixed framework mode.

## Interaction Modes

`sh-doctorjob` can use target interactions, native RedM prompt groups, or 3D text prompts.

```lua config/target.lua theme={null}
Config.Target = {
    Mode = 'target', -- 'target', 'ingame', 'text3d', or 'auto'
    Resource = 'ox_target',
    Fallback = {
        Enabled = true,
        Mode = 'ingame',
    },
}
```

Use `auto` if you want `ox_target` when available and prompt fallback when it is not.

## Supported Jobs

Medical job access is configured in `Config.Jobs`:

```lua config/main.lua theme={null}
Config.Jobs = {
    doctor = true,
    doctorR = true,
    doctorS = true,
    medic = true,
    shaman = true,
}
```

Doctor alerts and shaman alerts are routed separately:

```lua config/main.lua theme={null}
Config.DoctorAlertJobs = {
    doctor = true,
    doctorR = true,
    doctorS = true,
    medic = true,
}

Config.ShamanJob = 'shaman'
Config.ShamanAlertJobs = {
    shaman = true,
}
Config.RequestShamanCommand = 'alertshaman'
```

## Features

<Accordion title="Death, Bleedout, And Respawn">
  The built-in death handler puts players into a downed state, starts a bleedout timer, supports a free-roam downed camera, and can show an optional NUI deathscreen with configurable buttons. Defaults are `Config.Death.BleedoutMs = 600000` and `Config.Death.ReviveWindowMs = 480000`.
</Accordion>

<Accordion title="Patient State, Injuries, And Diseases">
  Patient state is saved by character in `sh_doctor_patients`. The system tracks body-region injuries, bleeding, broken bones, cold exposure, heat stroke, and snake bites. Diseases can auto-heal, progress by stage, apply damage or hydration effects, and be cured through configured treatments.
</Accordion>

<Accordion title="Medical Dashboard">
  Doctors can open the dashboard with `/medui`, at configured medical UI stations, or through the `doctorfieldkit` usable item. The UI shows patient injuries, diseases, and available medical actions.
</Accordion>

<Accordion title="Treatment And Revive Items">
  Items are configured in `Config.Items.Catalog`. Default treatment items include `bandages`, `splints`, `antibiotic`, `antipoison`, and `cool_rag`. Default revive items are `syringe` and `firstaidkit`, restricted to doctor/shaman jobs.
</Accordion>

<Accordion title="Duty System">
  Doctors toggle duty at station points or with `/docduty`. Server validation can require the player to be near a station. On-duty status controls dispatch visibility, restricted actions, payroll, storage access, and NPC fallback checks.
</Accordion>

<Accordion title="Alerts And Shaman Alerts">
  `/alertdoctor` sends a medical alert to on-duty doctor jobs. `/alertshaman` routes to configured shaman jobs. Staff can respond with `/responddoctor` and clear alerts with `/cleardoctoralerts`.
</Accordion>

<Accordion title="NPC Services">
  Static hospital NPC doctors can heal or revive players for a configured price. NPC service can be blocked while doctors are on duty, with an exception for doctor self-service. Optional ledger deposits send service revenue to the clinic ledger.
</Accordion>

<Accordion title="NPC Dispatch Revive">
  `/sendmedic` summons a traveling NPC doctor when `Config.NPCRevive.Enabled = true`. By default this is blocked when any doctor is on duty. The NPC can charge payment, spawn with a horse, approach the patient, play a revive animation, and leave after a delay.
</Accordion>

<Accordion title="Boss Menu, Ledger, And Payroll">
  Managers open `/docboss` or use boss stations to hire, fire, promote, demote, set grades, and withdraw from the clinic ledger. Payroll pays on-duty staff every `Config.Payroll.IntervalMinutes` using per-job, per-grade rates.
</Accordion>

<Accordion title="Clinic Storage">
  Clinic storage points use `Config.DoctorStorages`. Storage can require on-duty status and supports RSG stash IDs, VORP inventory IDs, max weight, slots, job, and minimum grade.
</Accordion>

<Accordion title="Crafting Stations">
  Doctor crafting is configured in `config/crafting.lua`. Default recipes create `bandages`, `splints`, and `firstaidkit`, and can require doctor job, duty state, station proximity, job allowlists, blocked jobs, and minimum grades.
</Accordion>

<Accordion title="Medical Wagons">
  Hospitals can define `wagonStation` spawn points. Default wagon types are `wagondoc01x` with storage and `buggy02` without storage. Wagon stash IDs can be personal per character or shared by wagon type.
</Accordion>

<Accordion title="Discord Logging">
  `Config.Logging` can log revives, treatments, admin commands, boss actions, duty toggles, alerts, and wagon events to per-channel Discord webhooks or a default webhook.
</Accordion>

## Hospitals

Clinic locations are configured in `config/hospitals.lua`. The resource includes these default hospital IDs:

| ID           | Label             | Default job ID | Includes                                          |
| ------------ | ----------------- | -------------- | ------------------------------------------------- |
| `valentine`  | Valentine Clinic  | `doctor`       | Respawn, med UI, crafting, boss, duty, wagon, NPC |
| `rhodes`     | Rhodes Clinic     | `doctor`       | Respawn, med UI, crafting, boss, duty, wagon, NPC |
| `strawberry` | Strawberry Clinic | `doctor`       | Respawn, med UI, crafting, boss, duty, wagon, NPC |
| `blackwater` | Blackwater Clinic | `doctor`       | Respawn, med UI, crafting, boss, duty, wagon, NPC |
| `Armadillo`  | Armadillo Clinic  | `doctor`       | Respawn, med UI, crafting, boss, duty, wagon, NPC |

Example hospital entry:

```lua config/hospitals.lua theme={null}
{
    id = 'valentine',
    label = 'Valentine Clinic',
    jobId = 'doctor',
    showBlip = true,
    blipSprite = -1739686743,
    coords = vector3(-286.40, 809.88, 118.03),
    radius = 20.0,
    respawn = vector4(-286.80, 819.30, 118.03, 144.57),
    meduiStation = vector4(-288.96, 814.65, 118.03, 303.31),
    craftingStation = vector4(-291.06, 814.22, 117.91, 90.71),
    bossStation = vector4(-289.70, 808.47, 118.03, 192.76),
    dutyStation = vector4(-284.00, 808.15, 118.03, 192.76),
    wagonStation = {
        spawn = vector4(-284.52, 793.54, 118.66, 99.13),
        returnDist = 6.0,
    },
    npc = {
        enabled = true,
        model = 'u_m_m_valdoctor_01',
        coords = vector4(-284.32, 811.02, 118.03, 87.87),
        range = 2.0,
        disableWhenDoctorsOnline = true,
        allowDoctorSelfService = true,
        jobs = { 'doctor' },
        healCost = 15,
        reviveCost = 30,
        reviveTime = 15,
    },
}
```

## Commands

| Default command                | Config key                          | Description                                                                |
| ------------------------------ | ----------------------------------- | -------------------------------------------------------------------------- |
| `/medui`                       | `Config.Commands.OpenMedicalUi`     | Open the Medical Dashboard for self or nearest/selected patient            |
| `/alertdoctor`                 | `Config.Commands.RequestDoctorHelp` | Request help from on-duty doctors                                          |
| `/alertshaman`                 | `Config.RequestShamanCommand`       | Request help from configured shaman jobs                                   |
| `/responddoctor <alertId>`     | `Config.Commands.RespondHelp`       | Respond to a medical alert, or the first open alert when no ID is supplied |
| `/cleardoctoralerts <alertId>` | `Config.Commands.ClearHelp`         | Clear a medical alert response                                             |
| `/docboss`                     | `Config.Commands.BossMenu`          | Open the boss management panel                                             |
| `/docduty`                     | `Config.Commands.ToggleDuty`        | Toggle duty state                                                          |
| `/sendmedic`                   | `Config.NPCRevive.Command`          | Request an NPC doctor dispatch                                             |
| `/revive [playerId]`           | `Config.Admin.Commands.Revive`      | Admin revive, self if no ID is supplied in-game                            |
| `/respawn [playerId]`          | `Config.Admin.Commands.Respawn`     | Admin respawn, self if no ID is supplied in-game                           |
| `/heal [playerId]`             | `Config.Admin.Commands.Heal`        | Admin full recovery heal                                                   |

Set a command to `nil` in config to disable it where supported.

## Keybind Defaults

```lua config/main.lua theme={null}
Config.Keys = {
    DutyToggle = { Control = 0x760A9C6F, Label = 'G' },
    CraftingToggle = { Control = 0xF3830D8E, Label = 'J' },
    BossMenu = { Control = 0xF3830D8E, Label = 'J' },
    MedicalUi = { Control = 0x760A9C6F, Label = 'G' },
    NpcHeal = { Control = 0x760A9C6F, Label = 'G' },
    NpcRevive = { Control = 0xF3830D8E, Label = 'J' },
    WagonOpen = { Control = 0x760A9C6F, Label = 'G' },
    Storage = { Control = 0x760A9C6F, Label = 'G' },
}
```

## Items

`Config.Items.RegisterAsUsable = true` lets the script register framework usable item handlers automatically.

| Item             | Class          | Default purpose                                                        |
| ---------------- | -------------- | ---------------------------------------------------------------------- |
| `bandages`       | `treatment`    | Stops bleeding, restores health/core, reduces injuries                 |
| `splints`        | `treatment`    | Applies fracture support and movement limits                           |
| `antibiotic`     | `treatment`    | Cures cold; self-use only by default                                   |
| `antipoison`     | `treatment`    | Cures snake bite; self-use only by default                             |
| `cool_rag`       | `treatment`    | Cures heat stroke; self-use only by default                            |
| `syringe`        | `revive`       | Revives downed players; doctor/shaman jobs                             |
| `firstaidkit`    | `revive`       | Revives downed players; doctor/shaman jobs                             |
| `doctorfieldkit` | dashboard item | Opens the Medical Dashboard when `Config.MedicalUiItem.Enabled = true` |

Medical item use runs a server precheck, optional client animation/progress UI, final server validation, item consumption, and treatment application.

## Death Handling

Core settings live in `config/death.lua`:

```lua config/death.lua theme={null}
Config.Death = {
    Enabled = true,
    BleedoutMs = 600000,
    ReviveWindowMs = 480000,
    RespawnWhenBleedoutEnds = false,
    RespawnCost = 0,
    RespawnRemoveDiseases = true,
    RespawnClearInjuries = true,
    ReviveClearInjuries = true,
    AllowHeadshotRevive = true,
}
```

Enable the optional NUI deathscreen with:

```lua config/death.lua theme={null}
Config.Death.Deathscreen.Enabled = true
```

The deathscreen buttons are configurable and can call built-in actions such as `request_help` and `respawn`, or run custom commands with `executeCommand`.

## Diseases And Injuries

Disease definitions live in `config/diseases.lua`.

| Disease      | Default behavior                                                                             |
| ------------ | -------------------------------------------------------------------------------------------- |
| `bleeding`   | Causes health loss, can auto-heal after 300 seconds, usually treated with `bandages`         |
| `brokenBone` | Causes movement penalties and ragdoll risk, requires splints or medical attention by default |
| `cold`       | Builds from low temperature, water, and rain exposure; supports clothing warmth mitigation   |
| `heatStroke` | Builds from hot temperature exposure and can drain hydration                                 |
| `snakeBite`  | Can trigger from snake bites and does not auto-heal by default                               |

Tracked body regions are `head`, `chest`, `abdomen`, `leftArm`, `rightArm`, `leftLeg`, and `rightLeg`.

## Boss, Ledger, And Society Integration

Boss access is controlled by `Config.Management`:

```lua config/main.lua theme={null}
Config.Management = {
    Enabled = true,
    MinGrade = 3,
    HireGrade = 3,
    CrossDepartment = false,
    RosterScope = 'job', -- 'job' or 'all'
    UnemployedJob = 'unemployed',
    DefaultJob = 'doctor',
}
```

Ledger provider is configured in `config/integration.lua`:

```lua config/integration.lua theme={null}
Config.Integration.Ledger = {
    Provider = 'sh-society', -- 'sh-society', 'syn_society', or 'internal'
}
```

Use `internal` to store balances in `sh_doctor_ledger` and `sh_doctor_ledger_tx`.

## Payroll

Payroll pays on-duty doctors at a fixed interval:

```lua config/main.lua theme={null}
Config.Payroll = {
    Enabled = true,
    IntervalMinutes = 15,
    CurrencyRSG = 'cash',
    CurrencyVORP = 0, -- 0 = cash, 1 = gold
    Jobs = {
        doctor = {
            [0] = 5,
            [1] = 10,
            [2] = 15,
            [3] = 20,
            [4] = 25,
            [5] = 30,
            [6] = 35,
            [7] = 40,
            [8] = 50,
        },
    },
}
```

Add matching entries for every job you want paid.

## Storage

Clinic storage is configured with `Config.DoctorStorage` and `Config.DoctorStorages`.

```lua config/main.lua theme={null}
Config.DoctorStorage = {
    Enabled = true,
    RequireOnDuty = true,
    MaxOpenDistance = 3.0,
    DefaultMaxWeight = 2000000,
    DefaultSlots = 60,
}
```

Each storage point supports `id`, `label`, `job`, `minGrade`, `pedModel`, `pedCoords`, `rsgStashId`, `rsgStashSize`, and `vorpInvId`.

## Crafting

Doctor crafting can require doctor job, duty state, and station proximity:

```lua config/crafting.lua theme={null}
Config.Crafting = {
    Enabled = true,
    RequireDoctorJob = true,
    RequireOnDuty = true,
    RequireNearStation = true,
    StationDistance = 3.0,
}
```

Default recipes create `bandages`, `splints`, and `firstaidkit`. Recipe-level restrictions can use `allowedJobs`, `blockedJobs`, `minGrade`, and `minGradeByJob`.

## Wagons

Medical carts are configured in `config/wagons.lua`:

```lua config/wagons.lua theme={null}
Config.Wagons = {
    OpenRange = 4.0,
    Stash = {
        Prefix = 'doctor_wagon_',
        Label = 'Medical Supply Cart',
        MaxWeight = 2000000,
        Slots = 40,
        Personal = true,
    },
    Types = {
        {
            id = 'wagondoc01x',
            label = 'Medical Supply Wagon',
            model = 'wagondoc01x',
            minGrade = 0,
            storageEnabled = true,
        },
        {
            id = 'buggy02',
            label = 'Medical Buggy',
            model = 'buggy02',
            minGrade = 0,
            storageEnabled = false,
        },
    },
}
```

When `Personal = true`, wagon storage persists per character.

## NPC Services

Static hospital NPCs are configured per hospital. Global NPC settings include:

```lua config/main.lua theme={null}
Config.NpcService = {
    Heal = {
        CureBrokenBoneWhenNoDoctorsOnDuty = false,
    },
    Ledger = {
        EnableDeposits = true,
        DepositPercent = 100,
    },
}
```

NPC dispatch revive is separate:

```lua config/main.lua theme={null}
Config.NPCRevive = {
    Enabled = true,
    Command = 'sendmedic',
    DisableWhenDoctorsOnline = true,
    CooldownMs = 30000,
    RequestTimeoutMs = 90000,
    Payment = {
        Enabled = true,
        Amount = 500,
        MoneyType = 'cash',
    },
}
```

## Admin Access

Admin commands use `Config.Admin`:

```lua config/main.lua theme={null}
Config.Admin = {
    Enabled = true,
    AllowConsole = true,
    Ace = {
        Enabled = true,
        Permission = 'sh.doctorjob.admin',
    },
    CharGroups = { 'admin', 'god', 'superadmin', 'mod' },
    UserGroups = { 'admin', 'god', 'superadmin', 'mod' },
}
```

ACE example:

```cfg server.cfg theme={null}
add_ace group.admin sh.doctorjob.admin allow
add_principal identifier.license:YOUR_LICENSE_IDENTIFIER group.admin
```

## External Revive And Respawn

Use the built-in compatibility events/exports when another resource revives or respawns a player.

Server events:

```lua theme={null}
TriggerEvent('sh-doctorjob:server:externalRevive', targetSource, payload)
TriggerEvent('sh-doctorjob:server:externalRespawn', targetSource, payload)
```

Client events:

```lua theme={null}
TriggerEvent('sh-doctorjob:client:externalReviveSync', payload)
TriggerEvent('sh-doctorjob:client:externalRespawnSync', payload)
```

You can also register third-party alias events in `Config.Integration.ExternalRevive` and `Config.Integration.ExternalRespawn`.

Respawn payloads may include `hospitalId`, `hospital`, or direct `coords`/`spawn` data. If no location is provided, the script falls back to the nearest hospital.

## Exports

Server exports:

```lua theme={null}
exports['sh-doctorjob']:GetPatientState(targetSource)
exports['sh-doctorjob']:IsDoctor(source)
exports['sh-doctorjob']:IsDoctorManager(source)
exports['sh-doctorjob']:IsPlayerOnDuty(source)
exports['sh-doctorjob']:SetPlayerOnDuty(source, true)
exports['sh-doctorjob']:TogglePlayerDuty(source)
exports['sh-doctorjob']:GetDoctorsOnlineSources(true)
exports['sh-doctorjob']:CountDoctorsOnline(true)
exports['sh-doctorjob']:GetOnDutyMedics()
exports['sh-doctorjob']:GetOnDutyMedicCount()
exports['sh-doctorjob']:GetAllMedics()
exports['sh-doctorjob']:GetAllMedicCount()
exports['sh-doctorjob']:ApplyTreatment(doctorSource, targetSource, itemName)
exports['sh-doctorjob']:RevivePlayer(doctorSource, targetSource, reviveItem)
exports['sh-doctorjob']:HandleExternalRevive(targetSource, payload)
exports['sh-doctorjob']:HandleExternalRespawn(targetSource, payload)
exports['sh-doctorjob']:RequestDoctorHelp(source)
exports['sh-doctorjob']:RequestShamanHelp(source)
exports['sh-doctorjob']:OpenMedicalCart(source, cartNetId)
exports['sh-doctorjob']:GetDoctorLedgerBalance(jobId)
```

Client exports:

```lua theme={null}
exports['sh-doctorjob']:HandleExternalRevive(payload)
exports['sh-doctorjob']:HandleExternalRespawn(payload)
```

## Database Tables

`sql/install.sql` creates:

| Table                 | Purpose                             |
| --------------------- | ----------------------------------- |
| `sh_doctor_patients`  | Per-character saved patient state   |
| `sh_doctor_ledger`    | Internal ledger balances by job     |
| `sh_doctor_ledger_tx` | Internal ledger transaction history |

`sql/items.sql` contains VORP item inserts. `sql/rsgitems.txt` contains RSG shared item definitions.

## Localization

Set the active locale in `config/main.lua`:

```lua config/main.lua theme={null}
Config.Locale = 'en' -- en, es, or de
```

Editable language files are included in `locales/en.lua`, `locales/es.lua`, and `locales/de.lua`.

## Discord Webhooks

Configure webhook logging in `Config.Logging`:

```lua config/main.lua theme={null}
Config.Logging = {
    Enabled = true,
    LogFailures = true,
    Username = 'DoctorJob Logs',
    IncludeIdentifiers = true,
    IncludeCoords = true,
    IncludeJobInfo = true,
    Webhooks = {
        Default = '',
        Revive = '',
        Treatment = '',
        Admin = '',
        Boss = '',
        Duty = '',
        Alert = '',
        Wagon = '',
    },
}
```

Leave a channel URL empty to disable that channel or fall back to the default where supported.

## Troubleshooting

| Issue                                    | Check                                                                                                          |
| ---------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
| Resource starts but no persistence works | Confirm `oxmysql` starts first and `sql/install.sql` was imported                                              |
| Medical items do nothing                 | Confirm items exist in your framework, are usable, and `Config.Items.RegisterAsUsable = true`                  |
| Downed/revive state behaves strangely    | Disable other death handlers such as VORP death handling or `rsg_medic`                                        |
| Doctors do not receive alerts            | Confirm the doctor is on duty and their job is in `Config.DoctorAlertJobs` or `Config.ShamanAlertJobs`         |
| NPC heal/revive is unavailable           | Check `disableWhenDoctorsOnline`, `Config.NPCRevive.DisableWhenDoctorsOnline`, cooldowns, and payment settings |
| Boss menu is denied                      | Check job, grade, `Config.Management.MinGrade`, station distance, and duty requirements                        |
| Storage is denied                        | Check `Config.DoctorStorage.RequireOnDuty`, storage job, min grade, and distance                               |
| Payroll does not run                     | Confirm `Config.Payroll.Enabled = true`, the player is on duty, and their job/grade has a salary entry         |
| RSG stash does not open                  | Confirm `rsg-inventory` is installed and the configured stash IDs are valid                                    |
| VORP stash or items fail                 | Confirm `vorp_inventory` integration and imported VORP item rows                                               |
| Target options do not appear             | Confirm `ox_target` is running, or set `Config.Target.Mode = 'auto'` with fallback enabled                     |
