Skip to content

๐Ÿ“ฆ Containers

Low-level runtime data structures for item storage and equipment layouts.
These are the core objects that SceneInventoryService creates and manages. You rarely instantiate them directly โ€” and typically access them through the service layer.

โœ… Runtime-safe
โŒ Not designed for standalone serialization
โœ… Supports granular change events
โœ… Shared logic between inventory and equipment


Purpose

Defines how items are stored, stacked, filtered, and mutated inside the Inventory system.
Everything above this layer (services, UI, gameplay) builds on these containers.


Contents

Type Purpose
InventoryContainer General-purpose slot list with stack, split, merge, swap, filter, and resize support.
EquipmentContainer Fixed single-item layout for gear slots, each with its own EquipmentFilter.
InventoryDelta Immutable struct describing per-slot diffs for UI or syncing.
ItemStackUtil / InventorySortHelpers Internal helpers for equality, metadata, and sort keys.

๐ŸŒ What Is a Container?

A container is an ordered list of slots.
Each slot holds a single ItemStack, optionally constrained by a filter:

  • InventorySlot.Accepts(ItemDefinition) for inventory
  • EquipmentFilter for equipment

Two concrete container types exist:

  • InventoryContainer โ†’ dynamic, stack-based storage
  • EquipmentContainer โ†’ fixed, one-item-per-slot layout

๐Ÿš€ Quick Start

Containers are normally retrieved via the service:

var svc = SceneInventoryService.Instance;

// Backpack
var backpack = svc.Get(player, "Backpack");

// Equipment
var eq = player.GetComponent<CharacterEquipment>().Equipment;

Direct mutation (bypasses authority):

var sword = db.GetByGuid("sword");
backpack.TryAddAllResult(new ItemStack { def = sword, quantity = 1 });

โš ๏ธ Direct container calls bypass authority.
Use SceneInventoryService for authoritative gameplay changes.


๐Ÿ”” Events & Deferral

Both container types expose:

event Action OnChanged;

This fires whenever contents change.

Bulk writes can be coalesced using deferral scopes:

using (backpack.DeferEvents())
{
    backpack.TryRemoveResult("potion", 1);
    backpack.TryAddAllResult(new ItemStack { def = sword, quantity = 1 });
}
// โ†’ OnChanged fires once

โœจ Common InventoryContainer Operations

inv.TryAddAllResult(stack);                        // full-stack add or fail
inv.AddMaxResult(stack, out var remainder);        // partial add allowed
inv.TryRemoveFromSlotResult(index, qty);           // strict remove
inv.TrySplitResult(from: 0, to: 5, amount: 2);     // split stack
inv.TryMergeThenSwapResult(from: 0, to: 1);        // merge else swap
inv.TryResize(newSize: 32, allowTruncate: true, out var truncated);

Behaviour Highlights

  • Strict removal โ€” TryRemoveResult(guid, qty) requires full quantity
  • Partial merges track exact per-slot placement for safe rollback
  • Slot filters (Accepts) can block merges, swaps, and splits
  • Resize supports truncation and triggers a delta resnap via the service
  • Deferral scopes coalesce multiple changes into a single event

โœจ Common EquipmentContainer Operations

eq.TryEquipStrict("Weapon", new ItemStack { def = sword, quantity = 1 }, out var swapped);
eq.TryUnequip("Helmet", out var removed);
eq.SetSlotFilter("Accessory1", ringFilter);
eq.ClearAll();

Behaviour Highlights

  • Exactly one item per slot
  • Filters must pass (EquipmentFilter.Matches)
  • Duplicate layout IDs warn but last wins
  • Layout changes and bulk updates should use DeferEvents()
  • All changes raise OnChanged

๐Ÿ”Ž InventoryDelta

InventoryDelta is produced only by SceneInventoryService when a tracked container changes, by diffing internal snapshots against container state.

svc.OnContainerChanged += delta =>
{
    foreach (var change in delta.changes)
        Debug.Log($"{delta.container}[{change.index}]: {change.before.def} โ†’ {change.after.def}");
};

Key Points

  • Contains only changed slots (per-slot diff)
  • Slot-count changes (resize) emit a full resnap delta
  • Ideal for UI repainting or lightweight syncing

๐Ÿงช Equality & Metadata (ItemStackUtil)

ItemStackUtil.StacksEqual(a, b) compares:

  • Item definition
  • Quantity
  • Durability within DurabilityEpsilon
  • Metadata as an unordered dictionary (last-write-wins semantics)

Used by:

  • InventoryDelta snapshot comparison
  • Sorting
  • Mutations that require value equality checks

โš ๏ธ Notes & Gotchas

  • InventoryContainer is runtime-only (not serializable)
  • EquipmentContainer always enforces quantity = 1
  • Resize may fail unless truncation is allowed
  • Slot and equipment filters can block operations
  • DeferEvents() prevents event spam during batch updates
  • Authority is enforced only by the service, never by containers

  • Data โ†’ item definitions, stacks, and databases
  • Services โ†’ container creation, authority, and deltas
  • Equipment โ†’ slot layouts and filters
  • UI โ†’ reacting efficiently to InventoryDelta