Introduction
aty_mdt — Advanced Mobile Data Terminal
A professional-grade, multi-department Mobile Data Terminal for FiveM servers running ESX or QBCore, authored by atiysu.
Overview
aty_mdt is a comprehensive, full-featured Mobile Data Terminal built on a modern Vue.js NUI frontend. It is designed from the ground up to serve Police, Sheriff, and EMS departments with a rich set of tools for record-keeping, surveillance, evidence management, and real-time communication.
Key design goals:
- Multi-Framework: Native support for ESX (Legacy & Modern) and QBCore/Qbox.
- Permission-Driven: Every action is gated by a per-job, per-grade permission system configurable in
config.lua. - Live Sync: Data updates (dispatches, unit statuses, chat messages, new records) are pushed in real-time to all open MDTs using
GlobalStateand client events. - Dynamic Themes: The tablet's color scheme, logo, and department title auto-adapt to the player's current job.
- OneSync Compatible: Bodycam and vehicle cam positions are broadcast server-side at 100ms intervals, ensuring compatibility with OneSync Infinity where entity streaming is limited.
Features
🏛️ Law Enforcement Suite
| Feature | Description |
|---|---|
| Citizen Records | Full profile view with mugshot, DOB, licenses, vehicles, properties, warrants, notes, reports, and charges |
| Vehicle Records | Lookup by plate, view owner details, registered notes, BOLOs, and vehicle history |
| Reports | Create, update, and delete detailed incident reports with officer/suspect tagging |
| Incidents | Track open/closed major incidents with citizens, priority, and tags |
| Warrants | Issue and close arrest warrants for specific citizens |
| BOLOs | Issue and resolve "Be On the Lookout" notices for people and vehicles |
| Penal Code | Manage charge categories and individual charges with fine/sentence ranges |
| Judgements | Issue fines and prison sentences; automatically creates billing entries |
| Weapons Registry | Register and track weapon serial numbers and owners |
| License Management | Add or suspend any of the configured license types per citizen |
| GPS Codes | Store and share named GPS waypoints within the department |
| Outfit Codes | Store and share saved outfit configurations by rank |
| Staff Management | HR-level ability to fire staff members directly from the MDT |
| Internal Chat | Real-time, job-isolated messaging for on-duty officers |
| Mugshot Camera | First-person mode integration to capture in-game citizen mugshots |
🏥 EMS Suite
| Feature | Description |
|---|---|
| Treatment Logs | Full patient treatment history with body regions and tags |
| Medications Database | Manage a medication catalog with price and dosage information |
| Medical Bills | Issue and view medical bills; integrates with billing scripts |
| Blood Tests | Request a voluntary blood draw from an online citizen; result stored to their profile |
| Death Records | Formal documentation of in-game deaths with cause and damage details |
📹 Surveillance System
| Feature | Description |
|---|---|
| Static CCTVs | View any configured fixed camera around the map |
| Live Bodycams | View the real-time position and heading of any officer wearing their bodycam |
| Vehicle Cameras | Attach a live feed to any vehicle the officer is in |
| CCTV Creator | In-world prop placement tool to add new cameras to the database |
| CCTV Editor | In-world editor to rename, delete, or move existing cameras |
| Vision Modes | Cycle between Normal, Thermal, and Night Vision |
| Zoom | Mouse scroll wheel zooming for static cameras (10°–60° FOV) |
| Entity Detection | Raycast hover shows player name/DOB and vehicle make/plate while in camera view |
🧬 Evidence System
| Feature | Description |
|---|---|
| Shell Drops | Every player firing a weapon drops a shell at their location with a configurable timeout |
| Flashlight Detection | Officers holding a flashlight item can see nearby shells highlighted on screen |
| Shell Collection | Pick up shells (requires evidence bag item) for submission to the locker |
| DNA Lab | Process shells at a dedicated lab location to link them to a weapon/suspect |
| Evidence Locker | Secure storage for collected evidence entries |
Dependencies
| Resource | Purpose | Required |
|---|---|---|
aty_lib |
Core multi-framework library (ESX/QBCore), callbacks, SQL | ✅ Yes |
ox_lib |
Blood test progress bar UI and alert dialogs | ⚠️ Recommended |
aty_dispatchv2 |
Dispatch system integration for incoming calls | ⚠️ Optional |
aty_fingerprint |
Fingerprint merge into citizen profiles | ⚠️ Optional |
Installation Guide
Step 1 — Download & Place
Place the aty_mdt folder inside your resources directory, typically inside a category folder:
resources/
[aty]/
aty_lib/ ← must be present and started first
aty_mdt/ ← this resource
Step 2 — Database Setup
[!CAUTION] The
drop_create.sqlfile will DROP and recreate all MDT tables. Only run it on a fresh installation. For existing servers, import only the tables you are missing.
Execute the SQL files in the data/ folder in this order:
| File | When to Run | Description |
|---|---|---|
drop_create.sql |
Fresh install only | Creates all MDT tables and seeds default charges and medications |
cctv.sql |
Fresh install | Pre-populates the cctv table with city-wide camera positions |
esx.sql |
If using ESX | Framework-specific column additions for ESX user tables |
qb.sql |
If using QBCore | Framework-specific column additions for QBCore player tables |
seed.sql |
Optional | Additional seed data for charges and categories |
For ESX servers, the following columns are automatically added to your users table:
ALTER TABLE `users` ADD COLUMN IF NOT EXISTS `blood_type` VARCHAR(10) DEFAULT NULL;
ALTER TABLE `users` ADD COLUMN IF NOT EXISTS `mugshot` LONGTEXT DEFAULT NULL;
Step 3 — Add Items to Inventory
The MDT uses up to five items. Register them with your inventory of choice.
For ox_inventory — Copy from items/ox.lua and paste into your ox_inventory/data/items.lua:
['mdt'] = { label = 'MDT', weight = 1200, stack = true, close = true },
['bodycam'] = { label = 'Bodycam', weight = 1200, stack = true, close = true },
['vehcam'] = { label = 'Vehicle Camera', weight = 1200, stack = true, close = true },
['bullet_shell'] = { label = 'Bullet Shell', weight = 1200, stack = true, close = true },
['evidence_bag'] = { label = 'Evidence Bag', weight = 1200, stack = true, close = true },
For QBCore — Copy from items/qb.lua and paste into your qb-core/shared/items.lua. Item images (mdt.png, bodycam.png, etc.) should be placed in qb-inventory/html/images/.
For ESX — Add items via your database items table or through your ESX admin panel. Item names must match exactly.
Step 4 — Resource Startup
Add both resources to your server.cfg. Order matters — aty_lib must start first:
ensure aty_lib
ensure aty_mdt
Configuration Manual
All configuration is done in shared/config.lua. This file is shared between the client and server.
Core Settings
Config.UseQbox = false -- Set to true if using Qbox (a modified QBCore)
Config.Phone = "none" -- Phone integration: "none" or "lb" (lb-phone)
Access & Opening Methods
Config.Command = "mdt" -- Slash command to open the MDT (/mdt)
Config.Key = "F6" -- Default keybind (configurable per-player in GTA settings)
Config.ItemRequired = true -- If true, player must have the item in their inventory
Config.ItemName = "mdt" -- The exact item name required
Config.OpenWithItem = true -- Allow opening via item use
Config.OpenWithCommand = true -- Allow opening via the command/keybind
[!NOTE] If
ItemRequiredistrue, the server validates the item server-side before the tablet opens, preventing exploits.
Billing Integration
The MDT's judgement system and medical bills can automatically send invoices via a supported billing script.
Config.BillingScript = "esx_billing" -- Options: "none", "auto", "esx_billing", "okokBilling", "RxBilling"
Config.BillingAccount = "society_police" -- The society account fines are paid into
| Value | Behavior |
|---|---|
"none" |
Bills are recorded in the MDT database only; no in-game money is deducted |
"auto" |
Automatically detects which billing script is running on the server |
"esx_billing" |
Uses the standard ESX billing script |
"okokBilling" |
Uses okokBilling or okokBillingV2 |
"RxBilling" |
Uses the RxBilling resource |
Tablet Themes & Branding
Each job can have its own custom theme. The theme is applied dynamically when the MDT is opened.
Config.TabletTypes = {
["police"] = {
logo = "police_logo.png", -- Filename in web/build/assets/
title = "Police Department", -- Department name shown in the UI header
primaryColor = "#69FFEC", -- Main accent/highlight color (hex)
accentColor = "#69FFEC", -- Secondary accent (usually same as primary)
backgroundColor = "#0e1117", -- Main background color
secondaryColor = "#161922", -- Card/panel background color
},
["sheriff"] = { ... },
["ambulance"] = { ... },
}
[!TIP] Logo files must be placed inside
web/build/assets/. Thetitlefield appears in the top-left of the tablet header.
Adding a new department:
- Add a new entry to
Config.TabletTypeswith your job name as the key. - Add the job name as a key in
Config.Permissionswith the allowed actions. - Restart the resource.
Evidence System
Config.EvidenceSystem = {
Enable = true, -- Toggle the entire evidence system on/off
MenuCoords = vector3(0, 0, 0), -- Location of the evidence submission menu (set your coords)
LockerCoords = vector3(0, 0, 0), -- Location of the evidence storage locker
ShellItem = "bullet_shell", -- Item name for bullet casings
EmptyBagItem = "evidence_bag", -- Item required to pick up shells from the ground
FlashlightItems = {
"weapon_flashlight", -- Weapon model name that acts as a flashlight
"flashlight", -- Alternative item name
},
DnaTestDuration = 12, -- Duration of the DNA test animation (in seconds)
DropTimeout = 10000, -- Minimum cooldown between shell drops (milliseconds)
LabCoords = vector4(441.39, -993.46, 30.68, 251.28), -- Location of the DNA Lab (MRPD default)
SaveInterval = 600000, -- How often (ms) shell data is saved to DB (10 minutes)
}
[!IMPORTANT] Set
MenuCoordsandLockerCoordsto real in-world locations on your server. The defaultvector3(0, 0, 0)will spawn the interaction points at the world origin.
CCTV & Surveillance
Config.AllowCopsEditCCTV = true -- If true, authorized grades can use the CCTV Creator/Editor
Config.JobRestrictedCCTV = true -- If true, officers only see cameras tagged with their own job
Config.CCTVJobs = { -- Jobs that appear in the "Active Units" list and can use bodycams
"police",
"ambulance",
}
Config.BodycamItem = "bodycam" -- Item name required to toggle bodycam (if itemRequired logic applies)
Config.VehcamItem = "vehcam" -- Item name required to toggle vehicle cam
[!NOTE] When
JobRestrictedCCTV = true, apoliceofficer will NOT see CCTV cameras tagged withjob = "ambulance"in the database, and vice versa. Cameras with aNULLor emptyjobcolumn are visible to all departments.
Personal Lockers
Lockers are personal stash points per-job, using your inventory's stash system via aty_lib.
Config.Lockers = {
["police"] = {
{ coords = vector3(450.44, -992.33, 30.68), label = "Personal Locker" }, -- MRPD
{ coords = vector3(-1107.82, -825.21, 19.31), label = "Personal Locker" }, -- Vespucci
},
["sheriff"] = {
{ coords = vector3(1851.52, 3690.62, 34.22), label = "Personal Locker" },
},
["ambulance"] = {
{ coords = vector3(301.88, -597.51, 43.28), label = "Personal Locker" },
}
}
Each entry creates an interaction zone near the specified coordinates. The stash holds up to 50 slots and 100,000 weight units.
Permissions System
This is the most powerful part of the configuration. Every action in the MDT is gated by this table.
Config.Permissions = {
["police"] = {
-- Set to `true` to allow ALL grades in the job to perform this action.
-- Set to a table of grade numbers (e.g., {2, 3, 4}) to restrict to those grades only.
["create_report"] = true, -- All police can create reports
["update_report"] = {4}, -- Only grade 4 can edit reports
["delete_report"] = {4},
["create_evidence"] = true,
["delete_evidence"] = {4},
["create_warrant"] = true,
["close_warrant"] = {1, 2, 3, 4},
["create_bolo"] = true,
["update_bolo"] = true,
["resolve_bolo"] = true,
["create_incident"] = true,
["update_incident"] = true,
["delete_incident"] = {4},
["create_weapon"] = {2, 3, 4},
["delete_weapon"] = {2, 3, 4},
["add_license"] = {2, 3, 4},
["suspend_license"] = {2, 3, 4},
["create_note"] = true,
["delete_note"] = {2, 3, 4},
["manage_cctv"] = {4}, -- Only grade 4 can add/edit/delete CCTV cameras
["use_bodycam"] = true,
["use_vehcam"] = true,
["declare_deceased"] = {2, 3, 4},
["create_judgement"] = true, -- All officers can issue fines/sentences
["create_charge"] = {4}, -- Only grade 4 can add new penal code charges
["update_charge"] = {4},
["delete_charge"] = {4},
["create_charge_category"] = {4},
["update_charge_category"] = {4},
["delete_charge_category"] = {4},
["manage_gps_codes"] = {3, 4},
["manage_outfit_codes"] = {3, 4},
["manage_staff"] = {4}, -- Only grade 4 can fire staff from MDT
},
["ambulance"] = {
["create_treatment"] = true,
["delete_treatment"] = {3},
["create_medication"] = {3},
["delete_medication"] = {3},
["create_bill"] = true,
["create_note"] = true,
["delete_note"] = {2, 3},
["manage_gps_codes"] = {2, 3},
["manage_outfit_codes"] = {2, 3},
["manage_staff"] = {3},
["declare_deceased"] = {2, 3},
["create_evidence"] = true,
["delete_evidence"] = {3},
["create_report"] = true,
["update_report"] = {3},
["delete_report"] = {3},
},
}
[!IMPORTANT] Jobs NOT listed in
Config.TabletTypescannot open the MDT at all, regardless of permissions.Config.Permissionsonly controls what actions are available to jobs that CAN open the tablet.
Firing (HR action):
Config.FireJob = "unemployed" -- The job name assigned when firing a staff member
Config.FireGrade = 0 -- The grade assigned after firing
Licenses Configuration
These are the in-game licenses that can be added/suspended per-citizen.
Config.Licenses = {
{ id = 1, type = "drivers_license", label = "driver_license" },
{ id = 2, type = "pilot_license", label = "pilot_license" },
{ id = 3, type = "weapon_license", label = "weapon_license" },
{ id = 4, type = "boat_license", label = "boating_license" },
}
The type field is the value stored in the database; label is what is displayed in the UI locale system.
Database Table Names
If your server uses non-standard table names (e.g., QBCore uses players instead of users), update these:
Config.Tables = {
Users = "users", -- Main player table (ESX: "users", QBCore: "players")
Vehicles = "owned_vehicles", -- Vehicle table (ESX: "owned_vehicles", QBCore: "player_vehicles")
-- All other tables are MDT-specific and do not need to be changed normally
Note = "mdt_user_notes", Warrant = "mdt_user_warrants", Report = "mdt_reports",
Evidence = "mdt_evidences", Treatment = "mdt_treatments", Medication = "mdt_medications",
Bill = "mdt_bills", ChatMessage = "mdt_chat_messages", CCTV = "cctv",
Incident = "mdt_incidents", Charge = "mdt_charges", ChargeCategory = "mdt_charge_categories",
Bolo = "mdt_bolos", Weapon = "mdt_weapons", Death = "mdt_deaths",
Judgement = "mdt_user_judgement", UserReports = "mdt_user_reports",
VehicleBolos = "mdt_vehicle_bolos", GpsCode = "mdt_gps_codes",
OutfitCode = "mdt_outfit_codes", BloodTest = "mdt_user_bloodtests",
}
BOLO Settings
Controls the date picker range when setting a BOLO's expiration date.
Config.BoloSettings = {
MinDays = 1, -- Minimum days from today the expiration date can be set
MaxDays = 30, -- Maximum days from today the expiration date can be set
}
Date Formats
Controls how dates are displayed throughout the MDT UI.
Config.DateFormat = "%d/%m/%Y" -- Used for dates (e.g., 05/04/2026)
Config.DateTimeFormat = "%d/%m/%Y %H:%M" -- Used for timestamps (e.g., 05/04/2026 14:30)
These use standard Lua os.date format strings.
Fingerprint Integration
If you run aty_fingerprint alongside the MDT, citizen profiles can be automatically enriched with fingerprint data.
Config.FingerprintMerge = {
Enabled = false, -- Set to true to enable
Resource = "aty_fingerprint", -- The resource name of your fingerprint script
}
When enabled, the MDT citizen list is merged with fingerprint records. Only DB fields that exist in both systems are overwritten. Matching is done by identifier (ESX) or citizenid (QBCore/Qbox).
Phone Integration
Config.Phone = "lb" -- Set to "lb" to enable lb-phone integration for phone number lookups
Other Settings
Config.EnableTransparencyOnLeave = true -- Tablet becomes semi-transparent when mouse is outside the NUI area,
-- allowing the player to move while the MDT is open
Config.AutoRegisterWeapons = true -- Automatically pulls weapons from player inventories and
-- displays them in the Weapons Registry
Config.BloodTypes = {"A+","A-","B+","B-","AB+","AB-","O+","O-"} -- Valid blood type options
Config.BloodTestDuration = 10000 -- How long the blood draw animation takes (ms)
Config.DefaultProfileImage = "https://..." -- URL for citizens who have not had a mugshot taken
Config.ImageWebhook = "https://discord.com/api/webhooks/..." -- Discord webhook for uploading mugshot photos
Config.ReportLocations = { -- World positions where officers can create a report by pressing E
vector3(441.21, -981.85, 30.69), -- MRPD
vector3(-1098.37, -830.7, 19.3), -- Vespucci
}
Localization
The MDT supports multiple languages. The language is set at the top of shared/locale.lua:
Locale = "en" -- Change this to switch language
Supported languages out of the box:
| Code | Language |
|---|---|
en |
English |
tr |
Turkish |
fr |
French |
es |
Spanish |
de |
German |
pt |
Portuguese |
Adding a new language:
- Open
shared/locale.lua - Find the
["en"] = { ... }block and copy it entirely - Paste it below, rename the key to your language code (e.g.,
["it"]) - Translate all string values inside the block
- Change
Locale = "it"at the top of the file
Usage Guide — Law Enforcement
Opening the MDT
There are three ways to open the MDT:
| Method | Requirement | Notes |
|---|---|---|
Keybind (default F6) |
Player must be a valid job | Rebindable in GTA V key settings |
Command (/mdt) |
OpenWithCommand = true in config |
Can be disabled |
| Item Use | ItemRequired = true and possess mdt item |
Item is validated server-side |
When the MDT opens, the player's character plays a tablet-holding animation and a physical tablet prop (prop_cs_tablet) is attached to the left hand.
Dashboard & Active Units
The Dashboard is the first screen shown. It displays:
- Your profile card: Name, rank, department, and current status
- Active Units: A real-time list of all online officers that belong to
Config.CCTVJobs, showing their name, rank, callsign, and current street location (updated every 10 seconds) - Recent Dispatches: The last 50 incoming dispatch alerts from
aty_dispatchv2
Changing Your Status: Click your status button on your profile card to cycle through:
- Available
- Busy
- Off Duty
- On Scene
Your status is broadcast server-side via GlobalState and appears on all other officers' dashboards in real-time.
Responding to a Dispatch: Click Approve on any incoming dispatch to set your GPS waypoint to the incident location. Click Decline to dismiss it from your view.
Citizen Records
Navigate to the Citizens tab to search and browse all players registered in your server's database.
Search: Type any part of a citizen's name or identifier to filter the list instantly.
Citizen Profile View: Click on a citizen to expand their full profile, which includes:
| Section | Contents |
|---|---|
| Personal Info | Mugshot, full name, DOB, sex, phone number, blood type |
| Licenses | Active/suspended status for all configured license types |
| Vehicles | All registered vehicles with model, make, and plate |
| Properties | Any registered properties |
| Notes | Internal department notes (visible/restricted by job) |
| Reports | All reports the citizen is linked to |
| Warrants | Active and closed warrants |
| Judgements | All issued fines and prison sentences across all departments |
| Blood Tests | Results from any blood tests conducted by EMS |
Taking a Mugshot: From the citizen profile, click the Take Mugshot button. The MDT will close and put you into a first-person camera mode. Press Left Click or D-Pad Down (controller) to capture the screenshot. The image is uploaded to your configured Discord webhook and saved to the citizen's profile.
Vehicle Records
Navigate to the Vehicles tab to search all registered vehicles.
Search by Plate: Type a plate number to instantly filter.
Vehicle Profile View:
- Owner name and identifier
- Vehicle make and model (resolved from GTA V game hashes)
- Registration status
- Attached notes and BOLOs
Reports
| Action | Required Permission | Description |
|---|---|---|
| Create | create_report |
Opens a form to document an incident with officers, suspects, vehicles, weapons, and evidence fields |
| Update | update_report |
Edit an existing report's content |
| Delete | delete_report |
Permanently remove a report |
Reports support a risk level (Low / Medium / High / Critical) with color coding.
Reports can also be created in-world by walking up to one of the Config.ReportLocations coordinates and pressing E.
Incidents
Incidents are for tracking ongoing major events (e.g., a bank robbery in progress).
| Field | Description |
|---|---|
| Name | The incident title |
| Priority | Low / Medium / High / Critical |
| Status | Open / Closed |
| Citizens | Tag citizens involved |
| Tags | Freeform tags |
| Description | Detailed narrative |
Warrants
| Action | Permission | Notes |
|---|---|---|
| Create Warrant | create_warrant |
Must specify citizen, reason, and expiration date |
| Close Warrant | close_warrant |
Requires selecting a closure type (e.g., "Served", "Dismissed") |
Active warrants appear prominently on a citizen's profile.
BOLOs (Be On the Lookout)
BOLOs are broadcast alerts for suspects or vehicles that officers should watch for.
BOLO Fields:
- Title and description
- Suspect tags (dangerous, armed)
- Optional vehicle information
- Priority (Low / Medium / High / Critical)
- Expiration date (within the
MinDays–MaxDaysconfigured range)
Charges & Penal Code
The Charges tab lists all configured penal code entries. These are used when creating a Judgement against a citizen.
| Action | Permission |
|---|---|
| Create charge category | create_charge_category |
| Create charge | create_charge |
| Update charge | update_charge |
| Delete charge | delete_charge |
Each charge has: category, title, type (Infraction/Misdemeanor/Felony), minimum/maximum fine, minimum/maximum sentence, and priority.
Weapons Registry
Weapons can be registered manually or automatically:
- Manual: From the Weapons tab (requires
create_weaponpermission) - Automatic: Set
Config.AutoRegisterWeapons = trueto pull all weapons from player inventories - Via Export: Other scripts can register weapons using
exports['aty_mdt']:RegisterWeapon(data)
Each weapon has: serial number, type, label (friendly name), owner name, date registered, and created-by officer.
Evidence System Usage
Shell Collection Workflow:
- An officer fires their weapon → a shell is automatically dropped at their position (subject to
DropTimeoutcooldown). - Any officer holding a flashlight item (see
Config.EvidenceSystem.FlashlightItems) will see nearby shells highlighted with yellow markers. - Walk within 1.5m of a shell and press
E(while holding the flashlight) to pick it up. You must have anevidence_bagin your inventory. - The collected shell appears in the MDT Evidence tab.
DNA Testing: Go to the Config.EvidenceSystem.LabCoords location. Open the MDT Evidence tab and click Run DNA Test on a collected shell. After a short animation, the shell is linked to the weapon used to fire it (and by extension, to the weapon's registered owner).
Evidence Locker: Evidence can be submitted to a secure locker at Config.EvidenceSystem.LockerCoords. This changes the evidence status from collected to deposited.
GPS & Outfit Codes
Officers with manage_gps_codes or manage_outfit_codes permission can access these sections.
- GPS Codes: Named waypoints that any officer in the department can quickly set as their GPS destination (e.g.,
MRPD Front,Hospital). - Outfit Codes: Stored outfit configurations indexed by rank/grade. Officers can apply a saved outfit instantly.
Internal Chat
The Chat tab provides a department-isolated messaging channel. Messages are stored in the database and loaded on MDT open. New messages are pushed in real-time to all online officers of the same job.
The chat is visible to all grades of the department but cannot be seen by officers of other departments.
Staff Management
Officers with the manage_staff permission (typically grade 4) can access the Staff tab to view all online/offline staff and fire them. Firing a staff member assigns them Config.FireJob and Config.FireGrade.
Usage Guide — EMS / Ambulance
EMS players see a different set of tabs when they open their MDT (with a red ambulance theme).
Patient Management (Treatments)
| Action | Permission |
|---|---|
| Create treatment log | create_treatment |
| Delete treatment log | delete_treatment |
Treatment logs include: patient name, citizen identifier, treatment title, description, body regions affected, and tags.
Medications Database
| Action | Permission |
|---|---|
| Add medication | create_medication |
| Delete medication | delete_medication |
Medications have: name, description, price, and optional dosage range (min/max). These are used as a reference catalog for available medications.
Medical Bills
| Action | Permission |
|---|---|
| Create bill | create_bill |
Bills can be sent to an online citizen. If Config.BillingScript is set (not "none"), the money is deducted from the citizen's in-game bank account. Bills also appear in the citizen's Judgements tab for the police to see (labeled [EMS]).
Blood Tests
The blood test system allows EMS to formally document a citizen's blood type.
Workflow:
- EMS officer opens a citizen profile in the MDT.
- Clicks Request Blood Test.
- If the citizen is online, they receive an
ox_libalert dialog asking them to consent. - If they accept, a progress bar animation plays (
ox_librequired). - Once complete, the citizen's blood type is determined (from DB or randomly assigned) and saved to the
mdt_user_bloodteststable. - The result appears on the citizen's profile in the MDT.
[!NOTE] Blood tests require
ox_libto be installed and started. Without it, only a basic notification is shown and the interactive flow does not work.
Death Records
| Action | Permission |
|---|---|
| Declare deceased | declare_deceased |
Death records include: citizen identifier, name, reason, damage description, status (e.g., DOA), tags, date of death, and the declaring officer.
Surveillance System
Static CCTV Cameras
The MDT Surveillance tab displays all cameras from the cctv database table. Clicking View on a camera closes the tablet and activates the camera feed.
CCTV camera props are dynamically spawned in-world when a player gets within 50 meters of the camera's coordinates. They are automatically cleaned up when the player moves beyond that distance.
Live Bodycams
When an officer activates their bodycam item (bodycam), they register a "dynamic camera" on the server under type = "bodycam". This feed appears in the MDT Surveillance list and can be viewed by any authorized officer.
Server-Side Position Broadcast: Due to OneSync Infinity limitations (entities outside a viewer's streaming range cannot be found by network ID), the bodycam position is broadcast server-side every 100ms directly from the owner's ped coordinates. This ensures the camera feed follows the officer accurately regardless of distance.
Vehicle Cameras (Vehcam)
Similar to bodycam but attached to a vehicle. The officer must be inside a vehicle to activate it. The vehicle's plate and model are used as the camera label.
Only one vehicle camera can be active per player at a time.
CCTV Creator Mode
Officers with the manage_cctv permission can add new cameras to the world.
How to Start: From the MDT Surveillance tab → Add Camera button (or via a server event trigger aty_mdt:startCCTVCreator).
Creator Controls:
| Key | Action |
|---|---|
| Move mouse | The camera prop follows your gameplay camera raycast |
← / → Arrow Keys |
Rotate camera heading |
↑ Arrow Key |
Cycle to next camera prop model |
↓ Arrow Key |
Cycle to previous camera prop model |
G |
Toggle pole attachment on/off |
Enter |
Confirm placement (opens name input) |
Backspace |
Cancel and exit creator |
Prop Models Available:
prop_cctv_cam_06aprop_cctv_cam_02am24_1_prop_m24_1_carrier_bank_cctv_02prop_cctv_cam_05ap_cctv_sprop_cctv_cam_04chei_prop_bank_cctv_01xm_prop_x17_server_farm_cctv_01
After pressing Enter, a text input appears. Type the camera name and submit to save it to the database.
CCTV Editor Mode
Hover your gameplay camera over any existing CCTV prop to select it (it will highlight in red). When selected:
| Key | Action |
|---|---|
DEL |
Delete the camera from the database |
R |
Rename the camera (opens name input) |
E |
Delete and re-enter Creator mode with the same prop model to reposition |
Backspace / ESC |
Exit editor mode |
Camera View Controls
When viewing any camera (static or dynamic):
| Key | Action |
|---|---|
W / S |
Tilt camera up / down |
A / D |
Pan camera left / right |
Mouse Scroll Up |
Zoom in (static cams only, min FOV: 10°) |
Mouse Scroll Down |
Zoom out (static cams only, max FOV: 60°) |
E |
Cycle vision mode (Normal → Thermal → Night Vision) |
Backspace |
Exit camera view and return to normal |
Vision Modes are disabled for dynamic (bodycam/vehcam) feeds; they only work on static CCTV cameras.
Entity Detection: While in camera view, a raycast runs continuously from the camera's crosshair. If it hits a player, their name, DOB, and sex are shown in a HUD overlay. If it hits a vehicle, the make, plate, and class are displayed.
Dispatch Integration
aty_mdt listens for the aty_dispatchv2:client:sendDispatch event from aty_dispatchv2. When a dispatch is received:
- It's added to an in-memory list (max 50 entries)
- It's pushed to the MDT NUI in real-time
- Officers can Approve (sets GPS waypoint) or Decline (dismisses) the call
Expected Dispatch Data Format:
TriggerClientEvent("aty_dispatchv2:client:sendDispatch", -1, {
title = "Armed Robbery",
code = "10-30",
message = "Armed robbery in progress",
jobs = {"police", "sheriff"}, -- Which jobs receive this dispatch
coords = vector3(441.0, -981.0, 30.0), -- Map blip location
image = "https://...", -- Optional image URL
information = {
description = "Suspect armed with pistol",
caller = "John Doe",
gender = "Male",
street = "Elgin Avenue, Downtown LS",
phone = "555-0100",
}
})
Personal Lockers Usage
Once configured in Config.Lockers, a persistent stash interaction appears near each locker coordinate.
- Walk within interaction range and press
Eto open your personal locker. - The stash is linked to your player identifier, so only you can access it.
- Capacity: 50 slots / 100,000 weight units.
- Lockers use your server's inventory system via
aty_lib'sRegisterStashmodule.
Developer Exports
Server-Side: RegisterWeapon
Register a weapon serial number into the MDT database from any other resource. Triggers a live update for all open MDT tablets.
---@param data table
---@return boolean success
local success = exports['aty_mdt']:RegisterWeapon({
serialNumber = "SN-123456", -- required: unique serial number string
owner = "John Doe", -- required: the owner's display name
type = "WEAPON_PISTOL", -- optional: weapon type/model string
label = "Combat Pistol" -- optional: friendly weapon label
})
if success then
print("Weapon registered in MDT successfully.")
end
Use Cases:
- A gun shop script can call this when a weapon is sold.
- A crafting script can register crafted weapons.
- An admin script can bulk-register weapons.
Database Schema Reference
The following tables are created by data/drop_create.sql:
| Table | Purpose |
|---|---|
mdt_user_licenses |
Citizen license records (type, status) |
mdt_user_judgement |
Police fines and prison sentences |
mdt_user_notes |
Internal department notes per citizen |
mdt_user_warrants |
Active and closed arrest warrants |
mdt_user_reports |
Reports that a citizen appears in |
mdt_user_bloodtests |
Blood test results (auto-created on first run) |
mdt_reports |
Full report documents with all linked data |
mdt_incidents |
Major incident tracking |
mdt_evidences |
Physical evidence entries |
mdt_evidences_shells |
Bullet casings collected from crime scenes |
mdt_bolos |
"Be On the Lookout" notices |
mdt_vehicle_bolos |
Vehicle-specific BOLOs |
mdt_vehicle_photos |
Photos attached to vehicle records |
mdt_vehicle_notes |
Notes attached to vehicle records |
mdt_weapons |
Weapon serial number registry |
mdt_charges |
Penal code charges |
mdt_charge_categories |
Categories for penal code charges |
mdt_treatments |
EMS treatment logs |
mdt_medications |
EMS medication catalog |
mdt_bills |
Medical bills issued by EMS or fines recorded internally |
mdt_deaths |
Death records declared by EMS or police |
mdt_chat_messages |
Persistent department chat history |
cctv |
Static CCTV camera locations |
mdt_gps_codes |
Named department GPS waypoints |
mdt_outfit_codes |
Saved outfit configurations by grade |
Columns added to your framework's player table:
-- For ESX (users table):
ALTER TABLE `users` ADD COLUMN IF NOT EXISTS `blood_type` VARCHAR(10) DEFAULT NULL;
ALTER TABLE `users` ADD COLUMN IF NOT EXISTS `mugshot` LONGTEXT DEFAULT NULL;
Troubleshooting
MDT won't open
- Ensure
aty_libis started beforeaty_mdtinserver.cfg. - Ensure your player's job name exists as a key in
Config.TabletTypes. - If
ItemRequired = true, confirm the player has themdtitem in their inventory. - Check the server console for errors from
aty_liboraty_mdt.
"You do not have permission" when trying an action
- Check
Config.Permissionsfor the player's job. - Verify the player's grade matches the required grade list for that action.
- Grades in QBCore start at
0, in ESX they may start at0or1depending on your setup.
Blank citizen profiles / "Could not retrieve MDT data"
- Verify
Config.Tables.Usersmatches your database's player table name. - Ensure your database user has
SELECTprivileges on that table. - Check that
aty_lib's SQL module is correctly initialized.
"Camera not found" error
- Ensure you imported
data/cctv.sqlinto your database. - Try refreshing cameras from the CCTV tab in the MDT.
- Wait a few seconds after opening — the server throttles CCTV loads to every 5 seconds.
Bodycam / Vehcam feed is frozen or not moving
- This is normal if the officer is not moving — the server only updates every 100ms.
- Ensure the officer is online and within rendering range is not required due to the server-side broadcast system.
- If
aty_mdt:forceCloseDynamicCamwas triggered, the officer's camera was deactivated (they disconnected or died).
Blood test doesn't work / no prompt appears
- Install and ensure
ox_libis started on your server. - Without
ox_lib, only a notification is shown; no interactive flow is triggered.
MDT UI is blank after modifying the web source
- Run
npm installfollowed bynpm run buildinside theweb/directory. - Ensure the built files exist in
web/build/.
Dispatch calls not appearing in MDT
- Ensure
aty_dispatchv2is started and sending theaty_dispatchv2:client:sendDispatchclient event. - Check that the dispatch's
jobsarray includes the officer's job name. - The MDT only receives dispatches if the tablet is already open at the time the event fires; previously received dispatches are stored in memory for the session.
FAQ
Q: Can I add more than 3 jobs (police, sheriff, ambulance)?
A: Yes. Add a new entry to Config.TabletTypes and Config.Permissions with your custom job name. Any job listed in TabletTypes can open the MDT.
Q: Can civilians open the MDT?
A: No. Only jobs listed in Config.TabletTypes can open the tablet, regardless of item possession.
Q: Do I need to restart the resource after editing config.lua?
A: Yes. The config is loaded at startup on both client and server. A full restart aty_mdt is required for config changes to take effect.
Q: How do I change the CCTV camera range for prop spawning?
A: The prop spawning range (50m) and the locker interaction range are hardcoded in client/system/cctv.lua. You can modify the dist < 50.0 check to your desired value.
Q: Does this work with ox_inventory?
A: Yes. aty_lib abstracts the inventory interaction, supporting ox_inventory, qb-inventory, ESX's default inventory, and others.
Q: Can two officers view the same bodycam simultaneously?
A: Yes. The server-side observer system (DynamicCamObservers) supports multiple viewers per dynamic camera. Each viewer receives position broadcasts independently.
Q: Is there a way to programmatically open the MDT for a player?
A: Yes. Trigger the following event from the server:
TriggerClientEvent("aty_mdt:client:open", playerSource)
This runs all the same item and job checks as the keybind/command.
Developed by atiysu — part of the [aty] resource collection.