# esx_weaponshop (/docs/addons/esx_weaponshop)

esx_weaponshop is the official weapon store for ESX Legacy. It adds Ammu-Nation locations and a hidden black market dealer, opens a NUI catalog, and lets players buy weapons, a weapon license, ammo, attachments and tints. All purchases are validated and charged on the server.

## Overview [#overview]

| | |
| --- | --- |
| Resource | `esx_weaponshop` (version `1.0`) |
| Side | Client + server, NUI (`web/dist`) |
| Dependencies | `esx_lib`, `es_extended`, `esx_license`, OneSync (`/onesync`) |
| Optional | `ox_inventory` (detected from `ESX.GetConfig().OxInventory`) |
| SQL file | `esx_weaponshop.sql` (adds the `weapon` license) |
| Config files | `shared/config/main.lua`, `shops.lua`, `upgrades.lua`, `weapon_images.lua` |

In-game flow:

- **Legal shops** (`Legal = true`, default `GunShop`) charge **cash**. With `Config.LicenseEnable`, players without the `weapon` license get a menu to buy it for `Config.LicensePrice` first.
- **Illegal shops** (`Legal = false`, default `BlackWeashop`) charge **black_money**, never need a license and have their blip disabled by default.
- Players can buy ammo, components and tints for a weapon they own and that the zone sells (when `Config.WeaponShopUpgrades.Enabled` is on).

## Installation [#installation]

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

Run `esx_weaponshop.sql`. It only inserts the `weapon` license into the `licenses` table (from [esx_license](/docs/addons/esx_license)).

```sql
INSERT IGNORE INTO `licenses` (`type`, `label`) VALUES ('weapon', "Weapon licence");
```

The files in `localization/` still create a legacy `weashops` table. The current version does not read it, prices come from the Lua config.
</Step>
<Step>
### Ensure the resource [#ensure-the-resource]

```bash title="server.cfg"
ensure oxmysql
ensure esx_lib
ensure es_extended
ensure esx_license
ensure esx_weaponshop
```

OneSync must be enabled, the manifest declares `/onesync` as a dependency.
</Step>
</Steps>

## Configuration [#configuration]

### Main config (`shared/config/main.lua`) [#main-config-sharedconfigmainlua]

| Key | Default | Description |
| --- | --- | --- |
| `Config.Locale` | `GetConvar('esx:locale', 'en')` | Locale. |
| `Config.OxInventory` | `ESX.GetConfig().OxInventory` | Uses ox_inventory for weapons and attachments when true. |
| `Config.FallbackWeaponImage` | `''` | Image shown when a weapon image cannot be loaded. |
| `Config.WeaponImages` | per-weapon map | Image overrides. `weapon_images.lua` fills this with bundled images. |
| `Config.InitialWeaponAmmo` | `42` | Ammo given with a weapon bought through the ESX loadout. |
| `Config.LicenseEnable` | `true` | Require the `weapon` license in legal shops. |
| `Config.LicensePrice` | `5000` | Price of the weapon license (cash). |
| `Config.ServerDistanceBuffer` | `3.0` | Extra meters added to `InteractionDistance` for the server distance check. |
| `Config.PurchaseCooldown` | `1500` | Minimum ms between purchases of the same type per player. |
| `Config.LicenseCallbackTimeout` | `5000` | Timeout for esx_license calls in ms. |
| `Config.ClientCallbackTimeout` | `5000` | Timeout when asking the client for its ammo state. |
| `Config.DrawDistance` | `10.0` | Marker draw distance. |
| `Config.Size` | `{ x = 1.5, y = 1.5, z = 0.5 }` | Marker size. |
| `Config.Color` | `{ r = 0, g = 128, b = 255 }` | Marker color. |
| `Config.Type` | `1` | Marker type. |
| `Config.InteractionDistance` | `2.0` | Distance to open the shop. |
| `Config.InteractionKeyLabel` | `'E'` | Key label shown in the UI. |

### Shop zones (`shared/config/shops.lua`) [#shop-zones-sharedconfigshopslua]

```lua title="shared/config/shops.lua"
Config.Zones = {
    GunShop = {
        Legal = true,
        Blip = { Enabled = true, Sprite = 110, Color = 81, Display = 4, Scale = 0.7, ShortRange = true },
        Items = {
            { name = 'WEAPON_PISTOL', price = 500 },
            { name = 'WEAPON_BAT', price = 50 },
        },
        Locations = {
            vector3(-662.1, -935.3, 20.8),
        },
    },
}
```

