Documentation

MDT & Businesses

Post dispatch calls, bulletins and tow requests to the MDT, read its records, and take over business orders from another resource.

The MDT is the records app for the police, EMS and mechanic departments, and the till for player-run businesses. Other resources post calls on its dispatch board, post bulletins and calendar events, set unit statuses, register weapon sales, read medical records, death and roadworthiness certificates, dispense prescriptions at a pharmacy, and make a business's orders, such as a cooking script for a restaurant.

Departments are set in shared/unlocked/mdt.lua: the defaults are lspd (police), sams (EMS), autorepair (mechanic) and digitalden (Digital Den). Businesses are the blocks under Config.Businesses in shared/unlocked/business.lua, and each makes a department of its own with the block's key as its id, such as burgershot. A departmentId on this page is one of those ids. See the Config.

These exports only exist while Config.Mdt.enabled is true. SendMdtAlert and CreateMdtCall also need Config.Mdt.dispatch.enabled. The business exports need Config.Business.enabled and a business that is enabled, or they return false. A resource that works without the MDT wraps its calls in pcall.

General Information

SendMdtAlert is a client export. Every other export on this page is a server export.

A character is named by its framework identifier, the same one the MDT's person records use. Plates are read in upper case with single spaces, as the plate reads on the car.

Exports marked Yields wait on the database. Call them from a thread, an event handler or a command, not while your script loads. Until the MDT's tables are ready on start they return nil, false or an empty list.

Who may use the app is checked with the CanUseMdt and CanUseMdtPermission hooks in server/unlocked/hooks.lua, and CanSellBusinessItem can refuse a business line. See Can-Check Hooks.

Types / Classes

MdtPatient, MdtDeathCertificate, MdtRoadworthiness and MdtHistoryEntry are in types/mdt.lua, and BusinessOrder and BusinessOrderLine in types/business.lua, inside zyke_laptop.

#Client Sided Exports

#Send Mdt Alert

Posts a call on the dispatch board from the player's client, for a robbery, an alarm or a shooting script that runs on the client. The server places the call where it sees the player. coords is used only when it is within 150 m of the player, and the call is placed at the player otherwise. Without location, the street the call is on is used. The call's source is the resource that called the export.

It takes the same fields as Create Mdt Call, with coords optional. Nothing is returned. The server drops the alert without a word when the data isn't valid, no department matches, or the player sent more than rateLimits.alert alerts (6 in 60 seconds by default). The same alert again within two minutes and 30 m adds a note to the open call instead of posting another. A new call fires zyke_laptop:mdt:CallCreated with the player's server id.

Example:

lua
---@param data {title: string, description?: string, code?: string, priority?: integer, coords?: vector3, location?: string, departments?: string | string[], jobs?: string | string[], types?: string | string[], caller?: string, details?: table}
exports["zyke_laptop"]:SendMdtAlert({
    title = "Store robbery",
    code = "10-90",
    priority = 1,
    description = "Silent alarm at the register.",
    jobs = "leo",
    details = {sex = "m", weapon = "Pistol", camera = "12"},
})

#Dispatch

Calls go to the departments in departments, else to those of the framework jobs in jobs, else to every department of the types in types, else to every police department. jobs also takes job types: leo, police and sheriff reach the police departments, ems, ambulance, medic, doctor and fire the EMS ones, and mechanic and tow the mechanic ones. Each list is a string or up to 8 strings. Business departments take no calls.

Scripts written for ps-dispatch, cd_dispatch, qs-dispatch or rcore_dispatch keep working while that resource isn't started: the MDT answers their exports and events and posts the calls here. The named alerts they send are listed in shared/unlocked/mdt_shims.lua, and Config.Mdt.dispatch.shims turns each shim off.

#Create Mdt Call

Posts a call on the dispatch board and notifies the on-duty members of new priority 1 and 2 calls. Returns the call id, or nil when the data isn't valid or no department matches. The same title from the same source within two minutes and 30 m of an open call adds a note to that call and returns its id. A new call fires zyke_laptop:mdt:CallCreated with plyId 0. Yields.

Example:

lua
---@param data {title: string, coords: vector3 | {x: number, y: number, z?: number}, description?: string, code?: string, priority?: integer, location?: string, departments?: string | string[], jobs?: string | string[], types?: string | string[], caller?: string, source?: string, details?: table}
---@return integer? callId
local callId = exports["zyke_laptop"]:CreateMdtCall({
    title = "Bank alarm",
    code = "10-90",
    priority = 1,
    coords = vector3(150.2, -1040.1, 29.4),
    location = "Fleeca Bank, Legion Square",
    description = "Vault door forced.",
    types = {"police"},
    caller = "Fleeca Security",
})
FieldDefaultDescription
titleRequiredUp to 120 characters.
coordsRequiredWhere the call is. A vector3 or {x, y, z?}.
descriptionNoneUp to 1000 characters, line breaks kept.
codeNoneUp to 16 characters, such as "10-90". The codes members pick from are in dispatch.codes.
priority21 urgent, 2 normal or 3 low. Any other value makes the call invalid.
locationNoneThe place, up to 80 characters.
departmentsNoneDepartment ids.
jobsNoneFramework job names or job types.
types"police"Department types: "police", "ems" or "mechanic".
callerNoneWho called it in, up to 64 characters.
sourceThe calling resourceShown on the note of a repeated call, and part of what makes two calls the same.
detailsNone{vehicle?, plate?, sex?, weapon?, camera?}. The server adds a line for each to the description, in the server's language. sex is "m" or "f".

#Set Mdt Unit Status

Sets a player's unit status in their department, such as "enroute" when a dispatch resource assigns them to a call. status is a status id of the department's statuses. Returns false when the player's job belongs to no department or the department has no such status. Setting the status they already have returns true and changes nothing. Fires zyke_laptop:mdt:StatusChanged.

Example:

lua
---@param plyId integer
---@param status string
---@return boolean set
exports["zyke_laptop"]:SetMdtUnitStatus(plyId, "enroute")

#Department

#Get Mdt Department

Returns the id of the department whose MDT the player may use right now, or nil. A player whose department has requireDuty must be on duty, and CanUseMdt must allow them.

Example:

lua
---@param plyId integer
---@return string? departmentId
local departmentId = exports["zyke_laptop"]:GetMdtDepartment(plyId)

#Create Mdt Bulletin

Posts a bulletin as the department itself, with the department's short name as the author. The department's online members are notified. Returns the bulletin id, or nil for an unknown department or invalid data. Fires zyke_laptop:mdt:BulletinPosted with plyId 0. Yields.

Example:

lua
---@param departmentId string
---@param data {title: string, body: string, priority?: "info" | "important", mustAck?: boolean, expiresAt?: integer}
---@return integer? bulletinId
local bulletinId = exports["zyke_laptop"]:CreateMdtBulletin("lspd", {
    title = "Court closed Friday",
    body = "All hearings move to Monday. Check the calendar for the new times.",
    priority = "important",
    mustAck = true,
    expiresAt = os.time() + 3 * 86400,
})
FieldDefaultDescription
titleRequiredUp to 120 characters.
bodyRequiredUp to 4000 characters, line breaks kept.
priority"info""info" or "important".
mustAckfalsetrue asks every member to mark it read.
expiresAtNoneUnix seconds, in the future.

#Create Mdt Event

Adds a calendar event as the department itself. Attendees are notified when they are online. Returns the event id, or nil for an unknown department or invalid data. Yields.

Example:

lua
---@param departmentId string
---@param data {kind: string, title: string, start: integer, end: integer, location?: string, coords?: {x: number, y: number, z?: number}, notes?: string, attendees?: string[]}
---@return integer? eventId
local eventId = exports["zyke_laptop"]:CreateMdtEvent("lspd", {
    kind = "court",
    title = "Hearing: Smith",
    start = os.time() + 3600,
    ["end"] = os.time() + 7200,
    location = "Courthouse, Room 2",
    attendees = {officerIdentifier},
})
FieldDefaultDescription
kindRequiredA kind of the department's calendarKinds, such as "court".
titleRequiredUp to 80 characters.
start, endRequiredUnix seconds. end is after start, at most limits.eventDays (14) days later. end is a Lua keyword, so write it as ["end"].
locationNoneUp to 80 characters.
coordsNone{x, y, z?}.
notesNoneUp to 1000 characters.
attendeesNoneUp to 30 character identifiers. They must be members of the department, unless the kind has external = true, such as "appointment". One that isn't makes the event invalid.

#Get Mdt Person Flags

Returns the flag ids the department set on the character's record, such as "armed". Flags no longer in the department's config are left out. Returns an empty list for an unknown department or a character without flags. Yields.

Example:

lua
---@param departmentId string
---@param identifier string
---@return string[] flags
local flags = exports["zyke_laptop"]:GetMdtPersonFlags("lspd", identifier)

#Police

#Register Mdt Weapon

