Koja Scripts

Integrations

Framework, inventory, jail, fine, impound, phone, dispatch and koja-lib bridges in editable/ and how to swap them for your own scripts.

Integrations

koja-mdt talks to the rest of your server through three small files that are not escrowed and can be edited:

FileSideContains
editable/server/framework.luaServerBridge: people, vehicles, licenses, roster queries
editable/server/integrations.luaServerIntegrations: jail, release, impound, fines, society payout, medical bills, sentence handling, events
editable/client/integrations.luaClientClientIntegrations: 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:

ValueImplementationSource tables
esxbuilt inusers, owned_vehicles, user_licenses
qbbuilt inplayers, player_vehicles, player metadata for licenses
anything elsecustom stub that returns empty resultsnone

Functions every implementation provides:

FunctionPurpose
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_inventory started (and Config.Inventory.system = 'auto'), the MDT registers its items there with sizes and images from Config.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-inventory and codem-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).

SystemJailRelease from the MDTLive state
koja-policeJailPlayer exportReleasePlayer exportIsJailed
pickle_prisonsJailPlayerUnjailPlayernone
rcore_prisonJailUnjailnone
qb-prisonplayer metadata plus client event prison:client:Entermetadata plus prison:client:UnjailPersonnone
xt-prisonclient event xt-prison:client:enterJailno, the MDT fires koja-mdt:server:releasePlayernone
esx_jailesx_jail:sendToJailno, the MDT fires koja-mdt:server:releasePlayernone

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:

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 calls ReleaseImpound(plate, waived) when present, otherwise it ends the hold in koja_police_impound.
  • garage: ESX owned_vehicles.stored (2 is impounded, 1 is in the garage) or QB player_vehicles.state (2 impounded, 1 garage) with depotprice. 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:

editable/client/integrations.lua
function ClientIntegrations.GetPhoneNumber()
    return exports['my_phone']:GetOwnNumber()
end

Opening rules and notifications

In the same client file:

FunctionDefault
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):

TypeResources
Radial menukoja-police, ox_lib, qb-radialmenu
Targetox_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.