Script docs
Boat Licenses
A boating school with a theory test, a jet ski exam and a boat exam that ends with a scored docking.

- Version
- v1.0.0
- Frameworks
- ESX · QBCore · Qbox
- Requires
- ox_lib, oxmysql, OneSync, esx_license (ESX only)
- Source
- Escrow
- resmon
- 0.00 ms idle · 0.02 ms in an exam
- Updated
- 10 Oct 2026
Boat Licenses: a boating school (Marine Academy in game) for ESX, QBCore and Qbox. Players take a theory test, then a watercraft exam and a boat exam. The exams run through slow zones and a buoy course, and the boat exam ends by docking in a berth. The docking gets a score and a grade, shown in a docking report.
The server checks everything. It spawns the boat and checks every buoy against the boat’s real position. Once per second it measures speed and damage, and it scores the docking from the boat’s position, heading and health. No license is granted without passing those checks.
Features
- Theory test: random questions from a bank in English and Spanish, corrected on the server (the answers never reach the client). Fee charged on start, optional wait before retrying after a fail.
- Watercraft exam: jet ski through the harbor slow zone and the buoy course, then stop back at the start.
- Boat exam: same course in a boat, then dock in the berth. Docking score of 0–100 and a grade from S to F, from how centered the boat is, how well it lines up with the berth (bow or stern in) and any impact while docking.
- Faults measured on the server: speeding in a slow zone (one fault per breach, with a tolerance) and collisions. One fault over the limit ends the exam.
- Exam HUD: current step, distance, time left, speed in knots, the zone’s speed limit and the fault count. Slow zones are drawn on the map during the exam.
- Academy tablet: exams with their fee and status, licenses, best docking score and exam history. Opens anywhere
with
/marinepad. Brand name and accent color configurable. - Instructor at the desk: an NPC with an ox_target / qb-target option. Without a target resource (or without
the NPC) the desk is a marker and an
[E]prompt. - Admin tool:
/marinetool desk | buoy | zone | berth | spawnprints a line ready to paste intoconfig.luaordata/routes.lua. - API: exports to check, grant and revoke licenses, and events for logs, battle passes or anticheats.
- Optimized: nothing runs while nobody is taking an exam. During an exam the client checks 10 times per second and draws markers every frame only near the current buoy. The server checks once per second, and only while an exam is running.
Requirements
- ox_lib and oxmysql
- One framework: ESX (with
esx_license), QBCore or Qbox - OneSync (on by default on current servers)
- Optional: ox_target or qb-target (without one, the desk uses a marker and an
[E]prompt)
Install
- Drop
xex_marineinto your resources folder. - ESX only: run
install/esx.sql(adds the three license types). The exam history table is created automatically. - Add to
server.cfgafter your framework, ox_lib and oxmysql:ensure xex_marine - Set the language with
setr ox:locale en(ores).
How it works
- Marine Academy (blip at Paleto Cove): talk to the instructor to open the tablet. The theory test comes first.
- Practical exams: each exam spawns its own boat at the dock and seats the player. The HUD shows the next buoy and the speed limit when inside a slow zone.
- Faults: going over the limit in a slow zone for more than a moment, or hitting something, adds a fault. Too many faults, too much damage or running out of time fails the exam.
- Docking (boat exam): stop inside the berth and hold still for two seconds. The server scores the docking and the report shows the grade.
- License: granted on the spot when the exam is passed. Every attempt goes to the exam history.
- Back to the desk: when the exam ends (passed, failed or cancelled) the player is taken back to where they started it, so nobody is left in the water.
The exam fee is not refunded when an exam is failed or cancelled. It is refunded if the boat cannot be spawned.
Configure
| File | What it contains |
|---|---|
config.lua |
All settings (table below) |
data/routes.lua |
Spawn point, slow zones, buoys and finish (stop or berth) of each exam |
questions/<locale>.lua |
Theory questions (server only) |
locales/<locale>.json |
Every text: notifications, HUD and UI |
bridge/server.lua |
Framework, money, licenses, vehicle keys, fuel |
bridge/client.lua |
Client-side fuel resources, notifications |
| Setting | What it does |
|---|---|
Config.Framework |
auto, or force esx / qb / qbox |
Config.Target |
auto (ox_target, then qb-target), force one, or none for the marker and [E] prompt |
Config.Accounts.school |
Account the exam fees are taken from |
Config.Licenses |
License names. Change them to match your existing license system |
Config.UI |
Brand name, accent color, tablet command and optional key |
Config.School |
Location and heading, instructor NPC (model, animation, spawn and prompt distance), blip, theory test (price, questions, correct answers needed, retry wait), and for each exam: on/off, price, boat model, route, faults allowed, damage allowed, time limit and (boat) minimum docking score |
Config.Faults |
Speed tolerance in knots, seconds over the limit before it counts, damage that counts as a collision |
Config.Docking |
Offset and angle that still score full marks, angle that scores 0, weight of the alignment, bow or stern in, impact penalty and crash threshold, seconds to hold still |
Config.Grades |
Score needed for each grade |
Config.Session |
Time allowed outside the boat, maximum plausible speed, plate prefix, history size |
Existing licenses
If your server already has boat licenses, put their names in Config.Licenses and players keep them:
Config.Licenses = { theory = 'theoretical_boat', watercraft = 'jetski', boat = 'practical_boat' }
Only want one practical exam? Set enabled = false on the other in Config.School.practical.
Moving the school or adding routes
Admins can run /marinetool in game, on the water or sitting in a boat:
| Command | Prints |
|---|---|
/marinetool desk |
Where the instructor stands and faces (Config.School.coords), from your own position |
/marinetool spawn |
Where the exam boat appears (position and heading) |
/marinetool buoy |
One buoy of the course |
/marinetool zone |
A slow zone centered here |
/marinetool berth |
The berth for the docking, with the heading the boat should face |
The line is printed in F8 and copied to the clipboard, ready to paste into config.lua or data/routes.lua.
Keys and fuel
bridge/server.lua gives keys with qbx_vehiclekeys or qb-vehiclekeys and sets fuel for ox_fuel.
bridge/client.lua covers LegacyFuel, cdn-fuel and ps-fuel. Add your own resource there if it isn’t listed.
Notifications go through Bridge.Notify in bridge/client.lua: swap lib.notify there for your own resource.
For developers
-- key = 'theory' | 'watercraft' | 'boat'
exports.xex_marine:HasLicense(source, key)
exports.xex_marine:GrantLicense(source, key) -- e.g. from a staff command
exports.xex_marine:RevokeLicense(source, key) -- e.g. after a court ruling or too many fines
exports.xex_marine:IsInExam(source)
| Event (server, local) | Arguments |
|---|---|
xex_marine:examStarted |
source, exam |
xex_marine:examFinished |
source, { exam, passed, reason, score, grade, faults, duration } |
xex_marine:licenseGranted |
source, licenseKey, licenseName |
xex_marine:suspicious |
source, reason, details (e.g. impossible speed between two checks) |
Use them for battle passes, logs or your anticheat without editing the resource.
Security
- The server spawns the exam boat, keeps the route and accepts each buoy only if the boat is really there and the player is driving it.
- Speed in slow zones, collisions, the time limit and the docking are measured on the server, not reported by the client.
- A boat that moves faster than
Config.Session.maxSpeedbetween two checks ends the exam and firesxex_marine:suspicious. - The theory answers stay on the server, and each test can be submitted once. Closing the test halfway uses up the attempt.
- Fees are charged on the server before the exam starts.
FAQ
Can players use their own boat? No. Exams use a boat spawned by the server, so the route, the speed and the damage can be checked.
Why does an exam say the spot is taken? One exam boat fits at the spawn point at a time. The next player can start as soon as the boat leaves.
Does it work with my license item or ID card?
Licenses are stored the framework’s way (esx_license or player metadata). ID card and vehicle shop resources that
read those licenses see them. Use the license names they expect in Config.Licenses.
Can I translate it?
Copy locales/en.json and questions/en.lua to your language and set setr ox:locale <code>.