Registers a weapon to a character in the police weapons registry, which every police department shares, such as when a gun shop sells one. A serial already in the registry moves to the new owner and is set back to registered. The serial is 3 to 32 letters, digits and -, read in upper case. modelLabel is up to 64 characters. Returns false for an invalid serial, identifier or label. Yields.

Example:

lua
---@param identifier string
---@param serial string
---@param modelLabel string
---@return boolean registered
exports["zyke_laptop"]:RegisterMdtWeapon(identifier, "AMM-48213", "Pistol .50")

#Medical

#Get Mdt Patient

Returns the character's medical record, shared by every EMS department, or nil when EMS never wrote one. bloodType is "unknown" when it isn't set, and insurance is the first EMS department's default tier when the record has none. Yields.

Example:

lua
---@param identifier string
---@return MdtPatient? patient @ {bloodType, allergies, conditions, medications, dnr, organDonor, emergencyContact, insurance, notes}
local patient = exports["zyke_laptop"]:GetMdtPatient(identifier)

if (patient and patient.dnr) then
    -- Do not resuscitate
end

#Get Mdt Death Certificate

Returns the character's death certificate, or nil without one. A character has one at most: issuing it again replaces it. manner is "natural", "accident", "homicide", "suicide", "undetermined" or "pending". Yields.

Example:

lua
---@param identifier string
---@return MdtDeathCertificate? certificate @ {id, identifier, name, at, location, cause, manner, reportId?, certifiedBy, certifiedByName, certifiedAt}
local certificate = exports["zyke_laptop"]:GetMdtDeathCertificate(identifier)

#Dispense Mdt Prescription

Dispenses an active prescription once, for a pharmacy resource. A prescription with refills left stays active with one refill fewer, and one without refills becomes dispensed, so a prescription with 2 refills is dispensed 3 times. Returns what to hand over, or nil when the prescription doesn't exist or isn't active. Yields.

The prescription id is on the printed prescription: with printPrescriptions = true on the department, the paper document's item metadata has it as prescription, next to medication, dose and quantity.

Example:

lua
---@param prescriptionId integer
---@return {identifier: string, medication: string, label: string, dose: string, quantity: integer, refillsLeft: integer}? dispensed
local dispensed = exports["zyke_laptop"]:DispenseMdtPrescription(item.metadata.prescription)

if (dispensed) then
    -- dispensed.medication is the catalogue id in shared/unlocked/mdt_medical.lua
    exports.ox_inventory:AddItem(plyId, dispensed.medication, dispensed.quantity)
end

#Mechanic

#Create Mdt Tow Request

Posts a tow request to a mechanic department's tow queue in the department's own name, such as from a dispatch or after a crash. The department's on-duty members are notified. Returns the request id, or nil when the department isn't a mechanic one or the data isn't valid. Fires zyke_laptop:mdt:TowRequested with plyId 0. Yields.

Example:

lua
---@param departmentId string
---@param data {plate?: string, coords?: vector3 | {x: number, y: number, z?: number}, location?: string, reason?: string, source?: "export" | "police"}
---@return integer? towId
local towId = exports["zyke_laptop"]:CreateMdtTowRequest("autorepair", {
    plate = "46EEK572",
    coords = vector3(-212.4, -1325.8, 30.9),
    location = "Strawberry Ave",
    reason = "Collision, front wheel off",
})
FieldDefaultDescription
plateNoneThe vehicle's plate.
coordsNoneWhere the vehicle is. Either coords or location is needed.
locationNoneThe place, up to 80 characters.
reasonNoneUp to 200 characters.
source"export""police" shows it as a police request.

#Get Mdt Certificate

Returns the plate's newest roadworthiness certificate from any shop, passed or failed, or nil without one. A failed one expires the moment it is issued, so check both passed and expiresAt. Yields.

Example:

lua
---@param plate string
---@return MdtRoadworthiness? certificate @ {id, plate, passed, at, expiresAt, by, byName, orderId?}
local certificate = exports["zyke_laptop"]:GetMdtCertificate(plate)
local roadworthy = certificate ~= nil and certificate.passed and certificate.expiresAt > os.time()

#Get Mdt Service History

Returns everything the shops know of the plate, newest first, up to 50 entries: repair orders, inspections, odometer readings, roadworthiness certificates, finished tows and zyke_towing rope tows, the impound it is in through zyke_garages, and its current mileage through zyke_lib. Returns an empty list for an invalid plate. Yields.

