# Bridge System

***

### Overview

The **Bridge** is the foundation of X8-Stash. Instead of writing separate logic for each framework or inventory system, the bridge exposes one unified API. The core script calls bridge functions, and the bridge translates those calls to the correct underlying resource at runtime.

Write the script once → run it on QBCore + ox\_inventory + ox\_target, or Qbox + qb-inventory + qb-target, or any other combination, without touching the core code.

***

### File Structure

The bridge consists of **5 files** that all attach to a single global `Bridge` table:

| File            | Purpose                                                |
| --------------- | ------------------------------------------------------ |
| `Loader.lua`    | Detects the running environment and stores the result  |
| `Core.lua`      | Player data, permissions, callbacks, notifications     |
| `Inventory.lua` | Stash registration (server) and stash opening (client) |
| `Target.lua`    | Interaction zones and entity targeting                 |
| `Init.lua`      | Activates the target mode after detection completes    |

***

### Boot Sequence

```
1. Config.lua loads (user preferences)
2. Loader.lua → detects Framework / Inventory / TargetMode
3. Core.lua → exposes player & callback API
4. Inventory.lua → wraps stash operations
5. Target.lua → wraps interaction system
6. Init.lua → activates the target mode
```

All bridge files share state through the `Bridge` global table, so any module can read `Bridge.Framework`, `Bridge.Inventory`, or `Bridge.TargetMode` after the loader runs.

***

### Loader

The loader exposes three detection cascades. Each one follows the same priority logic:

1. Use the explicit value from `Config.x8` if it's not `'auto'`
2. Otherwise, check which resources are currently started
3. If nothing matches, fall back to a sensible default

#### Framework Detection

| Priority | Source                             | Result     |
| -------- | ---------------------------------- | ---------- |
| 1        | `Config.x8.Framework = 'qbx_core'` | `qbx_core` |
| 2        | `Config.x8.Framework = 'qb-core'`  | `qb-core`  |
| 3        | `qbx_core` resource is started     | `qbx_core` |
| 4        | `qb-core` resource is started      | `qb-core`  |
| 5        | Fallback                           | `qb-core`  |

#### Inventory Detection

| Priority | Source                                 | Result         |
| -------- | -------------------------------------- | -------------- |
| 1        | `Config.x8.Inventory = 'ox_inventory'` | `ox_inventory` |
| 2        | `Config.x8.Inventory = 'qb-inventory'` | `qb-inventory` |
| 3        | `ox_inventory` resource is started     | `ox_inventory` |
| 4        | `qb-inventory` resource is started     | `qb-inventory` |
| 5        | Fallback                               | `qb-inventory` |

#### Target Detection

| Priority | Source                           | Result      |
| -------- | -------------------------------- | ----------- |
| 1        | `Config.x8.Target = 'ox_target'` | `ox_target` |
| 2        | `Config.x8.Target = 'qb-target'` | `qb-target` |
| 3        | `Config.x8.Target = 'interact'`  | `interact`  |
| 4        | `ox_target` resource is started  | `ox_target` |
| 5        | `qb-target` resource is started  | `qb-target` |
| 6        | `interact` resource is started   | `interact`  |
| 7        | Fallback                         | `qb-target` |

#### Public Variables

After the loader runs, these globals are available everywhere:

```lua
Bridge.Framework   -- 'qb-core' or 'qbx_core'
Bridge.Inventory   -- 'ox_inventory' or 'qb-inventory'
Bridge.TargetMode  -- 'ox_target', 'qb-target', or 'interact'
Bridge.IsStarted   -- helper function: Bridge.IsStarted('resource_name')
```

***

### Core API

The Core module splits into **server-side** and **client-side** branches based on `IsDuplicityVersion()`. All functions live under `Bridge.Core`.

#### Server Functions

**`Bridge.Core.GetPlayer(src)`**

Returns the framework's player object.

* On Qbox: calls `exports.qbx_core:GetPlayer(src)`
* On QBCore: calls `QBCore.Functions.GetPlayer(src)`

**`Bridge.Core.GetPlayerData(src)`**

