Integrations
Every integration below is optional and switches itself on when the other resource runs. They are configured on the Integrations and Modules pages of /dispatchadmin.
koja-mdt
Works automatically when koja-mdt is started and mdt.enabled is on. Nothing has to be changed in the MDT. The Integrations page shows whether koja-mdt is not installed, running or installed but not started. The resource name is Config.Mdt.resource.
Two-way sync
| Direction | What happens | Setting |
|---|---|---|
| Dispatch to MDT | Incidents appear in the MDT call list with code 911 (Config.Mdt.code), category, note, street, priority and the needed department types. They close in the MDT when they end here. | mdt.pushIncidents: off, real (from players, API, manual, sensors and the MDT), all (also generated calls) |
| MDT to dispatch | MDT calls (911 from the MDT, panic, ALPR, CreateDispatch from other scripts) ring at the console or become incidents. Only while a dispatcher is on shift; otherwise they stay MDT-only. Closing them in the MDT resolves them here. | mdt.pullCalls: off, calls, incidents |
| Assignments | Assigning a player unit attaches them to the MDT call; attaching or detaching in the MDT assigns or releases them here. | mdt.syncStatus |
| Unit status | Status changes sync both ways (available, busy, break, en route, on scene). | mdt.syncStatus |
| Duty | Player units follow MDT duty: an officer off duty in the MDT is not a unit in the CAD. | mdt.enabled |
| Callsigns | Player units show their MDT callsign. | mdt.useCallsigns |
| No dispatcher | mode.noDispatcher = 'mdt' sends /911, phone calls and CreateCall straight to the MDT. | mode.noDispatcher |
10-codes
Field units set their status with the field status codes (key mappings, /tencode, quick menu). With koja-mdt the status goes to the MDT too, backup also creates a 10-78 MDT call with the officer attached, and 10-99 calls the MDT panic. Officers who are on duty in the MDT can use the codes even when they are not a CAD unit. Without koja-mdt, 10-99 creates a priority 1 incident here (mdt_status.panicFallback). See Commands and keybinds.
BOLO
BOLOs are created in the CAD's MDT workspace (key B). Other resources can create and close them with the server exports CreateBolo and CloseBolo (see Server exports); they follow the same source rules.
mdt_tools.boloSource | Behaviour |
|---|---|
auto | koja-mdt when it runs, the internal list otherwise. |
mdt | Always koja-mdt. New BOLOs are refused while it is down. |
internal | Only the internal list in koja_dispatch_bolos. |
With koja-mdt, BOLOs are created and listed in the MDT, so MDT and CAD share one list; location, colour and the linked incident are kept in koja_dispatch_bolos. Internal BOLOs are sent to on-duty police field units as a notification (mdt_tools.notifyUnits). An ALPR hit in koja-mdt on a BOLO plate alerts the dispatchers (mdt_tools.alpr).
MDT workspace
With koja-mdt running the workspace also has person search (flags, warrants, record, licenses), plate lookup with stolen and wanted flags (Config.MdtTools.flags), call notes on the linked MDT call, bulletins, mail and the list of officers on duty. Without koja-mdt only the internal BOLO list is available. The drawer is switched with mdt_tools.monitor.
Settings
| Key | Default | |
|---|---|---|
mdt.enabled | on | The whole integration. |
mdt.pushIncidents | real | Incidents sent to the MDT. |
mdt.pullCalls | calls | MDT calls taken by the CAD. |
mdt.syncStatus | on | Status and assignments. |
mdt.useCallsigns | on | MDT callsigns. |
mdt_status.* | Field codes, HUD chip, backup units, cooldown, panic fallback. | |
mdt_tools.* | Workspace, BOLO source, ALPR alerts, unit notifications. |
Voice
When a dispatcher answers a 911 call from a real player (/911 or a phone call), the caller and the dispatcher are put on a private call channel. Without a voice resource, or with voice.enabled off, the call stays text. Generated callers are never on voice.
| Resource | How |
|---|---|
| pma-voice | exports['pma-voice']:setPlayerCall(source, channel). Call channels must be enabled in pma-voice (voice_enableCalls, on by default). |
| mumble-voip | SetCallChannel on the clients. |
voice.provider = 'auto' uses pma-voice first, then mumble-voip. Channels are voice.channelBase (default 91100) plus 0 to 899 (Config.Voice.channelRange); keep that range away from your phone script's call channels.
In the CAD the dispatcher can mute, hold, transfer the voice call to an on-duty player unit (voice.allowTransfer) and end it. With voice.endOnDispatch on the voice call ends when the call is dispatched; off, the caller stays on the line until the dispatcher ends it. Callers and units leave with /hangup911. Developers can open and close voice with the exports ConnectVoice, DisconnectVoice and GetVoiceSession (see Server exports).
Phones
Calls and texts to the service number reach the dispatcher like /911: the same modes, fallbacks, follow-up messages and replies. With a supported phone, dispatcher replies are sent back as messages from the service number for 20 minutes (Config.Phones.threadMinutes); otherwise they arrive as normal notifications.
| Setting | Default | |
|---|---|---|
phones.enabled | on | Phone 911, including the generic entry points. |
phones.resource | auto | auto uses the first running phone in Config.Phones.order, or force one. |
phones.number | 911 | The number players dial or text. |
| Phone | Status | In | Replies |
|---|---|---|---|
| lb-phone | Built-in adapter, verified | A phone call to the service number or to a company in Config.Phones.companies (police, sheriff, ambulance, ems, fire, lspd, bcso, safd); a company app message; a text to the service number. The phone call is ended and a text thread opens. | Messages from the service number |
| npwd | Built-in adapter, verified | A phone call or text to the service number (exports.npwd:onCall / onMessage). The call gets an automatic reply and ends. | Messages from the service number |
| qs-smartphone-pro | Built-in adapter, not verified in game | The job alert events qs-smartphone:server:sendJobAlert and qs-smartphone-pro:server:sendJobAlert. | Messages through sendNewMessageFromApp |
| gksphone | No adapter; use the generic entry points | Notifications | |
| yseries | No adapter; use the generic entry points | Notifications |
Generic entry points for any phone or app. They only need phones.enabled; no supported phone has to run. Put one of them in the phone's emergency number or service app config:
TriggerServerEvent('koja-dispatcher:phone:call', 'text of the call')exports['koja-dispatcher']:PhoneCall(source, 'text of the call')Dispatch script compatibility
Many scripts (robberies, drug sales, car theft) send alerts to a dispatch script. koja-dispatcher understands the alerts of the common ones, so those scripts work without changes. Alerts are mapped to a category, a priority (1-3) and needs (jobs containing police or sheriff, ambulance or ems, fire pick the departments; otherwise defaults per category).
| Setting | Default | |
|---|---|---|
compat.enabled | on | Accept the alerts. |
compat.mode | call | call rings at the console through the normal router (so modes and fallbacks apply). incident opens an incident straight away. |
compat.autoAssign | on | Incidents only: send the nearest free units. |
The same alert at the same spot within 20 seconds is dropped (Config.Compat.dedupeSeconds), players are rate limited (Config.Compat.rate), and positions more than 600 m from the sending player are moved to the player (Config.Compat.maxDistance).
Events
These events are handled only while the original resource is not running (a shim counts as not running), so nothing is doubled if you keep the original script.
| Script | Event |
|---|---|
| cd_dispatch | cd_dispatch:AddNotification |
| ps-dispatch | ps-dispatch:server:notify |
| rcore_dispatch | rcore_dispatch:server:sendAlert |
| qs-dispatch | qs-dispatch:server:CreateDispatchCall |
| core_dispatch | core_dispatch:addCall |
| linden_outlawalert | wf-alerts:svNotify, wf-alerts:svNotify911 |
Client-side triggers of ps-dispatch:client:notify, cd_dispatch:AddNotification, rcore_dispatch:server:sendAlert and qs-dispatch:server:CreateDispatchCall are forwarded too.
Shim resources
Scripts that call exports of a dispatch script need that resource name to exist. The release contains shims in koja-dispatcher/compat/ that provide the original name and forward to koja-dispatcher:
| Shim | Exports |
|---|---|
ps-dispatch | CustomAlert, the preset alerts (Shooting, StoreRobbery, FleecaBankRobbery, OfficerDown, OfficerBackup, VehicleTheft, CarJacking, DrugSale, Fight, InjuredPerson, SpeedingVehicle...), SendTargetedAlert, GetVehicleData (client); CustomAlert (server) |
cd_dispatch | GetPlayerInfo, GetVehicleData (client) |
core_dispatch | sendAlert, addCall (client and server) |
qs-dispatch | getSkinData, GetPlayerInfo (client) |
Install a shim:
- Stop and remove the original dispatch resource.
- Copy the folder from
koja-dispatcher/compat/<name>into your resources. Keep the folder name (for exampleps-dispatch). - Add
ensure <name>afterensure koja-dispatcher.
Other scripts can also send any of these formats with exports['koja-dispatcher']:CompatAlert(name, data).
Discord logs
Webhook URLs are read only from server convars, at send time. They are never sent to clients or printed. Use set, not setr. A URL must start with https://.
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/..."A channel without its own URL uses default. With no URL at all nothing is sent.
| Channel | What is logged | Setting |
|---|---|---|
shifts | Shift started (dispatcher, station) and ended (duration, calls, resolved, failed, missed, score, rating, pay). | discord.shifts |
incidents | Ended incidents: category, priority, outcome, location, response time, duration, dispatcher, units. Only priorities up to discord.minPriority. | discord.incidents |
incidents | Missed calls (caller, location, how long it rang) and dropped calls (and who hung up). | discord.calls |
incidents | Pursuit started and ended, mass casualty incident started and ended. | discord.events |
admin | Settings changed (key, value, previous), stations, jobs and hires. | discord.admin |
Embeds are queued and sent in batches of up to 10 every 2 seconds per webhook (Config.Discord.interval); a rate limit answer from Discord pauses that webhook. Other resources can post through the same queue with exports['koja-dispatcher']:DiscordLog(channel, title, description, fields, color).
Own code and other resources
- Inside the resource: put your own Lua files in
server/custom/andclient/custom/. The manifest loadsserver/custom/*.luaandclient/custom/*.luaafter all built-in files, so you can useKOJA.Server.*,Configand the hooks there. Keep these folders when you update. - From other resources: use the exports, hooks and events in the Developer API. Before hooks (
callCreate,incidentCreate,unitAssign,shiftStart) can returnfalse, 'reason'; the reason is passed on to the caller instead of a genericcancelled. See Hooks. - Own settings:
exports['koja-dispatcher']:RegisterSetting(key, kind, default, options)adds a setting that is saved and edited in the admin menu's Modules page like the built-in ones. See Server exports.
Framework bridge
koja-dispatcher talks to the framework only through koja-lib, so ESX, QBCore and Qbox work the same. The calls are collected in server/bridge.lua (not escrowed) if you need to change one:
| Function | Used for |
|---|---|
Bridge.Job | Job name, grade and duty of a player (koja-lib GetJobInfo). |
Bridge.Department | Which department a player belongs to, from Config.Departments[].jobs, and only while on duty. |
Bridge.CanDispatch | Job and minimum grade from the allowed list. Also decides who sees the station prompt. |
Bridge.SetDuty | Framework duty on and off with the shift (Config.Dispatcher.setDuty). |
Bridge.Pay | Shift pay into Config.Pay.account. |
Bridge.Notify | Notifications through koja-lib. |
Bridge.Jobs, Bridge.SetJob | The job list and hiring in the admin menu. |
Bridge.CreateJob | Creating a job from the admin menu. |
Creating a job
Create job in the admin menu works on ESX and QBCore:
- ESX:
ESX.CreateJob(name, label, grades)with each grade's name, label and salary. - QBCore:
exports['qb-core']:AddJob(name, job)withdefaultDuty = trueand the highest grade as boss.
The new job is added to the jobs allowed to dispatch with the minimum grade you picked. On other frameworks the menu says creating jobs is not supported; add the job in your framework and allow it on the Access & jobs page.
Target
The station start uses koja-lib's target bridge (ox_target, koja-interactions or qb-target). Without a target resource players get a text prompt and press E. Either way, only players whose job may dispatch see it. The startup banner shows which target was found.