> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dietrich-development.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Setup

> Install and configure DD Inventory on VORP, RSG or standalone - requirements, load order, first boot, every config block, admin commands, and troubleshooting

## Requirements

| Requirement      | Notes                                                                |
| ---------------- | -------------------------------------------------------------------- |
| **oxmysql**      | Database access.                                                     |
| **ox\_lib**      | Callbacks and module loading.                                        |
| **dd\_lib**      | Free foundation resource - framework bridge and capability registry. |
| **dd\_shellkit** | Free foundation resource - the NUI panel host.                       |

No framework is required. DD Inventory calls no framework API of its own, so neither VORP nor RSG is a manifest dependency: `dd_lib` detects at boot whether `vorp_core`, `rsg-core` or neither is running and routes every identity, money and notification call accordingly. Three deployments are supported.

| Detected as  | When                   | Read this                                                                                                   |
| ------------ | ---------------------- | ----------------------------------------------------------------------------------------------------------- |
| `vorp`       | `vorp_core` is started | The installation steps below as written                                                                     |
| `rsg`        | `rsg-core` is started  | The steps below, plus [Installation differences](/scripts/inventory/compatibility#installation-differences) |
| `standalone` | neither is started     | The steps below, plus [Running without a framework](#running-without-a-framework)                           |

<Warning>
  **Standalone servers must change one setting before first boot.** The default `currencyMode = 'framework'` has no wallet to read when no framework is present, so every balance reads as zero and every credit or debit fails silently. Set `currencyMode = 'native'` in `config/config.lua`. See [Currency mode](#currency-mode).
</Warning>

`dd_lib` and `dd_shellkit` are free and MIT licensed. Both are fully open and editable, nothing in them is encrypted. Grab the zip from the releases page of each and drop the folder into your `resources` directory.

<Columns cols={2}>
  <Card title="dd_lib" icon="github" href="https://github.com/Dietrich-Development/dd_lib">
    Detects your framework and routes every money, identity, notification and logging call. Carries `config/overrides.lua`, where you can re-point any of those calls at your own resources.
  </Card>

  <Card title="dd_shellkit" icon="github" href="https://github.com/Dietrich-Development/dd_shellkit">
    Hosts the inventory's browser page, owns NUI focus, and provides the `Alt`-held cursor mode plus the right-click player context menu the trade flow uses. If you already run `dd_hud`, dd\_shellkit stays passive.
  </Card>
</Columns>

<Info>
  No build step is required - the interface ships pre-built.
</Info>

## Installation

<Steps>
  <Step title="Download">
    Get the latest release from our [Tebex store](https://tebex.dietrich-development.com) and download it from your [Keymaster](https://portal.cfx.re/assets/granted-assets).
  </Step>

  <Step title="Extract">
    Drop the folders into your `resources/` directory. The exact bracket folders do not matter, the load order in `server.cfg` does.

    ```
    resources/
    ├── [standalone]/
    │   ├── dd_lib/
    │   └── dd_shellkit/
    └── [dd]/
        └── dd_inventory/
    ```
  </Step>

  <Step title="Copy your item icons">
    Only if you are replacing an existing inventory. With the server stopped, copy its icons across. Skip files that already exist if asked - DD Inventory ships around 980 standard icons.

    <CodeGroup>
      ```bash VORP (Linux) theme={null}
      cp -n resources/[VORP]/vorp_inventory/html/img/items/*.png resources/[dd]/dd_inventory/img/items/
      ```

      ```bat VORP (Windows cmd) theme={null}
      robocopy resources\[VORP]\vorp_inventory\html\img\items resources\[dd]\dd_inventory\img\items *.png /XC /XN /XO
      ```

      ```bash RSG (Linux) theme={null}
      cp -n resources/[rsg]/rsg-inventory/html/images/*.png resources/[dd]/dd_inventory/img/items/
      ```
    </CodeGroup>

    On a standalone server there is nothing to copy: drop your own PNGs into `dd_inventory/img/items/`, named after the item.
  </Step>

  <Step title="Delete the old inventory">
    Only if you are replacing one. Delete the whole `resources/[VORP]/vorp_inventory/` folder, or `resources/[rsg]/rsg-inventory/`. Keep a backup zip somewhere outside `resources/` if you like. Most servers need nothing else here - if a resource then refuses to start, see "If a script still expects the old inventory" below.
  </Step>

  <Step title="Set the load order">
    Add the block below to `server.cfg`, after your framework core if you run one, and before any resource that consumes the inventory.
  </Step>

  <Step title="Restart">
    Start the server and watch the console for the first-boot lines.
  </Step>
</Steps>

### If a script still expects the old inventory

Skip this on a standalone server, there is no old inventory to miss.

Every `exports.vorp_inventory:*` call, and on RSG every `exports['rsg-inventory']:*` call, is answered by DD Inventory, so scripts keep working. Two things still look for the *name* of the deleted resource, and both only matter once you have deleted the folder:

* a manifest line `dependencies { 'vorp_inventory' }`, which stops that resource from starting at all
* a runtime check `GetResourceState('vorp_inventory') == 'started'`, which makes that script quietly skip its inventory path

**Normally you just delete the line.** In `vorp_fishing/fxmanifest.lua`, remove `'vorp_inventory'` from its `dependencies` block and it starts again. The same applies to any open script with that check, on either framework.

**If you cannot edit the script** - an encrypted or escrowed paid resource, for example - copy `extras/vorp_inventory/` out of this download into `resources/[VORP]/`, or `extras/rsg-inventory/` into `resources/[rsg]/`. It is a single manifest with no scripts and no files, and it exists only so the name resolves. Nothing to rename, nothing to configure.

<Warning>
  Start the placeholder before DD Inventory, and never `restart` it on a live server: FXServer stops every resource that declares it as a dependency and does not bring them back.
</Warning>

### Third-party UIs with hardcoded image paths

VORP only in practice: `vorp_menu` and `vorp_stores`, or another UI that shows item images, hardcode the old image URL and need a one-line path change from `nui://vorp_inventory/html/img/items/` to `nui://dd_inventory/img/items/`. On RSG, `rsg-inventory`, `rsg-core` and `rsg-weapons` resolve item images relatively and need no change; only your own scripts that hardcode `nui://rsg-inventory/html/images/` do.

```bash theme={null}
sed -i "s#nui://vorp_inventory/html/img/items/#nui://dd_inventory/img/items/#g" \
    'resources/[VORP]/vorp_stores/client/client.lua' \
    'resources/[VORP]/vorp_menu/html/js/app.js'
```

`vorp_stores/client/client.lua` has one occurrence, `vorp_menu/html/js/app.js` has two. Any other custom UI that loads item images the same way needs the same one-liner.

### server.cfg load order

Place this block **after** your framework core, if you run one, and **before** any resource that consumes the inventory (stores, banking, housing, stables, jobs, your own scripts):

<CodeGroup>
  ```cfg VORP theme={null}
  ensure vorp_core
  ensure vorp_inventory   # only if you installed the placeholder - name resolution only
  ensure dd_lib
  ensure dd_shellkit
  ensure dd_inventory
  ```

  ```cfg RSG theme={null}
  ensure rsg-core
  ensure rsg-inventory    # only if you installed the placeholder - name resolution only
  ensure dd_lib
  ensure dd_shellkit
  ensure dd_inventory
  ```

  ```cfg Standalone theme={null}
  ensure dd_lib
  ensure dd_shellkit
  ensure dd_inventory
  ```
</CodeGroup>

The order is not cosmetic: `dd_lib` must be loaded before anything that uses the bridge, `dd_shellkit` must exist before the inventory registers its panel, and the placeholder, if you needed one, must start before the scripts whose dependency checks it resolves.

### First boot

There is no manual SQL to run. On the first start DD Inventory creates all of its tables (`inventories`, `inventory_rights`, `item_definitions`, `item_instances`, `inventory_audit`, `character_currency`, `account_sigils`) with `CREATE TABLE IF NOT EXISTS`. What happens next depends on the detected framework.

| Framework  | Item catalogue at first boot                                                                                                             |
| ---------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| VORP       | Adds a `rarity` column to VORP's `items` table, then imports every row of `items` into `item_definitions`.                               |
| RSG        | Imports `RSGCore.Shared.Items`, and re-imports whenever rsg-core broadcasts a core object update.                                        |
| Standalone | Nothing to import. The catalogue starts empty and you fill it yourself, see [Running without a framework](#running-without-a-framework). |

On VORP, watch the console for these lines:

```
[dd_inventory] Inventory system loaded
[dd_inventory:compat] VORP item import: 412 imported, 0 already present (412 rows scanned)
[dd_inventory:compat] VORP rarity sync: 412 definition(s) updated from items.rarity
[dd_inventory:compat] compat layer ready: 6 custom inventories restored, 412 items imported, ...
```

The import runs a few seconds after start, reads your `items` table, and inserts every name that does not yet exist as an item definition - carrying over label, description, stack limit (`-1`/unlimited becomes 100), type, weight and the `usable` flag. Existing DD Inventory definitions are never overwritten, so the import is safe to let run on every boot. The vorp compatibility layer is not gated on the detected framework, so this import is attempted everywhere. On a database with no `items` table it logs `items table not found - skipping VORP item import` and moves on, which is the normal line on a standalone or RSG server.

### Running without a framework

DD Inventory runs on a bare FXServer, with `DDLib.framework` detected as `standalone`. Everything that is purely inventory logic works unchanged. Four consequences follow from there being no framework to ask, and all four are worth planning for before you open the server.

<AccordionGroup>
  <Accordion title="Set currencyMode to native">
    This is the one that bites silently. The money bridge has no wallet to read, so with the default `currencyMode = 'framework'` every balance reads as zero, and every credit or debit returns a failure code (`FRAMEWORK_ADD_FAILED`) rather than an error you would notice. Set `currencyMode = 'native'` and the cash and gold balances live in DD Inventory's own `character_currency` table, where they behave exactly as on a framework server. The account-wide premium balance is unaffected either way, it is always DD Inventory-owned.
  </Accordion>

  <Accordion title="One identity per account">
    With no character record to resolve, the bridge falls back to the account's license as the character id. A standalone server therefore has exactly one inventory per account, and no concept of multiple characters. If you run your own character system, point `DDLibOverrides['identity.charId']` at it (see [Framework overrides](#framework-overrides)) and multiple characters work normally.
  </Accordion>

  <Accordion title="No character-selection signal">
    `dd_lib` has no standalone branch for the character lifecycle: it hooks `vorp:SelectedCharacter` on VORP and `RSGCore:Server:PlayerLoaded` on RSG, and on standalone there is no equivalent event to hook. The client gates the open keybind on an active session, and on standalone that session is only marked active by the replay DD Inventory runs a couple of seconds after the resource starts, for players who are connected at that moment. Players who connect later are not covered by that replay.

    Two ways around it, both server-side: trigger `dd_inventory:client:sessionActive` towards a player once your own spawn or character flow has finished, or drive the whole lifecycle from your own resource. There is no override key for this hook today.
  </Accordion>

  <Accordion title="Empty item catalogue and notifications">
    There is no framework item table to import from, so `item_definitions` starts empty and `/dd_give` answers `Unknown item` until you populate it. Register definitions from your own resource with `exports.dd_inventory:registerItemDefinition({ ... })`, documented in the [developer reference](/scripts/inventory/developers#item-definitions-and-rarity), or insert the rows directly.

    Notifications fall back to `chat:addMessage`, which reaches the player on a default FXServer chat resource and does nothing at all if you removed chat. Point `DDLibOverrides['notify']` at your own notification resource.
  </Accordion>
</AccordionGroup>

## Player controls

| Action                 | How                                                                                                                         |
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| Open / close inventory | Press `I` (configurable). `Escape` closes.                                                                                  |
| Move an item           | Left-click and drag into another slot or the second panel.                                                                  |
| Quick-move a stack     | `Shift` + left-click - sends the whole stack to the other open panel.                                                       |
| Split a stack          | `Ctrl` + left-click - opens a split dialog.                                                                                 |
| Use an item            | Right-click it.                                                                                                             |
| Sort                   | The sort button in the panel header - orders by item type, then alphabetically, and compacts partial stacks.                |
| Search                 | Type in the search field; non-matching slots dim out instantly.                                                             |
| Inspect                | Hover any item for a type-aware tooltip: rarity, weight, description, plus weapon/consumable/tool specifics and bind state. |
| Buy slots              | Click the `+` button next to the slot counter, or a greyed-out locked slot.                                                 |
| Trade                  | Hold `Alt`, right-click the other player, choose **Trade**.                                                                 |

## Configuration

<Warning>
  **Upgrading from 2.3.0 or older:** the main config moved from `shared/config.lua` to **`config/config.lua`**. Carry your settings over into the new file. The global is still called `DDInvConfig`, so nothing else changes. `shared/config.lua` is no longer read.
</Warning>

Everything you configure lives in the `config/` folder: `config/config.lua` holds the global `DDInvConfig`, `config/language.lua` holds every piece of player-facing text. Both files - along with the framework compat shim and the NUI host indirection - are escrow-ignored and stay fully readable and editable after purchase.

### Slots, weight and grid

```lua config/config.lua theme={null}
DDInvConfig = {
    defaultMaxSlots = 40,
    maxMaxSlots = 160,
    enableTestTui = false,

    -- Weight system
    defaultMaxWeight = 420.0,   -- LT (Last/weight unit)
    weightUnit = 'LT',
    overweightDebuffKey = 'inv_overweight',  -- legacy single debuff key (kept for reference)

    -- Graduated overweight penalties (replaces single debuff)
    overweightTiers = {
        { key = 'inv_overweight_mild',     minRatio = 1.0, maxRatio = 1.2, speedPenalty = -10 },
        { key = 'inv_overweight_moderate', minRatio = 1.2, maxRatio = 1.5, speedPenalty = -20 },
        { key = 'inv_overweight_severe',   minRatio = 1.5, maxRatio = 999, speedPenalty = -30, blockSprint = true },
    },

    -- Grid layout
    slotsPerRow = 8,
    visibleRows = 8,

    -- Keybind to open the player inventory (Windows virtual-key code)
    openKey = 0x49, -- I key
```

Overweight tiers are matched on `carriedWeight / maxWeight`. `speedPenalty` is a percentage, `blockSprint` additionally disables sprinting. Add, remove or re-tune tiers freely, just keep the ranges contiguous.

<Info>
  The penalties are applied through a stat/buff provider (a `dd_buffcore`-style export surface). Without one installed nothing breaks: the weight bar and all limits still work, only the movement debuff is skipped.
</Info>

`openKey` is a **Windows virtual-key code**, not a RedM control hash: the key is polled with `IsRawKeyDown`. The codes you are most likely to want:

| Key   | Code   | Key  | Code   | Key  | Code   |
| ----- | ------ | ---- | ------ | ---- | ------ |
| `I`   | `0x49` | `B`  | `0x42` | `M`  | `0x4D` |
| `TAB` | `0x09` | `O`  | `0x4F` | `P`  | `0x50` |
| `U`   | `0x55` | `F1` | `0x70` | `F2` | `0x71` |

A value outside `0x01`-`0xFF` is refused at start with a console warning and falls back to `I`, so a mistyped config never leaves your players without an inventory key.

The same code is registered with the panel host, so the key also closes the inventory while the interface has focus. That part covers the letters, the digits, `F1`-`F12`, the arrow keys, `TAB`, `ENTER`, `SPACE`, `BACKSPACE` and `ESC`. A code outside that set still opens the inventory from the game world, it just cannot close it again from inside the panel, and the console says so once at start.

To confirm what the key resolved to, from a client script:

```lua theme={null}
exports.dd_inventory:getOpenKey()
--> { configured = 0x49, vk = 73, jsCode = 'KeyI' }
```

### Currency mode

```lua config/config.lua theme={null}
    -- ── Currency backend ───────────────────────
    -- 'framework' : breakdust/seamshards are the HOST FRAMEWORK's wallet
    --               (VORP dollars / gold, RSG cash / bloodmoney) via dd_lib.
    --               The `character_currency` table is not read or written.
    -- 'native'    : legacy behavior - dd_inventory owns the balances in its own
    --               `character_currency` table.
    -- Sigils are ALWAYS dd_inventory-owned (account_sigils table) in both modes.
    currencyMode = 'framework',
```

| Value         | VORP                                                                                                                                     | RSG                                                                         | Standalone                                |
| ------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- | ----------------------------------------- |
| `'framework'` | Dollar and Gold **are** VORP's own money and gold, read and written through dd\_lib. `character_currency` is never touched. Recommended. | Dollar and Gold **are** rsg-core's `cash` and `gold` accounts. Recommended. | **Do not use.** No wallet exists to read. |
| `'native'`    | DD Inventory owns the balances itself in `character_currency`. For a deliberately self-contained economy.                                | Same.                                                                       | **Required.** The only mode that works.   |

<Warning>
  On a standalone server `currencyMode` must be `'native'`. `dd_lib`'s money bridge has no framework branch to fall through to, so in `'framework'` mode every balance reads as `0` and every credit or debit fails with `FRAMEWORK_ADD_FAILED`. Nothing throws and nothing is logged as an error, so the symptom is a currency bar stuck at zero rather than a visible failure.
</Warning>

Note for both frameworks: an offline character has no reachable wallet in `'framework'` mode, because the framework wallet is only addressable through a connected player object. `getCharacterCurrency` reports zero for an offline character and `addCurrency` returns `PLAYER_OFFLINE` rather than guessing.

### Currencies

The three config keys (`breakdust`, `seamshards`, `sigils`) are internal identifiers used by the export API and the database columns. No player ever sees a key - every visible detail comes from the `label` and `icon` you set here.

```lua config/config.lua theme={null}
    currencies = {
        breakdust  = { label = 'Dollar', icon = 'dollar', enabled = true },
        seamshards = { label = 'Gold',   icon = 'gold',   enabled = true },
        sigils     = { label = 'Sigils', icon = 'sigil',  enabled = true },
    },

    -- Which currency buys inventory slot expansions: 'breakdust' | 'seamshards' | 'sigils'
    -- 'sigils' debits the account-wide premium balance (shared across characters),
    -- the other two debit the character wallet (framework money in 'framework' mode).
    -- If this points at a disabled currency, dd_inventory logs a warning at boot and
    -- falls back to the first enabled currency.
    slotPurchaseCurrency = 'sigils',
```

* `breakdust` is the cash wallet. In `'framework'` mode this **is** the framework's money: VORP currency `0`, or rsg-core's `cash` account.
* `seamshards` is the gold wallet. In `'framework'` mode this **is** the framework's gold: VORP currency `1`, or rsg-core's `gold` account. RSG's `bloodmoney` is a separate currency and is deliberately never used as a gold stand-in. rsg-core also rejects fractional amounts on `gold`, so round before crediting if your VORP setup deals in fractions.
* `sigils` is the account-wide premium balance, owned by DD Inventory itself (`account_sigils` table) in both modes and on every framework, and shared across all of a player's characters. It is keyed on the account license rather than a character, which is why it is the default currency for slot purchases: it works identically on VORP, RSG and standalone.
* `icon` selects a built-in inline SVG: `'dollar'`, `'gold'` or `'sigil'`.
* `enabled = false` hides that currency everywhere - currency bar, trade window, bank controls. Omitting `enabled` means enabled.

### Slot purchase tiers

Prices are in `slotPurchaseCurrency`.

```lua config/config.lua theme={null}
    -- Slot purchase tiers (cost in DDInvConfig.slotPurchaseCurrency)
    slotPurchaseTiers = {
        { slots = 1,  price = 100,  label = '+1 Slot' },
        { slots = 4,  price = 300,  label = '+4 Slots' },
        { slots = 8,  price = 533,  label = '+8 Slots' },
        { slots = 16, price = 966,  label = '+16 Slots' },
    },
```

### Rarities

The list order is the ascending prestige order, and it is also the NUI's sort priority. Rename labels, change colours, remove entries or add your own keys.

```lua config/config.lua theme={null}
    rarities = {
        { key = 'common',    label = 'Common',    color = '#9d9d9d' },
        { key = 'uncommon',  label = 'Uncommon',  color = '#1eff00' },
        { key = 'rare',      label = 'Rare',      color = '#0070dd' },
        { key = 'epic',      label = 'Epic',      color = '#a335ee' },
        { key = 'legendary', label = 'Legendary', color = '#ff8000' },
    },
```

The `key` must match the value stored in the `rarity` column of DD Inventory's `item_definitions`, and on VORP also in the `rarity` column DD Inventory adds to the `items` table. An item with an unlisted rarity falls back to the first entry's styling. Assign rarities live with `/dd_setrarity <item> <key>`, list the valid keys with `/dd_rarities`.

### Sort priority and trade

```lua config/config.lua theme={null}
    -- Sort priority: items are ordered by type first, then alphabetically by name
    sortTypePriority = {
        weapon     = 1,
        tool       = 2,
        consumable = 3,
        material   = 4,
        clothing   = 5,
        quest      = 6,
        misc       = 7,
    },

    -- Trade settings
    trade = {
        maxSlots    = 6,    -- max item slots per side in the trade window
        maxDistance = 5.0,  -- max world distance in metres between the two players
    },
```

`maxDistance` is enforced on the **server**, so a modified client cannot trade across the map. It is checked when the trade window is requested, once a second for as long as it stays open, and one last time immediately before the items change hands. Walk out of range and the trade cancels itself for both players with a message. Set it to `0` to switch the distance check off entirely.

To see what the server measures, for example when a player reports a trade cancelling on its own:

```lua theme={null}
exports.dd_inventory:getActiveTrades()
--> { { tradeId = 3, state = 'open', distance = 2.4, maxDistance = 5.0,
--      inRange = true, initiator = {...}, target = {...} } }
```

### Bind types and bind restrictions

| `bind_type`     | Trade            | Guild-type storage | Account-wide storage |
| --------------- | ---------------- | ------------------ | -------------------- |
| `tradable`      | yes              | yes                | yes                  |
| `bind_on_equip` | yes, until bound | yes, until bound   | yes, until bound     |
| `soulbound`     | no               | no                 | no                   |
| `family_bound`  | no               | no                 | yes                  |

Which inventory types enforce these rules is configurable, so a custom storage resource gets the same protection just by listing its type:

```lua config/config.lua theme={null}
    -- Inventory types that enforce bind rules (soulbound / family_bound / bind-on-equip)
    -- Used by moveItems to reject deposits; dd_bank uses bank_guild, bank_accountwide; dd_mail uses mail_storage
    bindRestrictions = {
        guildBankTypes   = { 'guild', 'bank_guild', 'mail_storage' },  -- soulbound and family_bound cannot deposit
        accountBankTypes = { 'account_bank', 'bank_accountwide' },      -- soulbound cannot; family_bound can
    },
}
```

A `bind_on_equip` item becomes bound when your gear or equipment resource calls `exports.dd_inventory:setItemBound(itemId)` on first equip.

### Framework overrides

Every framework call DD Inventory makes goes through `dd_lib`, and every one of them can be replaced without touching DD Inventory itself. This is how you run a custom notification, economy or identity system, and it is also how you drive DD Inventory from a core that is neither VORP nor RSG. Leave a key at `nil` to keep the built-in behaviour for the detected framework, so you can override one call and leave the rest alone. Reroute the bridge in `@dd_lib/config/overrides.lua`:

```lua config/overrides.lua theme={null}
DDLibOverrides['notify'] = function(src, text, ntype, duration)
    exports.my_notify:show(src, text, ntype, duration)
end

DDLibOverrides['money.add'] = function(src, amount, kind)  -- kind: 'cash' | 'gold'
    exports.my_economy:add(src, amount, kind)
end
```

Available keys: `identity.charId`, `identity.charName`, `identity.license`, `money.add`, `money.remove`, `money.get`, `money.onChanged`, `notify`, `log` - all documented inside the file itself. `money.remove` must return `false` when the player cannot pay, because DD Inventory reads that boolean to decide whether a purchase went through.

<Note>
  There is no override key for the character-selection hook. On VORP and RSG the bridge subscribes to the framework's own event; a core that fires neither has to trigger `dd_inventory:client:sessionActive` towards the player itself.
</Note>

### Language

Every player-facing string - notifications, admin command feedback and all UI text - lives in `config/language.lua`. English and German ship out of the box:

```lua config/language.lua theme={null}
DDLanguage = 'en'   -- 'en' | 'de', or '' to follow the server-wide convar
```

Set `DDLanguage = ''` to follow a server-wide setting instead, so all your DD resources switch together:

```cfg theme={null}
setr dd:language de
```

To add your own language, copy a whole `Translations['en'] = { ... }` block, rename the key to your language code, and translate the values only. Keys must stay identical across blocks, and every `%s` / `%d` placeholder must be kept in the same order. Keys with a dot in them (`trade.accept`, `tooltip.label_weight`) are the ones shipped to the UI.

<Note>
  Currency names, rarity names and the weight unit are not in the language file - they come from `config/config.lua`. Item labels come from your `items` database table.
</Note>

## Admin commands

All commands are registered through `ox_lib` and gated by the ace object `command.<name>`, granted to the principal `group.admin` on first boot. Grant it to anyone else with `add_ace <principal> command.<name> allow` in `server.cfg`. All of them work from the server console, RCON and txAdmin as well as in-game; when run from the console the reply is printed to the console instead of being sent as an in-game notification.

| Command                         | Ace object             | Restricted     | Purpose                                                                      |
| ------------------------------- | ---------------------- | -------------- | ---------------------------------------------------------------------------- |
| `/dd_give <item> [qty]`         | `command.dd_give`      | `group.admin`  | Add an item to your own character's inventory.                               |
| `/dd_remove <item> [qty]`       | `command.dd_remove`    | `group.admin`  | Remove items from your own character's inventory.                            |
| `/dd_inv`                       | `command.dd_inv`       | *unrestricted* | List your inventory: inventory id, slot count, and a line per occupied slot. |
| `/dd_transfer <itemId> <invId>` | `command.dd_transfer`  | `group.admin`  | Move a single item instance into another inventory - the item-recovery tool. |
| `/dd_setrarity <item> <rarity>` | `command.dd_setrarity` | `group.admin`  | Change an item's rarity live, everywhere.                                    |
| `/dd_rarities`                  | `command.dd_rarities`  | `group.admin`  | List every configured rarity key, label and colour.                          |
| `/dd_debug <target> <id>`       | `command.dd_debug`     | `group.admin`  | Inspect an inventory, item, session, audit trail or the cache.               |

These commands are DD Inventory's own and work on every framework. Your framework's existing admin commands (VORP's `/additem`, `/giveitem`, `/setjob`, RSG's equivalents) keep working unchanged, because DD Inventory answers the inventory exports they call. It does not replace or remove any of them.

<AccordionGroup>
  <Accordion title="/dd_give and /dd_remove">
    ```
    /dd_give apple
    /dd_give consumable_medkit 5
    /dd_remove apple 3
    ```

    `/dd_give` fails with `Unknown item: <name>` if no `item_definitions` row exists for that name. Every grant is written to the audit log with a correlation id.
  </Accordion>

  <Accordion title="/dd_transfer">
    Moves a single item instance (by its `item_instances.item_id`) into another inventory id. Useful for recovering items that ended up in a stash or bank the owner can no longer reach.

    ```
    /dd_transfer 48213 7
    ```
  </Accordion>

  <Accordion title="/dd_setrarity">
    Sets an item definition's rarity and pushes the change live to every online player holding that item - no restart, no relog.

    ```
    /dd_setrarity apple legendary
    /dd_setrarity gold_bar epic
    ```

    The write goes to both `item_definitions.rarity` and, on a VORP install, the framework's own `items.rarity` column, so the change survives the item re-import that runs on every boot.

    Unknown rarity keys are allowed - you may add your own keys to `DDInvConfig.rarities` later. The command then prints a warning and the NUI falls back to default styling for that item until the key is configured. Error replies: `MISSING_ITEM_NAME`, `MISSING_RARITY_KEY`, `ITEM_NOT_FOUND`.
  </Accordion>

  <Accordion title="/dd_rarities">
    Prints every configured rarity - key, label and colour - in ascending prestige order. Use it to find the exact key strings `/dd_setrarity` expects.

    ```
    /dd_rarities
      common - Common (#9d9d9d)
      uncommon - Uncommon (#1eff00)
      rare - Rare (#0070dd)
      epic - Epic (#a335ee)
      legendary - Legendary (#ff8000)
    ```
  </Accordion>

  <Accordion title="/dd_debug">
    | Target      | `id` means       | Prints                               |
    | ----------- | ---------------- | ------------------------------------ |
    | `inventory` | character id     | inventory id, slot count, item count |
    | `item`      | `item_id`        | the raw item instance row as JSON    |
    | `session`   | player server id | license + active character id        |
    | `audit`     | correlation id   | up to 20 matching audit rows         |
    | `cache`     | *(ignored)*      | cache size + hit/miss rate           |

    ```
    /dd_debug inventory 2
    /dd_debug cache 0
    ```
  </Accordion>

  <Accordion title="Optional client debug commands">
    If `DDInvConfig.enableTestTui = true`, a text-mode debug UI registers the extra client commands `/dd_invtui`, `/dd_invtui_close`, `/dd_invtui_refresh`, `/dd_invtui_select`, `/dd_invtui_use`, `/dd_invtui_drop`, `/dd_invtui_move`. Leave `enableTestTui = false` in production.
  </Accordion>
</AccordionGroup>

## Restarting and updating

* Restart in dependency order: `dd_lib` then `dd_shellkit` then `dd_inventory`.
* Restarting `dd_shellkit` alone is safe - the inventory detects the reloaded shell and re-registers its panel and keybind automatically.
* Updating DD Inventory itself: `restart dd_inventory` is enough. Schema migrations and the item import are idempotent and re-run safely on every start.

<Warning>
  `restart dd_lib` stops its dependents. FXServer brings down `dd_inventory` with it and does not necessarily bring it back - run `start dd_inventory` (and any other dd\_lib consumer) again afterwards. On a live server, prefer a full restart.

  The same applies to the placeholder, whichever name you installed it under: never `restart vorp_inventory` or `restart rsg-inventory` on a live server. FXServer stops `dd_inventory` alongside it and does not bring it back. If it happens, run `start dd_inventory`. The placeholder has no code, so there is never a reason to restart it.
</Warning>

## Troubleshooting

<AccordionGroup>
  <Accordion title="Item images are missing, everything shows a placeholder letter">
    In the inventory itself: the icons were not copied into `dd_inventory/img/items/` - redo the icon copy step and `restart dd_inventory` (new files also need a `refresh` first). In third-party UIs such as `vorp_menu` or `vorp_stores`: those hardcode the old path `nui://vorp_inventory/html/img/items/...`, which no longer resolves once the original resource is deleted - apply the one-line path change above.
  </Accordion>

  <Accordion title="An item does nothing when I right-click it">
    Two things must be true. First, the item must be flagged usable - check `is_usable` in `item_definitions`, which is imported from VORP's `usable` column or RSG's `useable` field. Second, something has to handle the use: a `registerUsableItem` callback on VORP, a `CreateUseableItem` callback on RSG, or a server-side handler registered via `registerItemUseHandler` on any framework. With neither, the use is allowed but nothing happens.
  </Accordion>

  <Accordion title="A new item I added to the framework's item table does not appear">
    The import runs at boot. Restart `dd_inventory` and watch for the `VORP item import: N imported` line, or its RSG equivalent. Existing definitions are never overwritten, so if you edited an item that was already imported, change it in `item_definitions` or use `registerItemDefinition`. On standalone there is no import at all and `registerItemDefinition` is the only route.
  </Accordion>

  <Accordion title="Every currency shows zero and payments quietly fail">
    Standalone servers: `currencyMode` is still on `'framework'` and there is no framework wallet to read. Set it to `'native'`. On VORP and RSG the same symptom for a single character usually means the character is offline, which is reported as `PLAYER_OFFLINE` rather than guessed at.
  </Accordion>

  <Accordion title="Standalone: pressing I does nothing for players who joined after the server started">
    There is no character-selection event on standalone, so the session that gates the keybind is only marked active by the replay that runs shortly after `dd_inventory` starts. Trigger `dd_inventory:client:sessionActive` towards the player from your own spawn flow. See [Running without a framework](#running-without-a-framework).
  </Accordion>

  <Accordion title="My weapons behave slightly differently than before">
    Equipping works, the legacy deep holster and component subsystem does not exist in v1. See the weapons note on the [Compatibility](/scripts/inventory/compatibility) page.
  </Accordion>

  <Accordion title="A player's slots shrank and items are stuck in grey slots">
    That is intentional. A temporary slot buff expired. The items are safe and can be dragged out, but nothing new goes in until the player re-buys or re-buffs those slots.
  </Accordion>

  <Accordion title="slotPurchaseCurrency warning at boot">
    It points at a currency you disabled. DD Inventory falls back to the first enabled one - set it explicitly to silence the warning.
  </Accordion>

  <Accordion title="Nothing opens when I press I">
    Check that `dd_shellkit` is started (or `dd_hud`, if you run it - it takes over as shell), and that `openKey` in `config/config.lua` is not colliding with another resource's bind.
  </Accordion>
</AccordionGroup>

## Support

<Columns cols={2}>
  <Card title="Discord support" icon="discord" href="https://discord.gg/4jhQsPr6pe">
    Include your server's console output from resource start, your `config/config.lua`, and the exact steps to reproduce.
  </Card>

  <Card title="Developer reference" icon="code" href="/scripts/inventory/developers">
    Exports, events, use handlers and move validators.
  </Card>
</Columns>