Returns `Player.PlayerData` table. Returns `nil` if the player is not loaded.

**`Bridge.Core.GetCitizenId(src)`**

Returns the player's `citizenid` string.

**`Bridge.Core.GetName(src)`**

Returns full character name as `"Firstname Lastname"`. Falls back to `GetPlayerName(src)` if charinfo is missing.

**`Bridge.Core.HasPermission(src, job, grade)`**

Returns `true` when:

* The player has the specified job, AND
* Their grade level is greater than or equal to the required grade

Handles both numeric and table grade formats (`pdata.job.grade` or `pdata.job.grade.level`).

```lua
if Bridge.Core.HasPermission(src, 'police', 2) then
    -- player is police grade 2+
end
```

**`Bridge.Core.RegisterCallback(name, cb)`**

Registers a server callback.

* On Qbox: registers via `lib.callback.register` (ox\_lib)
* On QBCore: registers via `QBCore.Functions.CreateCallback`

```lua
Bridge.Core.RegisterCallback('x8-stash:checkPassword', function(source, cb, vaultId, password)
    cb(password == 'secret123')
end)
```

#### Client Functions

**`Bridge.Core.GetPlayerData()`**

Returns local player data (no `src` argument).

**`Bridge.Core.GetCitizenId()`**

Returns local citizenid.

**`Bridge.Core.GetJobInfo()`**

Returns two values: `jobName, gradeLevel`.

```lua
local job, grade = Bridge.Core.GetJobInfo()
-- job = 'police', grade = 4
```

**`Bridge.Core.GetGangInfo()`**

Returns two values: `gangName, gradeLevel`.

**`Bridge.Core.Notify(message, type)`**

Shows a notification.

* Prefers `lib.notify` from ox\_lib if available
* Falls back to `QBCore.Functions.Notify`
* Default type: `'inform'` (ox) or `'primary'` (qb)

```lua
Bridge.Core.Notify('Vault opened', 'success')
```

**`Bridge.Core.LoadModel(model)`**

Requests and waits for a model to load. Accepts a string hash or numeric joaat. Times out after **5 seconds**. Returns `true` on success.

```lua
if Bridge.Core.LoadModel('prop_ld_int_safe_01') then
    -- safe to spawn the object
end
```

**`Bridge.Core.TriggerCallback(name, cb, ...)`**

Triggers a registered server callback.

* On Qbox: uses `lib.callback.await` inside a thread
* On QBCore: uses `QBCore.Functions.TriggerCallback`

#### Compatibility Bridge

The Core module also forwards the Qbox `qbx_core:client:playerLoaded` event to the QBCore `QBCore:Client:OnPlayerLoaded` event, so any code listening on the QB event will work on both frameworks.

***

### Inventory API

Two functions only — one for each side.

#### `Bridge.Inv.RegisterStash(stashId, label, slots, weight, owner)` — Server

Registers a new stash. Only used when running **ox\_inventory** (qb-inventory does not require pre-registration).

| Parameter | Type         | Default   | Description                                      |
| --------- | ------------ | --------- | ------------------------------------------------ |
| `stashId` | string       | —         | Unique stash identifier                          |
| `label`   | string       | `stashId` | Display name                                     |
| `slots`   | number       | `30`      | Slot count                                       |
| `weight`  | number       | `300000`  | Max weight in grams                              |
| `owner`   | string/false | `false`   | CitizenID for personal stashes, false for shared |

```lua
Bridge.Inv.RegisterStash('vault_001', 'Police Locker', 50, 200000, false)
```

#### `Bridge.Inv.OpenStash(stashId, slots, weight, label)` — Client

Opens a stash for the local player.

* On ox\_inventory: calls `exports.ox_inventory:openInventory('stash', stashId)`
* On qb-inventory: triggers `inventory:server:OpenInventory` and sets the current stash

```lua
Bridge.Inv.OpenStash('vault_001', 50, 200000, 'Police Locker')
```

***

### Target API

Unified interaction layer. All functions live under `Bridge.Target` and are **client-only**.

#### Setup

The target mode must be activated before any function is called. This happens automatically in `Init.lua`:

```lua
Bridge.Target.SetMode(Bridge.TargetMode)
```

#### `Bridge.Target.AddEntity(entity, opt, distance)`

Adds a single interaction option to a spawned entity (object, ped, or vehicle).

```lua
Bridge.Target.AddEntity(safeEntity, {
    id     = 'vault_open_001',
    label  = 'Open vault',
    icon   = 'fas fa-lock',
    action = function() openVault('vault_001') end,
    canInteract = function() return canPlayerOpen() end,
}, 2.0)
```

#### `Bridge.Target.AddZone(id, coords, radius, opt, distance)`

Adds a single interaction option to a spherical zone (no entity required).

```lua
Bridge.Target.AddZone('vault_zone_001', vector3(198.24, -1010.11, 29.31), 1.5, {
    id     = 'vault_open_001',
    label  = 'Open vault',
    icon   = 'fas fa-lock',
    action = function() openVault('vault_001') end,
}, 2.0)
```

#### `Bridge.Target.AddEntityMulti(entity, opts, distance)`

Same as `AddEntity` but accepts an array of options.

#### `Bridge.Target.AddZoneMulti(id, coords, radius, opts, distance)`

Same as `AddZone` but accepts an array of options.

#### `Bridge.Target.Remove(id, entity)`

Removes a previously added interaction. Pass the `id` (used when registering) and optionally the entity reference.

```lua
Bridge.Target.Remove('vault_open_001', safeEntity)
```

#### Option Specification

A target option is a table with these fields:

| Field         | Type     | Required    | Description                                                     |
| ------------- | -------- | ----------- | --------------------------------------------------------------- |
| `id`          | string   | recommended | Unique identifier for the option                                |
| `label`       | string   | ✓           | Text shown to the player                                        |
| `icon`        | string   | optional    | FontAwesome class, default: `fas fa-lock`                       |
| `action`      | function | ✓           | Function called when the player triggers the option             |
| `canInteract` | function | optional    | Returns true/false to show or hide the option (ox\_target only) |

#### Translation Behavior

The bridge automatically converts the option spec to the correct format:

* **ox\_target** → uses `name`, `onSelect`, `canInteract`, `distance` fields
* **qb-target** → uses `label`, `icon`, `action` (drops `canInteract`)
* **interact** → uses `label`, `icon`, `action` with default `distance = 3.0` and `interactDst = 1.5`

***

### Init

Tiny file. Runs after the loader and target module are loaded, then activates the target mode:

```lua
if not IsDuplicityVersion() and Bridge and Bridge.Target and Bridge.Target.SetMode then
    Bridge.Target.SetMode(Bridge.TargetMode)
end
```

This is client-only because the target system runs on the client.

***

### Adding a New Framework

To add support for a third framework (example: ESX), you would:

1. Add a new option to `Config.x8.Framework`
2. Extend `detectFramework()` in `Loader.lua` to return your new value
3. Add a new branch in every `Bridge.Core.*` function for the new framework string
4. Test that callbacks, player data, and permissions resolve correctly

The same pattern applies to adding new inventory or target resources.

***

### Adding a New Inventory

1. Add the new option to `Config.x8.Inventory`
2. Extend `detectInventory()` in `Loader.lua`
3. Add the new branch in `Bridge.Inv.RegisterStash` (server) and `Bridge.Inv.OpenStash` (client)

***

### Adding a New Target

1. Add the new option to `Config.x8.Target`
2. Extend `detectTarget()` in `Loader.lua`
3. Add the new branch in `Bridge.Target.AddEntityMulti`, `AddZoneMulti`, and `Remove`

***

### Why a Bridge?

Without the bridge, every event handler in the script would need conditional branches like:

```lua
if framework == 'qbx_core' then
    -- 10 lines
elseif framework == 'qb-core' then
    -- 10 different lines
end
```

With the bridge, the same code becomes:

```lua
local citizenid = Bridge.Core.GetCitizenId(src)
```

This keeps the core logic clean, makes the script portable, and means a single change in `Bridge/Core.lua` updates behavior across the entire codebase.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://x8project.gitbook.io/x8project/projects/stash/bridge-system.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
