> For the complete documentation index, see [llms.txt](https://docs.rainmad.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.rainmad.com/resources/gta-dual-wield-two-handed-weapons/systems/bridges.md).

# Bridges

Everything framework-specific lives in `bridge/`, outside escrow protection. If your server runs something not supported out of the box, you can add it yourself by dropping in one file.

```
bridge/
├── framework/      qb, esx
├── inventory/      ox_inventory, qb-inventory, ak47_inventory, origen_inventory
├── notification/   ox_lib, qb, esx, okokNotify, ps-ui
└── progressbar/    ox_lib, qb, esx
```

Each file guards itself: it checks `cfg` and the running resources, and returns early if it is not the right one. Only one bridge per category ever binds.

***

### Framework bridge

Provides player lookup and usable-item registration.

```lua
function registerUsableItem(item, cb)   -- cb(playerId)
function getPlayerIdentifier(playerId)  -- stable unique string
function getPlayer(playerId)
```

`getPlayerIdentifier` is used as the SQL ammo key, so it must be **stable across sessions**. Returning something session-scoped would reset ammo on every reconnect.

***

### Inventory bridge

Provides item access, and declares whether metadata ammo is possible.

```lua
inventoryName = 'ox_inventory'
inventorySupportsMetadata = true

function getItemMetadata(playerId, item)              --> metadata, slot
function setItemMetadata(playerId, item, slot, meta)  --> boolean
function getItemCount(playerId, item)                 --> number
function removeItem(playerId, item, count)            --> boolean
```

Setting `inventorySupportsMetadata = false` is legitimate. The ammo system falls back to SQL automatically. Only the last two functions are then required.

#### Writing your own

Create `bridge/inventory/myinventory.lua`:

```lua
if cfg.inventory == 'auto' then
    if not GetResourceState('my-inventory'):find('start') then return end
    cfg.inventory = 'my-inventory'
elseif cfg.inventory ~= 'my-inventory' then
    return
end

inventoryName = 'my-inventory'
inventorySupportsMetadata = false

function getItemCount(playerId, item)
    return exports['my-inventory']:GetItemCount(playerId, item) or 0
end

function removeItem(playerId, item, count)
    return exports['my-inventory']:RemoveItem(playerId, item, count) and true or false
end
```

The glob in `fxmanifest.lua` picks up any `.lua` in the folder, so no manifest edit is needed.

{% hint style="warning" %}
Keep the auto-detect guard at the top. Without it your bridge binds even when a different inventory is configured, and whichever file loads last wins.
{% endhint %}

***

### Notification bridge

```lua
notify = function(text, type)              -- client
notify = function(playerId, text, type)    -- server
```

`type` is one of `'success'`, `'error'`, `'inform'`.

Unlike the others, notifications are **not** auto-detected, so set `cfg.notification` to match your server.

***

### Progress bar bridge

Shown while equipping a dual gun or loading ammo, for `cfg.progress_duration_ms`. The action cannot be cancelled.

***

### Load order

The manifest loads bridges before the main scripts, in this order:

```
1. framework bridge    → registerUsableItem, getPlayerIdentifier
2. inventory bridge    → inventorySupportsMetadata, item functions
3. ammo system         → depends on both above
4. main script         → depends on all above
```

This is why the ammo mode line appears in your console at start-up: by then the inventory bridge has already declared its capabilities.

{% hint style="info" %}
If your console reports `inventory=none` when you expect a real inventory, your inventory resource is starting **after** `rm_dualgun`. Fix the order in `server.cfg`.
{% endhint %}
