OPX//77
opx_infinityinventory

inventory

Last updated on

The bag, fixed stashes, vehicle trunks and gloveboxes, piles on the ground, weapons drawn from the bag and eddies carried as an item.

Holds every item a character carries. I opens a grid of slots with a weight bar; beside the bag the screen can show a second container: a stash, a vehicle's trunk or glovebox, a pile on the ground, or (for staff) another player's bag. Items are dragged between the two, used, split, sorted, handed to a nearby player or dropped. Weapons in the bag are drawn by using them; ammunition is loaded into the drawn weapon the same way. The server decides every move; the screen only sends what the player asked for. Use it whenever your code gives, takes, counts or checks items.

Sideboth
Requirescharacter (hard)
Optionalvehicles (owned trunks are stored by plate), target (pile, trunk, glovebox and stash rows), downed (no screen while down), panel (declared, not used yet). The client also reads needs, progress and animations when an item is used.
Contractinventory v1 — server, client
Configconfig/inventory.lua (shared); catalogue in data/items.lua, data/weapons.lua
Dataopx77_inventories, opx77_inventory_items

Containers

A container is a (kind, owner) pair. Positive id: a database row. Negative id: lives only in memory, never written.

KindOwnerSizeStoredOpened by
character (the bag)citizen idBAG, fixed when the row is createdyesthe open key, or /opx.inventory.open (staff search)
stashthe stash namefrom STASHES or the caller of OpenStashyesa target row at a configured stash, or OpenStash
trunkplate of an owned vehicle, or vehicle:<id>TRUNK; a bike gets it divided by BIKES.TRUNK_DIVISORonly for an owned vehiclea target row on the vehicle, standing outside it
gloveboxplate, or vehicle:<id>GLOVEBOX; bikes have noneonly for an owned vehiclethe open key or a target row, seated in the vehicle
drop (a pile)—DROPS.SLOTSneverits prompt-strip row or a target row near the pile (never the open key)
  • A vehicle nobody owns (no plate known to vehicles) gets memory-only storage that lives as long as the vehicle.
  • TRUNK_OWNER_ONLY (default true) refuses an owned trunk to anyone but its owner, with not_yours.
  • A locked vehicle refuses its trunk to everyone, with locked. An open trunk closes when the vehicle is locked. The lock is the host's own lock state (Open77.vehicles.isLocked); vehiclekeys moves it. The glovebox ignores both rules: it opens only from a seat.
  • Reach is measured on the server: REACH.DISTANCE to a stash, pile or player; REACH.VEHICLE from a vehicle's centre to its trunk. A second container out of reach is closed by a sweep every second.
  • A pile is swept after DROPS.LIFETIME_MINUTES untouched; nothing in it survives a restart.
  • A client only draws and offers the piles of its own routing bucket. The server tells it which bucket it is in (opx:net:inventory:bucket).
  • Changes are written SAVE.DELAY_MS after the last change, in batches. Any number of stacks is saved (12 item rows per INSERT, at most 64 statements per transaction); a batch that fails is retried one container at a time.
  • While a use handler runs, its slot is held: moving, splitting, sorting or using it again is refused with in_use, and removing by name skips it.
  • An item whose use plays a timed gesture (eating, drinking) holds the next use until the gesture ends: four hotbar presses inside one bite eat one item.