kind is "order", "inspection", "certificate", "tow", "impound" or "mileage". detail is the order's status, an inspection's green/yellow/red counts such as "12/3/0", "passed" or "failed", a tow's reason or "rope", the impound fee, or the miles.

Example:

lua
---@param plate string
---@return MdtHistoryEntry[] entries @ {at, kind, label, refId?, detail?, stars?}
local entries = exports["zyke_laptop"]:GetMdtServiceHistory(plate)

#Businesses

Lines with prepare = true in a business's catalogue are made to order: with orders.enabled on the business they open an order on its Orders tab and order screens, from the counter or from the website. Staff tick the lines off, mark the order ready and hand it over at the counter. These exports let another resource make the lines instead, such as a cooking script.

#Set Business Line Handler

Sets the function called for every line of each new order of the business. order is the order without the customer's identifier, index the line's place in order.lines from 1, and line the line. An error in the handler is printed in the server console. A business has one handler: setting it again replaces it, nil removes it, and it is removed when its resource stops. Returns false for a department that isn't a business.

Staff can still tick the lines off themselves, and the order only becomes ready when staff mark it ready.

Example:

lua
---@param departmentId string
---@param handler? fun(order: BusinessOrder, index: integer, line: BusinessOrderLine)
---@return boolean set
exports["zyke_laptop"]:SetBusinessLineHandler("burgershot", function(order, index, line)
    -- line: {entryId, label, qty, item?, amount, done}
    QueueCooking(order.id, index, line.entryId, line.qty)
end)

#Set Business Order Line Done

Ticks a line of an order off, or back on with done = false. The Orders tab and the order screens show it at once. Returns false when the order doesn't exist, is no longer open, or has no such line. Yields.

Example:

lua
---@param orderId integer
---@param index integer @ The line, from 1
---@param done? boolean @ true when left out
---@return boolean set
exports["zyke_laptop"]:SetBusinessOrderLineDone(orderId, index)

#Events

Local server events, listened to with AddEventHandler. They can't block an action. plyId is 0 when an export did it. server/unlocked/webhooks.lua sends most of them to Discord.

EventArguments
zyke_laptop:mdt:CallCreatedplyId, departments, call
zyke_laptop:mdt:CallClosedplyId, departments, call
zyke_laptop:mdt:StatusChangedplyId, department, status, previous
zyke_laptop:mdt:BulletinPostedplyId, department, bulletin
zyke_laptop:mdt:ReportSubmittedplyId, department, report
zyke_laptop:mdt:ReportReviewedplyId, department, report, decision, note
zyke_laptop:mdt:WarrantRequested, WarrantServedplyId, department, warrant
zyke_laptop:mdt:WarrantDecidedplyId, department, warrant, approved, note
zyke_laptop:mdt:BoloPostedplyId, department, bolo
zyke_laptop:mdt:ChargesProcessedplyId, department, sentence
zyke_laptop:mdt:VehicleImpoundedplyId, department, vehicle
zyke_laptop:mdt:LicenseRevokedplyId, department, license
zyke_laptop:mdt:CaseOpened, CaseClosedplyId, department, case
zyke_laptop:mdt:DeviceSeized, DeviceReleasedplyId, department, device
zyke_laptop:mdt:DriveImaged, ImageUnlockedplyId, department, image
zyke_laptop:mdt:CareBilledplyId, department, bill
zyke_laptop:mdt:PreAlertSentplyId, department, alert
zyke_laptop:mdt:PatientAdmitted, PatientDischargedplyId, department, admission
zyke_laptop:mdt:PrescriptionWrittenplyId, department, prescription
zyke_laptop:mdt:CertificateIssuedplyId, department, certificate
zyke_laptop:mdt:OrderStatusChanged, OrderBilled, OrderPaidplyId, department, order
zyke_laptop:mdt:OrderApprovalAnsweredplyId, department, answer, plyId being the customer
zyke_laptop:mdt:TowRequested, TowFinishedplyId, department, tow
zyke_laptop:mdt:RoadworthinessIssuedplyId, department, certificate
zyke_laptop:mdt:CatalogueChangedplyId, department, change
zyke_laptop:business:OrderPlaced, OrderReady, OrderCancelleddepartment, order
zyke_laptop:business:OrderCollecteddepartment, order, customerPlyId
zyke_laptop:business:ReceiptIssuedstaffPlyId, department, receipt
zyke_laptop:business:StaffChangeddepartment, onDutyCount
zyke_laptop:den:TicketCreated, TicketReturnedplyId, department, ticket