ZOA Inventory
Build containers with the space and rules your items need.
A backpack, a chest, and an equipment slot can use different shapes and acceptance rules. Inventory lets you define those containers, set item footprints and stack limits, and track where each item is during play.
Open Items & Economy → Inventory to create items and containers with the guided wizards. Use the adjacent browsers to edit existing definitions. Gameplay code can add, move, and query items through the inventory service.
Tooling
Editor tools
Create Item and Create Inventory
Workbench → Items & Economy → Inventory
Create Item guides you through an item’s properties. Create Inventory lets you choose a container shape, configure its capabilities and rules, and review the result before creating assets.
Items
Workbench → Items & Economy → Inventory → Items
Browse ItemDefinition assets, edit their properties, and inspect validation. The browser also provides creation, duplication, deletion, and asset-location actions.
Inventories
Workbench → Items & Economy → Inventory → Inventories
Select a container definition and assign its shape and rule set. Configure stacking, rotation, and weight capabilities, then validate the asset.
Rule Sets and Inventory Browser
Workbench → Items & Economy → Inventory
Rule Sets edits the acceptance constraints referenced by inventory definitions. Inventory Browser lists the definitions with their configured shapes, rules, and capabilities.
IInventoryAuthoringService
Call BuildGridShape, BuildRuleSet, and BuildInventoryDefinition from an editor script or generator. These operations provide a programmatic route to authoring the assets used by the runtime.
ZOAIconBaker
Batch and per-item icon generation over Foundation's icon generator. It owns the output convention (Assets/ZOA/Generated/Icons), idempotence through a sidecar bakeinfo keyed on prefab GUID, prefab mtime, and a render-options hash, default render profiles per item archetype, and sprite assignment back onto the ItemDefinition through SerializedObject so inspectors and OnValidate observers see the change.
From the development project
Screenshots