Give and drop

  • Count. Give or Drop on a stack of more than one opens the count dialog (Give how many? / Drop how many?), starting at 1; its button says Give or Drop and is the confirm step. A single item goes in one click.
  • Who. The give list shows people within arm's reach as where they stand and how far, To your left · 1.2 m / À votre gauche · 1,2 m, with their server id (#12). Never a name: in roleplay a name is learnt by meeting someone (opx:net:inventory:nearby). Only the giver sees the list; it is fetched again every time a bag slot's card opens.
  • The receiver says yes first. Give makes an offer; nothing moves yet. The giver reads You offered 2x Water. Waiting for an answer. The receiver gets a menu card (SOMEBODY HANDS YOU SOMETHING) with the item, the count and where the giver stands (To your left · 1.2 m, never a name), and picks Accept or Refuse; closing the card is a refusal.
    • The offer stands 15 s. Unanswered, it is withdrawn from the receiver's screen and the giver reads Your offer of … was not answered.
    • On Accept the server checks everything again: both players can act, they are still in reach, the same stack is still in the giver's slot, the receiver has room. Only then does the stack move: You were handed 2x Water. / the giver's success line. Notes handed over are told to both as money.
    • One offer out per giver (give_pending), one in per receiver (give_busy), 3 s between two offers from one giver. The answer counts against RATE_LIMIT. Only the receiver can answer an offer, and a replayed answer moves nothing.
    • A staff give (/opx.inventory.give) asks nobody.
  • Refusals. A refused move, split, sort or drop toasts the server's reason (full trunk, no room…) once; the page's own "That did not work." is only for a timeout or a request that did not get through. The cooldown stays silent.
  • Secondary panel. Its Close button closes it on the server too. A trunk shut by its lock says "That vehicle is locked."
  • Money notes. You took 1,000,000 €$ out as notes. / You put 4,500 €$ in notes back in your wallet., formatted with OPX.Locale.Money; /withdraw adds no second toast (the bag's +N line leaves notes out).
  • Weapons. Drawing a weapon from the bag closes the screen, as eating does; putting it away leaves it open.
  • A stash loaded by name (by the stash functions) or opened by a player is saved and put away after SERVER.EXPORTS.STASHES.IDLE_MS (60 s) without use; a sweep runs every 15 s. A stash is never put away while someone has it open; its idle time counts from the last sweep that saw it open.
  • Deleting a character deletes all its containers and their items.

Weight and slots

  • Weight is in whole grams. A change that would pass maxWeight is refused too_heavy; one that finds no slot, no_room.
  • A stackable item holds up to MAX_STACK units per slot. An ammunition slot holds at most the ammo item's AMMO.MAX (rounds per box, e.g. 500 for handgun), never above MAX_STACK.
  • A non-stackable item and every weapon hold one per slot.
  • Two stacks merge only when their metadata is equal.
  • A stored item the catalogue no longer has weighs DEFAULT_ITEM_WEIGHT per unit.

Medical items

An item named in HEALING heals on use: the server reads the player's health and raises it by that share of the maximum, capped at the maximum.

  • Refused at full health (full_health, You are already at full health.) and while down (dead): none of them revives.
  • The unit is consumed only when the heal went through; nothing is used up otherwise.
  • A use handler registered for the item (RegisterUsableItem) replaces the heal.

Eddies as an item

When CURRENCY is on, cash can be carried as the eddies item. The balance stays the real money; the item is a note drawn against it.

  • /withdraw <amount> (or Withdraw) takes the amount from the EDDIES balance, then puts that many eddies in the bag. If the item cannot be added, the money is refunded.
  • Using an eddies stack (or Deposit) removes the units, then credits the balance. If the credit fails, the units are put back.
  • Shops, garages and the dealership read the balance only. Notes in a bag cannot be spent until deposited.
  • eddies weighs 0 and has DROP = false: it cannot be left on the ground or put in a memory-only trunk.
  • The bridge does not wire itself (boot warning) when CURRENCY.ITEM is not in the catalogue, MONEY_TYPE is not a declared money type, or there is no character contract.

Commands

CommandAccessArgumentsEffect
/opx.inventory.giveACL command.opx.inventory.give<playerId|citizenId> <item> [count]Adds items to a bag (online or offline). Count 1 to MAX_COMMAND_COUNT.
/opx.inventory.removeACL command.opx.inventory.remove<playerId|citizenId> <item> [count]Removes items from a bag.
/opx.inventory.clearACL command.opx.inventory.clear<playerId|citizenId>Empties a bag.
/opx.inventory.openACL command.opx.inventory.open<playerId|citizenId>Opens that bag beside yours (staff search). In game only. Reach does not close it.
/opx.inventory.holdersACL command.opx.inventory.holders<item>Up to 20 containers holding the item, largest stacks first. Reads the database, so changes not yet written are missing.
/opx.withdraw (alias /withdraw)everyone<amount>Turns balance into eddies items. 1 to CURRENCY.MAX_WITHDRAW. In game only.

Staff commands are audited (inventory.give, inventory.remove, inventory.clear, inventory.search, inventory.holders).

Keys

Mapping idDefaultEffect
opx.inventory.openIToggles the screen. Seated: opens the glovebox. Otherwise: the bag. It never takes a pile; a pile is picked up from its own row on the key strip or the eye.
opx.inventory.hotbar1 … hotbar54 … 8Uses bag slot 1 … 5 with the screen closed. An empty slot does nothing.
opx.inventory.peekYShows the hotbar row for HOTBAR.PEEK_MS.

Players rebind them in Pause → Settings → KEY BINDINGS.

Configuration

config/inventory.lua sets OPX.Config.MODULES.inventory. Shared script. Out-of-range values fall back to the default with a boot warning.

KeyDefaultEffect
enabledtruefalse switches the module off.
BAG{ SLOTS = 40, MAX_WEIGHT = 30000 }Size of a new bag. Slots 1–200, grams. An existing bag keeps its size; use ResizeContainer.
DEFAULT_ITEM_WEIGHT100Grams per unit for an item the catalogue no longer has, and for a catalogue row with a bad WEIGHT.
MAX_STACK1000000Most units per slot of a stackable item (ammunition: its AMMO.MAX, if lower).
MAX_METADATA_BYTES1024Largest encoded metadata per stack (64–4096).
REACH{ DISTANCE = 3.0, VEHICLE = 4.5 }Metres. DISTANCE: to a stash, pile or player. VEHICLE: vehicle centre to trunk; also the vehicle target rows' distance.
HOTBAR{ ENABLED = true, SLOTS = 5, PEEK_MS = 4000 }Bag slots usable by key without the screen (0–9), and how long the peek shows the row (500–30000 ms).
KEYS{ OPEN = 'I', HOTBAR = { '4','5','6','7','8' }, PEEK = 'Y' }Default keys. false registers no mapping. A hotbar or peek key equal to the open key is dropped.
USE_COOLDOWN_MS750Least time between two uses by one player. Also the live tunable INVENTORY_USE_COOLDOWN_MS.
USE_HANDLER_MS5000Time a use handler has to answer (500–25000).
RATE_LIMIT{ WINDOW_MS = 1000, REQUESTS = 20 }Screen requests per player per window. Refused requests are still answered too_fast.
SAVE{ DELAY_MS = 2000, SWEEP_MS = 1000, BATCH = 16 }A container is written DELAY_MS after its last change, BATCH per transaction, looked for every SWEEP_MS.
DROPS{ ENABLED = true, SLOTS = 25, LIFETIME_MINUTES = 30, MAX = 200, MAX_PER_CHARACTER = 10, COOLDOWN_MS = 1000, DISTANCE = 1.5, MODEL = 'crate.small', PROMPT_RADIUS = 20.0 }Piles on the ground. DISTANCE: a drop this close to a pile joins it. MODEL: prop alias for a pile whose item has none. PROMPT_RADIUS: where a pile gets a target row.
TRUNK{ SLOTS = 30, MAX_WEIGHT = 80000 }Trunk size. SLOTS = 0 gives vehicles no trunk.
GLOVEBOX{ SLOTS = 10, MAX_WEIGHT = 10000 }Glovebox size. SLOTS = 0 gives none.
TRUNK_OWNER_ONLYtrueAn owned vehicle's trunk opens for its owner only. false lets anyone in reach open it.
BIKES{ PATTERNS = { 'sportbike', '_bike_' }, TRUNK_DIVISOR = 3 }A vehicle record containing a pattern is a bike: no glovebox, trunk divided by TRUNK_DIVISOR.
STASHES{}Fixed stashes, each { NAME, LABEL?, SLOTS? (50), MAX_WEIGHT? (100000), POSITION = { X, Y, Z }, BUCKET? (0) }. NAME is the storage key: renaming it gives an empty stash. Anyone in reach may open one.
WEAPONS{ ENABLED = true, SLOT = 1, REMOVE_UNBACKED = true, SCAN_MS = 5000, AMMO_SYNC_MS = 2000 }Weapons from the bag. SLOT: game weapon slot 1–3. REMOVE_UNBACKED: take off weapons no bag item backs.
NEARBY{ MAX = 4, SCAN_MS = 1000 }Players offered in "give to", and the refresh rate.
TABSall, weapons (weapon, ammo), food (food, drink), medical, materials (material, tool), misc (REST = true)Category tabs over the grid. REST takes every category no other tab names.
CURRENCY{ ENABLED = true, ITEM = 'eddies', MONEY_TYPE = 'EDDIES', MAX_WITHDRAW = 1000000 }The money bridge. MAX_WITHDRAW caps one withdraw.
MAX_COMMAND_COUNT10000Largest count a staff command accepts.
HEALING{ bandage = 15, bounce_back = 40, maxdoc = 75 }What a medical item heals, in percent of the player's maximum health (above 0, at most 100), applied at once by the server. See Medical items.

Live tunables (operator panel): INVENTORY_SAVE_DELAY_MS (2000), INVENTORY_RATE_REQUESTS (20), INVENTORY_DROP_LIFETIME_MIN (30), INVENTORY_USE_COOLDOWN_MS (750).

Refusal codes

Toasted to the player as locale inventory.error.<code> (commands use inventory.command.error.<code>). An unknown code shows inventory.error.failed.

CodeMeaning
bad_argumentA contract argument is invalid (detail names it).
bad_requestA request or a slot number is malformed.
bad_targetNot a player id or a citizen id.
not_loadedNo character loaded on that player id.
no_characterNo living character has that citizen id.
storageThe character contract or the database is unavailable.
not_readyThe player is held at the readiness gate.
deadThe player is dead.
not_foundThe container or pile is gone.
empty_slotNothing in that slot.
bad_countCount out of range.
bad_slotThat slot cannot take it.
not_enoughFewer units than asked for.
no_roomNo free or stackable slot.
too_heavyOver the weight limit.
cannot_swapOnly whole stacks swap places.
unknown_itemNot in the catalogue.
not_usableNo USE and no handler.
use_refusedThe use handler answered ok = false with no code.
handler_failedThe use handler did not return a table.
handler_timeoutThe use handler raised or took longer than USE_HANDLER_MS.
too_fastRate limit, use cooldown, the 1 s pile cooldown, or a use while the last item's eat/drink gesture still plays (not toasted).
stash_namespaceA resource tried to create a stash outside its own <resource>.<name> namespace.
stash_capThat resource already created CREATE_CAP stashes.
hands_fullThe player is carrying a hauling crate; no use or draw.
too_farOut of reach. A give checks reach first: any id out of reach answers too_far, whether that player exists, is joining or is loaded.
target_unavailableThe player to hand to (within reach) cannot receive.
give_pendingYou are already offering something; wait for an answer.
give_busyThe receiver is already considering an offer.
give_goneThe offer is no longer there (answered, withdrawn or expired). Not toasted.
give_changedOn Accept: the offered stack is no longer in the giver's slot.
full_healthA medical item at full health.
drops_disabledPiles are switched off.
drop_limitToo many piles on the server or for this character.
no_dropThe item has DROP = false.
in_vehicle / seatedGet out of the vehicle first.
not_seatedThe glovebox needs a seat.
no_positionThe player's position could not be read.
no_vehicleThe vehicle is gone.
no_storageThe vehicle has no trunk or glovebox.
not_yoursSomeone else's owned trunk, with TRUNK_OWNER_ONLY on.
lockedThe vehicle is locked.
weapons_unavailableNo weapon relay on this host.
weapon_refusedThe host refused to draw the weapon.
no_weapon_for_ammoNo drawn weapon takes this ammunition.
weapon_fullThe weapon is fully loaded.
holsteringA drawn weapon is being put away (its rounds are read first). Try again in a moment.
in_useThe slot is held while its use handler runs.
not_armedHolsterWeapon: the player holds no weapon from this module.
not_enough_moneyBalance too low to withdraw.
unavailableThe money bridge is off, or a stash could not be opened.
load_timeoutA container did not load in time.

On this page