# esx_dmvschool (/docs/addons/esx_dmvschool)

esx_dmvschool is the driving school for ESX Legacy. Players first pass a multiple-choice theory test (`dmv` license) and then take practical road tests for cars (`drive`), motorcycles (`drive_bike`) and trucks (`drive_truck`). Licenses are stored through [esx_license](/docs/addons/esx_license), so police and vehicle scripts can check them.

## Overview [#overview]

| Fact | Value |
| --- | --- |
| Resource | `esx_dmvschool` |
| Side | Client + server + NUI |
| Dependencies | `esx_lib`, `es_extended`, `esx_license` (plus `oxmysql`) |
| Licenses | `dmv`, `drive`, `drive_bike`, `drive_truck` |

### In-game flow [#in-game-flow]

1. Go to the DMV marker (blip sprite `408`) and press **E**.
2. Pay for the theory test. Answer 10 questions; 5 correct answers pass and grant `dmv`.
3. With `dmv`, the menu offers the road tests you do not own yet. The test vehicle spawns at `VehicleSpawnPoint`.
4. Drive through all checkpoints. Speeding and vehicle damage count as errors. Speed limits switch between `residence`, `town` and `freeway` at certain checkpoints.
5. At the last checkpoint the vehicle is deleted and, if you passed, the license is granted.

## Installation [#installation]

<Steps>
<Step>

### Database [#database]

esx_dmvschool uses the esx_lib SQL catalog and inserts the four `licenses` rows automatically, labeled in your `esx:locale`. The bundled `esx_dmvschool.sql` contains French labels; localized versions live in `localization/`.

```sql title="esx_dmvschool.sql (English labels)"
INSERT INTO `licenses` (`type`, `label`) VALUES
	('dmv', 'Driving Permit'),
	('drive', 'Drivers License'),
	('drive_bike', 'Motorcycle License'),
	('drive_truck', 'Commercial Drivers License');
```

</Step>
<Step>

### server.cfg [#servercfg]

```bash title="server.cfg"
ensure esx_license
ensure esx_dmvschool
```

</Step>
</Steps>

## Configuration [#configuration]

| Key | Default | Description |
| --- | --- | --- |
| `Config.DrawDistance` | `10.0` | Marker draw distance. |
| `Config.MaxErrors` | `5` | Error budget. More than this ends the test early; you pass only with fewer than this. |
| `Config.SpeedMultiplier` | `3.6` | Converts m/s to your unit. `3.6` = km/h, `2.236936` = mph. |
| `Config.SpeedingErrorDelay` | `5000` | Cooldown in ms after a speeding error before the next one can count. Values below 5000 are raised to 5000. |
| `Config.Locale` | `GetConvar('esx:locale', 'en')` | Locale used for texts. |
| `Config.Prices` | see below | Price per test type, paid in cash. |
| `Config.VehicleModels` | see below | Vehicle model per road test. |
| `Config.SpeedLimits` | see below | Speed limit per zone type, in the unit of `SpeedMultiplier`. |
| `Config.Zones` | `DMVSchool`, `VehicleSpawnPoint` | Marker zones. `Type = -1` hides a marker. |
| `Config.CheckPoints` | 16 checkpoints | Ordered route with `Pos` and an `Action` function. |

```lua title="config.lua (defaults)"
Config.Prices = {
	dmv         = 500,
	drive       = 2500,
	drive_bike  = 3000,
	drive_truck = 5000
}

Config.VehicleModels = {
	drive       = 'blista',
	drive_bike  = 'sanchez',
	drive_truck = 'mule3'
}

Config.SpeedLimits = {
	residence = 50,
	town      = 80,
	freeway   = 120
}
```

### Checkpoints [#checkpoints]

Each checkpoint runs its `Action` when the player reaches it. The function receives the player ped, the test vehicle and a setter for the current speed zone:

```lua title="config.lua"
{
	Pos = { x = -453.790, y = -1444.726, z = 27.665 },
	Action = function(playerPed, vehicle, setCurrentZoneType)
		setCurrentZoneType('freeway')
		DrawMissionText(TranslateCap('hway_time', Config.SpeedLimits['freeway']), 5000)
	end
},
```

The last checkpoint must stay near the DMV: the server only grants road-test licenses when the player is within 35 m of the **last** checkpoint, and its default action deletes the test vehicle.

<Callout type="info">
Adding a new test type needs three changes: a `Config.Prices` entry, a `Config.VehicleModels` entry and a matching row in `licenses`. The server rejects any type that is missing from `Config.Prices` or (for road tests) `Config.VehicleModels`. The menu itself only lists the three built-in road tests, so add a menu entry in `client/main.lua` too.
</Callout>

## Server callbacks and events [#server-callbacks-and-events]

### esx_dmvschool:canYouPay (callback) [#esx_dmvschoolcanyoupay-callback]

Called with a test type. The server checks that the type is valid, the player is within 20 m of the DMV marker, does not own that license yet and has enough cash. It then removes the money and records a paid test that is valid for **15 minutes**.

### esx_dmvschool:addLicense (net event) [#esx_dmvschooladdlicense-net-event]

```lua
TriggerServerEvent('esx_dmvschool:addLicense', type)
```

<Callout type="warn" title="Client-triggerable">
Any client can fire this event. The server only grants the license if the player paid for that exact test in the last 15 minutes and stands near the DMV (theory) or the last checkpoint (road tests). The test result itself is decided on the client (NUI quiz and checkpoint loop), so a modified client that paid could skip the actual test. Treat DMV licenses as roleplay gates, not as hard security.
</Callout>

### esx_dmvschool:loadLicenses (client event) [#esx_dmvschoolloadlicenses-client-event]

Sent by the server with the player's license list on `esx:playerLoaded` and after a license is added. The client uses it to build the menu.

## Integration tips [#integration-tips]

- Check licenses from other resources with esx_license:

```lua title="server/main.lua"
TriggerEvent('esx_license:checkLicense', source, 'drive', function(hasLicense)
    if not hasLicense then
        TriggerClientEvent('esx:showNotification', source, 'You need a drivers license.')
    end
end)
```

- Police can revoke `drive*` licenses through [esx_policejob](/docs/addons/esx_policejob), which uses esx_license as well.
- The test vehicle gets 100 fuel and the `_FUEL_LEVEL` decor, so legacy fuel scripts do not drain it.

## Common issues [#common-issues]

- **Prices or menu text show the wrong language**: set `setr esx:locale "en"` before resources start. The license labels come from the SQL catalog in the same locale.
- **Paid but no license**: the paid test expired (15 minutes) or you were too far from the last checkpoint when it finished.
- **Speeding errors on the first meters**: if you use mph limits, also change `Config.SpeedMultiplier` to `2.236936`.

## Related [#related]

- [esx_license](/docs/addons/esx_license)
- [esx_policejob](/docs/addons/esx_policejob)
- [esx_boat](/docs/addons/esx_boat)
- [Server events](/docs/es_extended/events/server)
