How Integrations Work#
Nothing in the job's own logic knows which framework, key script or clothing system you run. Each of those sits behind a small, open bridge file, and the right one is picked from what is already running when the resource starts:
Layer | Supported | Without it |
|---|---|---|
Framework | es_extended · qb-core · qbx_core | Required. There is no standalone mode. |
Vehicle keys | qs-vehiclekeys · qb-vehiclekeys · qbx_vehiclekeys · F_RealCarKeysSystem · fivecode_carkeys · glfp10_carkeys · mono_carkeys · ti_vehicleKeys · vehicles_keys · wasabi_carlock · xd_locksystem | No key is handed out. The bus spawns with its engine running. |
Target | ox_target · qb-target | Players walk up to the dispatcher and press E. |
Clothing | esx_skin + skinchanger · qb-clothing, and anything that answers its events | Turn |
Fuel | LegacyFuel, or any resource with a | The tank stays as the bus spawned. |
Text UI | qs-textui | Plain 3D text drawn by the script. |
Notifications | Built in, or any system through | — |
Inventory | qs-inventory | No inventory lock while changing clothes, and no quests. |
Job money | qs-job-management | Route payouts go into the driver's cash. |
Scoreboard | qs-scoreboard, or any script that reads | — |
Database | oxmysql | Required. |
Detection only sees resources that are already started. Anything you want it to find has to be
ensured aboveqs-busjobinserver.cfg.
Notifications and Prompts#
Notifications#
By default the job draws its own notifications — level-ups, payouts, crash penalties, fares — with a sound for each kind. To send them to your own system instead, turn Config.DefaultNotifications off and fill in SendTextMessage in config/config.lua. It runs on the client and receives the text and a type — 'info', 'error' or 'success':
-- config/config.lua
Config.DefaultNotifications = false
function SendTextMessage(msg, type)
-- ox_lib
lib.notify({ description = msg, type = type == 'info' and 'inform' or type })
-- or any other resource, e.g.:
-- exports['okokNotify']:Alert('Bus', msg, 5000, type)
endThe "press E" hints in the corner are part of the interface and are drawn either way.
3D prompts and the currency#
With qs-textui running, the "[E]" prompts at the dispatcher, the stops and the hand-back point are drawn by it; otherwise the script draws plain 3D text. Config.DrawText3D = false leaves only the hint in the corner. Every price shown uses the CURRENCY_TYPE symbol from your language in translations.lua, placed by Config.CurrencyPosition — 'prefix' for $500, 'suffix' for 500 zł.
Vehicle Keys#
The keys of the bus are handed to the driver when it spawns and taken back when it is removed, through whichever key script is running. Each one has its own bridge in client/vehiclekeys/, and the detected name ends up in Config.Vehiclekeys. To pin one by hand — when two key scripts run at once, or yours was renamed — replace the detection line:
-- config/config.lua
-- Auto, the default: the first running resource in the list wins.
Config.Vehiclekeys = dependencyCheck(vehicleKeys) or 'none'
-- Pinned: always this bridge, whatever is running.
Config.Vehiclekeys = 'qs-vehiclekeys'Adding a key system#
Copy a bridge
Copy
client/vehiclekeys/none.lua— or the bridge closest to yours — to a new file named after your key resource. Every file in that folder is loaded, and each one only runs when its name matchesConfig.Vehiclekeys.Change the guard and the two calls
lua -- client/vehiclekeys/my_keys.lua if Config.Vehiclekeys ~= 'my_keys' then return end function AddVehiclekeys(vehicle, plate) exports['my_keys']:GiveKeys(plate) end function RemoveVehiclekeys(vehicle, plate) exports['my_keys']:RemoveKeys(plate) endRegister it for detection
Add one line to the
vehicleKeyslist inconfig/config.lua— the running resource's name, then your bridge's name:lua local vehicleKeys = { ['qs-vehiclekeys'] = 'qs-vehiclekeys', -- ... ['my_keys'] = 'my_keys', }
Target#
The target is not detected: switch it on and name it. The dispatcher then carries a Start working option within 3 m (TARGET_START_WORKING in translations), and the key prompt at the dispatcher is turned off. Stops and the hand-back point always use the key.
Config.Target = true
Config.TargetScript = 'ox' -- 'ox' for ox_target, 'qb' for qb-targetClothing#
On ESX the uniform is put on through esx_skin and skinchanger:loadClothes, and the character's saved skin is loaded back afterwards. On QBCore and Qbox it goes through qb-clothing:client:loadOutfit and is restored with qb-clothes:loadPlayerSkin, so appearance scripts that answer the qb-clothing events work as they are. For illenium-appearance, client/clothing.lua has its reload event ready as a commented line beside the qb-clothes one:
-- client/clothing.lua, when the route ends (QBCore / Qbox)
TriggerServerEvent("qb-clothes:loadPlayerSkin")
--TriggerEvent("illenium-appearance:client:reloadSkin")The outfit itself is set at the top of the same file — see The Uniform.
Fuel#
With Config.Fuel = true the bus is filled to 100 when it spawns, through Config.FuelExport:SetFuel(vehicle, 100.0). Point it at any fuel resource with that export:
Config.Fuel = true
Config.FuelExport = exports['LegacyFuel']
-- Config.FuelExport = exports['cdn-fuel']
-- Config.FuelExport = exports['ps-fuel']Leave Config.Fuel off for fuel scripts without a SetFuel export.
Discord Logs#
Everything that moves money or experience can be posted to Discord as an embed, coloured by kind so a channel reads at a glance. Logging is off until you set Enabled and paste a webhook URL.
Event | Sent when | Fields |
|---|---|---|
| A route is taken from the panel. | Bus, stops, pay, experience |
| The bus is handed back and the route pays. | Pay, experience, crash penalty |
| A route is abandoned from the panel, or left behind by disconnecting. | Passengers aboard |
| A route payout, and every ticket fare a driver receives. | Amount, what for, passenger |
| Experience is granted. | Gained, level before → after, toward next, total |
-- config/config.lua
Config.Webhooks = {
Enabled = true,
Url = 'https://discord.com/api/webhooks/…', -- the shared channel
Name = 'qs-busjob',
Avatar = '', -- an image URL, or '' for none
Timestamp = true,
Events = {
MissionStarted = true, -- to the shared Url
MissionFinished = true,
MissionAbandoned = false, -- not sent
MoneyPaid = 'https://discord.com/api/webhooks/…', -- a staff channel of its own
ExperienceGiven = true,
},
}Each event takes true (the shared Url), false (off) or a URL of its own. Every embed's footer names the player, their server id and their identifier.
qs-inventory Quests#
With qs-inventory running and Config.QuasarInventoryQuests = true, three bus driver quests are created for every player when they load in — and for everyone already online when the resource starts — and progress is reported as they play:
Quest | Progresses when |
|---|---|
| The driver levels up. |
| A route is completed and paid. |
| A player passenger pays the driver a ticket. |
Titles, descriptions and rewards are set in server/missions.lua. Keep the quest name values as they are: progress is reported against those names.
Job Money and the Scoreboard#
When qs-job-management is running, each route payout is handed to it (AddJobMoney, under the bus_driver job) instead of going straight into the driver's cash. Ticket fares and leaderboard prizes are always paid as cash.
qs-scoreboard counts the bus drivers on duty through the getOnDutyCount export. A driver is on duty from the moment a route starts until it is completed or abandoned. Any other scoreboard can read the same export — see Commands and Exports.
Languages#
Eleven complete translations ship in config/translations.lua: English (en), Italian (it), Spanish (es), French (fr), Portuguese (pt), German (de), Russian (ru), Turkish (tr), Dutch (nl), Simplified Chinese (cn) and Traditional Chinese (zh-tw). Pick one with Config.Language at the top of the file.
-- config/translations.lua
Config.Language = 'es'Any line can be reworded in place — change only the text inside the quotes. A line missing from your language falls back to English rather than showing its raw key. For a language that is not listed, copy the ['en'] block, give it a new code, translate it and set Config.Language to that code.



