# Installation

:::::steps
### Install the dependencies

koja-dispatcher needs these resources started before it:

- `oxmysql`
- your framework (`es_extended`, `qb-core` or `qbx_core`)
- [`koja-lib`](https://github.com/Koja-Scripts/koja-lib)

The manifest declares `oxmysql` and `koja-lib` as dependencies, so the resource does not start without them.

### Add the resource

Use the release zip. It already contains the built interface in `web/build`, the caller voices and the scanned 3D map data, so there is nothing to build. Put the `koja-dispatcher` folder in your resources directory.

:::hint{type="warning" title="Keep the folder name"}
The folder must be called `koja-dispatcher`. Other resources call `exports['koja-dispatcher']`, and the dispatch script shims in `compat/` depend on that name.
:::

If you work from the source code instead of a release, build the interface once:

```bash
cd web
npm install
npm run build
```

Own code that should run inside the resource goes into `server/custom/*.lua` and `client/custom/*.lua`. The manifest loads those folders after the built-in files, so your files survive updates as long as you keep the folders.

### Edit server.cfg

```cfg title="server.cfg"
ensure oxmysql
ensure es_extended   # or qb-core / qbx_core
ensure koja-lib
ensure koja-mdt      # optional, before koja-dispatcher
ensure pma-voice     # optional, for voice 911 calls
ensure koja-dispatcher
```

Optional resources (koja-mdt, a voice resource, a phone) can start before or after koja-dispatcher; it picks them up when they start. Starting them first avoids a short gap after a restart.

### Database

Nothing to import. On start the resource runs `CREATE TABLE IF NOT EXISTS` for every table it uses, so restarts are safe. The core tables (`koja_dispatch_shifts`, `koja_dispatch_callsigns`, `koja_dispatch_settings`, `koja_dispatch_avatars`) are created first; the modules create their own tables (career, history, scenarios, fleet editor, BOLOs, patrol zones). See [Database](/new/dispatcher/database).

If a statement fails, the console prints `migration failed` with the SQL error, and the admin settings fall back to the `config.lua` defaults until the database works.

### Give yourself admin access

The admin menu opens with `/dispatchadmin` for players in one of the framework groups in `Config.Admin.groups` (`admin`, `god`, `superadmin`) or with the ACE `koja-dispatcher.admin`:

```cfg title="server.cfg"
add_ace group.admin koja-dispatcher.admin allow
add_principal identifier.license:xxxxxxxxxxxxxxxx group.admin
```

### First-time setup in /dispatchadmin

Join the server and open `/dispatchadmin`. Changes are saved in the database and apply at once.

1. **Access & jobs.** Pick which jobs may work as dispatcher and from which grade. The defaults are `dispatch` (grade 0), `police` (grade 4) and `ambulance` (grade 4). If your server has no dispatch job, create one with **Create job** (ESX and QBCore) and hire players into it with **Hire**.
2. **Stations.** Walk to the desk where dispatchers should sit or stand and press **Place a station**. A see-through copy of your character plays the pose, and you line it up with a 3D gizmo. The default station is at Mission Row.
3. **Modes.** Choose where calls come from (generated, real or mixed), which units are dispatched (AI, players or mixed) and what happens to a player's `/911` when nobody is on shift (reject, auto-dispatch or send to koja-mdt).

Details for every page are in [Admin menu](/new/dispatcher/admin-menu). Until something is saved in the menu, `Config.Consoles`, `Config.Dispatcher.jobs` and `Config.Modes` are used.

### Run dispatchcheck

Type `dispatchcheck` in the server console. It prints `[ OK ]` or `[FAIL]` with a hint for each check:

| Check | Fails when |
|---|---|
| koja-lib started, oxmysql started | The resource is not started. Fix the `ensure` order. |
| framework detected | koja-lib did not detect ESX or QBCore. |
| database tables | The migration failed. See the error above it. |
| dispatcher job "name" exists | A job in the dispatcher access list does not exist in the framework. |
| AI fleet | `Config.AiUnits.enabled` is on but no AI unit spawned. Check the fleet stations and unit types. |
| locations | No locations for generated calls. |
| web UI built | `web/build/index.html` is missing. |
| AI callers | Only when `Config.AI.enabled`: the OpenAI key, and the ElevenLabs key when `tts = 'elevenlabs'`. |
| settings loaded from the database | The database was not ready, so the config defaults are used. |
| stations | There is no station and the remote CAD is off. |
| koja-mdt integration | koja-mdt is installed and the integration is on, but koja-mdt is not started. |

It also prints the current modes. To ring a test call while you are on shift, run `dispatchtest` (random scenario) or `dispatchtest store_robbery`.

### Optional pieces

::::tabs
:::tab{title="koja-mdt"}
Install and start koja-mdt. Nothing has to be changed in the MDT. The link turns on by itself and is configured on the **Integrations** page of the admin menu. See [Integrations](/new/dispatcher/integrations).
:::
:::tab{title="Voice"}
Start `pma-voice` or `mumble-voip`. With pma-voice, call channels must stay enabled (the pma-voice convar `voice_enableCalls`, which is on by default). koja-dispatcher puts the caller and the dispatcher on call channel `91100` and up; keep that range away from your phone script. See [Integrations](/new/dispatcher/integrations).
:::
:::tab{title="Phones"}
lb-phone and npwd work without changes: calling or texting the service number (`911` by default) rings at the dispatcher, on lb-phone also the police, ambulance and fire company apps. Any other phone can use the generic event or export. See [Integrations](/new/dispatcher/integrations).
:::
:::tab{title="Dispatch script shims"}
If other scripts call exports of ps-dispatch, cd_dispatch, core_dispatch or qs-dispatch, stop the original resource, copy the folder from `koja-dispatcher/compat/<name>` into your resources (keep the folder name) and `ensure <name>` after `koja-dispatcher`. See [Integrations](/new/dispatcher/integrations).
:::
:::tab{title="Discord"}
Add webhook URLs as server convars with `set`:

```cfg title="server.cfg"
set koja_dispatcher_webhook_shifts "https://discord.com/api/webhooks/..."
set koja_dispatcher_webhook_incidents "https://discord.com/api/webhooks/..."
set koja_dispatcher_webhook_admin "https://discord.com/api/webhooks/..."
set koja_dispatcher_webhook_default "https://discord.com/api/webhooks/..."
```

See [Integrations](/new/dispatcher/integrations).
:::
:::tab{title="AI callers"}
Live AI callers are off by default and cost money per request. Put your keys in `server.cfg` with `set`, never `setr` and never in a config file:

```cfg title="server.cfg"
set koja_dispatch_openai_key "sk-..."
set koja_dispatch_elevenlabs_key "..."
```

Then set `Config.AI.enabled = true` in `shared/config.lua` (or turn on **AI callers** on the Features page of the admin menu) and restart. Without the OpenAI key AI callers stay off and the scripted calls are used. `dispatchcheck` reports whether the keys were found. Options are in [Configuration](/new/dispatcher/configuration).

**Privacy and cost.** With AI callers on, what the dispatcher types and says into the microphone during a call is sent to OpenAI, and caller lines are sent to ElevenLabs or OpenAI for speech. Every AI reply is a paid request. Tell your players, and set usage limits in both dashboards.
:::
::::

### Restart and read the startup banner

A few seconds after start the console prints a banner. The values below are examples; yours show your server:

```text
  Version v1.1.0 · Framework ESX · koja-lib 1.x · Locale en
  Database connected (MariaDB 10.11) · Settings saved
  Modes calls mixed · units mixed · no dispatcher reject
  Stations 1 · Dispatcher jobs 3 · Remote CAD off · Admin /dispatchadmin
  AI fleet 19 · Scenarios 169 · Voices 20 · AI callers off
  Roads 4664 junctions · 6906 roads · 3D terrain scanned
  Integrations mdt koja-mdt · target ox_target
```

| Line | What to look for |
|---|---|
| Database | `connected` with the server version. `not ready` or `unreachable` means oxmysql is not working. |
| Settings | `saved` when values were loaded from the database, `config defaults` otherwise. |
| Modes, Stations, Dispatcher jobs | The current admin menu values. |
| AI fleet | AI unit count and how many are off duty. |
| Voices | Number of recorded caller voices. `not built` means `web/build/voices` is missing. |
| AI callers | `off`, `on`, or `on, no OpenAI key`. |
| Roads | Road graph size. `straight lines` means `data/roads.json` was not loaded and AI units drive in a straight line. |
| 3D terrain | `scanned` with the shipped heightmap. `flat` means `web/build/terrain/height.json` is missing. See [Troubleshooting](/new/dispatcher/troubleshooting). |
| Integrations | koja-mdt state and the target resource found, `none` when there is none. |

After the banner the version check runs through koja-lib (it needs koja-lib started and waits up to 20 seconds for its report):

| Message | Meaning |
|---|---|
| `Up to date` | You run the latest release. |
| `Update available` | A newer version exists. Up to three changelog lines and the download link follow. |
| `CRITICAL UPDATE REQUIRED` | Your version is below the minimum supported version. |
| `Development build` | Your version is newer than the latest release. |
| `Version check: no release information` | The version service has nothing for this version yet. |

Turn the check off with `Config.VersionCheck.enabled = false`.
:::::

## First-run checklist

- [ ] Server starts without `migration failed` in the console.
- [ ] `koja-lib`, `oxmysql` and your framework start before `koja-dispatcher`.
- [ ] `dispatchcheck` shows no `[FAIL]` line.
- [ ] You can open `/dispatchadmin`.
- [ ] At least one job is allowed to dispatch, and you have it at the minimum grade.
- [ ] A station is placed where you want it, or the remote CAD (`/cad`) is on.
- [ ] Modes match your server: on a small server keep units on `mixed` or `ai`.
- [ ] Walk up to the station and start a shift. The intro plays and the CAD opens.
- [ ] Press Space to answer a call, ask for the location, dispatch it and send a unit.
- [ ] Set `Config.Locale` to your language. See [Translations](/new/dispatcher/translations).
- [ ] With a police job on duty, check that you appear as a unit in the CAD.
