Examples
Complete recipes built only from the documented server exports, client exports, hooks and events.
Robbery script creating a dispatch call
-
Register the call type
my-robberies/server.lualocal mdt = exports['koja-mdt'] CreateThread(function() mdt:RegisterDispatchCode('ROB-STORE', { title = 'Store robbery', priority = 1, types = { 'leo' }, blip = { sprite = 52, color = 1, scale = 1.1 }, }) end) -
Create the call when a validated robbery starts
my-robberies/server.lualocal active = {} RegisterNetEvent('my-robberies:server:start', function(storeId) local src = source local store = Config.Stores[storeId] if not store or active[storeId] then return end if #(GetEntityCoords(GetPlayerPed(src)) - store.coords) > 15.0 then return end if mdt:CountOnDuty({ type = 'leo' }) < store.minPolice then return end active[storeId] = mdt:CreateDispatch({ code = 'ROB-STORE', message = ('Silent alarm at %s'):format(store.label), coords = store.coords, caller = { name = 'Alarm company' }, source = src, }) end) -
Update and close it
my-robberies/server.luaRegisterNetEvent('my-robberies:server:safeOpened', function(storeId) local callId = active[storeId] if not callId then return end mdt:UpdateDispatch(callId, { message = 'Safe opened, suspects leaving' }) mdt:AddDispatchNote(callId, 'Camera shows 2 suspects, masks', 'Store CCTV') end) RegisterNetEvent('my-robberies:server:finished', function(storeId) local callId = active[storeId] active[storeId] = nil SetTimeout(10 * 60 * 1000, function() local call = callId and mdt:GetCall(callId) if call and call.status == 'active' and #call.units == 0 then mdt:CloseDispatch(callId) end end) end)
Speed camera issuing citations
local mdt = exports['koja-mdt']
local flashed = {}
RegisterNetEvent('my-speedcams:server:flash', function(cameraId, plate, speed)
local src = source
local camera = Config.Cameras[cameraId]
if not camera or type(plate) ~= 'string' or type(speed) ~= 'number' then return end
if #(GetEntityCoords(GetPlayerPed(src)) - camera.coords) > 60.0 then return end
local key = plate .. ':' .. cameraId
if flashed[key] and os.time() - flashed[key] < 60 then return end
flashed[key] = os.time()
local vehicle = mdt:GetVehicle(plate)
mdt:ReportAnprRead({ plate = plate, cameraLabel = camera.label, coords = camera.coords, department = 'sahp' })
if not vehicle or vehicle.unregistered then return end
local citation, err = mdt:IssueCitation({
identifier = vehicle.owner,
plate = vehicle.plate,
vehicleModel = vehicle.model,
violations = { speed - camera.limit > 35 and 'V.C. 7002' or 'V.C. 7001' },
location = camera.label,
coords = camera.coords,
notes = ('Speed camera: %d in a %d zone'):format(math.floor(speed), camera.limit),
department = 'sahp',
author = 'Speed Camera ' .. cameraId,
})
if not citation then print('citation failed: ' .. tostring(err)) return end
mdt:UpdateVehicle(vehicle.plate, { points = (vehicle.points or 0) + 1 })
end)ReportAnprRead logs the plate only when it is a hit (stolen, BOLO, warrant), and with Config.Anpr.autoDispatch raises a call for SAHP. The citation reaches the owner's notification center and citation list; license points are applied by the MDT.
Jail script reporting sentences
local mdt = exports['koja-mdt']
AddEventHandler('koja-mdt:server:jailPlayer', function(target, months, reason, officerSrc)
StartSentence(target, months, reason)
end)
AddEventHandler('koja-mdt:server:releasePlayer', function(target, identifier, reason)
if target then EndSentence(target) end
end)
function JailFromJudgeNpc(src, months, reason)
StartSentence(src, months, reason)
mdt:ReportJail(src, months, { reason = reason, facility = 'Bolingbroke', officer = 'Judge NPC' })
end
function OnSentenceServed(src)
mdt:ReportRelease(src, 'released', { by = 'Bolingbroke' })
end
function OnEscape(src)
mdt:ReportRelease(src, 'escaped', { by = 'Bolingbroke' })
local coords = GetEntityCoords(GetPlayerPed(src))
mdt:CreateDispatch({ code = '10-98', title = 'Prison escape', coords = coords, priority = 1, types = { 'leo' } })
end
RegisterNetEvent('my-prison:server:remaining', function()
local src = source
local sentence = mdt:GetActiveSentence(src)
TriggerClientEvent('my-prison:client:remaining', src, sentence and sentence.secondsLeft or 0)
end)Sentences from MDT processing already create the jail record; only report the ones your script starts on its own, and always report the end of a sentence. See integrations for writing a jail adapter instead.
Phone app reading warrants and citations
local mdt = exports['koja-mdt']
local lastRequest = {}
RegisterNetEvent('my-phone:server:records', function()
local src = source
if lastRequest[src] and os.time() - lastRequest[src] < 5 then return end
lastRequest[src] = os.time()
local warrants = {}
for _, warrant in ipairs(mdt:GetCitizenWarrants(src)) do
if warrant.status == 'active' then
warrants[#warrants + 1] = { id = warrant.id, type = warrant.type, reason = warrant.reason, expiresAt = warrant.expiresAt }
end
end
local owed, count = mdt:GetOutstandingTotal(src)
TriggerClientEvent('my-phone:client:records', src, {
warrants = warrants,
citations = mdt:GetUnpaidCitations(src),
owed = owed,
openCitations = count,
})
end)
RegisterNetEvent('my-phone:server:payCitation', function(citationId)
local src = source
if type(citationId) ~= 'number' then return end
local own
for _, citation in ipairs(mdt:GetUnpaidCitations(src)) do
if citation.id == citationId then own = citation end
end
if not own then return end
local paid, err = mdt:PayCitation(own.id, { charge = true })
TriggerClientEvent('my-phone:client:paid', src, paid ~= nil, err)
end)
AddEventHandler('playerDropped', function() lastRequest[source] = nil end)PayCitation with charge = true takes the money through the MDT fine integration. Only act on a player's own records: the pay handler only accepts a citation id from the caller's own unpaid list.
Garage checking impounds
local mdt = exports['koja-mdt']
RegisterNetEvent('my-garage:server:takeOut', function(plate)
local src = source
if type(plate) ~= 'string' then return end
local held = mdt:GetHeldImpound(plate)
if held then
TriggerClientEvent('my-garage:client:notify', src, ('Impounded at %s. Fee $%d.'):format(held.lot, held.fee))
return
end
if mdt:HasVehicleFlag(plate, 'stolen') then
TriggerClientEvent('my-garage:client:notify', src, 'This vehicle is reported stolen.')
return
end
SpawnVehicle(src, plate)
end)
RegisterNetEvent('my-garage:server:payImpound', function(plate)
local src = source
local held = type(plate) == 'string' and mdt:GetHeldImpound(plate)
if not held or held.holdLeft > 0 then return end
local record, err = mdt:ReleaseImpound(plate, { feeStatus = 'charge', note = 'Paid at impound lot' })
TriggerClientEvent('my-garage:client:notify', src, record and 'Vehicle released' or ('Release failed: ' .. tostring(err)))
end)
AddEventHandler('my-tow:server:towed', function(plate, reason)
mdt:ReportImpound(plate, { reason = reason, fee = 350, hours = 0, lot = 'davis' })
end)ReleaseImpound with feeStatus = 'charge' takes the fee from the online owner; ReportImpound records a tow your script already performed.
Rules as hooks
local mdt = exports['koja-mdt']
mdt:registerHook('citation:create', function(payload)
if payload.total > 5000 then return false, 'over_limit' end
end, { stage = 'before' })
mdt:registerHook('incident:process', function(payload)
if payload.months > 60 then return { months = 60 } end
end, { stage = 'before' })
mdt:registerHook('mdt:open', function(payload)
return exports.ox_inventory:Search(payload.source, 'count', 'mdt_tablet') > 0
end, { stage = 'before' })
AddEventHandler('koja-mdt:server:dutyChanged', function(src, onDuty, department)
if src then exports['my-hud']:SetBadge(src, onDuty and department or nil) end
end)