Skip to content

🛠️ Services

The Services folder contains the runtime backbone of the Inventory system:
SceneInventoryService — the scene-scoped manager that owns all containers, applies mutations, and raises per-slot diffs.

UI bridges and authoritative gameplay flows route through this service.
CharacterInventory also exposes direct container access for local / single-player or UI-only usage.


🌍 What Is SceneInventoryService?

SceneInventoryService is the central runtime authority for inventory state in a scene.

It:

  • Acts as a scene-local singleton (extra instances self-destroy or self-disable)
  • Lazily creates (owner, container)InventoryContainer pairs
  • Computes slot-level diffs via snapshots and raises OnContainerChanged
  • Enforces authority using IInventoryAuthority (default = allow)
  • Exposes a fully result-first API (InvOpResult)
  • Applies container sizing using ContainerSizePolicy and backpack rules

It is the only class that: - Owns container lifetimes - Emits InventoryDelta - Enforces authority


🚀 Quick Start

var svc = SceneInventoryService.Instance;

// Add a sword (authority-safe)
svc.GiveExact(
    player,
    new ItemStack { def = sword, quantity = 1 },
    "Backpack"
);

// Listen for diffs
svc.OnContainerChanged += delta =>
{
    Debug.Log($"{delta.container}: {delta.changes.Count} slot(s) changed");
};

Setup steps:

  1. Create an empty GameObject
  2. Add SceneInventoryService
  3. Assign an ItemDatabase
  4. (Optional) Assign a ContainerSizePolicy

🔔 Events

OnContainerChanged

Raised after service-level mutations.

Each InventoryDelta contains:

  • owner → GameObject that owns the container
  • container → canonical container name (trimmed + lowercase)
  • changes → per-slot diffs (index, before, after)

Notes:

  • Uses internal snapshots to compute diffs
  • Resize operations emit a full resnap (all slots included)
  • Ideal for incremental UI repainting or lightweight syncing

🔑 Authority

All service-level mutating operations begin with an authority gate:

if (!RequireAuthority(owner, out var fail))
    return fail;

Behaviour:

  • No binder present → defaults to allowed (single-player friendly)
  • InventoryAuthorityBinder → grants or denies mutations
  • Custom binder → implement netcode-appropriate rules

Denied mutations return:

InvOpResult.Fail(InvOpCode.NoAuthority,
    "No authority to mutate this inventory.");

Example:

var res = svc.RemoveByGuid(player, potionGuid, 1, "Backpack");
if (res.Code == InvOpCode.NoAuthority)
    Debug.LogWarning("Not allowed!");

Reads (Get, Search, etc.) are never gated.


✨ Common Operations (Result-First)

svc.GiveExact(player, stack, "Backpack");                           // strict add
svc.AddMax(player, stack, out var rem, "Backpack");                 // partial add
svc.RemoveByGuid(player, guid, 2, "Backpack");                      // strict remove
svc.MergeThenSwap(player, "Backpack", 0, 1);                        // merge → swap
svc.ResizeContainer(player, "Backpack", 32, false, out var truncated);

All service APIs return InvOpResult, containing:

  • Success
  • Code
  • Message

🔎 Sorting & Searching

Sorting

svc.Sort(player, "Backpack", InventorySortSpec.ByNameAsc());
  • Stable sorting
  • Optional empty-slots-last behaviour
  • Idempotent when data is unchanged

Searching

svc.Search(player, "Backpack", "potion");     // full signature

Via extensions:

svc.Search(player, "potion");             // defaults to "Backpack"
svc.Search(player, "potion", "Hotbar");   // explicit container
  • Case-insensitive search over name, category, and tags

🧠 Container Creation & Sizing

Containers are created lazily on first access:

  • Get(owner, container)
  • GiveExact, AddMax, SplitResult, etc.

Sizing rules:

  1. Apply ContainerSizePolicy if present
  2. Backpack special-case via CharacterInventory.backpackSize
  3. Fallback defaults (Backpack = 24, others = 12)

Changing policies does not resize existing containers.


⚠️ Gotchas

  • Authority: Always check result codes — never assume success
  • Deferral: Resize, clear, and sort collapse into a single delta
  • Singleton:
  • Duplicate services self-destroy in Awake
  • Duplicate services self-disable in OnEnable
  • Canonical names: Container names are normalized (trim + lowercase)
  • Backpack sizing: May be overridden by policy when policyOverridesBackpack is enabled

  • Abstractions → interfaces and result contracts
  • Containers → slot logic and delta foundations
  • Data → item definitions and databases
  • UI → reacting to deltas with InventoryUiBridge