# esx_clotheshop (/docs/addons/esx_clotheshop)

esx_clotheshop places clothing stores around the map where players change their outfit through the `esx_skin` menu and pay with cash. After paying, players can optionally save the outfit to their wardrobe (the `property` datastore used by `esx_property`).

## Overview [#overview]

| Fact | Value |
| --- | --- |
| Resource | `esx_clotheshop` |
| Side | Client + server |
| Dependencies | `esx_lib`, `es_extended`, `esx_skin` (plus `oxmysql`) |
| Optional | `esx_datastore` with a `property` datastore for saved outfits |
| Database | None of its own |

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

1. Walk into a store marker and press **E**.
2. Change clothing in the restricted skin menu (t-shirt, torso, decals, arms, pants, shoes, bags, chain, helmet, glasses, watches).
3. Confirm the purchase. The server charges cash and saves the new skin through `esx_skin`.
4. If a `property` datastore exists, choose whether to save the outfit and type a name.

If the player cancels, leaves the marker or cannot pay, the previous skin is restored.

## Installation [#installation]

There is no SQL file. Make sure the dependencies start first:

```bash title="server.cfg"
ensure esx_skin
ensure esx_datastore
ensure esx_clotheshop
```

To enable saved outfits, create the per-player datastore once:

```sql
INSERT INTO `datastore` (`name`, `label`, `shared`) VALUES ('property', 'Property', 0);
```

## Configuration [#configuration]

| Key | Default | Description |
| --- | --- | --- |
| `Config.Locale` | `GetConvar('esx:locale', 'en')` | Locale used for texts. |
| `Config.Price` | `250` | Price of a purchase (or of each changed piece). |
| `Config.ChargePerPiece` | `false` | Charge `Config.Price` per changed item in `Config.SkinProps` instead of a flat price. |
| `Config.ShopDistance` | `3.0` | Max distance in meters from a shop that the server accepts for a purchase. |
| `Config.PurchaseSessionDuration` | `60000` | Time in ms after paying during which the outfit can be saved. |
| `Config.MaxOutfits` | `20` | Max outfits in the `dressing` list. New saves are ignored when full. |
| `Config.MaxOutfitLabelLength` | `40` | Outfit names are trimmed to this length. Control characters are removed. |
| `Config.DrawDistance` | `10.0` | Marker draw distance. |
| `Config.MarkerSize` | `{ x = 1.5, y = 1.5, z = 1.0 }` | Marker size. `x` is also the interaction radius. |
| `Config.MarkerColor` | `{ r = 102, g = 102, b = 204 }` | Marker color (alpha is fixed at 100). |
| `Config.MarkerType` | `1` | Marker type. |
| `Config.Shops` | 14 `vector3` positions | Store locations. Each also gets a blip (sprite `73`, color `47`). |
| `Config.SkinProps` | `{ 'tshirt', 'torso', 'decals', 'arms', 'pants', 'shoes', 'bags', 'chain', 'helmet', 'glasses', 'watches' }` | Items compared for per-piece pricing (`<name>_1` and `<name>_2`). |

```lua title="config.lua"
-- Charge 150 per changed piece, minimum 150
Config.Price = 150
Config.ChargePerPiece = true
```

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

### esx_clotheshop:buyClothes (callback) [#esx_clotheshopbuyclothes-callback]

Triggered by the client with `(newSkin, oldSkin)`. Returns `bought, failure` where `failure` is `'not_enough_money'` or `'save_failed'`.

Server-side safeguards:

- The player must be within `Config.ShopDistance` of a shop.
- `newSkin` is validated with `exports.esx_skin:ValidateSkin`.
- The price is calculated against the skin stored on the server (`exports.esx_skin:GetSkin`), not the `oldSkin` sent by the client.
- The money operation is wrapped in `beginAccountOperation`/`endAccountOperation` and refunded if `esx_skin:SaveSkin` fails.
- Only one purchase per identifier can run at a time.

### esx_clotheshop:checkPropertyDataStore (callback) [#esx_clotheshopcheckpropertydatastore-callback]

Returns `true` when the player has a `property` datastore. The client uses it to decide whether to show the save prompt.

### esx_clotheshop:saveOutfit (net event) [#esx_clotheshopsaveoutfit-net-event]

```lua
TriggerServerEvent('esx_clotheshop:saveOutfit', label, skin)
```

<Callout type="info" title="Client-triggerable">
This event can be fired by any client. It is safe by design: it only works within `Config.PurchaseSessionDuration` after a successful purchase, ignores the `skin` argument and stores the skin that was actually paid for, sanitizes the label and respects `Config.MaxOutfits`.
</Callout>

Saved outfits are appended to `dressing` in the player's `property` datastore as `{ label = string, skin = table }`.

## Integration tips [#integration-tips]

- `esx_property` reads the same `dressing` list, so outfits bought here show up in property wardrobes.
- To read outfits from your own resource on the server:

```lua title="server/main.lua"
TriggerEvent('esx_datastore:getDataStore', 'property', xPlayer.getIdentifier(), function(store)
    local outfits = store and store.get('dressing') or {}

    for i, outfit in ipairs(outfits) do
        print(i, outfit.label)
    end
end)
```

- Payment uses the cash account (`xPlayer.removeMoney`). Edit `server/main.lua` if you want bank payments.

## Common issues [#common-issues]

- **Purchase always fails with a "save failed" message**: the server distance check uses `Config.ShopDistance` (3.0). If you moved a shop marker, update the `Config.Shops` entry, not just the visual marker.
- **Outfit not saved**: the `property` datastore is missing, the 60 second window expired, or the player already has `Config.MaxOutfits` outfits.
- **Skin resets after buying**: `esx_skin` failed to save the skin. Check the server console; the purchase is refunded in that case.

## Related [#related]

- [esx_skin](/docs/core/esx_skin)
- [skinchanger](/docs/core/skinchanger)
- [esx_datastore](/docs/addons/esx_datastore)
- [esx_barbershop](/docs/addons/esx_barbershop)
- [esx_property](/docs/addons/esx_property)
