Clock on at the depot, take a bucket truck and fix the city's traffic lights and distribution cabinets. Fetch the right parts from the truck, climb into the bucket, and repair every fault by hand in close-up — alone or as a crew of two.
A full electrician job around a working bucket truck, with every repair done by hand in close-up.
What is in the resource
A streamed utility truck with a working boom driven by a remote, a bucket you climb into through a fold-down ladder gate, a ladder rack and eight side compartments full of equipment.
905 traffic lights and 100 distribution cabinets taken from the GTA map and checked in game, so jobs work all over the map without placing anything.
Close-up repairs with the mouse: real screws that unscrew, bulbs and wiring in the signal head, fuses and terminals in the cabinet, a multimeter, isolating and energizing — and shocks when you work live.
Equipment that has to be fetched from the truck: every job needs a different fuse rating, lug size or bulb wattage, and only the compartments that hold what you need open.
Crew work for two electricians: a signal controller, a tool rope up to the bucket, cable tracing between two cabinets, remote isolation and pulling a colleague off a live cable.
Pay, levels, a crew bonus and a fast repair bonus.
A job app for phones and tablets that installs itself on lb-phone, lb-tablet, Quasar, yPhone, okokPhone and GKS, and plugs into the Meteo Job Tablet as a full dispatch job.
An admin panel for the depot, truck bays, pay, levels and job types, and Discord logging.
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 /elecadmin,
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, a vehicle keys script, a fuel script, a phone or tablet.
Installation
Start it after your framework and phone, then check the depot in game.
Put 9labs_electrician in your resources folder and keep that exact name.
Add it to server.cfg, after your framework, target, keys, fuel and phone resources.
Set the basics in config.lua.
Restart the server.
As an admin, run /elecadmin and check the depot, truck bays and pay for your server.
ensure 9labs_electrician
Players must reconnect after you update the resource. It streams a vehicle, props and
animations, 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 /elecadmin.
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. Crew members get keys too. With qb-vehiclekeys 1.6 and newer the keys go through its server exports
fuel
auto or a resource name
auto supports ox_fuel, LegacyFuel, cdn-fuel and native fuel
interaction
9labs_ui, ox_target, qb-target, custom
How players talk to the depot foreman
notify
9labs_ui, ox_lib, native, custom
Notification style
controls
control id and label per action
Keys of every on-screen action, see below
remoteKey
key name
Default key for the boom remote (F5). 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. true works with ox_inventory, qb-inventory and every inventory that respects inv_busy
adminAces
list of ACEs
Who can open /elecadmin and use /electruck and /electest. command works for txAdmin admins without a restart
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 job comes in by itself. false: jobs only on request, see Jobs on request
advanced.requestCommand
command name or false
Chat command that asks for a job when autoDispatch = false; default eleccall
advanced.jobHud
true / false
false hides the on-screen job panel. Notifications, blips, markers, the compartment markers and the pay summary stay
debug
true / false
Extra console output
On-screen keys
Every prompt reads its key from controls. When two actions share a key, the more important one wins and the other is shown greyed out.
controls = { bucket = { control = 38, label = 'E' }, -- climb into / out of the bucket ladder = { control = 47, label = 'G' }, -- take, carry, lean and stow the ladder bay = { control = 74, label = 'H' }, -- open a side compartment take = { control = 38, label = 'E' }, -- send an item up the rope service = { control = 38, label = 'E' }, -- start working on the traffic light or cabinet depot = { control = 38, label = 'E' }, -- talk to the depot foreman accept = { control = 246, label = 'Y' }, -- accept a crew invite decline = { control = 306, label = 'N' }, -- decline a crew invite ctrl = { control = 38, label = 'E' }, -- signal controller, pulling a colleague off a live cable ctrlAlt = { control = 47, label = 'G' }, -- signal controller: switch the signal off again},
If you do not use the command ACE, give admin access the usual way:
add_ace group.admin electrician.admin allow
First setup
/elecadmin writes to storage/settings.json on the server, and changes apply live.
Positions can be picked in the world, taken from your position, or typed in. Traffic lights and cabinets need no setup at all — they come from the GTA map.
Section
What you set
General
Required job and grade (leave empty for everyone), deposit, depot and foreman position, foreman ped, whether the foreman is used at all, truck bays, return radius, map blip
Pay
Account, pay range for traffic lights and for cabinets, bonus per level, fast repair bonus and its time, crew bonus
Levels
XP per job, XP per level, max level
Jobs
Dispatch distance, delay between jobs, cooldown after dropping a job, share of bulb and wiring faults, share of fuse and terminal faults, and which job types are on and how often they come
Sound
Volume and range
Discord logs
Webhook and what to log
How the job works
Take a truck, fetch the parts, climb up or open the cabinet, and repair the fault by hand.
Start a shift at the depot foreman. A deposit is taken and a company truck is spawned with keys and fuel.
The dispatcher sends a job: a traffic light or a distribution cabinet. A GPS route and a pulsing marker above the faulty object show the way; the marker disappears once you are there.
Fetch the equipment. The HUD lists what the job needs, and the compartments that hold it are marked around the truck in order: the next one gets a big arrow, the rest a number. Press H at a compartment to open it and look inside, hover over an item to see what it is and whether you need it, and click to take it. Compartments with nothing you need stay shut.
Work in close-up with the mouse: hold the left button on a marker. Doing things in the wrong order, or touching anything live, gets you a shock.
When the job is paid the tools go back into the truck, so the next job starts with fetching again.
Traffic lights
Raise the bucket to the signal head. Unscrew the four screws, open the cover, replace the burnt bulb or reconnect the loose wire, close it and screw it tight. The job is done the moment the last screw is in, and the light runs a test cycle.
Distribution cabinets
Unlock with the triangle key and open the doors. Probe every phase with the multimeter until you find the dead or unstable one, switch off the main isolator, replace the fuse or tighten the terminal, switch back on, close and lock. Probing too fast, or touching a live part, gives you a shock.
Cabinets with the doors against a wall are skipped automatically. When you arrive the
doors are checked, the dispatcher sends a new job, and the cabinet is left out for you. Once
two different players report the same one it is removed for good
(data/cabinets_blocked.json).
Cabinet locations and custom maps
The job uses 100 cabinets spread over the whole map, in pairs close enough for crew work. Every
one was checked in game: free space in front of the doors, room to stand, a clear close-up view,
and no tilted cabinets or cabinets set into walls. If your server replaces parts of the map, run
/elecsurvey as an admin: it visits every location, checks the same things and writes
the result to storage/cabinet_survey.json, with the reason for every location that
fails. /elecsurvey 40 starts at location 40, and running the command again stops it.
The bucket and remote
Climb in through the ladder gate, then drive the boom from the bucket or from the ground.
Stand behind the truck under the stowed bucket and press E. The gate folds down into a ladder,
the telescopic sections slide down to the ground, you climb up and the gate closes behind you.
Leaving works the same way in reverse. While the gate is open the boom cannot move.
The remote (F5) works in the bucket and next to the truck, but only on shift. The bucket always
stays level, the rider turns with the camera, and the side ladder can be carried off the rack
and leaned against a wall or pole (G).
Only one player can hold the remote of a truck at a time, on the ground or in the bucket. The
next one gets it once the remote is put away. If the holder crashes, disconnects, dies or walks
away, the remote is released by itself within about 8 seconds and the boom stops. Other players
see the rider standing in the bucket and moving with it.
Crew work
Two electricians, one truck, one job — and a crew bonus for both.
On shift, open the foreman's tablet and invite a colleague standing next to you. They accept
with Y or decline with N. The colleague uses your truck and keys without a deposit and sees
the same jobs. Both are paid for every job they were present for, with the crew bonus from
/elecadmin → Pay, and both get XP.
Working alone stays exactly as described in How the job works. In a crew the jobs change:
Traffic lightsA signal controller cabinet near the crossing is marked on the map. The colleague on the ground switches the signal off there before the one in the bucket touches bulbs or wires, and at the end switches it on and runs the light test. The one on the ground can also send items up to the bucket on a rope and drive the boom with the remote.
CabinetsTwo linked cabinets, A and B, and the fault is in B. At A you find the dead phase and clip on a tone generator. At B the colleague probes the cables until one beeps. Then A switches off the feed, B repairs, A switches it back on, and both cabinets are closed and locked.
RescueWhoever touches a live part gets stuck on the cable. The colleague has a few seconds to run over and pull them off; too late means a serious injury.
The crew leader can remove the colleague, and the colleague can leave, on the tablet.
When the leader ends the shift the colleague's shift ends too. Only the leader can drop a job.
Player controls
Every prompt shows its key on screen; these are the defaults.
Key
Action
F5
Take out or put away the boom remote, next to the truck or in the bucket (on shift)
W / S
Reach out or in
A / D
Slew left or right, always as seen from the camera
E / Q
Bucket up or down
1
Stow the boom
Space
Emergency stop
Backspace / Esc
Put the remote away
E behind the truck
Climb into or out of the bucket
G
Take, carry, lean and stow the ladder
H
Open a side compartment and look inside; click an item to take it, H or Esc closes
E at the job
Start working on the traffic light or cabinet
Left mouse (close-up)
Hold on a marker to do that step
Mouse wheel (close-up)
Zoom
Esc / Backspace (close-up)
Leave the close-up
Y / N
Accept or decline a crew invite
Commands
Command
Who
What
/elecadmin
Admin
Opens the admin panel
/electruck
Admin
Spawns a truck next to you, for testing
/electest [traffic|cabinet]
Admin
Gives you the nearest job of that type
/eleccancel
Player
Drops the current job (in a crew only the leader). The next job comes after the cooldown
/eleccall
Player
Asks dispatch for a job. Only with advanced.autoDispatch = false; the name is advanced.requestCommand
/elecboom
Anyone
Takes out the boom remote next to a truck, also off shift, for testing
/elecsurvey [from]
Admin
Visits every cabinet location, checks it and writes storage/cabinet_survey.json. Run again to stop
elecprof (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
/elecadmin → Discord logs, paste it, save, and press Send test.
Category
Contents
Shifts
Start with deposit, account, truck plate and location; crew joins; end with reason, length, jobs, earnings and whether the deposit was refunded
Payouts
Job type, amount, location and crew size
Admin
Settings saved or reset with the exact changes
Security
Request floods, at most once a minute per player
The webhook stays on the server and is always masked in the panel.
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 job with its steps (drive, repair, close and test), the fault, the
crew, GPS to the job or the depot, dropping the job, the player's level and stats, and the best
electricians. 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, reject or cancel jobs, tablet groups, levels, rankings, achievements, task history and tournaments
Start 9labs_electrician after the phone or tablet. If the phone restarts,
the app is added again by itself. If it does not show up, the server console says
could not add the app to ….
Meteo Job Tablet
Clock In works at the depot, where the company truck is handed out; anywhere else the tablet
sets the GPS to the depot. New jobs appear as tasks: accepting one sets the GPS, and the panel
follows the job through driving, repairing and closing. Pay and XP are still handled by the
job, and the tablet records earnings, reputation, achievements and history. If you renamed the
tablet with Meteo's rename guide, change TABLET at the top of
server/integrations.lua.
With advanced.autoDispatch = false the tablet asks for the next job by itself after
Clock In, after a finished job and after a rejected one, so a new task always shows up on it.
Tablet groups work as crews: the group leader clocks in, and the first group member joins the
crew, shares the truck and gets paid with the crew.
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.
Jobs on request
Out of the box a job 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 job until the player asks for one requestCommand = 'eleccall', jobHud = false, -- optional: no on-screen job panel, the job lives on the phone},
The player then asks for a job with the Request a job button in the job app, with
/eleccall, or through your script with RequestJob. The cooldown after a
dropped job still applies. A cabinet that faces a wall is still replaced by a new job automatically.
What the phone can show
task.kind
task.fault
What the player does
traffic
bulb / wire
Bucket up to the signal head: unscrew four screws, open, replace the bulb or reconnect the wire, close, screw in
cabinet
fuse / lug
Unlock, open, measure the phases, isolate, replace the fuse or tighten the lug, energize, close, lock
task.stage is travel (nothing opened yet), fix (opened, fault not
fixed), close (fixed, close it up) or done. In a crew the traffic controller and
the second cabinet add their own steps, see Crew work.
Example: the job on your own phone
9labs_electrician:stateChanged fires on every change of the shift or job, so the phone never
has to poll. Any phone with a notification export works the same way.
local kinds = { traffic = 'Traffic light', cabinet = 'Distribution cabinet' }local faults = { bulb = 'burnt bulb', wire = 'loose wire', fuse = 'blown fuse', lug = 'loose terminal' }local function text(shift) if not shift then return 'Off shift' end local t = shift.task if not t then return shift.manual and 'Ready, request a job' or ('Next job in %ss'):format(shift.next or 0) end if t.stage == 'travel' then return ('%s: go to the site'):format(kinds[t.kind]) end if t.stage == 'close' then return ('%s: close it up'):format(kinds[t.kind]) end return ('%s: %s'):format(kinds[t.kind], faults[t.fault] or 'repair')endAddEventHandler('9labs_electrician:stateChanged', function(src, shift) exports['your_phone']:SendNotification(src, { app = 'Electrician', title = 'LSDWP', text = text(shift) })end)AddEventHandler('9labs_electrician:jobPaid', function(src, data) exports['your_phone']:SendNotification(src, { app = 'Electrician', title = 'Paid', text = ('$%d'):format(data.total) })end)-- a code or button in your phone app asks for a jobRegisterNetEvent('your_phone:electrician:request', function() local r = exports['9labs_electrician']:RequestJob(source) if not r.ok then exports['your_phone']:SendNotification(source, { app = 'Electrician', title = 'LSDWP', text = r.message }) endend)
Custom bridges
config.lua, locales/, both bridge files, both integration files and data/ 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
/elecadmin → 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' or lockInventory = 'custom' as needed, then fill
in the Custom block at the top of client/bridge.lua:
function Custom.showContext(items) end -- items = { { key = 'E', text = '...', disabled = nil|true }, ... }function Custom.hideContext() 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) endfunction Custom.lockInventory(locked) end -- used when lockInventory = 'custom'
Exports
Read the shift, the crew, the player's profile and the boom state, and start, end or drop work from your own scripts.
Server exports
-- true when the player is on an electrician shift (leader or crew member)local onDuty = exports['9labs_electrician']:IsOnDuty(source)-- level and statistics of the player's current character, or nillocal p = exports['9labs_electrician']:GetProfile(source)-- p.level, p.xp, p.levelXp, p.nextXp, p.maxLevel, p.jobs, p.earned, p.best, p.name-- the player's shift and current job, or false when off shiftlocal shift = exports['9labs_electrician']:GetShiftState(source)-- shift.truck (net id), shift.deposit, shift.jobs, shift.earned, shift.next (seconds to the next job or nil)-- shift.manual = true and shift.ready (seconds until a job can be requested) with advanced.autoDispatch = false-- shift.crew = { leader (bool), members = { { id, name, leader } } } or nil-- shift.task = { kind ('traffic' | 'cabinet'), fault ('bulb' | 'wire' | 'fuse' | 'lug'),-- stage ('travel' | 'fix' | 'close' | 'done'), x, y, z } or nil-- start or end a shift from your own scriptlocal r = exports['9labs_electrician']:StartShift(source) -- same rules as the foreman: only at the depotlocal r = exports['9labs_electrician']:StartShift(source, { spawn = vector4(x, y, z, h) })-- with an options table the depot distance is not checked; spawn is optional (default: a free truck bay)-- Returns { ok, truck (net id) } or { ok = false, error }local r = exports['9labs_electrician']:EndShift(source) -- { ok, summary }, or { ok = false, confirm = true, error } when the truck is not backlocal r = exports['9labs_electrician']:EndShift(source, true) -- end it anyway; the deposit is kept when the truck is not backlocal r = exports['9labs_electrician']:EndShift(source, { refund = true }) -- your script decides the deposit refundlocal r = exports['9labs_electrician']:AbandonJob(source) -- { ok } or { ok = false, error }-- hand out a job now (for advanced.autoDispatch = false, works in both modes; in a crew it is the crew's job)local r = exports['9labs_electrician']:RequestJob(source)-- { ok = true, task } or { ok = false, reason, message, seconds }-- reason: 'off_duty' | 'busy' | 'cooldown' (seconds left) | 'too_fast' (2 s between requests) | 'no_jobs'-- message is the localized text; the player is not notified, show it the way your script wants-- crew of a player, or nil: { leader (bool), members = { { id, name, leader } } }local crew = exports['9labs_electrician']:GetCrew(source)-- boom state of a truck by network id, or nillocal boom = exports['9labs_electrician']:GetBoomState(netId)-- boom.pose (yaw, lift, unfold), boom.rider, boom.operator, boom.holder (who holds the remote), boom.gate, boom.boarding, boom.alighting
Your own NPC or job script instead of the foreman
Switch Depot dispatcher off in /elecadmin → 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. Crew invites need the foreman's tablet,
so keep the foreman on if you want crews.
-- server side, e.g. when your own NPC hands out the trucklocal r = exports['9labs_electrician']:StartShift(source, { spawn = vector4(751.72, 130.15, 77.63, 275.74) })if not r.ok then print(r.error) end-- when your NPC takes the truck backexports['9labs_electrician']:EndShift(source, { refund = true })
Client exports
local shift = exports['9labs_electrician']:GetShiftState() -- same shape as the server exportexports['9labs_electrician']:SetJobWaypoint() -- GPS to the current jobexports['9labs_electrician']:SetJobWaypoint('depot') -- GPS to the depotlocal r = exports['9labs_electrician']:RequestJob() -- asks for a job for this player; the result message is shown as a notificationAddEventHandler('9labs_electrician:client:stateChanged', function(shift) end)
Server events
All of these are server-local TriggerEvent, so a client cannot fake them.
AddEventHandler('9labs_electrician:shiftStarted', function(src, data) -- data.truck (net id), data.plate, data.deposit, data.accountend)AddEventHandler('9labs_electrician:shiftEnded', function(src, data) -- data.reason: 'returned' | 'away' | 'dropped' | 'character' | 'crew' -- data.jobs, data.earned, data.deposit, data.refunded (boolean)end)AddEventHandler('9labs_electrician:jobPaid', function(src, data) -- fires once for every crew member who was paid -- data.kind: 'traffic' | 'cabinet', data.fault: 'bulb' | 'wire' | 'fuse' | 'lug' -- data.pay, data.level, data.speed, data.crew (bonus amounts), data.total, data.paid (boolean), data.account -- data.xp, data.leveled, data.crewSizeend)AddEventHandler('9labs_electrician:stateChanged', function(src, shift) -- every shift or job change; shift has the same shape as GetShiftState (false = off shift)end)
Example: pay a society a cut of every job
AddEventHandler('9labs_electrician:jobPaid', function(src, data) if data.paid then exports['your_banking']:AddSocietyMoney('electrician', math.floor(data.total * .1)) endend)
The truck
Model 9leutility, streamed by the resource with its own handling, variations and carcols in data/.
A utility truck with a working three-part boom, a level bucket with a fold-down telescopic
ladder gate, a ladder rack, and eight side compartments stocked with keys, gloves, meters,
tools, fuses, lugs, bulbs and ferrules.
Performance
No idle cost on the server, and next to nothing on the client away from trucks and jobs.
Server timers only run while a lever is held, a job is active or a gate is moving, and state is
sent only when it changes. On the client, nothing runs every frame unless you are near a truck,
at a job or working. The tools and parts in the truck compartments are only created when a
compartment opens, so a parked truck stays light for everyone around it.
Run elecprof 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.
/elecadmin says no permission
Add one of adminAces to your admin group, or use the command ACE.
No jobs come in
Enable at least one job type in /elecadmin → Jobs and check the dispatch distance.
No keys for the truck
Check keys in config.lua. With auto, qbx_vehiclekeys or qb-vehiclekeys must be running; otherwise use custom.
"Someone else is operating this boom"
Another player holds the remote of that truck. Wait until they put it away; if they crashed or left, it is released within about 8 seconds.
Cabinet jobs at odd places on a custom map
Run /elecsurvey and check storage/cabinet_survey.json for the failing locations.
The remote key does nothing
It only works on shift and next to the truck. Another resource may use the same key: rebind it in Settings → Key Bindings → FiveM, or use /elecboom.
"There is nothing in this compartment that you need"
Only compartments that hold something the current job still needs open. The next one to visit has the big arrow.
"You need …" when starting a step
The job needs that item from the truck. The compartment that holds it lights up.
Anticheat kicks
Whitelist the events below.
Discord test fails
The webhook must be a discord.com/api/webhooks/... link, and it has to be saved before you press Send test.
Deposit not returned
The truck must stand within the return radius of the foreman or of a truck bay, and must not be wrecked.
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_electrician after the phone, and look for could not add the app in the server console.