> ## 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.

# Compatibility

> The optional vorp_inventory and rsg-inventory compatibility layers - covered exports, mapping model, and honest limitations

DD Inventory's core is framework-agnostic and calls no framework API, so it does not need a compatibility layer to run. What these layers add is *migration*: they let DD Inventory replace the inventory your framework shipped with, answering that inventory's export surface under its original name, so existing stores, banking, housing, stables, medic, police, crafting and your own custom jobs keep working without a single line changed.

Two layers ship, one per framework. Both work the same way: the original resource is deleted and its item icons are copied into DD Inventory. Only the *name* of the deleted resource can still be missed - by a `dependencies { ... }` manifest line or a `GetResourceState` check in a third-party script. Normally you delete that line; for scripts you cannot edit, a scripts-free one-file placeholder ships in `extras/` for both frameworks. There is deliberately no `provide` directive - FXServer's `provide` does not alias runtime exports.

<Info>
  **You may not need either layer.** A standalone server has no framework inventory to replace and no legacy callers, so neither layer applies. Nothing on this page is required reading for a fresh server built on DD Inventory's own [exports](/scripts/inventory/developers).
</Info>

|                                            | VORP                                                    | RSG                                                                                  | Standalone  |
| ------------------------------------------ | ------------------------------------------------------- | ------------------------------------------------------------------------------------ | ----------- |
| Layer                                      | [`vorp_inventory`](#vorp_inventory)                     | [`rsg-inventory`](#rsg-inventory)                                                    | none needed |
| Gated on the detected framework            | no, it registers on every framework                     | yes, `DDLib.framework == 'rsg'`                                                      | n/a         |
| Verification                               | exercised against the standard VORP suite in production | **never run against a live rsg-core**, built from published source and documentation | n/a         |
| Placeholder for unfixable dependency lines | `extras/vorp_inventory/`                                | `extras/rsg-inventory/`                                                              | n/a         |

<Note>
  The RSG layer is gated on `DDLib.framework == 'rsg'`, so on a VORP or standalone server not a single line of it executes. The vorp layer is deliberately not gated: its exports register on every framework and simply go uncalled where nothing looks for them.
</Note>

## vorp\_inventory

The complete `vorp_inventory` export and event surface is re-registered under both the `dd_inventory` and the `vorp_inventory` export names. That includes the legacy `vorp_inventoryApi()` factory, the `vorpCore:*` server events and the `vorp_inventory:*` client events.

```lua theme={null}
-- Give an item (callback style, exactly as with vorp_inventory)
exports.vorp_inventory:addItem(source, 'apple', 3)

-- Register a usable item
exports.vorp_inventory:registerUsableItem('apple', function(data)
    local src = data.source
    exports.vorp_inventory:subItem(src, 'apple', 1)
    TriggerClientEvent('myresource:ateApple', src)
end)

-- Register a custom (shared) inventory
exports.vorp_inventory:registerInventory({
    id = 'saloon_stash',
    name = 'Saloon Stash',
    limit = 40,
    acceptWeapons = false,
    shared = true,
    ignoreItemStackLimit = true,
})
exports.vorp_inventory:openInventory(source, 'saloon_stash')

-- Read a player's items
exports.vorp_inventory:getUserInventoryItems(source, function(items)
    for _, item in ipairs(items) do print(item.name, item.count) end
end)
```

### Covered exports

VORP semantics are preserved, including the sync-return plus optional-callback duality of `respond()`.

<AccordionGroup>
  <Accordion title="Items">
    `addItem`, `subItem`, `subItemID`, `subItemById`, `getItemCount`, `getItemDB`, `getItem`, `getItemById`, `getItemByMainId`, `getItemByName`, `getItemContainingMetadata`, `getItemMatchingMetadata`, `setItemMetadata`, `getUserInventoryItems`, `getUserInventory`
  </Accordion>

  <Accordion title="Capacity, usables, open and close">
    `canCarryItem`, `canCarryItems`, `canCarryWeapons`, `registerUsableItem`, `unRegisterUsableItem`, `openInventory`, `closeInventory`, `openPlayerInventory`
  </Accordion>

  <Accordion title="Custom inventories">
    `registerInventory`, `removeInventory`, `isCustomInventoryRegistered`, `getCustomInventoryData`, `updateCustomInventoryData`, `updateCustomInventorySlots`, `getCustomInventorySlots`, `setCustomInventoryItemLimit`, `setCustomInventoryWeaponLimit`, `addItemsToCustomInventory`, `addWeaponsToCustomInventory`, `getCustomInventoryItems`, `getCustomInventoryWeapons`, `getCustomInventoryItemCount`, `getCustomInventoryWeaponCount`, `removeItemFromCustomInventory`, `removeWeaponFromCustomInventory`, `removeCustomInventoryWeaponById`, `updateCustomInventoryItem`, `deleteCustomInventory`, `AddPermissionMoveToCustom`, `AddPermissionTakeFromCustom`, `AddCharIdPermissionMoveToCustom`, `AddCharIdPermissionTakeFromCustom`, `BlackListCustomAny`
  </Accordion>

  <Accordion title="Weapons and ammo">
    `createWeapon`, `giveWeapon`, `subWeapon`, `deleteWeapon`, `getUserInventoryWeapons`, `getUserWeapon`, `getWeaponBullets`, `getWeaponComponents`, `setWeaponCustomLabel`, `setWeaponSerialNumber`, `setWeaponCustomDesc`, `getUserAmmo`, `addBullets`, `subBullets`, `removeAllUserAmmo`
  </Accordion>

  <Accordion title="Legacy API">
    The `vorp_inventoryApi()` factory plus the full `vorpCore:*` and `vorp_inventory:Server:*` event list from `inventoryApiController.lua`.
  </Accordion>

  <Accordion title="Not provided">
    `getUserTotalCountItems` and `getUserTotalCountWeapons` (internal in the original), the pickup and drop API, and the client-side weapon-data exports `getWeaponDefaultWeight`, `getWeaponDefaultDesc`, `getWeaponDefaultLabel`, `getWeaponsDefaultData`, `getWeaponAmmoTypes`, `getAmmoLabel` and `getServerItem` - those read `config/weapons.lua` and `config/ammo.lua` tables that do not exist here.
  </Accordion>
</AccordionGroup>

DD Inventory has been tested against the standard VORP suite - stores, banking, medic, police, stables, housing (including the legacy inventory API), weapons v2, crafting and metabolism - plus custom scripts written against the vorp\_inventory exports.

### Mapping model

* **User inventory** is the active character's `personal` inventory, with `charId` resolved through the framework bridge.
* **Item name** is `item_definitions.item_name`, identical to VORP's `items.item`. The boot-time import guarantees every legacy name exists.
* **Quantity.** VORP stores one row per stack with an `amount`; DD Inventory stores one row per physical unit spread over slots. All read paths aggregate by `(item_name + metadata)` so `getItemCount` and `getUserInventoryItems` look like VORP; all write paths handle stacking.
* **Item id.** The VORP crafted id maps to the `item_instances.item_id` of the first unit in a stack group. `subItemById(src, id, cb, allow, amount)` removes that instance plus `amount - 1` siblings from the same group.

Item shape returned to consumers:

```
{ id, name, item, mainid, label, desc, metadata, type, count, limit, canUse,
  canRemove, group, weight, owner, createdAt, percentage, isDegradable,
  maxDegradation, degradation, useExpired }
```

`percentage` is pinned to `100` and `isDegradable` to `false`. `type` is `'item_weapon'` for weapon definitions and `'item_standard'` otherwise.

Weapon shape:

```
{ id, name, propietary, charId, used, used2, ammo, components, comps, desc,
  group = 5, source, label, serial_number, custom_label, custom_desc, weight,
  currInv, dropped }
```

A weapon is one item instance of a definition named `weapon_<lowercased vorp name>` (`item_type = 'weapon'`, `stack_size = 1`, auto-registered on first grant). All VORP weapon fields live in the instance metadata.

### Item import

On boot, DD Inventory reads `SELECT * FROM items` and inserts every missing name into `item_definitions`:

| VORP column            | `item_definitions`                                              |
| ---------------------- | --------------------------------------------------------------- |
| `item`                 | `item_name`                                                     |
| `label`                | `label`                                                         |
| `limit`                | `stack_size` (`<= 0` becomes 100; VORP uses `-1` for unlimited) |
| `type`                 | `item_type` (`item_weapon` becomes `weapon`, else `misc`)       |
| `usable`               | `is_usable`                                                     |
| `desc` / `description` | `description` (whichever column exists)                         |
| `weight`               | `weight` (`<= 0` becomes 0.25)                                  |

Existing DD Inventory definitions are never overwritten. The available column set is probed with `SHOW COLUMNS FROM items`, so version differences in VORP's schema are tolerated. Counts are printed on boot.

### Limitations

<AccordionGroup>
  <Accordion title="Weapon equip and holster UX">
    The original vorp\_inventory had a large client-side weapon subsystem: holster state, `used` and `used2`, weapon objects, component attachment and pickups. This layer has none of it. Using a weapon item in the DD NUI calls `GiveWeaponToPed`, `SetCurrentPedWeapon` and `SetPedAmmoByType` with the exact argument list vorp\_inventory itself used. Holstering, dual-wield attach points, `used2` semantics and weapon component visuals are not reproduced.
  </Accordion>

  <Accordion title="No degradation">
    VORP items can decay (`degradation`, `percentage`, `useExpired`). DD Inventory has no such concept - every item reports `percentage = 100` and `isDegradable = false`, so `vorp_stores` prices everything as pristine.
  </Accordion>

  <Accordion title="Job permissions on custom inventories">
    `PermissionMoveTo` and `PermissionTakeFrom` are stored in `compat_custom_inventories.config` and returned by `getCustomInventoryData`, but not enforced: they are keyed by job, and resolving a player's job would mean calling the host framework directly, which DD Inventory does not do.

    `CharIdPermissionMoveTo` and `CharIdPermissionTakeFrom` **are** enforced when `UsePermissions` is set. On registration they are projected onto DD Inventory's own `inventory_rights` access control list (`put` and `take` respectively), so there is one permission model rather than two competing ones. Clearing `UsePermissions`, or removing the last character, clears the list and reopens the container. See [Per-inventory access control](/scripts/inventory/developers#per-inventory-access-control).

    Also enforced: `acceptWeapons`, `UseBlackList` / `BlackListItems`, `whitelistItems` / `whitelistWeapons` (the matching `limited*` table doubles as the allow-list) and the `limitedItems` / `limitedWeapons` per-item count ceilings. All of these are deposit-side rules in VORP, so they gate what a container accepts, not what may be taken back out of it.
  </Accordion>

  <Accordion title="Capacity model">
    VORP is weight-based (`invCapacity`), DD Inventory is slot-based. `canCarryItems(src, amount)` answers "are there at least `amount` free slots". `canCarryItem` runs the add simulation, which is the accurate answer for the slot model. Scripts that pass a weight to `canCarryItems` will get a stricter answer than before.
  </Accordion>

  <Accordion title="Custom inventory limit">
    VORP's `limit` is a weight capacity for some scripts (`vorp_housing` passes `MaxWeight`) and a slot count for others. It is clamped into 10 to 100 slots, because `inventories.max_slots` is a `TINYINT`. Houses and stables configured with, for example, `MaxWeight = 500` become 100-slot containers.
  </Accordion>

  <Accordion title="openPlayerInventory">
    Opens the target's personal inventory as the caller's secondary panel. The original's per-item-type limits, cooldowns and blacklist payload are ignored.
  </Accordion>

  <Accordion title="subWeapon limbo">
    VORP's `subWeapon` detaches a weapon without deleting it, so the id stays resolvable. Detached weapons are parked in an internal inventory registered as `__vorp_weapon_limbo__`. If that inventory is full the weapon is hard-deleted instead, and the deletion is logged.
  </Accordion>

  <Accordion title="Shared custom inventories">
    `shared = true` is stored but has no effect - every VORP id maps to exactly one backing inventory, which is already shared between all characters that open it. Non-shared per-character storages (`shared = false`) are therefore also shared. Scripts that relied on per-character segmentation behind one id, as some `vorp_stables` configs do, will see one common container.
  </Accordion>
</AccordionGroup>

## rsg-inventory

DD Inventory can replace `rsg-inventory` on an RSG server. Your existing `rsg-*` scripts keep calling `exports['rsg-inventory']:AddItem(...)` and friends, and DD Inventory answers them.

<Warning>
  **Verification status.** The RSG code paths are written against the published `rsg-inventory` source and reference documentation, and pass a full Lua syntax sweep. They have **not** been run against a live RSG server. The VORP path is the one that is proven in production. Test on a staging server before production.
</Warning>

### What an RSG server gets

* DD Inventory's own NUI, slot model, weight model, rarities, trading and currency bar, in place of the `rsg-inventory` interface.
* All 29 `rsg-inventory` server exports, registered under both the `dd_inventory` and the `rsg-inventory` export names.
* The `rsg-inventory` client export `HasItem`.
* A one-way projection of the authoritative inventory into `Player.PlayerData.items`, so every `rsg-*` script that reads that table, and the client mirror rsg-core derives from it, sees the truth.
* An automatic import of `RSGCore.Shared.Items` into `item_definitions`, re-run whenever rsg-core broadcasts a core object update.
* Weapon handling: serial generation in the upstream format, `info.quality` initialisation, `player_weapons` bookkeeping, un-equipping a weapon that leaves the inventory, and dispatch to `rsg-weapons:client:UseWeapon`, `UseThrownWeapon` and `UseEquipment` with the unmodified RSG item shape.

`rsg-core` itself is not patched. Every access rsg-core makes to the inventory goes through an export or an event, so a script-free placeholder plus the export registrations is sufficient.

### Installation differences

Follow the standard [Setup](/scripts/inventory/setup), with three changes.

<Steps>
  <Step title="Icons live elsewhere">
    RSG keeps item images in `html/images/`, not `html/img/items/`:

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

      ```bat Windows (cmd) theme={null}
      robocopy resources\[rsg]\rsg-inventory\html\images resources\[dd]\dd_inventory\img\items *.png /XC /XN /XO
      ```
    </CodeGroup>

    `rsg-inventory`, `rsg-core` and `rsg-weapons` resolve item images relatively and need no path change. Only your own scripts that hardcode `nui://rsg-inventory/html/images/` need pointing at `nui://dd_inventory/img/items/`.
  </Step>

  <Step title="Keep the name resolvable, if anything still needs it">
    Every `exports['rsg-inventory']:*` call is answered by DD Inventory, so scripts keep working. What still looks for the name is a manifest line `dependencies { 'rsg-inventory' }`, which stops that resource from starting, and a runtime `GetResourceState('rsg-inventory')` check, which makes a script quietly skip its inventory path.

    Normally you just delete that line. If you cannot edit the script, because it is encrypted or escrowed, copy `extras/rsg-inventory/` out of this download 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. Start it before `dd_inventory`.

    ```cfg theme={null}
    ensure oxmysql
    ensure ox_lib
    ensure rsg-core
    ensure rsg-inventory      # only if you installed the placeholder
    ensure dd_lib
    ensure dd_shellkit
    ensure dd_inventory
    ```
  </Step>

  <Step title="Check four settings before first boot">
    See the table below.
  </Step>
</Steps>

| Setting                            | Where                            | Required value           | Why                                                                                                                                                                                                                                                 |
| ---------------------------------- | -------------------------------- | ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `RSGConfig.Money.EnableMoneyItems` | `rsg-core/config.lua`            | `false`                  | rsg-core otherwise models cash as inventory items and re-synchronises them on every player data update, which fights DD Inventory's currency handling. DD Inventory prints a boot warning if this is left on.                                       |
| `DDInvConfig.defaultMaxWeight`     | `dd_inventory/config/config.lua` | `35000.0`                | RSG item weights are in grams and RSG's default carry capacity is 35000. Importing RSG weights one to one and leaving the DD default of `420.0` would make every character instantly overweight.                                                    |
| `DDInvConfig.weightUnit`           | `dd_inventory/config/config.lua` | `'g'`                    | Cosmetic, matches the imported gram weights.                                                                                                                                                                                                        |
| `DDInvConfig.rsgStackSize`         | `dd_inventory/config/config.lua` | optional, default `1000` | RSG has no stack ceiling at all, only a weight limit. DD Inventory requires a `stack_size` per definition, so non-unique RSG items get this high ceiling and weight stays the effective limit. Add the key yourself if you want a different number. |

DD Inventory creates three extra tables on an RSG server:

| Table                    | Purpose                                                                                                                |
| ------------------------ | ---------------------------------------------------------------------------------------------------------------------- |
| `compat_rsg_citizenids`  | Maps each RSG `citizenid` (a string) to a stable surrogate integer, because `inventories.owner_char_id` is an `INT`.   |
| `compat_rsg_inventories` | Maps each RSG stash identifier string to its DD Inventory inventory id, plus the label, max weight and slot count.     |
| `player_weapons`         | The same table `rsg-inventory` creates. An existing one is reused untouched, so `rsg-weapons` keeps resolving serials. |

The boot log line to look for:

```
[dd_inventory:rsgcompat] layer ready: N stashes restored, N item definitions imported, N use handlers
```

### Server export reference

Call as `exports['rsg-inventory']:Name(...)` exactly as before, or as `exports.dd_inventory:Name(...)`. Signatures are the ones in `rsg-inventory/server/exports.lua`. Where the published documentation disagrees with that file, the file wins.

Status values: **implemented** behaves as upstream for the documented contract. **partial** works, with a behavioural difference called out in the note. **no-op** is accepted and returns the upstream return type, but does nothing, because DD Inventory has already done the work.

| Export               | Signature                                          | Status      | Note                                                                                                                                                                                                                                                         |
| -------------------- | -------------------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `LoadInventory`      | `(source, citizenid)`                              | implemented | Returns a live projection. Nothing is read from `players`.`inventory`; DD Inventory owns the data.                                                                                                                                                           |
| `SaveInventory`      | `(source, offline)`                                | no-op       | DD Inventory persists every mutation immediately. Writing `players`.`inventory` would create a second, stale copy.                                                                                                                                           |
| `SetInventory`       | `(source, items)`                                  | implemented | Clears the inventory and re-adds the supplied table. Slots are honoured where the target slot is free.                                                                                                                                                       |
| `SetItemData`        | `(source, itemName, key, val)`                     | implemented | `key = 'info'` replaces the whole metadata object. Any other key is stored inside the metadata object.                                                                                                                                                       |
| `GetItemWeight`      | `(itemName)`                                       | implemented | Read from `RSGCore.Shared.Items`, so it matches what the rest of your server believes.                                                                                                                                                                       |
| `UseItem`            | `(itemName, ...)`                                  | implemented | Both upstream call forms are accepted. Every `useable` RSG item is also bridged into DD Inventory's own use-handler registry, so right-clicking in the DD NUI reaches your `CreateUseableItem` callback.                                                     |
| `GetSlotsByItem`     | `(items, itemName)`                                | implemented | Pure function over the table you pass in. Ported verbatim.                                                                                                                                                                                                   |
| `GetFirstSlotByItem` | `(items, itemName)`                                | implemented | Pure function over the table you pass in. Ported verbatim.                                                                                                                                                                                                   |
| `GetItemBySlot`      | `(source, slot)`                                   | implemented | The RSG slot is the DD Inventory slot, so slot addressing is stable across relogs by construction.                                                                                                                                                           |
| `GetTotalWeight`     | `(items)`                                          | implemented | Pure function over the table you pass in. Ported verbatim.                                                                                                                                                                                                   |
| `GetItemByName`      | `(source, item)`                                   | implemented | Returns the first matching stack in RSG item shape.                                                                                                                                                                                                          |
| `GetItemsByName`     | `(source, item)`                                   | implemented | Returns every matching stack in RSG item shape.                                                                                                                                                                                                              |
| `GetSlots`           | `(identifier)`                                     | implemented | Returns `slotsUsed, slotsFree`. Uses DD Inventory's effective slot count, so purchased slot expansions are included.                                                                                                                                         |
| `GetItemCount`       | `(source, items)`                                  | implemented | Accepts a single name or an array of names.                                                                                                                                                                                                                  |
| `CanAddItem`         | `(source, item, amount)`                           | implemented | Returns `boolean` plus `'weight'`, `'slots'` or `nil`. Weight and slots are both checked for players, only weight for stashes, matching upstream.                                                                                                            |
| `GetFreeWeight`      | `(source)`                                         | implemented | Answers with DD Inventory's capacity, not `Player.PlayerData.weight`.                                                                                                                                                                                        |
| `ClearInventory`     | `(source, filterItems)`                            | implemented | `filterItems` accepts a name, an array of names, or nothing. Also un-equips the held weapon, as upstream does.                                                                                                                                               |
| `HasItem`            | `(source, items, amount)`                          | implemented | All three input forms (name, array of names, map of name to amount) are ported from upstream verbatim.                                                                                                                                                       |
| `CloseInventory`     | `(source, identifier)`                             | implemented | Also clears `state.inv_busy` and fires `rsg-inventory:client:closeInv`, as upstream does.                                                                                                                                                                    |
| `OpenInventoryById`  | `(source, targetId)`                               | partial     | Opens the target's inventory as DD Inventory's secondary panel. Upstream's 1.5 second delay and its `otherplayer-<id>` label payload do not apply, because the DD NUI builds its own header.                                                                 |
| `ClearStash`         | `(identifier)`                                     | implemented | Empties the stash and keeps it registered.                                                                                                                                                                                                                   |
| `SaveStash`          | `(identifier)`                                     | no-op       | DD Inventory persists stash contents on every write.                                                                                                                                                                                                         |
| `OpenInventory`      | `(source, identifier, data)`                       | implemented | With no `identifier` it opens the player's own inventory. With one it creates the stash on demand from `data` (`label`, `maxweight`, `slots`, `coords`) and opens it as the secondary panel. The `isOpen` single-occupant rule is enforced as upstream does. |
| `ForceDropItem`      | `(source, item, amount, info, reason)`             | partial     | **Destructive.** DD Inventory has no world drops, so the item is removed from the player and `false` is returned instead of a network id.                                                                                                                    |
| `AddItem`            | `(identifier, item, amount, slot, info, reason)`   | implemented | `identifier` is a player server id or a stash identifier string. On overflow the add fails and returns `false`; upstream would force-drop the overflow to the ground, which is not possible here. Nothing is destroyed.                                      |
| `RemoveItem`         | `(identifier, item, amount, slot, reason, isMove)` | implemented | Fires the documented third-party hook `rsg-inventory:server:itemRemovedFromPlayerInventory` with `{ amount, slot, info }`, and `rsg-core:client:RemoveWeaponFromTab` when `isMove` is set on a weapon.                                                       |
| `GetInventory`       | `(identifier)`                                     | implemented | Returns `{ items, label, maxweight, slots, isOpen, coords }`, with `items` in the RSG slot-indexed shape.                                                                                                                                                    |
| `CreateInventory`    | `(identifier, data)`                               | implemented | Idempotent. A second call for the same identifier only refreshes `label`, `maxweight` and `slots`; it never creates a second backing inventory. The identifier to inventory id mapping is persisted so stash contents survive restarts.                      |
| `DeleteInventory`    | `(identifier)`                                     | implemented | Mirrors upstream exactly: un-registers only. The stored items stay behind so a later `CreateInventory` finds them again.                                                                                                                                     |

**Totals: 25 implemented, 2 partial, 2 no-op, 0 not supported.**

`slots` on a stash is clamped to 100, because `inventories.max_slots` is a `TINYINT`. RSG's own default stash size is exactly 100, so only servers that raised it are affected.

`CreateShop`, `OpenShop`, `RestockShop` and `DoesShopExist` are **not supported** - see the limitations below.

### Client export and events

| Export    | Signature         | Status      | Note                                                                                                                                                                                   |
| --------- | ----------------- | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `HasItem` | `(items, amount)` | implemented | Reads `RSGCore.Functions.GetPlayerData().items`, exactly as upstream does. That table is kept current by the server layer's one-way projection, so no second client-side cache exists. |

Events fired towards other resources:

| Event                                                 | Fired | Note                                                                    |
| ----------------------------------------------------- | ----- | ----------------------------------------------------------------------- |
| `rsg-inventory:server:itemRemovedFromPlayerInventory` | yes   | The upstream hook for third-party resources. Same arguments.            |
| `rsg-core:client:RemoveWeaponFromTab`                 | yes   | On a weapon removal with `isMove`.                                      |
| `rsg-weapons:client:UseWeapon`                        | yes   | On use of a `weapon` item, and when a held weapon leaves the inventory. |
| `rsg-weapons:client:UseThrownWeapon`                  | yes   | On use of a `weapon_thrown` item.                                       |
| `rsg-weapons:client:UseEquipment`                     | yes   | On use of an `equipment` item.                                          |
| `rsg-inventory:client:ItemBox`                        | yes   | On add, remove and use.                                                 |
| `rsg-inventory:client:closeInv`                       | yes   | From `CloseInventory`.                                                  |
| `rsg-inventory:client:updateInventory`                | yes   | From `ClearInventory`.                                                  |
| `rsg-log:server:CreateLog`                            | no    | DD Inventory writes its own audit log with correlation ids.             |

`rsg-inventory:server:closeInventory`, `:useItem`, `:updateHotbar`, `:SetInventoryData`, `:openPlayerInventory`, `:openStash`, `:openDrop`, `:openVending` and the seven `:*Trade*` events are the transport protocol of the `rsg-inventory` web interface. DD Inventory ships its own NUI and that interface does not run, so these have no callers.

<Note>
  One consequence worth stating plainly: the restricted-stash prefix rules (`police-`, `marshal-`, `gang-`, `admin-`, `evidence-`) live only in those NUI event handlers upstream, never in the `OpenInventory` export. Access control on the export path is the caller's responsibility upstream and remains so here. If you need it enforced inside the inventory, register a [move validator](/scripts/inventory/developers#move-validators).
</Note>

### Item model mapping

`RSGCore.Shared.Items` is imported into `item_definitions` on boot and again on `RSGCore:Server:UpdateObject`. Existing DD Inventory definitions are never overwritten, so your own balance data stays authoritative.

| RSG field                                      | DD Inventory field | Mapping                                                                                                        |
| ---------------------------------------------- | ------------------ | -------------------------------------------------------------------------------------------------------------- |
| `name`                                         | `item_name`        | one to one, lowercased                                                                                         |
| `label`                                        | `label`            | one to one                                                                                                     |
| `description`                                  | `description`      | one to one                                                                                                     |
| `weight`                                       | `weight`           | one to one, in grams                                                                                           |
| `type`                                         | `item_type`        | `weapon` and `weapon_thrown` become `weapon`; `equipment` becomes `tool`; everything else becomes `misc`       |
| `unique`                                       | `stack_size`       | `true` becomes `1`; `false` becomes `DDInvConfig.rsgStackSize` (default `1000`)                                |
| `useable`                                      | `is_usable`        | one to one                                                                                                     |
| `info`                                         | `default_metadata` | one to one                                                                                                     |
| `image`                                        | icon lookup        | copy the PNGs during installation                                                                              |
| `decay`, `delete`, `shouldClose`, `combinable` | none               | read live from `RSGCore.Shared.Items` when an item is projected back into RSG shape; not stored on the DD side |

DD Inventory adds `rarity`, `sell_value`, `bind_type` and `equipment_data`, which RSG has no concept of. Imported items start on the defaults and can be filled in with `/dd_setrarity` and `registerItemDefinition`.

DD Inventory stores one row per physical unit, each carrying a slot. An RSG stack is therefore the set of instances sharing one slot, and the RSG slot **is** the DD Inventory slot. There is no separate slot projection table and slot addressing is stable across relogs by construction. `info` maps flat onto the instance `metadata` column: the metadata object **is** the `info` table. That makes DD Inventory's stack matching, which compares whole metadata objects, equivalent to RSG's rule that items with different `info` - specifically different `info.quality` - do not stack.

RSG weapons are ordinary items, which makes them considerably simpler than VORP's. Everything that identifies a specific gun lives in `info`:

| Field          | Handled by                                                                                                                                  |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `info.serie`   | Generated by DD Inventory on the first `AddItem` in the exact upstream format, so `player_weapons.serial` and `rsg-weapons` keep resolving. |
| `info.quality` | Initialised to `100`, then passed through untouched. `rsg-weapons` owns the value.                                                          |
| `info.ammo`    | Passed through untouched. `rsg-weapons` owns the value.                                                                                     |

### Documentation versus source deviations

Every signature was checked against both the published rsg documentation and `Rexshack-RedM/rsg-inventory` at `main`. Where they disagree, the source code is authoritative. These are the differences found.

<AccordionGroup>
  <Accordion title="AddItem argument order">
    Overview page: `AddItem(source, itemName, amount, info, slot, reason)`. Source, `server/exports.lua:619`: `AddItem(identifier, item, amount, slot, info, reason)`.

    **Source wins.** `slot` is the 4th argument and `info` is the 5th. Confirmed twice inside the upstream repository itself: `server/events/callbacks.lua:60` calls `Inventory.AddItem(target, item, amount, false, serverInfo, reason)`, and the published Weapons page shows `AddItem(source, 'weapon_revolver_cattleman', 1, nil, weaponInfo)`. Both put `slot` before `info`. The Functions reference agrees with the source. The Overview page is wrong.
  </Accordion>

  <Accordion title="RemoveItem missing argument">
    Overview page: `RemoveItem(source, itemName, amount, slot, reason)`. Source, `server/exports.lua:761`: `RemoveItem(identifier, item, amount, slot, reason, isMove)`.

    **Source wins.** The Overview page omits the 6th argument `isMove`, which controls whether `rsg-core:client:RemoveWeaponFromTab` fires. The Functions reference lists all six.
  </Accordion>

  <Accordion title="HasItem input forms">
    Overview page: `HasItem(source, itemName, amount)`. Source, `server/exports.lua:396`: `items` may be a string, an array of names, or a map of name to required amount.

    **Source wins.** All three forms are supported here.
  </Accordion>

  <Accordion title="CanAddItem return values">
    Overview page shows a plain boolean. Source, `server/exports.lua:296`: returns `boolean` plus a reason string `'weight'` or `'slots'`.

    **Source wins.** The Functions reference also documents the second return value.
  </Accordion>

  <Accordion title="UseItem call form">
    The export is declared `UseItem(itemName, ...)` at `server/exports.lua:134`, but `rsg-core/server/functions.lua:447` calls `exports['rsg-inventory']:UseItem(source, item)`.

    **Upstream contradicts itself**, so both forms are accepted. The first argument decides: an item name is never a number, a source always is.
  </Accordion>

  <Accordion title="OpenInventory third argument name">
    Overview page names the third argument `options`, `server/exports.lua:502` names it `data`. Cosmetic only, same table.
  </Accordion>

  <Accordion title="GetSlots on an unknown identifier">
    The Functions reference says it returns `number, number`, but `server/exports.lua:251` returns `0, maxSlots` when no inventory is found, and `maxSlots` is `nil` in that branch. That is an upstream latent bug. This implementation returns `0, 0` for an unknown identifier rather than `0, nil`.
  </Accordion>
</AccordionGroup>

### Limitations

Read this section before you deploy. These are deliberate boundaries, not bugs.

<AccordionGroup>
  <Accordion title="No world drops">
    DD Inventory has no ground item system. `ForceDropItem` removes the item from the player and returns `false` instead of a network id, so a script that calls it directly destroys the item rather than dropping it. `AddItem` on a full inventory simply fails and returns `false` rather than force-dropping the overflow, so nothing is lost on that path. The drop-related events (`createDrop`, `updateDrop`, `GetCurrentDrops`, `openDrop`, `setupDropTarget`, `removeDropTarget`) are not provided. If your server runs a death-loot or robbery script that expects world drops, this layer is not sufficient for it.
  </Accordion>

  <Accordion title="No shops">
    `CreateShop`, `OpenShop`, `RestockShop` and `DoesShopExist` are not provided, and neither is the `shop_stock` table, restock cron, `defaultstock`, `maxStock` or `minQuality`. DD Inventory has no pricing model, so this is a separate product rather than a compatibility gap.
  </Accordion>

  <Accordion title="No trade bridge">
    The seven `rsg-inventory:server:*Trade*` events are not reimplemented. DD Inventory ships its own player-to-player trade window, reached through `exports.dd_inventory:requestTrade(...)`.
  </Accordion>

  <Accordion title="No direct PlayerData.items writes">
    DD Inventory is authoritative and pushes a read-only projection into `Player.PlayerData.items`. There is no reverse channel. A resource that writes into `Player.PlayerData.items` directly and expects the change to persist is not supported: the write is not intercepted, and the next projection overwrites it. Upstream `rsg-weapons` does exactly this when it degrades or repairs a weapon. Use `exports['rsg-inventory']:SetItemData(source, itemName, 'info', info)` instead, which is supported and persists. Everything that reads `PlayerData.items` works unchanged.
  </Accordion>

  <Accordion title="Quality and decay are passed through, not simulated">
    DD Inventory has no degradation model. `info.quality` and `info.lastUpdate` are created where upstream creates them and then preserved verbatim, so `rsg-weapons` keeps full control of weapon wear and its repair loop behaves normally. What is not provided is `rsg-inventory`'s ambient decay: food and other items with a `decay` value in `RSGCore.Shared.Items` will not lose quality over time on their own, will never be auto-deleted at zero quality, and the `decay<N>` stash name suffix has no effect.
  </Accordion>

  <Accordion title="Job and gang stash prefixes are not enforced">
    Upstream only enforces the `police-`, `marshal-`, `gang-`, `admin-` and `evidence-` prefixes inside its own web-interface event handlers, never in the `OpenInventory` export. That behaviour is mirrored. Register a DD Inventory move validator if you want the rule enforced inside the inventory itself.
  </Accordion>

  <Accordion title="Money items must be off">
    `RSGConfig.Money.EnableMoneyItems = true` makes rsg-core model cash as inventory items and re-synchronise them on every player data update, which conflicts with DD Inventory's currency handling. Set it to `false`. DD Inventory prints a boot warning if it is left on.
  </Accordion>

  <Accordion title="Weights are in grams">
    RSG item weights are imported one to one, so set `DDInvConfig.defaultMaxWeight = 35000.0` to match RSG's default carry capacity. DD Inventory's own capacity is then the single authority: `GetFreeWeight`, `CanAddItem` and `GetSlots` answer from DD Inventory's numbers, including purchased slot expansions, not from `Player.PlayerData.weight` and `.slots`.
  </Accordion>

  <Accordion title="Stash slot ceiling of 100">
    `inventories.max_slots` is a `TINYINT`. RSG's own default stash size is 100, so only servers that raised it are affected; larger values are clamped.
  </Accordion>

  <Accordion title="Character ids are surrogates">
    RSG's `citizenid` is a string and DD Inventory's `owner_char_id` column is an `INT`, so the layer mints a stable surrogate integer per citizenid in `compat_rsg_citizenids`. This is transparent in normal operation, but if you query DD Inventory's tables by hand, join through that table rather than expecting a citizenid.
  </Accordion>

  <Accordion title="Not verified against a live RSG server">
    These paths are written against the published source and reference documentation and pass a full Lua syntax sweep, but they have not been exercised on a running RSG stack. Test on a staging server before production.
  </Accordion>
</AccordionGroup>
