Clock on at the yard, take a company truck, work city calls, haggle the price with the customer and drop the car where it belongs. The crane is driven by a remote in your hand, lever by lever.
A truck-mounted loader crane with a full towing job around it: company trucks, city calls, a cinematic price negotiation with the customer, pay, levels, and an in-game admin panel.
What is in the resource
A streamed crane truck with its own handling, variations and carcols.
A hand-held remote that works the crane lever by lever, with automatic hooking and bed loading.
Four call types: breakdowns, accidents, parking and repossessions.
Price negotiation with the customer, with personalities, tips and ratings.
Levels and XP that unlock the harder call types.
An admin panel that sets the yard, truck bays, call spots and drop-offs for your own map.
A job app for phones and tablets: the current call with GPS, levels, stats and the top operators. It installs itself on lb-phone, lb-tablet, Quasar, yPhone, okokPhone and GKS, and plugs into the Meteo Job Tablet as a full dispatch job.
Discord logging for shifts, deals, payouts, failures, admin actions and security events.
Almost nothing is configured in a file. Apart from the handful of options in
config.lua, the whole job is set up in game through /hiabadmin,
and changes save on the server and apply live.
Requirements
OneSync and a framework. Everything else is optional.
ServerOneSync.
FrameworkQbox, QBCore, ESX or standalone. Anything else can use the custom bridge.
StorageA writable storage/ folder. No SQL.
Optionalox_target or qb-target, ox_lib notifications, qbx_vehiclekeys or qb-vehiclekeys (any other keys script through the custom bridge), a fuel script, a phone or tablet.
Installation
Start it after your framework, then set up the map in game.
Put 9labs_hiab in your resources folder and keep that exact name.
Add it to server.cfg, after your framework, target, keys and fuel resources.
Set the basics in config.lua.
Restart the server.
As an admin, run /hiabadmin and set the yard, truck bays, call spots and drop-offs for your map.
ensure 9labs_hiab
Remove any other resource that streams mulef. Two copies of the same
vehicle model will fight each other.
Players must reconnect after you update the resource. It streams a vehicle, models
and an animation, and those only reload when a client connects — a resource restart alone
leaves people on the old data.
config.lua
Everything not listed here is set in game through /hiabadmin.
Option
Values
What it does
locale
en, de, bs
Language of the UI, notifications and logs
framework
auto, qbx, qb, esx, standalone, custom
auto picks the first running one: qbx_core, qb-core, es_extended, otherwise standalone
keys
auto, qbx, qb, none, custom
Keys for the company truck. auto uses qbx_vehiclekeys or qb-vehiclekeys, whichever is running; qbx and qb pick one of them. Any other keys script needs custom. With qb-vehiclekeys 1.6 and newer the keys go through its server exports
fuel
auto, ox_fuel, LegacyFuel, cdn-fuel, custom
auto uses whichever of ox_fuel, LegacyFuel or cdn-fuel is running. Any other value, or a named script that is not running, sets native fuel. custom calls Custom.setFuel in client/bridge.lua
interaction
9labs_ui, ox_target, qb-target, custom
How players talk to the yard foreman
interactKey, interactKeyLabel
control id and label
Key for the built-in prompt; default 38 / E
notify
9labs_ui, ox_lib, native, custom
Notification style
adminAces
list of ACEs
Who can open /hiabadmin. command works for txAdmin admins without a restart
remoteKey
key name
Default key for the crane remote (F7). Players rebind it in Settings → Key Bindings → FiveM
lockInventory
true, false, custom
Blocks the inventory and its hotbar keys while the remote is in hand, because the remote uses 1, 2 and 3. true works with ox_inventory, qb-inventory and every inventory that respects inv_busy
apps
phone / tablet names
Which phones and tablets get the job app. Set one to false to skip it. See Phone and tablet apps
standalone
startingCash, startingBank
Standalone only: starting balance of the built-in wallet, stored per licence
advanced.autoDispatch
true / false
true (default): a call comes in by itself. false: calls only on request, see Calls on request
advanced.requestCommand
command name or false
Chat command that asks for a call when autoDispatch = false; default hiabcall
advanced.jobHud
true / false
false hides the on-screen job panel and the "new call" banner. Notifications, blips, markers and the pay summary stay
advanced.privateTow
true / false
true (default): the crane lifts any empty car, players' own cars too, also while a call is open; the customer's car is still picked first when it is in reach. false: during a call only the customer's car can be hooked
debug
true / false
Extra console output
If you do not use the command ACE, give admin access the usual way:
add_ace group.admin hiab.admin allow
First setup
/hiabadmin writes to storage/settings.json on the server, and changes apply live.
Positions are set with Pick in world (aim, the mouse wheel turns drop-off bays, E
confirms), with My position, or by typing X, Y, Z and H. Any coordinate box also accepts
a pasted vector4(x, y, z, h).
Section
What you set
General
Required job and grade (leave empty for everyone), truck model, deposit, yard and foreman position, foreman ped, whether the yard foreman is used at all, truck bays, return radius, map blip, and whether the crane works outside the job
Pay
Account, base price, price per km, cap, bonus per level, max haggle, tip chance and maximum, late and damage penalties, cooldown after declining
Levels
XP per job, XP per level, max level, and which levels unlock accident and repossession calls
Calls
Time between calls, time limit, arrival and drop-off radius, call types and their weights, customer car models, call spots, and drop-offs
Customers
Ped models, time to decide in a conversation, and the weight of each personality
Crane
Speed of each movement, maximum distance from the truck for the remote, ground tolerance, leg reach, maximum hold time per lever, the maximum truck tilt (default 10°), and hooking: by hand (default) or automatic, with the size of the hook zone and the spreader angle tolerance
Sound
Volume and range
Discord logs
Webhook and what to log
Drop-off types
garageBreakdowns and accidents.
impoundAccidents, parking and repossessions.
scrapyardRepossessions.
A call goes to a drop-off of a matching type. If there is none of that type, it goes to
any drop-off you have set up, so add drop-offs of each type for calls to end in the right
place. The default settings have one impound, which takes every call until you
add more. Calls only stop when there are no drop-offs at all or every call spot is taken.
Player controls
F7 takes the remote out beside the truck. Everything else is on the remote.
Key
Action
F7
Take out or put away the remote, next to the truck
W / S
Hook out or in
A / D
Slew left or right, always as seen from the camera
Shift / Ctrl
Hook up or down
Q / E
Rotate the spreader
1
Stabilizer legs down, or fold the crane away
2
Hook the car once the hook hangs over it and the outline is green — on a call always the customer's car — or release it on the ground
3
Strap the car on the bed, or lift it off the bed
Space
Emergency stop
Backspace / Esc
Put the remote away
E, while driving the truck
Roof beacons on or off — native siren lights, silent
On a call the remote only works once the deal is agreed. The customer's car has an
outline on the ground: white when the crane cannot reach it, amber when it can, and green
once the hook hangs over the car, low enough and with the spreader in line with it. Then 2
grabs the wheels. Servers can switch on automatic hooking in /hiabadmin → Crane. At the drop-off a green bay marks where the car goes, and the hanging car's outline
turns green when the spot will be accepted.
Bring the truck back to get the deposit. Near the yard the truck bays show as green
boxes. The foreman shows whether the truck is in the yard, and asks first before ending a
shift without it.
The inventory is locked while the remote is in hand, so 1, 2 and 3 work the crane
instead of the hotbar.
Commands
Command
Who
What
/hiab
Player
Takes out or puts away the crane remote next to the truck. F7 is bound to it (remoteKey), and players rebind it in Settings → Key Bindings → FiveM
/hiabadmin
Admin
Opens the admin panel
/hiabcancel
Player
Drops the current call
/hiabcall
Player
Asks dispatch for a call. Only with advanced.autoDispatch = false; the name is advanced.requestCommand
hiabprof (F8)
Anyone
Measures this resource's client cost per part for five seconds and prints it to F8
Discord logs
Paste a webhook in the admin panel, save it, then send a test. Each category can be switched off.
In Discord: channel settings → Integrations → Webhooks → New webhook → Copy URL. Then in
/hiabadmin → Discord logs, paste it, save, and press Send test.
Category
Contents
Shifts
Start with deposit, account, truck plate and location; end with reason, length, jobs, earnings and whether the deposit was refunded
Deals
Call type, customer and personality, vehicle, pickup, drop-off, distance, offer, agreed price, and whether it was haggled
Payouts
Agreed price, late and damage penalties, pay, tip, total and account, payment result, rating, XP and level-up, and time on the call
Failed calls
Declined, customer left, abandoned or expired, with the reason
Admin
Settings saved or reset with the exact changes, and test calls
Security
Request floods and attempts to use someone else's truck, at most once a minute per player and reason
The webhook stays on the server and is always masked in the panel. Players are logged
with name, server ID, framework identity, licence and a Discord mention — and those mentions
never ping.
Phone and tablet apps
The job has its own app, and adds it by itself to every supported phone or tablet that is running.
The app shows the current call with its steps (drive, load, deliver), price and time left,
GPS to the car or the drop-off, cancelling the call, the player's level and stats, and the
top operators. On a tablet it uses a wider two-column layout.
Resource
How it is added
lb-phone, lb-tablet
Preinstalled custom app
qs-smartphone-pro, qs-smartphone
Custom app
yseries, yphone, yflip-phone
Preinstalled custom app
okokPhone
Custom app
gksphone, gks-tablet
Custom app
meteo-jobtablet
Registered as a job: Clock In and Out, live dispatch panel, accept or cancel calls, levels, rankings, achievements, task history and tournaments
Start 9labs_hiab after the phone or tablet. If the phone restarts, the app
is added again by itself. If it does not show up, the player's F8 console says
could not add the app to ….
Meteo Job Tablet
Clock In works at the yard, where the company truck is handed out. Anywhere else the tablet
says so and sets the GPS to the yard. New calls appear as tasks: accepting one sets the GPS,
and the panel follows the call through driving, loading and delivering. Pay, rating and XP are
still handled by the job, and the tablet records earnings, reputation and history. If you
renamed the tablet with Meteo's rename guide, change TABLET at the top of
server/integrations.lua.
RoadPhone Pro
RoadPhone has no export for apps. Add this to the AppStore list in its public/static/config/config.json:
To add a phone permanently, copy one of the entries in client/integrations.lua, which stays open under Asset Escrow.
Calls on request
Out of the box a call comes in by itself when the shift starts and after every job. Servers that
hand out work through their own phone, tablet or NPC can switch that off in the advanced
block of config.lua.
advanced = { autoDispatch = false, -- no call until the player asks for one requestCommand = 'hiabcall', jobHud = false, -- optional: no on-screen job panel, the job lives on the phone},
The player then asks for a call with the Request a call button in the job app, with
/hiabcall, or through your script with RequestMission. The cooldown between
jobs still applies, and the time limit of a call starts when it is handed out.
Example: the job on your own phone
9labs_hiab:stateChanged fires on every change of the shift or call, so the phone never has
to poll. Any phone with a notification export works the same way.
local function text(shift) if not shift then return 'Off shift' end local m = shift.mission if not m then return shift.manual and 'Ready, request a call' or ('Next call in %ss'):format(shift.next or 0) end if m.stage == 'travel' then return ('Go to the %s'):format(m.label or m.model) end if m.stage == 'load' then return ('Load the %s'):format(m.label or m.model) end return ('Deliver to %s'):format(m.dropLabel or 'the drop-off')endAddEventHandler('9labs_hiab:stateChanged', function(src, shift) exports['your_phone']:SendNotification(src, { app = 'HIAB', title = 'Towing', text = text(shift) })end)AddEventHandler('9labs_hiab:jobPaid', function(src, data) exports['your_phone']:SendNotification(src, { app = 'HIAB', title = 'Paid', text = ('$%d'):format(data.total) })end)-- a code or button in your phone app asks for a callRegisterNetEvent('your_phone:towing:request', function() local r = exports['9labs_hiab']:RequestMission(source) if not r.ok then exports['your_phone']:SendNotification(source, { app = 'HIAB', title = 'Towing', text = r.message }) endend)
Custom bridges
config.lua, locales/, both bridge files, both integration files and data/*.meta stay open under Asset Escrow.
Framework, money, jobs and keys
Set framework = 'custom', and keys = 'custom' if you also handle keys,
then fill in the Custom block at the top of server/bridge.lua:
function Custom.identity(src) return GetPlayerIdentifierByType(src, 'license') end -- stable character idfunction Custom.name(src) return GetPlayerName(src) endfunction Custom.addMoney(src, account, amount, reason) return false end -- return true on successfunction Custom.removeMoney(src, account, amount, reason) return false endfunction Custom.getMoney(src, account) return 0 endfunction Custom.hasJob(src, job, grade) return true end -- only used when a job is setfunction Custom.giveKeys(src, vehicle, plate) endfunction Custom.removeKeys(src, vehicle, plate) end
account is 'bank' or 'cash', whichever is set in
/hiabadmin → Pay. The money hooks must return true only once the
amount has really moved.
Prompt, target, notifications, fuel and inventory
Set interaction = 'custom', notify = 'custom', fuel = 'custom' or lockInventory = 'custom' as needed, then fill
in the Custom block at the top of client/bridge.lua:
function Custom.showPrompt(text, key) endfunction Custom.hidePrompt() endfunction Custom.notify(text, kind) end -- kind: 'info', 'success', 'error'function Custom.addTarget(id, coords, radius, options) return nil end -- options: { { label, icon, onSelect } }, return a handlefunction Custom.removeTarget(handle) endfunction Custom.setFuel(vehicle, level) end -- used when fuel = 'custom'function Custom.lockInventory(locked) end -- used when lockInventory = 'custom'
Exports
Read the shift, the player's profile and the crane state from the server, start and end shifts from your own UI, and drive the crane from the client.
Server exports
-- true when the player is on a HIAB shiftlocal onDuty = exports['9labs_hiab']:IsOnDuty(source)-- level and statistics of the player's current character, or nillocal p = exports['9labs_hiab']:GetProfile(source)-- p.level, p.xp, p.levelXp, p.nextXp, p.maxLevel, p.jobs, p.earned, p.best, p.km, p.rating, p.name-- the player's shift and current call, or false when off shiftlocal shift = exports['9labs_hiab']:GetShiftState(source)-- shift.truck (net id), shift.deposit, shift.next (seconds until the next call, automatic mode only)-- shift.manual = true and shift.ready (seconds until a call can be requested) with advanced.autoDispatch = false-- shift.mission = { id, kind, stage ('travel' | 'load' | 'deliver'),-- label, model, vehicle (net id of the customer's car), npc (net id of the customer),-- npcName, price, km, dropLabel, spot, drop, deadline (unix time), limit (seconds) }-- start or end a shift from your own scriptlocal r = exports['9labs_hiab']:StartShift(source) -- same rules as the foreman: only at the yardlocal r = exports['9labs_hiab']:StartShift(source, { spawn = vector4(x, y, z, h) })-- with an options table the yard distance is not checked; spawn is optional (default: a free truck bay)-- job, deposit and keys work as usual. Returns { ok, truck (net id) } or { ok = false, error }local r = exports['9labs_hiab']:EndShift(source) -- { ok, summary }, or { ok = false, confirm = true, error } when the truck is not backlocal r = exports['9labs_hiab']:EndShift(source, true) -- end it anyway; the deposit is kept when the truck is not backlocal r = exports['9labs_hiab']:EndShift(source, { force = true }) -- same as passing truelocal r = exports['9labs_hiab']:EndShift(source, { refund = true }) -- your script decides the deposit refund-- a truck that is back but still carries a car returns { ok = false, error } (no confirm): unload it firstlocal r = exports['9labs_hiab']:AbandonMission(source) -- { ok }-- hand out a call now (for advanced.autoDispatch = false, works in both modes)local r = exports['9labs_hiab']:RequestMission(source)-- { ok = true, mission } or { ok = false, reason, message, seconds }-- reason: 'off_duty' | 'busy' | 'cooldown' (seconds left) | 'too_fast' (2 s between requests) | 'no_calls'-- message is the localized text; the player is not notified, show it the way your script wants-- current crane state of a truck by network id, or nillocal state = exports['9labs_hiab']:GetCraneState(netId)-- state.pose (yaw, rotate, boom, jib, extension, legs, rigging, drop)-- state.mode ('empty' | 'lifted' | 'secured'), state.load, state.operator
Your own NPC or job script instead of the foreman
Switch Yard foreman off in /hiabadmin → General. The foreman, its blip and
prompt disappear, and shifts are started by your script — or by the Meteo Job Tablet, whose
Clock In then works anywhere and uses a free truck bay. Calls, the crane, pay, levels and the
phone and tablet apps keep working as usual.
-- server side, e.g. when your own NPC hands out the trucklocal r = exports['9labs_hiab']:StartShift(source, { spawn = vector4(471.0, -1305.0, 29.2, 115.0) })if not r.ok then print(r.error) end-- when your NPC takes the truck backexports['9labs_hiab']:EndShift(source, { refund = true })
Client exports
The action exports take the truck entity, or fall back to the nearest crane truck within reach
when it is left out. GetCranePose needs the truck entity.
exports['9labs_hiab']:DeployStabilizers(truck)exports['9labs_hiab']:StowCrane(truck)exports['9labs_hiab']:AttachNearbyVehicle(truck)exports['9labs_hiab']:LoadToBed(truck)exports['9labs_hiab']:ReleaseLoad(truck)local pose = exports['9labs_hiab']:GetCranePose(truck)local shift = exports['9labs_hiab']:GetShiftState() -- same shape as the server exportexports['9labs_hiab']:SetMissionWaypoint() -- GPS to the pickup or drop-off of the current callexports['9labs_hiab']:SetMissionWaypoint('depot') -- GPS to the yardlocal r = exports['9labs_hiab']:RequestMission() -- asks for a call for this player; the result message is shown as a notificationAddEventHandler('9labs_hiab:client:stateChanged', function(shift) end)
The server still checks distance, ownership and state exactly as it does for the
remote, so a client export cannot reach a truck the player could not operate by hand.
Server events
All of these are server-local TriggerEvent, so a client cannot fake them.
AddEventHandler('9labs_hiab:shiftStarted', function(src, data) -- data.truck (net id), data.plate, data.deposit, data.accountend)AddEventHandler('9labs_hiab:shiftEnded', function(src, data) -- data.reason: 'returned' | 'away' | 'truck_lost' | 'dropped' | 'character' -- data.jobs, data.earned, data.deposit, data.refunded (boolean)end)AddEventHandler('9labs_hiab:jobPaid', function(src, data) -- data.kind: 'breakdown' | 'accident' | 'parking' | 'repossession' -- data.pay, data.tip, data.total, data.paid (boolean), data.account -- data.stars, data.xp, data.leveled, data.km, data.late, data.damagedend)AddEventHandler('9labs_hiab:stateChanged', function(src, shift) -- every shift or call change; shift has the same shape as GetShiftState (false = off shift)end)AddEventHandler('9lh:craneEvent', function(truckNet, event, vehicleNet, at) -- event: 'deploy' | 'stow' | 'hooked' | 'secured' | 'unsecured' | 'released' -- at: world position where the car was set down ('released' only)end)
Example: pay a society a cut of every job
AddEventHandler('9labs_hiab:jobPaid', function(src, data) if data.paid then exports['your_banking']:AddSocietyMoney('tow', math.floor(data.total * .1)) endend)
The truck
Model mulef, streamed by the resource with its own handling, variations and carcols in data/.
The roof beacons use siren setting id 243. It lives in data/carcols.meta
and is referenced from data/carvariations.meta. If another resource on your
server already uses 243, change it in both files to a free id between 200 and 254 —
changing only one leaves the beacons dark.
Performance
Measured in game with the built-in profiler, not estimated.
Situation
Client cost
Away from any truck
about 0.00 ms
Near or inside a truck
0.03 to 0.05 ms
Operating the crane
about 0.12 ms
On the server, one light check runs every 15 seconds while nobody is on shift, and every 5
seconds during a shift. The crane only updates while a lever is held, and state is sent only
when it changes.
Run hiabprof and open F8 to measure it yourself: it reports this resource's client
cost per part over five seconds.
Troubleshooting
Problem
Fix
Old models, missing animation or odd handling after an update
Reconnect. Vehicle data and streamed files only reload on connect.
/hiabadmin says no permission
Add one of adminAces to your admin group, or use the command ACE.
No calls come in
Calls stop when there are no drop-offs at all or no free call spots; the player is told which. Add drop-offs and call spots in /hiabadmin → Calls. With advanced.autoDispatch = false calls only come on request.
Anticheat kicks when using the crane
Whitelist the events below.
Beacons do not light up
Siren id conflict. Change 243 in both meta files.
Discord test fails
The webhook must be a discord.com/api/webhooks/... link, and it has to be saved before you press Send test.
"The truck stands at X°"
The truck is on steeper ground than the crane allows. Park on flatter ground, or raise Maximum truck tilt in /hiabadmin → Crane, up to 15°. Keep call spots off steep dirt.
Deposit not returned
The truck must stand within the return radius of the foreman or of a truck bay (the green boxes), and must not be wrecked. The foreman shows whether the truck is in the yard.
"That is not the customer's car"
During a call the crane only takes the customer's car. Park so its outline turns green.
Inventory still opens with the remote out
Your inventory ignores invBusy and inv_busy. Set lockInventory = 'custom' and fill Custom.lockInventory in client/bridge.lua.
App missing on the phone
Start 9labs_hiab after the phone, and look for could not add the app in the player's F8 console.