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 | spawn prints a line ready to paste into config.lua or data/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

  1. Drop xex_marine into your resources folder.
  2. ESX only: run install/esx.sql (adds the three license types). The exam history table is created automatically.
  3. Add to server.cfg after your framework, ox_lib and oxmysql:
    ensure xex_marine
  4. Set the language with setr ox:locale en (or es).

How it works

  1. Marine Academy (blip at Paleto Cove): talk to the instructor to open the tablet. The theory test comes first.
  2. 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.
  3. 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.
  4. Docking (boat exam): stop inside the berth and hold still for two seconds. The server scores the docking and the report shows the grade.
  5. License: granted on the spot when the exam is passed. Every attempt goes to the exam history.
  6. 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.maxSpeed between two checks ends the exam and fires xex_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>.