# Settings keys

Every key below can be read with `GetSetting(key)` and changed with `SetSetting(key, value)`. Admins change the same keys in game with `/dispatchadmin` (groups in `Config.Admin.groups` or the ACE `koja-dispatcher.admin`). Changes are saved in the `koja_dispatch_settings` table and survive restarts; until something is saved, the defaults come from the config files.

```lua title="server.lua"
local dispatch = exports['koja-dispatcher']
print(dispatch:GetSetting('calls.patience'))            -- 45
local ok, err = dispatch:SetSetting('calls.patience', 60)
```

## Value rules

| Type | `SetSetting` accepts | Notes |
|---|---|---|
| bool | `true` or `false` | Strings like `'true'` are rejected with `invalid`. |
| int | any number | Rounded, then clamped to the range. |
| number | any number | Clamped to the range. The step is only used by the admin slider. |
| enum | one of the listed strings | Exact match, otherwise `invalid`. |

Every change fires the `settingChanged` hook and event. `GetSetting` returns `nil` for an unknown key.

## Core

The core keys are shown on the Modes, Calls, Units, Pay, Features and Integration pages of the admin menu.

| Key | Type | Range or values | Default | Effect |
|---|---|---|---|---|
| `mode.calls` | enum | `generated`, `real`, `mixed` | `mixed` (`Config.Modes.calls`) | Where calls come from. `generated`: only AI callers; player and API calls skip the console. `real`: only players, sensors, API and MDT; no generated calls. |
| `mode.units` | enum | `ai`, `players`, `mixed` | `mixed` | Which units exist. `ai`: no player units. `players`: AI units go `offline`. |
| `mode.noDispatcher` | enum | `reject`, `auto`, `mdt` | `reject` | What the call router does when nobody is on shift. |
| `access.maxDispatchers` | int | 0 to 32 | `0` | Dispatchers on shift at once. `0` = no limit. |
| `access.remote` | bool | | `false` (`Config.Dispatcher.remote`) | Allowed jobs can open the CAD anywhere with `/cad`. |
| `calls.intervalMin` | int | 10 to 600 s | `35` | Shortest gap between generated calls. |
| `calls.intervalMax` | int | 10 to 900 s | `80` | Longest gap between generated calls. |
| `calls.maxRinging` | int | 1 to 12 | `4` | No new generated call while this many ring. |
| `calls.maxActiveIncidents` | int | 1 to 50 | `10` | No new generated call while this many incidents are active. |
| `calls.patience` | int | 15 to 300 s | `45` | How long a generated caller waits before hanging up (missed call). |
| `calls.playerReplyWindow` | int | 30 to 900 s | `180` | How long a player's 911 line stays open for follow-up messages. |
| `calls.perDispatcherSpeedup` | number | 0 to 1, step 0.05 | `0.15` | Calls come this much faster for every extra dispatcher. |
| `units.disableWhenPlayers` | int | 0 to 32 | `3` | AI units of a department go off duty when this many players of it are on duty. `0` = never. |
| `pay.enabled` | bool | | `true` | Pay dispatchers when a shift ends. |
| `pay.perMinute` | int | 0 to 1000 | `6` | Money per minute on shift. |
| `pay.perPoint` | int | 0 to 100 | `2` | Bonus per score point. |
| `pay.minimumMinutes` | int | 0 to 120 | `3` | Shorter shifts are not paid. |
| `pay.maximum` | int | 0 to 1 000 000 | `15000` | Cap per shift. `0` = no cap. |
| `features.cinematic` | bool | | `true` | Camera fly-in when a dispatcher clocks in. |
| `features.aiCallers` | bool | | `false` (`Config.AI.enabled`) | Live AI voice callers (needs the OpenAI / ElevenLabs keys). |
| `features.sensorDowned` | bool | | `true` | A call when a player stays down. |
| `features.sensorShots` | bool | | `true` | A call when someone shoots in the city. |
| `features.sensorCrash` | bool | | `true` | A call after a heavy vehicle crash. |
| `features.showCivilians` | bool | | `true` | Players without a service job appear on the map. |
| `features.showNpcPeds` | bool | | `false` | NPC peds on the map (costs performance). |
| `features.fieldScenes` | bool | | `true` | Field units see props and peds at the scene. |
| `features.fieldWaypoint` | bool | | `true` | GPS waypoint for assigned field units. |
| `mdt.enabled` | bool | | `true` | The koja-mdt integration as a whole. |
| `mdt.pushIncidents` | enum | `off`, `real`, `all` | `real` | Send incidents to the MDT. `real`: from players, API, manual and MDT. `all`: also generated calls. |
| `mdt.pullCalls` | enum | `off`, `calls`, `incidents` | `calls` | MDT calls ring in the CAD (`calls`) or open as incidents (`incidents`). |
| `mdt.syncStatus` | bool | | `true` | Unit status and assignments sync both ways. |
| `mdt.useCallsigns` | bool | | `true` | Player units use their MDT callsign. |
| `api.clientAlerts` | bool | | `false` (`Config.Api.clientAlerts`) | Allow the client export `SendAlert`. |

