Skip to content

Custom frameworks & inventories

vns-core is built so that support for a new system is added in one file, once, and every VNS script picks it up automatically. This page is aimed at developers.

How adapters work

Each supported system lives in its own module file:

vns-core/
└── modules/
    ├── framework/   esx.lua, qbcore.lua, qbox.lua
    ├── inventory/   ox_inventory.lua, qb-inventory.lua, qs-inventory.lua,
    │                origen_inventory.lua, codem-inventory.lua,
    │                tgiann-inventory.lua, core_inventory.lua, esx_inventory.lua
    └── target/      ox_target.lua, qb-target.lua, qtarget.lua

Every file in these folders is loaded automatically (the manifest includes modules/framework/*.lua, modules/inventory/*.lua and modules/target/*.lua). Each module registers itself as a candidate:

VNSCore.Register('inventory', {
    name     = 'my_inventory',   -- the value used in Config.Inventory
    priority = 10,               -- higher wins when several are running
    resource = 'my_inventory',   -- resource name checked for auto-detection
    build    = build,            -- function returning the adapter table
})

The category is 'framework', 'inventory' or 'target'. Per category, vns-core then picks:

  1. the candidate whose name matches the value forced in config.lua, or
  2. when the option is 'auto', the highest-priority candidate whose resource is started.

The stock priorities are: QBox 20, QBCore 10, ESX 10; ox_inventory 20, other inventories 10, ESX built-in inventory 1; ox_target 20, qb-target 10, qtarget 10.

build() runs on both the client and the server. Use IsDuplicityVersion() inside it to define the server-side and client-side functions separately, as the stock modules do.

Adding your own inventory

  1. Copy the existing adapter closest to yours to modules/inventory/my_inventory.lua. For most custom inventories that is qb-inventory.lua.
  2. Implement the functions using your inventory's exports.

    Server side

    Function Returns
    AddItem(source, item, count, metadata) true on success
    RemoveItem(source, item, count, metadata) true on success
    GetItemCount(source, item) number
    HasItem(source, item, count) boolean
    GetItemLabel(item) label string (return item if unknown)
    RegisterStash(stashId, label, slots, weight) nothing (can be empty if your inventory creates stashes on open)
    OpenStashServer(source, stashId, opts) nothing. opts = { label, slots, weight }
    StashHasItems(stashId) boolean
    CanCarryItem(source, item, count) (optional) boolean. Treated as true if missing
    GetPlayerItems(source) (optional) { [name] = { name, label, count } }
    GetAllItems() (optional) { [name] = { name, label } }. If missing, the framework's shared item list is used

    Client side

    Function Returns
    GetItemCount(item) number
    HasItem(item, count) boolean
    OpenStash(stashId, opts) nothing
    OpenLocalStash(stashId) (optional) nothing. Called when the server fires vns-core:client:openLocalStash, for inventories whose stash UI has to be opened from the client

    If your inventory has no client-side count export, do what the stock modules do: lib.callback.await('vns-core:server:getItemCount', false, item). If opening a stash has to start on the server, trigger vns-core:server:openStash from OpenStash, which calls your OpenStashServer.

  3. Change name and resource in the VNSCore.Register call at the bottom.

  4. Restart. Either set Config.Inventory = 'my_inventory' or let auto-detection find it (give it a priority above 20 if ox_inventory is also running).

Adding a framework

Copy modules/framework/qbcore.lua (or esx.lua) and implement:

  • Both sides: GetPlayerData(source) (client: no argument) returning { source, identifier, name, job = { name, label, grade, gradeLabel, isboss, onduty } }, GetPlayerJob(source), IsPlayerBoss(source).
  • Client: GetMoney(moneyType).
  • Server: GetMoney, AddMoney, RemoveMoney (source, moneyType, amount), RegisterUsableItem(item, callback), GetNameByIdentifier(identifier), GetMetadata(source, key), GetSharedItems(), CreateJob, DeleteJob, UpdateJob, SetPlayerJob, SetDuty, GiveVehicle(source, model, plate), GetOwnedVehicles(source), PlateExists(plate).

Functions a script doesn't need can be left out. vns-core checks for them and returns a safe default (nil, 0, false or an empty table) when they are missing.

vns-core also needs to know when a character has loaded. It polls GetPlayerData() until it returns an identifier, so a correct GetPlayerData is enough.

Adding a target system

Copy modules/target/qb-target.lua and implement, on the client: AddSphereZone(opts), AddBoxZone(opts), RemoveZone(zoneId), AddEntityTarget(entity, options) and RemoveEntityTarget(entity). The option and zone shapes are listed in the Exports reference. On the server the adapter can return an empty table.

The bridge from a script's side

Scripts import @vns-core/shared/bridge.lua and call everything through the VNS table, for example VNS.HasItem(source, 'wood', 5) or VNS.Notify(source, { title = 'Base', description = 'Saved', type = 'success' }). Encrypted VNS scripts keep their open bridge files readable (client/client_open.lua / server/server_open.lua, or client/open_client.lua / server/open_server.lua in Base Building). That is where those calls are made, so you can always see and adjust how a script talks to your server. See the Exports reference for every function.

Warning

Keep a copy of your custom adapter file when updating vns-core. Updates replace the folder and don't know about your file; put it back after updating.