| Field | Description |
| --- | --- |
| `Legal` | `true` = cash + license check, `false` = black money, no license. |
| `Blip` | `Enabled`, `Sprite`, `Color`, `Display`, `Scale`, `ShortRange`. |
| `Items` | `{ name, price }` per weapon. Only weapons listed here can be bought or upgraded in this zone. |
| `Locations` | `vector3` list of counters. |

### Upgrades (`shared/config/upgrades.lua`) [#upgrades-sharedconfigupgradeslua]

| Key | Default | Description |
| --- | --- | --- |
| `Config.WeaponShopUpgrades.Enabled` | `true` | Master switch. With ox_inventory enabled, upgrades also require ox_inventory to be started. |
| `Ammo.Enabled` | `true` | Sell ammo. |
| `Ammo.PricePerRound` | `5` | Price per round. |
| `Ammo.DefaultAmount` / `MinAmount` / `MaxAmount` | `30` / `1` / `250` | Amount limits per purchase. |
| `Ammo.MaxTotal` | `250` | Maximum ammo a weapon may hold after buying. |
| `Ammo.QuickAmounts` | `{ 30, 60, 120, 250 }` | Buttons in the UI. |
| `Components.Enabled` | `true` | Sell attachments. |
| `Components.DefaultPrice` | `750` | Price when a component has no entry in `Prices`. |
| `Components.Blacklisted` | `{ clip_default = true }` | Components that are never sold. |
| `Components.Prices` | e.g. `suppressor = 1250`, `scope = 1500` | Per-component prices. |
| `Components.OxItemsByName` / `OxItemsByHash` | mapping | ESX component name or hash to ox_inventory attachment item. |
| `Tints.Enabled` | `true` | Sell weapon tints. |
| `Tints.DefaultPrice` | `350` | Price when a tint index has no entry. |
| `Tints.Prices` | `[0] = 0` ... `[7] = 900` | Price per tint index. |
| `Tints.Swatches` | hex colors | Colors shown in the UI. |

## Developer API [#developer-api]

The resource has no exports. Its server callbacks are used by the NUI:

| Callback | Arguments | Description |
| --- | --- | --- |
| `esx_weaponshop:buyWeapon` | `weaponName`, `zone` | Buys a weapon from a zone. |
| `esx_weaponshop:buyLicense` | none | Buys the `weapon` license near a legal shop. |
| `esx_weaponshop:buyUpgrade` | `data` (`{ weaponName, action, ... }`), `zone` | `action` is `'ammo'`, `'component'` or `'tint'`. |

The server also sends `esx_weaponshop:requestWeaponAmmoState` to the client and accepts the answer through the net event `esx_weaponshop:receiveWeaponAmmoState` (only from the same player, matched by request id).

<Callout type="info" title="Client-triggerable">
All three callbacks are client-callable. The server validates the zone, the weapon name, the distance to a zone location (`InteractionDistance + ServerDistanceBuffer`), the license, the price from config, the payment and the inventory, and refunds when adding the weapon fails. Requests are rate-limited per player and action. Never move prices to the client.
</Callout>

To check the weapon license from your own scripts, use [esx_license](/docs/addons/esx_license):

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

## Common issues [#common-issues]

- **Resource does not start.** `esx_license` is a hard dependency and OneSync must be on.
- **License menu never appears.** `Config.LicenseEnable` is `false`, or the zone has `Legal = false`.
- **Purchase fails silently.** The player is too far from a `Locations` entry, is spamming (cooldown), lacks cash / black money, or cannot carry the weapon.
- **Upgrades unavailable.** `Config.WeaponShopUpgrades.Enabled` is off, or `Config.OxInventory` is true and ox_inventory is not started. The player must own the weapon.
- **Changed prices in `weashops` have no effect.** Edit `Config.Zones` instead.

## Related [#related]

- [esx_license](/docs/addons/esx_license)
- [esx_shops](/docs/addons/esx_shops)
- [Weapons config](/docs/es_extended/config/weapons)
- [ox_inventory with ESX](/docs/guides/ox-inventory)
