Theme
Inventory (wf_inventory)
An ox_inventory-style inventory where the server decides everything. The client only sends intentions ("move my slot 3 to slot 7 of the open stash") and never an inventory id of its choice, a count the server did not check, or item metadata.
Items
Items are data, in wf_inventory/data/items.json:
json
"water": {
"label": "Bouteille d'eau",
"weight": 500,
"stack": true,
"image": "water.png",
"description": "Désaltère.",
"use": { "consume": 1 }
},
"medikit": { "label": "Trousse de secours", "weight": 1200, "stack": true, "stackMax": 5 },
"phone": { "label": "Téléphone", "weight": 190, "stack": false }| Field | Meaning |
|---|---|
label | name shown to players |
weight | grams per unit (integer) |
stack | false = one per slot |
stackMax | largest stack (default: unlimited) |
image | file name for a future HTML interface |
use | makes the item usable; consume = units removed on use; status = needs restored, e.g. { "hunger": 40 } |
type | "weapon" for weapons (with weapon, ammo, maxAmmo), see The RP character in game |
Item names are lower-case letters, digits and _, up to 32 characters. An item removed from items.json is skipped (and logged) when an inventory is loaded — it is never invented back.
Metadata
Each stack can carry metadata (a JSON object up to 4 KB): serial numbers, quality, the ID card holder… Two stacks only merge when their metadata is equal (key order does not matter). metadata.weight overrides the unit weight.
lua
exports.wf_inventory:AddItem(source, "lockpick", 1, { quality = 80 })
exports.wf_inventory:GetItemCount(source, "lockpick", { quality = 80 }) -- 1Inventory types
| Type | Id | Persistent | Access |
|---|---|---|---|
player | char:<characterId> | yes | the character; moderators with wf.inventory.open_any |
trunk | trunk:<PLATE> | yes | anyone (v1: the server does not know vehicle positions yet) |
stash | stash:<name> | yes | as registered: owner, job/group grade, distance |
drop | drop:<n> | no, expires after expireMinutes | anyone within interactDistance |
Access is checked again on every action: walking away from a stash or losing the job closes it.
lua
-- a police armory, grade 1 or more, usable within 3 m of its position
exports.wf_inventory:RegisterStash({
id = "police_armory", label = "Armurerie", slots = 50, maxWeight = 200000,
groups = { police = 1 }, coords = { x = -3110.0, y = 5530.0, z = 956.0 },
})
exports.wf_inventory:OpenInventory(source, "stash", "police_armory")Rules
- Weight: the sum of
weight × countmay not exceed the inventory'smaxWeight(too_heavy). - Slots: adding fills existing stacks first, then free slots in order (
inventory_full). - Moving (
wf_inventory:move): to an empty slot = move (part of a stack allowed); same stackable item and metadata = merge (up tostackMax); otherwise = swap, whole stacks only. - Giving (
wf_inventory:give): only to a player with a loaded character withininteractDistance, using the server's positions. Unknown position = refused. Without a target, the server picks the nearest player. - Dropping (
wf_inventory:drop): creates a ground inventory at the player's position, or adds to one withindropMergeDistance. - Using (
wf_inventory:use): calls the script registered withRegisterUsableItem, fireswf_inventory:usedItem, then removesuse.consumeunits unless the script returnedfalse. One use peruseCooldownMs.
All of these are covered by game-server/tests/wframework/inventory.test.js.
Usable items
lua
exports.wf_inventory:RegisterUsableItem("bandage", function(source, item, slot)
-- heal the player… return false to keep the item
end)
AddEventHandler("wf_inventory:usedItem", function(source, name, slot, data)
print(source, "used", name)
end)In game (v1)
The v1 interface is a keyboard list drawn with the client's native UI (no HTML):
| Key | Action |
|---|---|
F2 | open / close |
| Up / Down | select |
| Left / Right | your inventory / the open one (trunk, stash, ground) |
| Enter | use |
| Space | move the whole stack to the other side |
G | give 1 to the nearest player |
X | drop 1 on the ground |
E | open what is on the ground nearby |
Toward an HTML interface (NUI-like)
A real ox_inventory-style interface (grid, drag and drop, item images) needs things the client does not have yet:
- An embedded web view drawn over the game: e.g. a CEF or WebView2 off-screen browser rendered to a texture and shown through the ScriptHook overlay (
scripthook_ovl.cpp, ImGui on the game's swap chain) or as anSH_W_IMAGEwidget texture. - Input routing: mouse cursor and keyboard focus given to the page while it is open (the native UI's
ShUiFocusalready captures the keyboard; the mouse needs the same). - A message bridge like FiveM's
SendNUIMessage/RegisterNUICallbackbetween the Lua client scripts and the page's JavaScript. - Resource files served to the client (
filesinresource.json) and loaded from the local cache, as the launcher already does for the loading screen.
The server side does not change: the page sends the same wf_inventory:* intentions and receives the same views. Item image fields are already there for it.