Developers
The lation chop shop server and client exports, its events, its server and client hooks and the files that ship open, with signatures, return shapes and Lua examples.
Every export is called on exports.lation_chopshop. Server exports that act on a player take their server id as source; client exports work on the local player. A player's identifier is the ESX identifier on ESX and the citizenid on Qbox and QBCore.
Server exports
| Export | Returns |
|---|---|
getPlayerData(source, type?) | A player's record, or one field of it |
addPlayerData(source, dataType, amount) | Nothing |
getLevelData() | The XP for each level |
getContract(source) | The player's contract, or nil |
getContractDetails(source) | The contract as the paper shows it |
issueContract(source) | Nothing |
GetRuntimeConfig(section?) | The live configuration |
getPlayerData
Server export
exports.lation_chopshop:getPlayerData(source, type)Parameters
sourcenumber- The player's server id
typestringoptionaldefaultnil- One field:
identifier,name,level,exp,vehiclesorparts. The whole record when left out.
Returns
table | any | nil- The record (below), or the value of
type.nilwhen the player isn't loaded in your framework.
The first time a player is looked up, a record is created at level 1. A player whose stored level no longer fits the level ladder is moved to the level their XP reaches as they are looked up. The record is the cached one, so treat it as read-only and change it with addPlayerData.
{
identifier = 'ABC12345',
name = 'Jane Doe', -- character name
level = 3,
exp = 12450, -- total XP
vehicles = 18, -- vehicles chopped
parts = 214, -- parts received
}local level = exports.lation_chopshop:getPlayerData(source, 'level')
if level and level >= 3 then
-- unlock something
endaddPlayerData
Server export
exports.lation_chopshop:addPlayerData(source, dataType, amount)Parameters
sourcenumber- The player's server id
dataTypestringexp,vehiclesorpartsamountnumber- How much to add. Anything 0 or below does nothing.
Returns
- Nothing
Adding exp levels the player up past each threshold they cross and sends the level up notice (and the level_up log when it is on). Nothing is written straight away: the record is saved when the player leaves or logs out of the character, every 5 minutes while it has changes, when the resource stops and when txAdmin shuts the server down.
exports.lation_chopshop:addPlayerData(source, 'exp', 250)getLevelData
Server export
exports.lation_chopshop:getLevelData()Returns
table- The total XP for each level, keyed by level number
local levels = exports.lation_chopshop:getLevelData()
-- { [1] = 0, [2] = 5000, [3] = 10000, [4] = 25000, [5] = 75000 }getContract
Server export
exports.lation_chopshop:getContract(source)Parameters
sourcenumber- The player's server id
Returns
table | nil- The player's contract (below), or
nilwhen they have none
{
tierId = 'street', -- the tier's key
vehicleClass = 1, -- the class id it asks for (see vehicle class ids)
cashBonus = 500, -- paid when the car is finished in time
issuedAt = 1730000000, -- unix time
expiresAt = 1730003600, -- unix time the timer runs out
bonusEligible = true, -- false once the time has run out
activeNetId = nil, -- net id of the car being chopped on it
}The class ids are in vehicle class ids.
local contract = exports.lation_chopshop:getContract(source)
if contract and contract.bonusEligible then
print(('%d seconds left'):format(contract.expiresAt - os.time()))
endgetContractDetails
Server export
exports.lation_chopshop:getContractDetails(source)Parameters
sourcenumber- The player's server id
Returns
table- The contract as the paper shows it (below), or
{ status = 'none' }when they have none
{
status = 'active', -- 'active', 'in_progress' (a car is being chopped), 'expired' or 'none'
tierId = 'suv',
tierLabel = 'SUV',
vehicleClass = 2,
vehicleClassLabel = 'SUVs', -- in the server's language
issuedAt = 1730000000,
expiresAt = 1730004500,
remainingSeconds = 4455, -- 0 once it has run out
cashBonus = 750,
bonusEligible = true,
activeNetId = nil,
canCancel = true, -- the Allow cancelling setting
cooldownMinutes = 0, -- the Completion cooldown setting
account = 'cash', -- the bonus payout account
accountIsMoney = true, -- true for cash, bank or money; false for an item
}local details = exports.lation_chopshop:getContractDetails(source)
if details.status == 'active' then
print(details.tierLabel, details.vehicleClassLabel, details.remainingSeconds)
endissueContract
Server export
exports.lation_chopshop:issueContract(source)Parameters
sourcenumber- The player's server id
Returns
- Nothing
Gives the player a contract and its paper, with the same checks as Request Contract at the broker: their job (through CanRequestJob), the cooldown, a contract already running, the police count, a tier at their level and room for the paper. The player is told when it is refused. There is no distance check, so you can hand out contracts from your own NPC or job. Call getContract afterwards to see whether one was issued.
RegisterNetEvent('my_resource:takeChopJob', function()
local src = source
exports.lation_chopshop:issueContract(src)
end)GetRuntimeConfig
Server export
exports.lation_chopshop:GetRuntimeConfig(section)Parameters
sectionstringoptionaldefaultnilsetup,start,experience,contracts,locations,minigame,items,shopsorlogs; every section when left out
Returns
table- That section, or a table of every section keyed by name. Experience levels and rewards are keyed by level number, and coordinates are vectors.
The table is the live configuration: changes you make to it aren't saved, so change settings in the admin panel.
local contracts = exports.lation_chopshop:GetRuntimeConfig('contracts')
print(contracts.account, contracts.completionCooldown)The logs section holds your Discord webhook URL. Don't pass it to clients.
Client exports
| Export | Returns |
|---|---|
getPlayerData(type?) | The local player's record, or one field of it |
getLevelData() | The XP for each level |
hasActiveContract() | boolean |
openMenu() | Nothing |
getPlayerData (client)
Client export
exports.lation_chopshop:getPlayerData(type)Parameters
typestringoptionaldefaultnil- One field; the whole record when left out
Returns
table | any- The local player's record from the server, shaped like the server export's, or the value of
type
It waits for the server, so call it from a thread.
CreateThread(function()
local data = exports.lation_chopshop:getPlayerData()
print(('Level %s with %s XP'):format(data.level, data.exp))
end)getLevelData (client)
Client export
exports.lation_chopshop:getLevelData()Returns
table- The same level table as the server export
hasActiveContract
Client export
exports.lation_chopshop:hasActiveContract()Returns
booleantruewhen the local player has a contract with time left
It asks the server, so call it from a thread. A contract that has run out is cleared by the check, paper included.
CreateThread(function()
if exports.lation_chopshop:hasActiveContract() then
-- show a hint
end
end)openMenu
Client export
exports.lation_chopshop:openMenu()Returns
- Nothing
Opens the broker menu wherever the player is. A player with a blacklisted job gets the job notice instead. Contracts can be requested from it anywhere, but the shops still need the player within 15 m of a broker.
RegisterCommand('chopmenu', function()
exports.lation_chopshop:openMenu()
end, false)Events
lation_chopshop:configUpdated
Server event
AddEventHandler('lation_chopshop:configUpdated', function(config) end)Parameters
configtable- Every section, in the shape of an export file: level keys are strings and coordinates are plain tables, not vectors. The
logssection is included.
Fires on the server after the settings load on start, and again after every save, reset or import in the admin panel. It is a local server event: listen with AddEventHandler in a server script.
AddEventHandler('lation_chopshop:configUpdated', function(config)
print(('Contract bonuses pay into %s'):format(config.contracts.account))
end)lation_chopshop:runtimeReady
Client event
AddEventHandler('lation_chopshop:runtimeReady', function() end)Fires on the client each time the settings reach it: after joining, after a restart of the resource, and after every save, reset or import in the admin panel. It carries no arguments; use it to refresh anything of your own that follows chop shop settings. The client never receives the logs section.
AddEventHandler('lation_chopshop:runtimeReady', function()
print('chop shop settings are in')
end)lation_chopshop:notify
Client event
TriggerClientEvent('lation_chopshop:notify', source, message, notifyType)Parameters
messagestring- The text
notifyTypestringoptionaldefaultnilsuccess,error,informand the like, as your notification system reads them
Send it from the server to show a notification through the system set under Setup > Notifications.
TriggerClientEvent('lation_chopshop:notify', source, 'The broker has a job for you', 'inform')Hooks
Hooks are plain functions in files that ship open. Edit them in place to change who can do what. Every server hook below is called, so a hooks file kept from an older version must still define each of them.
Server hooks
In lua/server/utils/hooks.lua.
CanDoChopping
Server hook
function Hooks.CanDoChopping(source, notify) endParameters
sourcenumber- The player's server id
notifybooleantrueto tell the player why they are refused
Returns
booleanfalseto refuse
Called before a player starts a chop, starts or finishes taking a part off, and inside the default CanRequestJob and CanParticipate. By default it refuses blacklisted jobs.
CanRequestJob
Server hook
function Hooks.CanRequestJob(source) endParameters
sourcenumber- The player's server id
Returns
booleanfalseto refuse the contract
Called when a contract is requested, at the broker or through issueContract. By default it calls CanDoChopping with notify on.
function Hooks.CanRequestJob(source)
if not source then return false end
-- add your own check here, return false to refuse
return Hooks.CanDoChopping(source, true)
endCanChopVehicle
Server hook
function Hooks.CanChopVehicle(source, entity) endParameters
sourcenumber- The player's server id
entitynumber- The car
Returns
booleanfalseto refuse the chop
The server's say on a car when a chop starts, after its own checks have passed: the player's contract and its class, the distance, the chop location and the blacklisted models. The client's CanStartChopping asks first, so an honest player hears no early, but this is the answer that counts. A refusal tells the player nothing on its own, so send your own notice, as the default does.
By default, while Allow owned vehicles is off, it refuses a car whose plate is in the owned vehicles table, whoever owns it. The lookup is one query by plate, kept for a minute (IsPlayerOwnedVehicle in lua/server/utils/vehicles.lua).
function Hooks.CanChopVehicle(source, entity)
-- Never take a car out of the police impound lot
if #(GetEntityCoords(entity) - vector3(409.0, -1623.0, 29.3)) < 40.0 then
TriggerClientEvent('lation_chopshop:notify', source, 'Not from the impound lot', 'error')
return false
end
if not runtime.start.allowOwned then
if IsPlayerOwnedVehicle(GetVehicleNumberPlateText(entity)) then
TriggerClientEvent('lation_chopshop:notify', source, locale('notify.owned-vehicle'), 'error')
return false
end
end
return true
endCanParticipate
Server hook
function Hooks.CanParticipate(source, netId) endParameters
sourcenumber- The player's server id
netIdnumber | nil- The car's net id;
nilmeans any car being chopped nearby
Returns
booleanfalseto keep the player off the car
Called before placing or removing stands, raising or lowering the car, opening the hood, starting or finishing a part, and breaking the drill. By default it is true when the player passes CanDoChopping and is within 30 m of the car (of any car being chopped, when netId is nil). Each of those actions, except breaking the drill, also needs the player within 12 m of the car's center, which the server checks on its own after the hook.
VehiclePartRemoved
Server hook
function Hooks.VehiclePartRemoved(source, entity, model, typeId, partId) endParameters
sourcenumber- The player who took the part off
entitynumber- The car
modelstringclass_and the contract's class id, such asclass_4typeIdstringwheel,door, ormockfor an engine bay partpartIdstringwheel_lf,wheel_rf,wheel_lr,wheel_rr,door_lf,door_rf,door_lr,door_rr,engine_battery,engine_radiator,engine_alternator,engine_ecuorengine_turbo
Called after a part is credited to a player. Does nothing by default.
function Hooks.VehiclePartRemoved(source, entity, model, typeId, partId)
if partId == 'engine_turbo' then
-- give a rare bonus item
end
endClient hooks
In lua/client/utils/hooks.lua.
CanStartChopping
Client hook
function Hooks.CanStartChopping(model, entity) endParameters
modelstring- Always
'contract_vehicle' entitynumber- The car
Returns
booleanfalseto refuse
Called when the player picks Start chopping, before the server is asked. By default it refuses blacklisted jobs with a notice and, while Allow owned vehicles is off, a car whose plate is in the owned vehicles table. It only decides what an honest player is told first: the server asks again in CanChopVehicle, and that answer counts.
StartedChopping
Client hook
function Hooks.StartedChopping(entity, model) endParameters
entitynumber- The car
modelstring- Always
'contract_vehicle'
Called once the server has opened the chop on that car. Does nothing by default.
function Hooks.StartedChopping(entity, model)
-- tell your own dispatch script where the car is
TriggerServerEvent('my_dispatch:chopStarted', NetworkGetNetworkIdFromEntity(entity))
endThe hooks files also define Hooks.GetContract and Hooks.IsInitiator (server) and Hooks.CanInteractWithVehiclePart (client). Nothing calls them, so editing them changes nothing.
Open files
The files that ship open, so you can read and change them:
| Path | What it's for |
|---|---|
lua/bridge/client.lua, lua/bridge/server.lua | Framework and inventory support, with slots for a custom framework or inventory |
lua/client/utils/ui.lua | Notifications, progress bars (and cancelling one), menus and dialogs, with the custom slots |
lua/client/utils/interact.lua | The interaction systems, with the custom slot |
lua/client/utils/hooks.lua, lua/server/utils/hooks.lua | The hooks above |
lua/server/utils/perms.lua | IsAdmin, the check for /chopadmin |
lua/server/utils/logging.lua | How logs reach fivemanage and Discord |
lua/server/utils/vehicles.lua | Owned vehicle lookup by plate (kept for a minute) and deletion: owned_vehicles.owner on ESX, player_vehicles.citizenid on Qbox and QBCore |
lua/client/theme.lua | How the panels follow lation ui's theme |
lua/shared/defaults.lua | The first-start defaults, read by the server only |
lua/config/icons.lua, lua/config/animations.lua | Icons, progress bars and animations |
locales/*.json | Every string |
Still stuck? Ask in our Discord: the people who write the scripts answer.
Last updated