Koja Scripts

Client exports

koja-mdt client exports for opening the tablet, reading the local officer state, dispatch, duty and bodycam control.

Client exports

Client exports are called from client scripts:

client.lua
local mdt = exports['koja-mdt']

The client never gets more power than the player already has in the tablet. Exports that change something go through the same server actions as the tablet, so the player's permissions, duty rules and rate limits apply.

Tablet

ExportReturnsNotes
OpenMdt()Asks the server to open the tablet. Access, duty (Config.Open.allowOffDuty), tablet item and the mdt:open hook apply.
OpenMdtPage(page, id?, params?)Opens straight to a page, or navigates there when the tablet is already open.
CloseMdt()
ToggleMdt()booleanSame as the open key: closes when open, otherwise checks ClientIntegrations.CanOpen() (pause menu, death, vehicle-only) and asks the server. Returns true when an open was requested.
IsOpen()boolean
GetStreet(coords?)stringStreet, crossing and zone at coords (default: the player).

page is a tablet route: dashboard, dispatch, map, citizens, vehicles, alpr, weapons, incidents, citations, warrants, custody, bolos, evidence, charges, court, roster, logs, settings, mail, divisions, certifications, forms, reviews, profile, cameras, bodycams, calendar, patients, treatments, hospital, bills, docket, judicial, expungements or bar. id opens a record on that page, params are passed to the page.

client.lua
RegisterCommand('lookup', function(_, args)
    exports['koja-mdt']:OpenMdtPage('citizens', args[1])
end)

Local officer

The client caches the local unit and keeps it updated from live pushes. Values are nil when the player is not an MDT member.

ExportReturnsNotes
IsOnDuty()boolean
GetMyDepartment()departmentId, { id, label, short, type, color }
GetMyOfficer()officerCopy of the cached unit: source, identifier, name, callsign, badge, department, rank, gradeLabel, status, onDuty, call, image.
GetMyCallsign()string
GetMyStatus()status, callId
SetMyStatus(status)true or nil, errorCodeYields. status must be one of the department's statuses. Errors invalid, not_in_game, hook reason.
SetDuty(state)onDuty or nil, errorCodeYields. Clocks in (true) or out (false), with the same rules as the tablet toggle and the officer:duty hook.
ToggleDuty()onDuty or nil, errorCodeYields.
client.lua
CreateThread(function()
    local onDuty, err = exports['koja-mdt']:SetDuty(true)
    if onDuty then exports['koja-mdt']:SetMyStatus('available') end
end)

Dispatch

ExportReturnsNotes
SendDispatch(data, cb?)booleanSee below.
SendPanic()Same as the panic key (law enforcement only, server cooldown).
GetActiveCalls()call[] or nil, errorCodeYields. Active calls the player's department can see. Needs dispatch.view.
AttachToCall(callId)call or nil, errorCodeYields. Errors not_found, not_in_game, cancelled.
DetachFromCall(callId)call or nil, errorCodeYields.
SetCallWaypoint(callId)true or nil, errorCodeYields. Sets the GPS to the call.
GetQuickDispatchCalls()call[]The calls currently on the quick-dispatch cards.
ShowQuickDispatch(visible)booleanShows or hides the quick-dispatch cards. false when quick dispatch is disabled.

SendDispatch(data, cb?)

The client never creates calls freely. Two validated server paths are used:

  • Citizen path (default): { code, message, anonymous }. code must be a public command (911, 311) or a code registered with public = true (RegisterDispatchCode). The server takes the player's real position, applies the rate limit and Config.Dispatch.publicCooldown, and notifies the player. The phone number comes from ClientIntegrations.GetPhoneNumber().
  • Officer path: { asOfficer = true, code, title, message, priority }. Runs the dispatch:create tablet action at the player's position, so it needs dispatch.manage. cb(ok, callOrError) runs when the server answers.
client.lua
exports['koja-mdt']:SendDispatch({ code = '911', message = 'Shots fired near the pier' })

exports['koja-mdt']:SendDispatch({ asOfficer = true, code = '10-80', title = 'Pursuit', message = 'Northbound on Route 1', priority = 1 }, function(ok, result)
    if not ok then print('dispatch failed: ' .. tostring(result)) end
end)

Bodycam

ExportReturnsNotes
GetBodycamState()state, datastate: off, standby or recording. data is the last state the server sent (serial, reason, since, …) or nil.
IsBodycamRecording()boolean
SetBodycam(mode)booleanSends a request to the server: on, off, record (also rec, start), stop, toggle, power. The server decides (duty, department settings, item).
client.lua
AddEventHandler('my-weapons:drawn', function()
    if not exports['koja-mdt']:IsBodycamRecording() then exports['koja-mdt']:SetBodycam('record') end
end)

Client events

Local events, listen with AddEventHandler. They are listed with their arguments on the events page: koja-mdt:client:opened, koja-mdt:client:closed, koja-mdt:client:dutyChanged, koja-mdt:client:statusChanged, koja-mdt:client:newCall, koja-mdt:client:callUpdated, koja-mdt:client:notification and koja-mdt:client:bodycamChanged.

client.lua
AddEventHandler('koja-mdt:client:dutyChanged', function(onDuty, department)
    exports['my-hud']:SetBadgeVisible(onDuty)
end)