# Storage containers (storage.containers) ## What it is for Somewhere to put things. A container is any entity that holds items: a chest, a crate, a silo, a fridge, a warehouse shelf. It has a number of slots, a stack size and, when wanted, a list of the only items it takes. The player opens one and moves items between the bag and the container; scripts and other kits (carriers, machines, quests) put and take without the player. The contents live in the entity's component, so the save keeps them. The bag itself is [Inventory](guide:inventory). ## Turn it on ```text gripforge_gamekit_install { project_id: "gkp_…", id: "storage.containers" } gripforge_gamekit_configure { project_id: "gkp_…", id: "storage.containers", config: { "defaultSlots": 12, "defaultStack": 50, "reach": 4 } } ``` It requires only `game.engine`. The player side works with `inventory.items` (the bag), and a container shows its prompt through `interaction.use`; with `automation.logistics` installed the screen also offers the open container's logistics (offer its contents to drones, keep a number in stock). There are three ways to make a container: an archetype with the component `storage_container`, a structure of `build.placement` whose `components` include `"storage.container": { … }`, or `place(at, opts?)` from a script (`opts`: `label`, `slots`, `stack`, `accepts`, `items`, `variant`, `yaw`). ```json { "label": "Crate", "slots": 8, "stack": 50, "accepts": ["wood", "stone"], "items": { "wood": 20 } } ``` ## First example The parity scenario creates a chest with 2 slots, a stack of 8 and a list of accepted items, then calls the world side: `containers()` and `contents(entity)` describe it, `room(entity, item)` tells how many units still fit (0 for an item that is not accepted), `put(entity, item, n)` returns the units really stored and `take(entity, item, n)` the units really removed. Anything that does not fit stays with the caller: ```text room ["e2", "ore"] -> units that still fit put ["e2", "ore", 9] -> units stored (what the stack of 8 still leaves) put ["e2", "gold", 1] -> 0 (not in accepts) take ["e2", "half", 5] -> units removed (never more than it holds) ``` ## Settings and defaults | Setting | Default | Meaning | |---|---|---| | `openLabel` | `Open {name}` | prompt of a container; `{name}` is its label | | `defaultName` | Container | name of a container without a label | | `reach` | 4 | the screen closes when the player walks further than this (m) | | `defaultSlots` | 12 | distinct items a container holds when it does not say | | `defaultStack` | 50 | units per item of a container that does not say | A container's own fields are `label`, `slots` (one item takes one slot), `stack`, `accepts` (the only items that go in; absent: anything) and `items` (what it holds, `{ "": count }`). ## Services, events, variables - `storage.containers` (service), world side, never involving the player: `contents(entity)`, `count(entity, item)`, `room(entity, item)`, `put(entity, item, n)`, `take(entity, item, n)`, `total(item)`, `containers()`, `find(item)`, `place(at, opts?)`. - Player side: `deposit(entity, item, n?)` (all of it by default), `withdraw(entity, item, n?)` (as much as fits), `depositAll(entity)`, `withdrawAll(entity)`, each returning the units moved; `open(entity)`, `close()`, `current()`. - Events: `storage.changed` (`entity`, `item`, `count`, `delta`), `storage.full` (a put or deposit did not fully fit), `storage.opened`, `storage.closed`, `storage.refused` (`entity`, `action`, `item`, `reason`). - Variables: `storage.open`, `storage.containers`. - Condition `stored_at_least` (`item`, `count`): every container together holds at least that many. ## Hooks and modding The player side is asked; the world side (`put`, `take`) never is. | Hook | Payload | Effect | |---|---|---| | `storage.open` | `entity`, `label` | cancelled: the screen stays shut (a locked chest) | | `storage.deposit` | `entity`, `label`, `item`, `count` | mutable `count` (never more than the bag holds); cancelled: nothing moves | | `storage.withdraw` | `entity`, `label`, `item`, `count` | mutable `count` (never more than the container holds); cancelled: nothing moves | A cancelled hook emits `storage.refused` with the reason of the tap. ```json { "id": "locked_vault", "hook": "storage.open", "match": { "label": "Vault" }, "cancel": true, "reason": "locked" } ``` A mod may override `openLabel`, `defaultName`, `reach`, `defaultSlots` and `defaultStack`. The kit has no collection of its own. ## Engines The web player and the native kit host run the same rules. The storage screen shows the bag and the open container side by side (move one, move all, deposit all, take all), drawn as a `ui.webui` fragment. ## When it does not do what you expect - Nothing goes in: the item is not in `accepts`, the slots are used, or the stack is full; `room` says how many fit. - The prompt never appears: the entity has no `storage_container` component, or `interaction.use` is not installed. - The screen closes by itself: the player walked further than `reach`. - A deposit does nothing: a tap of `storage.deposit` cancelled it; read the reason in `storage.refused`. ## Limits - Nothing is ever lost: what does not fit stays with the caller. - It stores counts of named items. ## Versions Kit 1.1.1, service contract 1.0.0. 1.1.0: the `game.engine` hook contract and `storage.refused`; 1.0.0: first release.