## Modules

Module keys are registered by the feature files and shown on the Modules page of the admin menu, one group each.

| Key | Type | Range or values | Default | Effect |
|---|---|---|---|---|
| `career.enabled` | bool | | `true` | XP, ranks, simultaneous lines, call gates and supervisor powers. |
| `career.xpMultiplier` | number | 0.1 to 5, step 0.1 | `1.0` | Scales XP from score, incidents, codes and instructions. |
| `career.xpTimeMultiplier` | number | 0 to 5, step 0.1 | `1.0` | Scales XP per minute on shift. |
| `career.xpPenaltyMultiplier` | number | 0 to 5, step 0.1 | `1.0` | Scales XP lost for missed calls and failed incidents. |
| `career.gateCalls` | bool | | `true` | Low ranks cannot answer gated calls (pursuit, hostage, MCI). |
| `career.gateOwnership` | bool | | `true` | Only supervisors can reassign, cancel or reprioritise another dispatcher's incident. |
| `compat.enabled` | bool | | `true` | Accept alerts from other dispatch scripts. |
| `compat.mode` | enum | `call`, `incident` | `call` | Alerts ring at the console (`call`) or go straight to the map (`incident`). |
| `compat.autoAssign` | bool | | `true` | Incidents from alerts get the nearest free units at once. |
| `discord.enabled` | bool | | `true` | Discord logging (webhooks from server convars). |
| `discord.shifts` | bool | | `true` | Log shift start and end. |
| `discord.incidents` | bool | | `true` | Log ended incidents. |
| `discord.calls` | bool | | `true` | Log missed and dropped calls. |
| `discord.admin` | bool | | `true` | Log setting and admin changes. |
| `discord.events` | bool | | `true` | Log pursuits and mass casualty incidents. |
| `discord.minPriority` | int | 1 to 3 | `3` | Lowest incident priority to log. `3` logs all, `1` only priority 1. |
| `dynamics.events` | bool | | `true` | Scenario events during and after calls. |
| `dynamics.stages` | bool | | `true` | Multi-stage incidents. |
| `dynamics.weather` | bool | | `true` | Generated calls weighted by in-game hour and weather. |
| `dynamics.strength` | number | 0 to 2, step 0.1 | `1.0` | How strong the weighting is. `0` off, `2` double. |
| `fleet.enabled` | bool | | `true` | AI unit fatigue and damage. |
| `fleet.fatigueRate` | number | 0 to 30, step 0.5 | `6.0` | Fatigue points an AI unit gains per minute on scene (travel counts about a third). |
| `fleet.restSeconds` | int | 15 to 1800 | `180` | Rest time at the station. |
| `fleet.repairSeconds` | int | 15 to 1800 | `240` | Repair time at the station. |
| `fleet.damageChance` | number | 0 to 1, step 0.05 | `0.25` | Chance a unit on a violent call gets damaged. |
| `hospitals.enabled` | bool | | `true` | Transports fill hospital beds (`Config.HospitalBeds`). |
| `hospitals.turnoverSeconds` | int | 30 to 7200 | `600` | Seconds until an occupied bed frees up. |
| `mdt_status.enabled` | bool | | `true` | Field status codes (keys, `/tencode`, quick menu). Synced to clients. |
| `mdt_status.hud` | bool | | `true` | Status chip on the field unit's HUD. Synced to clients. |
| `mdt_status.backupUnits` | int | 1 to 6 | `2` | Extra units auto-assigned on a backup request. |
| `mdt_status.cooldown` | int | 5 to 600 s | `45` | Per officer, for backup and the panic fallback. |
| `mdt_status.panicFallback` | bool | | `true` | Without koja-mdt, `10-99` creates a priority 1 incident. |
| `mdt_tools.monitor` | bool | | `true` | MDT workspace drawer in the CAD (key `B`). |
| `mdt_tools.boloSource` | enum | `auto`, `mdt`, `internal` | `auto` | Where BOLOs live (also for `CreateBolo`). `auto`: koja-mdt when running, internal otherwise. `mdt`: refuse new BOLOs while the MDT is down. |
| `mdt_tools.alpr` | bool | | `true` | Alert dispatchers on ALPR hits (`ReportPlate`, `koja-mdt:anpr:hit`). |
| `mdt_tools.notifyUnits` | bool | | `true` | Send internal BOLOs to on-duty police units. |
| `patrol.enabled` | bool | | `true` | Patrol zones idle AI units drive in. |
| `patrol.maxZones` | int | 1 to 32 | `8` | Maximum patrol zones. |
| `patrol.speedFactor` | number | 0.2 to 1, step 0.05 | `0.6` | AI speed inside a patrol zone. |
| `phones.enabled` | bool | | `true` | Phone calls and texts (`PhoneCall`, `koja-dispatcher:phone:call`, supported phones) reach the dispatcher. |
| `phones.resource` | enum | `auto`, `lb-phone`, `qs-smartphone-pro`, `gksphone`, `npwd`, `yseries` | `auto` | Phone resource; `auto` takes the first running one in `Config.Phones.order`. |
| `phones.number` | int | 1 to 99999 | `911` | Service number players dial or text. |
| `pursuit.enabled` | bool | | `true` | Pursuit incidents get a moving suspect vehicle. |
| `pursuit.maxActive` | int | 0 to 10 | `2` | Live pursuits at once; more stay normal incidents. |
| `pursuit.loseSeconds` | int | 10 to 300 | `40` | Suspect is lost after this long without visual. |
| `pursuit.spikes` | int | 0 to 10 | `3` | Spike strips per pursuit. |
| `pursuit.roadblocks` | int | 0 to 10 | `2` | Roadblocks per pursuit. |
| `pursuit.spikeChance` | number | 0 to 1, step 0.05 | `0.65` | Chance a spike strip slows the suspect. |
| `pursuit.roadblockChance` | number | 0 to 1, step 0.05 | `0.5` | Chance the suspect stops at a roadblock. |
| `pursuit.speedFactor` | number | 0.5 to 2, step 0.05 | `1.15` | Suspect speed relative to a patrol car. |
| `pursuit.escalationChance` | number | 0 to 1, step 0.05 | `0.3` | Chance a suspect flees in a vehicle when a robbery, assault or shooting escalates. |
| `stats.enabled` | bool | | `true` | Store closed incidents for the heatmap and statistics. |
| `stats.retentionDays` | int | 1 to 365 | `30` | Older history rows are deleted. |
| `training.enabled` | bool | | `true` | Guided tutorial from the top bar. Synced to clients. |
| `training.offerFirstShift` | bool | | `true` | Offer the tutorial to dispatchers with no finished shifts. Synced to clients. |
| `training.practiceCalls` | bool | | `true` | Private practice calls for trainees. Synced to clients. |
| `voice.enabled` | bool | | `true` | Voice channel between dispatcher and player caller. Turning it off ends every voice call. |
| `voice.provider` | enum | `auto`, `pma-voice`, `mumble-voip` | `auto` | Voice resource; `auto` tries pma-voice first. |
| `voice.channelBase` | int | 1000 to 9 000 000 | `91100` | First call channel id; the next 900 ids are used. |
| `voice.endOnDispatch` | bool | | `true` | Off: the caller stays on the line after dispatch until the dispatcher ends it. |
| `voice.allowTransfer` | bool | | `true` | Dispatchers can hand the voice call to a player unit. |

