# esx_accessories (/docs/addons/esx_accessories)

esx_accessories adds shop markers where players buy masks, helmets, glasses and ear accessories, and a personal menu (default key `J`) to put them on and take them off again. Purchases are stored per character in [esx_datastore](/docs/addons/esx_datastore), so the accessory survives relogs.

## Overview [#overview]

| | |
|---|---|
| Resource | `esx_accessories` |
| Side | Client + server |
| Dependencies | `esx_lib`, `es_extended`, `esx_skin`, `esx_datastore`, `oxmysql` (loaded via `@oxmysql/lib/MySQL.lua`) |
| Database | Four rows in the `datastore` table |
| UI | `ESX.OpenContext` menus, `esx_skin` restricted skin menu |

In-game flow:

1. The player walks into a shop marker (`Config.Zones`) and presses `E`.
2. `esx_skin` opens a restricted menu that only shows the `<type>_1` and `<type>_2` sliders of that accessory.
3. On confirmation the server charges `Config.Price` from cash and stores the chosen drawable and texture.
4. Later the player presses `J`, picks Helmet, Ears, Mask or Glasses and toggles the saved item on or off.

## Installation [#installation]

<Steps>
<Step>
### Import the SQL [#import-the-sql]

Run `esx_accessories.sql` against your ESX database. It inserts the four datastores the script reads and writes:

```sql title="esx_accessories.sql"
INSERT INTO `datastore` (name, label, shared) VALUES
	('user_ears', 'Ears', 0),
	('user_glasses', 'Glasses', 0),
	('user_helmet', 'Helmet', 0),
	('user_mask', 'Mask', 0)
;
```

Translated labels are in the `localization/` folder (de, fr, it, nl, sl, sr, sv, tr). Import only one of them.
</Step>
<Step>
### Ensure the resource [#ensure-the-resource]

Start it after its dependencies:

```bash title="server.cfg"
ensure oxmysql
ensure es_extended
ensure esx_lib
ensure skinchanger
ensure esx_skin
ensure esx_datastore
ensure esx_accessories
```
</Step>
</Steps>

## Configuration [#configuration]

All keys live in `config.lua`.

| Key | Default | Description |
|---|---|---|
| `Config.Locale` | `GetConvar('esx:locale', 'en')` | Locale used for menus and notifications. |
| `Config.Price` | `100` | Cash price of one accessory. Charged with `xPlayer.removeMoney`. |
| `Config.ShopDistance` | `3.0` | Server-side maximum distance (meters) to a shop position for buying and saving. |
| `Config.PurchaseSessionDuration` | `60000` | Milliseconds after payment during which the server accepts the save event. |
| `Config.EnableControls` | `true` | Registers the `accessory` key mapping (default `J`) that opens the accessory menu. |
| `Config.DrawDistance` | `10` | Distance at which shop markers are drawn. |
| `Config.Size` | `{x = 1.5, y = 1.5, z = 1.0}` | Marker size. `Size.x` is also the interaction radius. |
| `Config.Color` | `{r = 50, g = 200, b = 50}` | Marker color (alpha is fixed to 255). |
| `Config.Type` | `1` | Marker type. Set to `-1` to create no markers at all. |
| `Config.ShopsBlips` | Only `Mask` at `vector3(-1338.1, -1278.2, 3.8)` | Map blips per accessory type: `Pos` (list of `vector3`) and `Blip = {sprite, color}`. |
| `Config.Zones` | `Ears`, `Mask`, `Helmet`, `Glasses` | Shop positions per accessory type, each `{ Pos = { vector3, ... } }`. |

<Callout type="warn" title="Zone keys are fixed">
The keys of `Config.Zones` must be `Ears`, `Mask`, `Helmet` or `Glasses`. The server normalizes the accessory name against that list and rejects anything else, so a custom key like `Hats` will never be purchasable.
</Callout>

Setting `Config.Type = -1` skips the whole marker thread. Because the markers also handle enter and exit detection, the shops then stop working. Use a very small `Config.Size` or a transparent color if you only want to hide the visuals.

The default `Config.ShopsBlips` only adds a blip for the mask shop because the other zones sit inside clothing stores that [esx_clotheshop](/docs/addons/esx_clotheshop) already marks on the map.

## Server events [#server-events]

### esx_accessories:pay [#esx_accessoriespay]

