Koja Scripts

Integrations

How koja-dispatcher works with koja-mdt, pma-voice and mumble-voip, phone resources, other dispatch scripts, Discord webhooks and ESX or QBCore through koja-lib.

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

DirectionWhat happensSetting
Dispatch to MDTIncidents 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 dispatchMDT 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
AssignmentsAssigning a player unit attaches them to the MDT call; attaching or detaching in the MDT assigns or releases them here.mdt.syncStatus
Unit statusStatus changes sync both ways (available, busy, break, en route, on scene).mdt.syncStatus
DutyPlayer units follow MDT duty: an officer off duty in the MDT is not a unit in the CAD.mdt.enabled
CallsignsPlayer units show their MDT callsign.mdt.useCallsigns
No dispatchermode.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.boloSourceBehaviour
autokoja-mdt when it runs, the internal list otherwise.
mdtAlways koja-mdt. New BOLOs are refused while it is down.
internalOnly 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

KeyDefault
mdt.enabledonThe whole integration.
mdt.pushIncidentsrealIncidents sent to the MDT.
mdt.pullCallscallsMDT calls taken by the CAD.
mdt.syncStatusonStatus and assignments.
mdt.useCallsignsonMDT 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.

ResourceHow
pma-voiceexports['pma-voice']:setPlayerCall(source, channel). Call channels must be enabled in pma-voice (voice_enableCalls, on by default).
mumble-voipSetCallChannel 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.

SettingDefault
phones.enabledonPhone 911, including the generic entry points.
phones.resourceautoauto uses the first running phone in Config.Phones.order, or force one.
phones.number911The number players dial or text.
PhoneStatusInReplies
lb-phoneBuilt-in adapter, verifiedA 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
npwdBuilt-in adapter, verifiedA 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-proBuilt-in adapter, not verified in gameThe job alert events qs-smartphone:server:sendJobAlert and qs-smartphone-pro:server:sendJobAlert.Messages through sendNewMessageFromApp
gksphoneNo adapter; use the generic entry pointsNotifications
yseriesNo adapter; use the generic entry pointsNotifications

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:

client side
TriggerServerEvent('koja-dispatcher:phone:call', 'text of the call')
server side
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).

SettingDefault
compat.enabledonAccept the alerts.
compat.modecallcall rings at the console through the normal router (so modes and fallbacks apply). incident opens an incident straight away.
compat.autoAssignonIncidents 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.

ScriptEvent
cd_dispatchcd_dispatch:AddNotification
ps-dispatchps-dispatch:server:notify
rcore_dispatchrcore_dispatch:server:sendAlert
qs-dispatchqs-dispatch:server:CreateDispatchCall
core_dispatchcore_dispatch:addCall
linden_outlawalertwf-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:

ShimExports
ps-dispatchCustomAlert, the preset alerts (Shooting, StoreRobbery, FleecaBankRobbery, OfficerDown, OfficerBackup, VehicleTheft, CarJacking, DrugSale, Fight, InjuredPerson, SpeedingVehicle...), SendTargetedAlert, GetVehicleData (client); CustomAlert (server)
cd_dispatchGetPlayerInfo, GetVehicleData (client)
core_dispatchsendAlert, addCall (client and server)
qs-dispatchgetSkinData, GetPlayerInfo (client)

Install a shim:

  1. Stop and remove the original dispatch resource.
  2. Copy the folder from koja-dispatcher/compat/<name> into your resources. Keep the folder name (for example ps-dispatch).
  3. Add ensure <name> after ensure 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://.

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/..."

A channel without its own URL uses default. With no URL at all nothing is sent.

ChannelWhat is loggedSetting
shiftsShift started (dispatcher, station) and ended (duration, calls, resolved, failed, missed, score, rating, pay).discord.shifts
incidentsEnded incidents: category, priority, outcome, location, response time, duration, dispatcher, units. Only priorities up to discord.minPriority.discord.incidents
incidentsMissed calls (caller, location, how long it rang) and dropped calls (and who hung up).discord.calls
incidentsPursuit started and ended, mass casualty incident started and ended.discord.events
adminSettings 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/ and client/custom/. The manifest loads server/custom/*.lua and client/custom/*.lua after all built-in files, so you can use KOJA.Server.*, Config and 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 return false, 'reason'; the reason is passed on to the caller instead of a generic cancelled. 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:

FunctionUsed for
Bridge.JobJob name, grade and duty of a player (koja-lib GetJobInfo).
Bridge.DepartmentWhich department a player belongs to, from Config.Departments[].jobs, and only while on duty.
Bridge.CanDispatchJob and minimum grade from the allowed list. Also decides who sees the station prompt.
Bridge.SetDutyFramework duty on and off with the shift (Config.Dispatcher.setDuty).
Bridge.PayShift pay into Config.Pay.account.
Bridge.NotifyNotifications through koja-lib.
Bridge.Jobs, Bridge.SetJobThe job list and hiring in the admin menu.
Bridge.CreateJobCreating 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) with defaultDuty = true and 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.