# esx_boat (/docs/addons/esx_boat)

esx_boat lets players buy boats at a marina, store them in boat garages along the coast and buy a boat license through `esx_license`. Purchased boats are saved in `owned_vehicles` with `type = 'boat'`, so they are separate from cars handled by [esx_garage](/docs/addons/esx_garage).

## Overview [#overview]

| Fact | Value |
| --- | --- |
| Resource | `esx_boat` |
| Side | Client + server |
| Dependencies | `esx_lib`, `es_extended`, `esx_vehicleshop` (plus `oxmysql`) |
| Optional | `esx_license` (when `Config.LicenseEnable = true`) |
| Database | `owned_vehicles` (from es_extended), `licenses` row `boat` |

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

1. At the boat shop marker press **E**. If the license is required and missing, a menu offers the license for `Config.LicensePrice`.
2. Pick a boat, preview it on the water and choose **Buy**. Money is taken from the cash account.
3. At a garage marker press **E** to take out a stored boat at the garage's `SpawnPoint`.
4. Drive into the large red marker (`StorePos`) as the driver and press **E** to store the boat. You are teleported to `StoreTP`.

## Installation [#installation]

<Steps>
<Step>

### Database [#database]

The resource uses the esx_lib SQL catalog and inserts the `boat` license row automatically, with a label in your `esx:locale`. The manual equivalent is `esx_boat.sql` (translations live in `localization/`):

```sql title="esx_boat.sql"
INSERT INTO `licenses` (`type`, `label`) VALUES
	('boat', 'Boat License');
```

</Step>
<Step>

### server.cfg [#servercfg]

```bash title="server.cfg"
ensure esx_vehicleshop
ensure esx_license
ensure esx_boat
```

</Step>
</Steps>

## Configuration [#configuration]

All keys live in `config.lua`.

| Key | Default | Description |
| --- | --- | --- |
| `Config.Locale` | `GetConvar('esx:locale', 'en')` | Locale used for texts. |
| `Config.LicenseEnable` | `true` | Require the `boat` license to open the shop. Needs `esx_license`. |
| `Config.LicensePrice` | `50000` | Cash price of the boat license. |
| `Config.MarkerType` | `1` | Marker type for shop and garage markers. |
| `Config.DrawDistance` | `10.0` | Distance at which markers are drawn. |
| `Config.Marker` | `{ r = 100, g = 204, b = 100, x = 1.5, y = 1.5, z = 1.0 }` | Color and size of shop and garage markers. `x` is also the interaction radius. |
| `Config.StoreMarker` | `{ r = 255, g = 0, b = 0, x = 5.0, y = 5.0, z = 1.0 }` | Color and size of the store marker. `x` is also the interaction radius. |
| `Config.Zones.Garages` | 6 garages | List of garages (see below). |
| `Config.Zones.BoatShops` | 1 shop (Shank St) | List of shops with `Outside` (`vector3`) and `Inside` (`vector4` preview spot). |
| `Config.Vehicles` | 11 boats | Boats for sale: `{ model, label, price, props? }`. |

Each garage entry has four positions:

| Field | Type | Description |
| --- | --- | --- |
| `GaragePos` | `vector3` | Marker to open the garage. |
| `SpawnPoint` | `vector4` | Where a boat is spawned (`w` = heading). |
| `StorePos` | `vector3` | Marker where you drive the boat in to store it. |
| `StoreTP` | `vector4` | Where the player is teleported after storing. |

Default boats and prices:

| Model | Price |
| --- | --- |
| `seashark`, `seashark3` | 7500 |
| `suntrap` | 10000 |
| `dinghy2` | 32500 |
| `dinghy` | 40000 |
| `jetmax` | 45000 |
| `squalo` | 61000 |
| `tropic2` | 65000 |
| `speeder` | 85000 |
| `toro` | 200000 |
| `submersible` | 750000 |

```lua title="config.lua"
Config.Vehicles = {
    { model = 'seashark', label = 'Seashark (Random Color)', price = 7500 },
    -- optional props are applied in the preview and saved on purchase
    { model = 'speeder', label = 'Speeder (Black)', price = 90000, props = { color1 = 0, color2 = 0 } },
}
```

## Exports [#exports]

### getGarages [#getgarages]

Shared export defined in `config.lua`. Returns the `Config.Zones.Garages` table, for example to show boat garages on a phone map.

```lua
local garages = exports['esx_boat']:getGarages()

for i, garage in ipairs(garages) do
    print(i, garage.GaragePos)
end
```

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

These are triggered by the esx_boat client. They are listed so you can audit them or call them from your own UI. All are validated on the server.

| Name | Type | Arguments | Server checks |
| --- | --- | --- | --- |
| `esx_boat:buyBoat` | callback | `vehicleProps` | Model must be in `Config.Vehicles`, player within 20 m of a shop `Outside` or 35 m of `Inside`, enough cash. Plate is generated on the server (`BO` + 2 letters + 4 digits); only `model`, `plate` and config `props` are stored. |
| `esx_boat:buyBoatLicense` | callback | none | Near a shop, enough cash, no `boat` license yet. Refunds if adding the license fails. |
| `esx_boat:getGarage` | callback | none | Within 20 m of a `GaragePos`. Returns the JSON of stored boats. |
| `esx_boat:storeVehicle` | callback | `plate` | Within 25 m of a `StorePos` and a boat with that plate within 20 m. Returns rows changed. |
| `esx_boat:takeOutVehicle` | net event | `plate` | Within 20 m of a `GaragePos`, boat owned and stored. |

<Callout type="warn" title="Security notes">
`esx_boat:takeOutVehicle` is a client-triggerable net event. It only flips `stored` to false in the database; the boat itself is spawned by the client. The license requirement is only checked on the client before the shop opens: `esx_boat:buyBoat` does not verify the `boat` license on the server. If the license matters on your server, add an `esx_license:checkLicense` check to that callback.
</Callout>

## Integration tips [#integration-tips]

- Other garage scripts can read boats with `SELECT * FROM owned_vehicles WHERE type = 'boat'`.
- The client still calls `exports['esx_vehicleshop']:GeneratePlate()` before buying, which is why `esx_vehicleshop` must be running even though the server replaces the plate.
- Blips: garages use sprite `356`, the shop uses sprite `427`, both color `3`.

## Common issues [#common-issues]

- **"Unknown model" in the garage**: the boat's model is no longer in `Config.Vehicles`. Keep old entries or change the label lookup.
- **Cannot store the boat**: you must be the driver and inside the red `StorePos` marker. The server also requires the boat with your plate to be within 20 m.
- **Shop does not open, license menu appears every time**: `esx_license` is not started or the `boat` row is missing in `licenses`.
- **Resource fails at start**: `esx_vehicleshop` is a hard dependency in `fxmanifest.lua`. Start it before esx_boat.

## Related [#related]

- [esx_vehicleshop](/docs/addons/esx_vehicleshop)
- [esx_garage](/docs/addons/esx_garage)
- [esx_license](/docs/addons/esx_license)
- [Database schema](/docs/guides/database-schema)