```lua
TriggerServerEvent('esx_accessories:pay', accessory)
```

| Name | Type | Description |
|---|---|---|
| `accessory` | `string` | `'Ears'`, `'Mask'`, `'Helmet'` or `'Glasses'` (case-insensitive). |

Charges `Config.Price` and opens a purchase session for that accessory. The server checks that the player exists, stands near a matching zone and has enough cash. The built-in client uses the `esx_accessories:buy` callback instead, which runs the same logic and returns the result.

<Callout type="info" title="Client-triggerable">
Any client can fire this event. It is safe because the server validates position and balance, but it only removes money; nothing is granted until `esx_accessories:save` follows.
</Callout>

### esx_accessories:save [#esx_accessoriessave]

```lua
TriggerServerEvent('esx_accessories:save', skin, accessory)
```

| Name | Type | Description |
|---|---|---|
| `skin` | `table` | Skin table from `skinchanger:getSkin`. Only `<type>_1` and `<type>_2` are stored. |
| `accessory` | `string` | Accessory type, same values as above. |

Writes `has<Type> = true` and `skin = { <type>_1, <type>_2 }` into the `user_<type>` datastore. The server rejects the call unless there is an unexpired paid session for that accessory and the player is still near the shop. The session is consumed on success.

## Callbacks [#callbacks]

Callbacks are registered with `xLib.callback.registerCompat`, so both `xLib.callback` and `ESX.TriggerServerCallback` can call them.

| Name | Arguments | Returns |
|---|---|---|
| `esx_accessories:get` | `accessory` | `hasAccessory` (`boolean`), `skin` (`table` with `<type>_1` and `<type>_2`) |
| `esx_accessories:buy` | `accessory` | `true` if payment succeeded and a session was opened |
| `esx_accessories:checkMoney` | `accessory` | `true` if the player is near the shop and has at least `Config.Price` cash |

Example: check whether the local player owns a helmet before offering it in your own menu.

```lua title="client/main.lua"
xLib.callback('esx_accessories:get', false, function(hasHelmet, helmetSkin)
    if not hasHelmet then
        return ESX.ShowNotification('You do not own a helmet yet.')
    end

    TriggerEvent('skinchanger:getSkin', function(skin)
        TriggerEvent('skinchanger:loadClothes', skin, helmetSkin)
    end)
end, 'Helmet')
```

## Client events [#client-events]

These are local events (not network events) fired by the marker zones. You can listen to them, for example to show your own help text.

| Event | Argument | Fired when |
|---|---|---|
| `esx_accessories:hasEnteredMarker` | `zone` (`string`, e.g. `'Mask'`) | The player enters a shop marker. |
| `esx_accessories:hasExitedMarker` | `zone` | The player leaves a shop marker. The open context menu is closed. |

## Reading the stored data from another resource [#reading-the-stored-data-from-another-resource]

The data is a normal private datastore, so server scripts can read it through esx_datastore:

```lua title="server/main.lua"
local xPlayer = ESX.GetPlayerFromId(source)

TriggerEvent('esx_datastore:getDataStore', 'user_mask', xPlayer.getIdentifier(), function(store)
    local owned = store.get('hasMask') or false
    local skin = store.get('skin') or {}
    print(('Owns mask: %s, drawable %s'):format(owned, skin.mask_1))
end)
```

## Common issues [#common-issues]

- **"No mask/helmet" notification although the item was bought.** The datastore rows from the SQL file are missing, or esx_datastore started after the purchase. Check that `user_mask` and the others exist in the `datastore` table.
- **Purchase charged but nothing saved.** The player walked out of `Config.ShopDistance` or took longer than `Config.PurchaseSessionDuration` in the confirmation menu. The money is not refunded in that case.
- **Shop does nothing when pressing E.** You changed `Config.Type` to `-1` or renamed a zone key. See the configuration notes above.
- **Masks taken off show a blank head.** Mask "off" sets drawable `0`; props (helmet, glasses, ears) use `-1`. Custom clothing packs may need other values.

## Related [#related]

- [esx_datastore](/docs/addons/esx_datastore)
- [esx_skin](/docs/core/esx_skin)
- [skinchanger](/docs/core/skinchanger)
- [esx_clotheshop](/docs/addons/esx_clotheshop)
- [esx_accessorymanager](/docs/addons/esx_accessorymanager)