Module defaults come from the config files in `shared/config/` (`career.lua`, `integrations.lua`, `dynamics.lua`, `fleet.lua`, `pursuit.lua`, `stats.lua`, `voice.lua`). The `mdt_status.*`, `mdt_tools.*`, `career.xp*`, `career.gate*` and `training.*` defaults are fixed in code.

## Side effects of a change

| Key | Extra effect |
|---|---|
| `mode.units`, `units.disableWhenPlayers` | Player and AI unit membership is re-applied at once. |
| `mode.calls` | The generated call timer restarts. |
| `mdt.*`, `mdt_tools.*` | The koja-mdt link and BOLO list refresh. |
| `access.remote`, `features.cinematic`, `features.fieldScenes`, `features.fieldWaypoint` and the client module keys | Pushed to every client (`koja-dispatcher:client:sync`). |
| `career.enabled` | Rank state is pushed to dispatchers. |
| `dynamics.weather`, `dynamics.strength` | The weather state is re-broadcast. |
| `voice.enabled = false` | Every running voice call ends (`reason = 'disabled'`). |

## Adding your own setting

Register your own keys with the [`RegisterSetting`](/new/dispatcher/api/server-exports) export from another resource, or with `KOJA.Server.Settings.Register` in a file in `server/custom/` inside koja-dispatcher. Both appear on the Modules page and work with `GetSetting` / `SetSetting`. See [Register your own admin setting](/new/dispatcher/api/examples) for the full recipe and the difference between the two.
