dd-cards · Reference
Custom bridges
bridge/ is outside escrow. Copy the closest file, adapt a handful of functions, and dd-cards will talk to a framework, inventory, target, menu or phone it does not ship support for.
dd-cards reaches the rest of your server only through bridge/. That whole directory is listed under
escrow_ignore, so it stays readable and editable after purchase — which means adding support for a
resource that is not on the shipped list is a matter of copying one file.
Every category works the same way: a dispatcher (bridge/framework.lua and friends) resolves the
implementation name from config, requires bridge/<name>/..., and errors clearly if the file is
missing.
The pattern
- Copy the reference file named in the table below to the new path.
- Adapt each function to your resource.
- Point the matching config option at your new name.
-- config/shared.lua
Framework = 'my_framework', -- loads bridge/my_framework/server.lua
The dispatcher checks the file exists before requiring it, so a typo produces
no framework bridge at bridge/my_framework/server.lua. Check Config.Framework. rather than a
stack trace.
Framework
Copy bridge/qbx_core/server.lua to bridge/<name>/server.lua, then set Config.Framework.
---@class FrameworkBridge
---@field getCitizenId fun(source: number): string?
---@field getMoney fun(source: number, account: string): number
---@field removeMoney fun(source: number, account: string, amount: number, reason: string): boolean
---@field addMoney fun(source: number, account: string, amount: number, reason: string): boolean
---@field resolveSourceByCitizenId fun(citizenid: string): number?
account is 'cash' or 'bank', matching
DefaultPaymentMethod.
resolveSourceByCitizenId is what lets an order finishing find a player who has since reconnected on
a different server id — return nil when they are offline.
Inventory
Copy bridge/ox_inventory/server.lua to bridge/<name>/server.lua, then set Config.Inventory.
---@class InventorySlot
---@field name string
---@field metadata table? normalised: qb-inventory calls this `info`
---@class InventoryBridge
---@field canCarry fun(source: number, item: string, count: number): boolean
---@field addItem fun(source: number, item: string, count: number, metadata: table?): boolean
---@field removeItem fun(source: number, item: string, count: number, slot: number?): boolean
---@field getItemCount fun(source: number, item: string): number
---@field setMetadata fun(source: number, slot: number, metadata: table)
---@field getSlot fun(source: number, slot: number): InventorySlot?
---@field registerUseableItems fun(handlers: table<string, fun(source: number, slot: number)>)
Important
getSlot must return the slot's metadata under metadata, whatever your inventory calls that
field. The shipped qb-inventory bridge exists largely to rename info to metadata — the box and
the card store their design and remaining count there, so getting this wrong loses both.
Interaction (targeting)
Copy bridge/ox_target/client.lua to bridge/<name>/client.lua, then set Config.Interaction.
---@class TargetBridge
---@field addFabric fun(entity: number, options: { name: string, icon: string, label: string, distance: number, onSelect: fun() }[])
---@field removeFabric fun(entity: number, names: string[])
Only two functions, because dd-cards attaches a single option per fabric ped and removes it by name when the ped despawns.
Menu
Copy bridge/menu/ox_lib.lua to bridge/menu/<name>.lua, then set Config.Menu.
---@class MenuOption
---@field title string
---@field description string?
---@field icon string?
---@field disabled boolean?
---@field onSelect fun()?
---@class MenuProps
---@field id string
---@field title string
---@field back { menu: string, onSelect: fun() }? a way back to the parent menu
---@field onExit fun()?
---@field options MenuOption[]
---@class MenuBridge
---@field open fun(props: MenuProps)
---@field close fun()
---@field isOpen fun(id: string): boolean
---@field supportsLiveRefresh boolean whether reopening an already-open menu in place is safe
Set supportsLiveRefresh = false if your menu resource cannot report whether the player has closed
it. dd-cards then stops refreshing the order list's countdown in place, rather than risking
reopening a menu the player just dismissed — this is exactly why qb-menu's countdown only advances
on reopen.
Notification
Copy bridge/notify/ox_lib.lua to bridge/notify/<name>.lua, then set
Config.Notification.provider.
---@class NotifyTarget
---@field source number? nil when the player is offline
---@field citizenid string
---@class NotifyBridge
---@field send fun(target: NotifyTarget, opts: { title: string, description: string? })
target.source is nil when the player is offline. A phone that addresses mail by citizenid can
still deliver in that case; one that addresses by live phone number cannot, and should return
without doing anything. See
offline delivery.
Where each dispatcher lives
| Category | Dispatcher | Reference file to copy | Config option |
|---|---|---|---|
| Framework | bridge/framework.lua | bridge/qbx_core/server.lua | Config.Framework |
| Inventory | bridge/inventory.lua | bridge/ox_inventory/server.lua | Config.Inventory |
| Interaction | bridge/target.lua | bridge/ox_target/client.lua | Config.Interaction |
| Menu | bridge/menu.lua | bridge/menu/ox_lib.lua | Config.Menu |
| Notification | bridge/notify.lua | bridge/notify/ox_lib.lua | Config.Notification.provider |
Each dispatcher carries the same instruction as a comment at the top of the file, so you never have to come back here to remember which file to copy.
Note
Type annotations for these interfaces also ship in types.lua, which is likewise outside escrow.
If you write bridges with the Lua language server installed, it will check your implementation
against them.