Declared ZOA dependencies (4)
How it works
Concepts
Cells are addressed by coordinate, so fit is one test
GridInventoryState keeps a flat cell array holding the InstanceId occupying each cell, plus a dictionary of PlacedItem records carrying origin, base footprint, and orientation. Occupancy and placement are stored separately because they answer different questions: the cell array answers "is this square free" in constant time, while the record answers "where does this item start and how big is it" without a scan.
Because a placement is an origin plus an oriented size, testing whether a 2x4 rifle fits is a single rectangle sweep through IsAreaFree, with an optional ignoreInstance so an item can be tested against a position it currently partially occupies. Rotation never mutates the authored footprint: PlacedItem stores BaseFootprint and Orientation, and EffectiveFootprint composes them through GridSize.Oriented at read time. TryMoveAndRotate is therefore atomic: there is no rotate-then-move pair that can strand an item halfway through.
TryFindFirstFit scans in row-major order and returns the first origin that accepts the size, which makes automatic placement deterministic. Two clients adding the same item to the same state land on the same cell, which matters for a networked inventory where server and client must agree without replicating every coordinate.
Item instances are immutable records with an identity
ItemInstance has no setters. Every mutation goes through a With* helper that returns a new instance carrying the same InstanceId: WithQuantity, WithCondition, WithCustomName, WithPinned, WithTag, WithStatePayload. Equality is identity only, so an instance compares equal to its own updated copy, and a UI can hold a stale reference without corrupting the container's copy.
The constructor is where the invariants live rather than the caller. A non-stackable item must have quantity exactly 1, a stackable one must be within [1..MaxStackSize], and condition is clamped to 0..1. Violations throw at construction, which puts the failure at the line that got it wrong instead of three systems downstream.
The strongest invariant is that an item carrying an IItemStatePayload must be non-stackable. A per-instance payload on a stack has no meaning, because there is no answer to whose magazine contents a stack of five rifles holds. Catching that in the constructor stops it from becoming a save-layer bug where a stack split silently duplicates or discards state.
Item state travels on the instance
A weapon's magazine contents, its installed attachments, an armour piece's durability model: these belong to one instance and must survive being stowed in a backpack, dropped in a crate, and picked up again. ItemInstance.StatePayload holds a package-defined POCO implementing IItemStatePayload, so the state travels with the item through every container operation without inventory knowing anything about ballistics.
Serialisation stays outside. ItemStatePayloadRegistry maps a PayloadTypeId string, conventionally zoa.<domain>.<name>.v<n>, to a factory that produces a default-valued payload, and packages self-register at boot. The save layer looks up the factory, hydrates the fields with whatever mechanism it prefers, and skips the payload entirely when the id was never registered. Skipping is the backward-compatibility policy: a save containing a payload from a package the player has since removed still loads, minus that state.
Reservations are soft locks with an explicit commit
A shop sale, a crafting recipe, and a drag in flight all have the same shape: quantity that is still in the container but must not be consumed by anything else. InventoryReservationLedger tracks reserved quantity per instance and every removal path consults it, so a reserved stack cannot be spent out from under the pending operation.
The lifecycle has three legs. TryReserve takes the soft lock, ReleaseReservation gives it back when the operation is abandoned, and TryCommitReservedRemoval consumes it as part of finalising the transaction. Committing is not the same as removing, because the commit path knows the quantity was already reserved and does not have to re-check availability against a value another system may have changed in between.
Rules are assets, evaluated against a context
An InventoryRuleSetDefinition holds a list of InventoryConstraintBase assets, and acceptance is the conjunction of all of them. Constraints are ScriptableObjects so a designer authors and shares them, but the interface they implement, IInventoryConstraint, is engine-free and takes an InventoryContext struct: the inventory's definition id, its effective capabilities, its shape descriptor, its current weight excluding the item under test, and an IItemDefinitionLookup.
The context carries current weight, so MaxWeightConstraint never queries the container. It is a pure function of its inputs, and the same rule set evaluates identically in a headless test and in a live scene. The shipped constraints cover the common filters: AllowByTagConstraint, AllowByItemTypeConstraint, AllowByExplicitDefinitionConstraint, AllowByEquipmentSlotConstraint, and MaxWeightConstraint. Every one returns a reason string on refusal, which a UI shows the player instead of a generic beep.
Definition data reaches the rules through IItemDefinitionLookup, whose Unity implementation projects an ItemDefinition into the engine-free ItemDefinitionInfo snapshot. The rules therefore see stack size, footprint, weight, category, tags, and rarity, and never a ScriptableObject they could accidentally mutate.
Capabilities are negotiated between definition and shape
InventoryCapabilities is a flags enum declared in Foundation, and an InventoryDefinition authors a base set. GetEffectiveCapabilities intersects that base set with what the shape can support, so a definition that requests Rotation on a bag shape, or on a grid shape with rotation disabled, has the flag stripped.
InventoryRuntime applies the same reduction in its constructor, which means the invariant holds even for a runtime constructed directly in a test with no definition asset present. No consumer has to check both the definition and the shape before offering a rotate affordance: reading Capabilities off the runtime is sufficient.
Containers inside containers, and containers that must be searched
ContainerItemDefinition is an ItemDefinition that also references an InventoryDefinition for its contents, which is how a backpack is an item you can carry and a container you can open. ContainerInventoryRegistry maps a container's InstanceId to the InventoryRuntime holding its contents, and ContainerItemInventoryService is the Unity-side owner that creates those runtimes on demand from the container definition. The registry is engine-free, so nesting can be reasoned about and tested without a scene.
Separately, an inventory can be gated behind a search. InventoryRuntime.IsSearchRequired plus SearchProgress model the survival-game loot container whose contents are hidden until the player has spent time on it, with IsItemRevealed and RevealItem controlling per-item visibility. InventorySearchManager drives that progressively over time with a coroutine per inventory, revealing items one at a time rather than flipping the whole container open at once.
Setup
Workflow
- 01
Choose a container shape
Open Items & Economy → Inventory → Create Inventory and choose a Grid, RPG, or Bag shape. Shape assets hold dimensions and rotation settings and can be shared by several container definitions.
- 02
Author the rule set
Add constraints for the items a container should accept, then reference them from an InventoryRuleSetDefinition. Tag constraints are useful when new items should qualify automatically by category.
- 03
Assemble the InventoryDefinition
Assign the shape and rule set, then choose the inventory’s capabilities. The shape can restrict those choices: bags and rotation-disabled grids cannot rotate items. Review the wizard’s output before creating assets, or use Inventories to edit an existing definition.
- 04
Rebuild the GameDatabase
New definitions must be indexed before the runtime can resolve them. Rebuild the definition index with the Foundation tool so InventoryComponent's lookup can resolve item IDs to footprints, weights, and tags at runtime.
- 05
Add an InventoryComponent and install the scene service
Drop an InventoryComponent on the carrier and assign the definition, or call Configure from a generator. Add a ZOAInventorySceneInstaller to the scene root so an IInventoryService is registered before consumers Awake, and add a loot scene installer alongside it if the scene contains ItemPickups, since grant routing moved to the loot package.
- 06
Drive it from gameplay
Build instances with ItemInstanceFactory, ask CanAccept before offering an affordance, and use TryAdd for automatic placement or TryAddAt when the player chose a cell. Subscribe to Changed rather than polling; both the component and the runtime raise it on every mutation.
Reference
Key types
InventoryRuntime
class
The engine-free type the rest of the package wraps. Owns a grid or bag backing store plus a reservation ledger, and implements every add, remove, move, rotate, split, and transfer operation. Constraint evaluation happens in InventoryComponent, above this layer.
- InventoryRuntime(DefinitionId inventoryDefinitionId, InventoryShapeDescriptor shape, InventoryCapabilities capabilities, IItemDefinitionLookup itemDefinitions = null)
- bool TryAdd(ItemInstance item, out string reason)
- bool TryAddAt(ItemInstance item, GridCoord origin, Orientation orientation, out string reason)
- bool TryAddQuantity(DefinitionId definitionId, int quantity, out string reason)
- bool TryRemove(InstanceId instanceId, int quantityToRemove, out string reason)
- bool TryMoveWithin(InstanceId instanceId, GridCoord newOrigin, out string reason)
- bool TryMoveTo(InstanceId instanceId, InventoryRuntime target, int quantityToMove, GridCoord? targetOrigin, out string reason, Orientation orientation = Orientation.Normal)
- bool TryRotate(InstanceId instanceId, out string reason)
- bool CanFit(ItemInstance item, out string reason)
- float CalculateTotalWeightKg()
- void Sort()
- event Action Changed
GridInventoryState
class
Cell occupancy and placement records for a spatial inventory. Low-level and mutation-only: it enforces geometry, never rules, cross-container moves, or reservation policy.
- bool IsAreaFree(GridCoord origin, GridSize size, InstanceId ignoreInstance = default)
- bool TryFindFirstFit(GridSize size, out GridCoord origin)
- bool TryPlaceNew(ItemInstance item, GridCoord origin, GridSize baseFootprint, Orientation orientation, out string reason)
- bool TryMoveAndRotate(InstanceId instanceId, GridCoord newOrigin, Orientation newOrientation, out string reason)
- bool TryRotateInPlace(InstanceId instanceId, out string reason)
- PlacedItem GetAt(GridCoord cell)
- IEnumerable<PlacedItem> EnumeratePlaced()
BagInventoryState
class
Non-spatial backing store: a dictionary of instances with no placement at all. Capacity limits live in the shape descriptor's stack cap and in weight constraints, not here.
- int Count
- bool TryAdd(ItemInstance item, out string reason)
- bool TryUpdate(ItemInstance updated, out string reason)
- bool TryRemove(InstanceId instanceId, out string reason)
- IEnumerable<ItemInstance> EnumerateItems()
InventoryReservationLedger
class
Per-instance soft locks. Every removal path checks it, so quantity promised to a pending shop sale or craft cannot be spent twice.
- int GetReserved(InstanceId instanceId)
- bool CanConsumeUnreserved(InstanceId instanceId, int availableQuantity, int requestedQuantity, out string reason)
- bool TryReserve(InstanceId instanceId, int availableQuantity, int quantityToReserve, out string reason)
- void Release(InstanceId instanceId, int quantityToRelease)
- bool TryConsumeReserved(InstanceId instanceId, int availableQuantity, int quantityToConsume, out int remainingReserved, out string reason)
ItemInstance
class
One concrete item at runtime: a definition reference, a quantity, and per-instance state. Immutable, identity-equal, and engine-free, so it serialises through an explicit save schema without dragging a ScriptableObject along.
- InstanceId InstanceId
- DefinitionId DefinitionId
- int Quantity, int MaxStackSize, bool IsStackable
- float Condition01, bool IsFoundInRaid, bool IsPinned
- string CustomName, TagColor, TagName
- IItemStatePayload StatePayload
- ItemInstance WithQuantity(int quantity) / WithCondition / WithPinned / WithTag / WithStatePayload
IItemStatePayload
interface
Marker for per-instance runtime state that travels with an item through storage. Carries only routing metadata; the concrete payload is a plain POCO owned by whichever package defines the stateful item.
- string PayloadTypeId
- int PayloadVersion
ItemStatePayloadRegistry
class
Thread-safe type-id to factory map the save layer uses to hydrate payloads. Registration is idempotent and overwrites, so editor hot reload does not throw. An unregistered id yields false instead of throwing, so an old save loads without the payload.
- static void Register(string payloadTypeId, Func<IItemStatePayload> factory)
- static bool TryCreate(string payloadTypeId, out IItemStatePayload payload)
- static bool IsRegistered(string payloadTypeId)
InventoryShapeDescriptor
struct
The engine-free shape configuration produced from a shape definition: kind, grid size, force-one-by-one, allow-rotation, and bag stack cap. Grid and Bag factory methods cover the common cases.
- InventoryShapeKind Kind
- GridSize GridSize, int Columns, int Rows
- bool ForceOneByOne, bool AllowRotation
- int MaxStackCount
- static InventoryShapeDescriptor Grid(int columns, int rows, bool allowRotation = true, bool forceOneByOne = false)
- static InventoryShapeDescriptor Bag(int maxStackCount = 0)
PlacedItem
class
A grid placement record: the instance, its origin, its authored base footprint, and its orientation. EffectiveFootprint composes footprint and orientation on read, which is why rotation never touches the definition.
- ItemInstance Item
- GridCoord Origin
- GridSize BaseFootprint
- Orientation Orientation
- GridSize EffectiveFootprint
IInventoryConstraint
interface
One accept-or-reject rule. Engine-free by design so rules run headless, and returns a reason string rather than a bool alone, because refusal messages are what the player actually reads.
- bool CanAccept(ItemInstance item, InventoryContext context, out string reason)
InventoryContext
struct
Everything a constraint is allowed to know: the inventory's definition id, effective capabilities, shape, current weight excluding the candidate item, and a definition lookup. The struct carries everything a rule reads, so evaluation never touches the container and stays pure.
- DefinitionId InventoryDefinitionId
- InventoryCapabilities Capabilities
- InventoryShapeDescriptor Shape
- float CurrentWeightKg
- IItemDefinitionLookup ItemDefinitions
IItemDefinitionLookup
interface
The engine-free view of item definition data used by rules and placement. UnityItemDefinitionLookup wraps an IDefinitionRegistry and projects ItemDefinition into the ItemDefinitionInfo snapshot.
- bool TryGet(DefinitionId id, out ItemDefinitionInfo info)
InventoryComponent
component
Binds a definition to a GameObject, builds the definition lookup, owns an InventoryRuntime, and adds rule evaluation on top of every mutation. This is the type gameplay code holds a reference to.
- InventoryRuntime Runtime
- InventoryDefinition Definition
- InventoryCapabilities EffectiveCapabilities
- bool CanAccept(ItemInstance item, out string reason)
- bool TryAdd(ItemInstance item, out string reason)
- bool TryAddQuantity(DefinitionId definitionId, int quantity, out string reason)
- bool TryMoveTo(InstanceId instanceId, InventoryComponent target, int quantityToMove, GridCoord? targetOrigin, out string reason, Orientation orientation = Orientation.Normal)
- bool TrySplit(InstanceId instanceId, int quantityToSplit, out string reason)
- void Configure(InventoryDefinition definition, GameDatabase databaseOverride = null)
- event Action Changed
IInventoryService
service
Answers "where is the player's bag" so pickups, mission rewards, and debug menus do not perform a scene-wide component search on every grant. It stays thin: the component already exposes the full API.
- InventoryComponent PlayerInventory
- void RegisterPlayerInventory(InventoryComponent inventory)
- bool TryAddToPlayer(ItemInstance item, out string reason)
- bool TryAddQuantityToPlayer(DefinitionId itemDefinitionId, int quantity, out string reason)
- event Action<InventoryComponent> PlayerInventoryRegistered
ContainerInventoryRegistry
class
Engine-free map from a container instance id to the InventoryRuntime holding its contents. Backs both wearable containers and world containers, with GetOrCreate taking a factory so hydration order does not matter.
- bool TryGet(InstanceId containerInstanceId, out InventoryRuntime inventory)
- InventoryRuntime GetOrCreate(InstanceId containerInstanceId, Func<InventoryRuntime> factory, out bool created)
- bool Remove(InstanceId containerInstanceId)
- IEnumerable<KeyValuePair<InstanceId, InventoryRuntime>> EnumerateAll()
ContainerItemInventoryService
component
Unity-side owner of the container registry. Creates a contents runtime on demand from a ContainerItemDefinition and hands it back for the lifetime of the container item instance.
- ContainerInventoryRegistry Registry
- bool TryGetContentsInventory(InstanceId containerItemInstanceId, out InventoryRuntime inventory)
- InventoryRuntime GetOrCreateContentsInventory(ItemInstance containerItem, ContainerItemDefinition containerDef)
- bool TryAddToContainer(ItemInstance containerItem, ContainerItemDefinition containerDef, ItemInstance payload, out string reason)
ItemInstanceFactory
class
Builds runtime instances from authoring assets without mutating them. CreateWithState forces MaxStackSize to 1 regardless of what the definition declares, because a stateful item cannot be fungible.
- static ItemInstance Create(ItemDefinition definition, int quantity = 1, InstanceId? instanceId = null, string customName = null, float condition01 = 1f)
- static ItemInstance CreateWithState(ItemDefinition definition, IItemStatePayload statePayload, InstanceId? instanceId = null, ...)
InventorySearchManager
component
Drives progressive reveal for search-gated containers, one coroutine per inventory, at a configurable seconds-per-item rate. Starting a search on an already-searching inventory is a no-op rather than a second coroutine.
- bool IsSearching(InventoryRuntime inventory)
- void StartSearch(InventoryRuntime inventory, Action onComplete = null)
- void StopSearch(InventoryRuntime inventory)
ItemPickup
component
A world pickup carrying an ItemDefinition and a quantity, built on the interaction package's ZOAPickupBase and implementing IZOAInteractable. Grants publish through the static Granted event; the loot package's adapter is what routes them into the player's bag.
- PickupPayload Payload
- string Prompt
- void SetPayload(ItemDefinition item, int quantity)
- static event Action<PickupPayload, GameObject> Granted
ZOAInventorySceneInstaller
component
Scene-root bootstrap at execution order -9450. Registers an IInventoryService into FoundryServiceRegistry, defers to any pre-registered override, and unregisters on destroy only when the registered instance is the one it created.
- IInventoryService Service
- void EnsureRegistered()
ItemIconUtility
class
Resolves an item's icon and silhouette with deterministic fallbacks, generating and caching a placeholder sprite keyed by DefinitionId when nothing is authored. It deals only in Unity primitives, so it works for uGUI and UI Toolkit alike.
- static Sprite ResolveIcon(ItemDefinition def)
- static Sprite ResolveSilhouette(ItemDefinition def)
- static Sprite GetOrCreateFallbackIcon(DefinitionId id, int sizePx = 64)
Reference
Authoring assets
ItemDefinition
asset
The authored item asset: icon, silhouette, optional 3D icon prefab, description, stack size, default grid footprint, weight, rarity, and category. Treated as immutable at runtime, with all mutable state living in ItemInstance. Create at Tools > ZOA > Inventory > Item Definition.
- Sprite Icon, Sprite Silhouette, GameObject IconPrefab
- int MaxStackSize, bool IsStackable
- GridSize DefaultFootprint
- float BaseWeightKg
- ItemRarity Rarity, string Category, string Description
InventoryDefinition
asset
A container's authored configuration: which shape it uses, which rule set governs acceptance, its base capability flags, and an optional UI grouping hint. GetEffectiveCapabilities is the value consumers should read, since the shape can veto flags the author requested.
- InventoryShapeDefinition ShapeDefinition
- InventoryRuleSetDefinition RuleSet
- InventoryCapabilities BaseCapabilities
- string UiGroup
- InventoryCapabilities GetEffectiveCapabilities()
GridShapeDefinition
asset
A general spatial grid with rows, columns, and a rotation toggle. Items occupy their authored footprint, so this is the shape behind the Tarkov-style backpack. Tools > ZOA > Inventory > Shapes > Grid Shape.
- int Rows, int Columns
- bool AllowRotation
- override InventoryShapeDescriptor CreateDescriptor()
RpgShapeDefinition
asset
A grid that forces every footprint to 1x1 and forbids rotation, which is the classic slot-based RPG bag. It reuses the same grid machinery rather than introducing a separate code path, so ordering, moves, and stacking behave identically.
- int Rows, int Columns
- override InventoryShapeDescriptor CreateDescriptor()
BagShapeDefinition
asset
A non-spatial container with an optional maximum stack count, zero meaning unlimited. Use it for weight-limited or purely list-based inventories, and layer real capacity limits through weight constraints.
- int MaxStackCount
- override InventoryShapeDescriptor CreateDescriptor()
InventoryRuleSetDefinition
asset
A shareable, ordered list of constraint assets. Acceptance is the conjunction: the first constraint to refuse supplies the reason. Author one rule set per container archetype and reuse it across every inventory of that kind.
- IReadOnlyList<InventoryConstraintBase> Constraints
- bool CanAccept(ItemInstance item, InventoryContext context, out string reason)
InventoryConstraintBase
asset
Abstract ScriptableObject base for constraints. Shipped subclasses: AllowByTagConstraint, AllowByItemTypeConstraint, AllowByExplicitDefinitionConstraint, AllowByEquipmentSlotConstraint, and MaxWeightConstraint. Each is created under Tools > ZOA > Inventory > Constraints.
- abstract bool CanAccept(ItemInstance item, InventoryContext context, out string reason)
ContainerItemDefinition
asset
An ItemDefinition that also owns an InventoryDefinition for its contents, which is how a backpack is both carried and openable. A nesting toggle lets a design forbid containers inside containers. Tools > ZOA > Inventory > Container Item Definition.
- InventoryDefinition ContentsInventoryDefinition
- bool DisallowNestedContainers
Usage
Examples
using ZOA.Foundation.Core.Ids;
using ZOA.Foundation.Core.Inventory;
using ZOA.Inventory.Core.Inventories;
using ZOA.Inventory.Core.Items;
// A 10x8 grid that stacks, rotates, and tracks weight. No scene,
// no ScriptableObjects, no play mode: this runs under plain NUnit.
var runtime = new InventoryRuntime(
DefinitionId.New(),
InventoryShapeDescriptor.Grid(columns: 10, rows: 8, allowRotation: true),
InventoryCapabilities.Stacking
| InventoryCapabilities.Rotation
| InventoryCapabilities.Weight
| InventoryCapabilities.Reservations);
var rifle = new ItemInstance(InstanceId.New(), rifleDefId, quantity: 1, maxStackSize: 1);
if (!runtime.TryAdd(rifle, out var reason))
Debug.LogWarning(reason);
// Placement at an explicit cell, rotated 90 degrees. A blocked origin
// fails outright rather than silently relocating the item.
runtime.TryAddAt(rifle, new GridCoord(row: 2, col: 0), Orientation.Rotated, out reason);using ZOA.Inventory.Unity.Components;
using ZOA.Inventory.Unity.Factories;
public bool TryGive(InventoryComponent bag, ItemDefinition def, int count)
{
var item = ItemInstanceFactory.Create(def, count);
// Two separate gates, two separate messages. CanAccept is the rule
// engine; CanFit is the geometry. Testing both up front lets the UI
// say "your pack refuses grenades" rather than "it didn't work".
if (!bag.CanAccept(item, out var ruleReason))
{
ShowToast(ruleReason);
return false;
}
if (!bag.Runtime.CanFit(item, out var fitReason))
{
ShowToast(fitReason);
return false;
}
return bag.TryAdd(item, out _);
}using ZOA.Foundation.Core.Ids;
using ZOA.Inventory.Unity.Components;
public sealed class ShopSale
{
private readonly InventoryComponent _seller;
private readonly InstanceId _stack;
private readonly int _quantity;
public bool Open()
{
// Soft lock: the stack stays visible and in place, but nothing
// else can consume the reserved quantity while the sale is open.
return _seller.TryReserve(_stack, _quantity, out _);
}
public bool Confirm()
// Commit consumes the reservation as part of the removal, so the
// availability check is not re-run against a value that may have
// moved between Open and Confirm.
=> _seller.TryCommitReservedRemoval(_stack, _quantity, out _);
public void Cancel() => _seller.ReleaseReservation(_stack, _quantity);
}using ZOA.Inventory.Core.Items;
using ZOA.Inventory.Unity.Factories;
public sealed class MagazineState : IItemStatePayload
{
public string PayloadTypeId => "mygame.weapon.magazine.v1";
public int PayloadVersion => 1;
public int RoundsRemaining;
}
// Once, at runtime boot. Registration is idempotent and overwrites,
// so a domain reload in the editor will not throw.
ItemStatePayloadRegistry.Register(
"mygame.weapon.magazine.v1",
() => new MagazineState());
// CreateWithState forces MaxStackSize to 1: a per-instance payload on
// a stack has no well-defined owner, so the factory refuses to make one.
var loaded = ItemInstanceFactory.CreateWithState(
rifleDefinition,
new MagazineState { RoundsRemaining = 30 });using UnityEngine;
using ZOA.Inventory.Core.Items;
using ZOA.Inventory.Core.Rules;
using ZOA.Inventory.Unity.Rules.Constraints;
[CreateAssetMenu(menuName = "Tools/MyGame/Constraints/Min Condition")]
public sealed class MinConditionConstraint : InventoryConstraintBase
{
[Range(0f, 1f)]
[SerializeField] private float minimumCondition = 0.25f;
public override bool CanAccept(ItemInstance item, InventoryContext context, out string reason)
{
if (item.Condition01 >= minimumCondition)
{
reason = null;
return true;
}
// The reason string is what the player reads, so write it for them.
reason = "This trader will not take badly damaged gear.";
return false;
}
}using ZOA.Foundation.Core.Grid;
using ZOA.Inventory.Unity.Components;
// Whole stack, automatic placement in the target.
crate.TryMoveTo(itemId, playerBag, quantityToMove: int.MaxValue,
targetOrigin: null, out var reason);
// Partial stack dropped on a specific cell, rotated. A partial move
// mints a new InstanceId for the moved portion and leaves the source
// stack behind with the remainder.
crate.TryMoveTo(itemId, playerBag, quantityToMove: 10,
targetOrigin: new GridCoord(row: 0, col: 3), out reason,
orientation: Orientation.Rotated);Integration
Behavior to account for
See also