Integrations
koja-mdt talks to the rest of your server through three small files that are not escrowed and can be edited:
| File | Side | Contains |
|---|---|---|
editable/server/framework.lua | Server | Bridge: people, vehicles, licenses, roster queries |
editable/server/integrations.lua | Server | Integrations: jail, release, impound, fines, society payout, medical bills, sentence handling, events |
editable/client/integrations.lua | Client | ClientIntegrations: death check, open rules, phone number, notifications |
The loading order is set in fxmanifest.lua: the editable files load before the rest of the resource, so everything else calls them.
koja-lib
koja-lib is required. It provides the cross-framework layer used everywhere in the MDT: player lookup by source or identifier, money (getMoney, removeMoney), job and duty changes, offline job changes, usable items, inventory access, notifications and the version check. It sets KojaLib.Framework, which the bridge below uses to choose its implementation. The resource also reads koja-lib's inventory functions to give and find items.
Frameworks
editable/server/framework.lua has one implementation per framework and picks it from KojaLib.Framework:
| Value | Implementation | Source tables |
|---|---|---|
esx | built in | users, owned_vehicles, user_licenses |
qb | built in | players, player_vehicles, player metadata for licenses |
| anything else | custom stub that returns empty results | none |
Functions every implementation provides:
| Function | Purpose |
|---|---|
SearchCitizens(query, limit), GetCitizen(identifier), GetCitizensByIdentifiers(list) | People search and lookup |
SearchVehicles(query, limit), GetVehicle(plate), GetVehiclesByPlates(plates), GetCitizenVehicles(identifier) | Vehicle lookup |
GetLicenses(identifier), RevokeLicense(identifier, type), GrantLicense(identifier, type) | Licenses |
GetRoster(jobs), GetOfflineJob(identifier) | Employee list and offline job lookup |
Optional extra: define Bridge.ChargeOffline(identifier, amount, account, reason) to let citizens pay citations on the web portal while offline.
Using a custom framework or custom tables
Fill in the Custom functions at the bottom of framework.lua and make sure koja-lib reports a framework value that is not esx or qb. Each function must return the same shape the built in versions return. Read the ESX version of the same function in the file and copy its output fields.
Citizens and vehicles are also the tables where your framework stores characters, so changes to column names (for example a custom users table) are made in the Esx or Qb functions in this file.
Inventory
All item handling goes through server/inventory.lua:
- With
koja_grid_inventorystarted (andConfig.Inventory.system = 'auto'), the MDT registers its items there with sizes and images fromConfig.Inventory.items. - Otherwise it uses the inventory supported by koja-lib: it registers usable items, gives items with metadata and reads metadata. The inventories the startup banner looks for are
koja_grid_inventory,ox_inventory,qs-inventory,qb-inventory,ps-inventoryandcodem-inventory.
You still define the items in your inventory resource, see Installation.
Jail
Integrations.Jail(target, months, reason, officerSrc, context) sends a player to jail. Config.Processing.jailSystem selects the system: 'auto' takes the first started resource in this list, or you name one, or use 'none' (the MDT only records the sentence).
| System | Jail | Release from the MDT | Live state |
|---|---|---|---|
koja-police | JailPlayer export | ReleasePlayer export | IsJailed |
pickle_prisons | JailPlayer | UnjailPlayer | none |
rcore_prison | Jail | Unjail | none |
qb-prison | player metadata plus client event prison:client:Enter | metadata plus prison:client:UnjailPerson | none |
xt-prison | client event xt-prison:client:enterJail | no, the MDT fires koja-mdt:server:releasePlayer | none |
esx_jail | esx_jail:sendToJail | no, the MDT fires koja-mdt:server:releasePlayer | none |
If no adapter handles the sentence, the MDT triggers koja-mdt:server:jailPlayer with target, months, reason, officerSrc so you can listen for it.
Adding your own jail script
Add an entry to the jailAdapters list in editable/server/integrations.lua:
{
resource = 'my_prison',
jail = function(target, months, reason, officerSrc)
exports.my_prison:Jail(target, months, reason)
return true -- return false to report a failure
end,
release = function(target, identifier, reason)
if not target then return false end
exports.my_prison:Release(target)
return true
end,
isJailed = function(target) -- optional
return exports.my_prison:IsJailed(target) == true
end,
},With isJailed, the MDT checks online inmates every 30 seconds and closes the record when the script no longer holds the player.
Jail scripts can also report their own sentences and releases to the MDT. See the Server exports and Events pages (ReportJail, ReportRelease, ReportImpound, ReportImpoundRelease, GetActiveSentence, IsImpounded).
Impound
Config.Custody.impoundSystem selects the adapter: 'auto', 'koja-police', 'garage' or 'none'.
- koja-police:
ImpoundVehicle(plate, fee, hours, reason). Release callsReleaseImpound(plate, waived)when present, otherwise it ends the hold inkoja_police_impound. - garage: ESX
owned_vehicles.stored(2 is impounded, 1 is in the garage) or QBplayer_vehicles.state(2 impounded, 1 garage) withdepotprice. This is the fallback used with your normal garage script as long as it reads those columns.
The MDT also sets the vehicle's impounded flag and fires koja-mdt:server:impoundVehicle and koja-mdt:server:releaseImpound. To use another garage or impound script, add an entry to impoundAdapters with impound(plate, data) and release(plate, data) functions.
Fines and society payout
Integrations.Fine(target, amount, reason, officerSrc) takes the fine from Config.Processing.fineAccount (bank), falls back to cash if the balance is too low, verifies the balance changed, and then calls Integrations.PaySociety(amount). Citations and court sentences use the same function.
Integrations.PaySociety pays the account in Config.Processing.fineSociety through the first of these that is started: esx_addonaccount, Renewed-Banking, qb-banking, qb-management. To use another banking resource, edit that function. Medical bills use Integrations.ChargeMedicalBill and Integrations.PayMedicalSociety the same way.
Sentences
Integrations.ApplySentence(target, sentence, judgeSrc) applies a court sentence: fine first, then jail. It fires koja-mdt:server:sentenced. Edit it if you need extra steps such as clearing a wanted level.
Phone
ClientIntegrations.GetPhoneNumber() in editable/client/integrations.lua returns nil by default. Return the player's phone number from your phone resource and it is attached to 911 calls:
function ClientIntegrations.GetPhoneNumber()
return exports['my_phone']:GetOwnNumber()
endOpening rules and notifications
In the same client file:
| Function | Default |
|---|---|
ClientIntegrations.IsDead() | Uses koja-lib, falls back to the native death check. |
ClientIntegrations.CanOpen() | Blocks the pause menu, dead players (unless Config.Open.allowWhenDead) and non-emergency vehicles when Config.Open.vehicleOnly is on. |
ClientIntegrations.Notify(title, desc, type) | Uses the koja-lib notification. Replace it to use your own notify resource. |
Dispatch
Dispatch is built into the MDT. Other scripts create calls with the CreateDispatch server export, see Server exports. With koja-police started, Config.Police controls the link: dispatch lets koja-police use the MDT dispatch and duty shares duty state. With koja-ambulance, see Config.Ems.ambulance in Configuration.
Radial menus and targets
The interaction menu detects these resources automatically (Config.Interact.radial and Config.Interact.target):
| Type | Resources |
|---|---|
| Radial menu | koja-police, ox_lib, qb-radialmenu |
| Target | ox_target, koja-interactions, qb-target |
With koja-police and Config.Interact.replacePoliceItems, koja-police's own ID, plate, bill and MDT radial items are replaced by the MDT ones.
Screenshots
screenshot-basic is optional. It is used for the Take a photo button (needs Config.Camera.uploadUrl) and for selfies. The profile pictures from Config.Avatars use the game's own head shot and do not need it.
Events and exports
The MDT fires server events you can listen to (for example koja-mdt:server:processed, koja-mdt:server:warrantActive, koja-mdt:server:sentenced, koja-mdt:server:custody). The full list, with arguments, is in Events. Hooks that can modify or cancel actions are in Hooks. Exports are in Server exports and Client exports. Start with the API overview.