Skip to content

Generated reference. This page mirrors Code/Inventories/README.md, this module's source-of-truth documentation (Standards/02_DOCUMENTATION_STANDARDS.md §1). Edit it there, not here -- this file is regenerated on every docs build and any local edit is silently overwritten.

Inventory

Purpose. One inventory implementation, used identically by a character, a world container, a vehicle, or a corpse. See Documentation/Architecture/08_DATA_MODEL.md §3 and ADR-0006. This is the direct fix for D-09 (character and container inventory as two separately-maintained systems) and D-10 (items with no identity, so two copies of the same thing are indistinguishable).

Responsibilities. - IInventory / Inventory — capacity maths (space, not weight; negative-size items grant capacity), stacking with a per-definition limit (D-03), and the pocket over-encumbrance guard on removal (Legacy/03_INVENTORY_AND_ITEMS.md §1.2). Inventory is the one shared implementation; nothing else implements IInventory. - IInventoryHolder — the thin "whatever has an inventory" concept a character or world container satisfies. - IInventoryService / InventoryStore — persistence (inventories/{ownerId}, 08_DATA_MODEL.md §8) and atomic cross-inventory transfer. See that document's §3 for exactly how atomicity is achieved without a real multi-document transaction. - InventoryConfiguration — the character base-capacity default (40, Legacy/03_INVENTORY_AND_ITEMS.md §1.2), following 05_CONFIGURATION.md's per-module GameResource pattern. - WorldContainerComponent — the first world container type, proving a second IInventoryHolder shares Inventory with no special case (ROADMAP.md Milestone 5). - InventorySlotView / CharacterInventoryComponent — the replicated, display-only snapshot a HUD panel reads instead of the live IInventory (Milestone 6's basic inventory panel, 11_UI_ARCHITECTURE.md §3). The same component also exposes RequestUseItem, the Interaction framework's first RPC entry point (10_INTERACTION_FRAMEWORK.md §3): validates the item is actually in the caller's own loaded inventory, then hands ItemDefinition.CreateUseInteraction's result to the sibling InteractionComponent's Runner.TryStart. Lives here rather than on a separate component so there is exactly one loaded Inventory per character, not two independently-loaded copies that could diverge. RequestAttachItem/RequestDetachItem (Milestone 12) live on the same component for the same reason: attaching moves an ItemInstance out of the flat, already-loaded Inventory into a weapon's nested WeaponAttachmentState (detaching reverses it), so it needs the one loaded copy too. See 09_ITEM_FRAMEWORK.md §2.2. Milestone 14: RequestUseItem now calls Applejack.Licences.ItemLicenceGate.IsPermitted before starting the interaction, refusing to equip/use an item whose category requires a licence the caller doesn't hold — this milestone's own real, wired exit criterion. See 28_LICENCES.md §4 and Licences. - WeaponAttachmentStateSerializer (Milestone 12) — persists a weapon's attached items. WeaponAttachmentState itself lives in Items (it holds only Items-owned types); this serializer lives here because, like ContainerStateSerializer, its payload nests real ItemInstances and needs InventoryStore's document-conversion recursion. Registered by InventoryModule immediately after ContainerStateSerializer. - InventoryModule — registers IInventoryService and /give (Milestone 10) — the admin/testing way to spawn an item into your own inventory that Guides/02_ADDING_AN_ITEM.md used to name as unbuilt. InventoryModule.GiveAsync is a static method taking its collaborators as parameters specifically so it's testable without booting the module (UnitTests/Inventories/InventoryModuleGiveTests.cs), the same shape CharacterInventoryComponent's own static extraction already uses. Self-only — no target-player argument, matching Commands' own established minimalism. InventoryPanel is NOT registered against IHudRegistry/IMenuRegistry -- it's a PanelComponent (resolves the local player's CharacterInventoryComponent via Applejack.Movement.LocalPawn), and neither registry accepts anything but TPanel : Panel, new(). Follows MoneyDisplay's scene-placed pattern for the standing-HUD half; its originally-intended dual-registration as the player menu's Inventory tab is a still-open question, since PlayerMenu's own tab-swapping only supports Panel-typed content — see Code/Ui/README.md's "Panel-vs-PanelComponent" entry (24_PLAYER_MENU.md §4).

Not responsible for. - Item templates, categories, behaviours, ItemInstance, and ItemId — all Items; this module consumes ItemInstance, it does not declare it. - Character inventory's own "which character owns which IInventoryHolder" wiring lives in CharacterInventoryHolder here (this module depends on Character, not the reverse) rather than inside Code/Characters/, keeping Character ignorant of Inventory entirely, per 08_DATA_MODEL.md §1, "One owner per piece of state". - Virtual/computed container contents (one vault, per-player view) — a real idea from the legacy design worth keeping (see Legacy/03_INVENTORY_AND_ITEMS.md §2) but out of scope until a system (banking) actually needs it. - Nested containers' UI/interaction surface — the data nests for free (a Container behaviour's state bag holds an IInventory), but opening one is Milestone 6's Interaction framework.

Dependencies. Core (IServiceRegistry, IServiceResolver, IPersistenceBackend, ISchemaRegistry, IGameLog), Items (ItemDefinition), Character (CharacterId, for CharacterInventoryHolder only), Interaction (reads a sibling InteractionComponent.Runner from CharacterInventoryComponent.RequestUseItem, per InventoryModule.DependsOn), Licences (Milestone 14: ILicenceService, resolved by CharacterInventoryComponent.OnAwake to gate RequestUseItem), the S&box engine (Component, Sync, Rpc) for WorldContainerComponent/CharacterInventoryComponent.

Public API. - IInventory, IInventoryHolder. - IInventoryServiceLoadOrCreateAsync, SaveAsync, TransferAsync. - CharacterInventoryHolder, WorldContainerComponent. - CharacterInventoryComponent.RequestUseItem — the [Rpc.Host] entry point for using an item. - CharacterInventoryComponent.RequestAttachItem/RequestDetachItem (Milestone 12) — the [Rpc.Host] entry points for attaching/detaching a weapon attachment. - InventoryModule.GiveAsync — the /give <itemAssetId> [count] command body, internal static for direct testability. - WeaponAttachmentStateSerializer (Milestone 12, internal) — persists Applejack.Items.WeaponAttachmentState.

Events published. None yet — a future ItemAdded/ItemRemoved/ItemTransferred trio is natural once a UI needs to react, deferred until that consumer exists.

Persistence. inventories/{ownerId} — one document per holder, schema version 1. See 08_DATA_MODEL.md §8.

Each stored ItemInstance carries its whole ItemStateBag, one entry per registered IItemStateSerializer. This module owns ContainerState and registers ContainerStateSerializer for it — it is an ordinary registrant, not a special case: nested containers used to be the only state shape the document could express, which silently dropped every other module's item state on save. See 09_ITEM_FRAMEWORK.md §2.1. An unregistered or no-longer-registered state entry is dropped with a warning; it never fails the save or makes the inventory unloadable.

Future improvements. - Compact representation for stackable, stateless instances if profiling shows the per-instance overhead is material — see ADR-0006's Notes; this is an optimisation behind IInventory, not a second model. - Virtual/computed container contents, once banking or a similar system needs it. - Whether S&box's [Sync] actually supports a List<InventorySlotView> (a plain record struct) is unverified this pass — no editor/compiler available. If it turns out unsupported, the fallback is a fixed-size array of slots on CharacterInventoryComponent, not a redesign of InventorySlotView itself.