ItemVault

ItemVault Integration Guide

Overview

ItemVault is a small, engine-agnostic inventory service. The canonical architecture is:

  • Core: ItemVault.Core interface + implementation (IInventoryService, InventoryService, Inventory, ItemStack, events).
  • Optional core locator: ItemVault.Core.InventoryServiceLocator.Current.
  • Optional Godot adapter: ItemVault.Godot.Nodes.InventoryServiceNode.
  • Host game (e.g. Thistletide) decides how to construct and own the service; adapters are thin convenience wrappers only.

1. Core Service (Engine-Agnostic)

1.1 Interfaces and types

Core lives under ItemVault.Core:

  • IInventoryService
    • GetOrCreateInventory(OwnerId ownerId, int capacity = 999)
    • HasItem(OwnerId ownerId, string itemId, int quantity = 1)
    • GetItemCount(OwnerId ownerId, string itemId)
    • TryGiveItem(OwnerId ownerId, string itemId, int quantity)
    • TryConsumeItem(OwnerId ownerId, string itemId, int quantity)
    • event EventHandler<InventoryChangedEvent>? InventoryChanged
  • IInventory + Inventory
    • Simple, in-memory inventory implementation.
  • InventoryId, OwnerId, InventoryChangedEvent
    • Pure C# record/struct types; no engine dependencies.

1.2 Default implementation

  • InventoryService : IInventoryService
    • Maintains a Dictionary<OwnerId, Inventory>.
    • Raises InventoryChanged whenever inventory state changes.
    • Suitable for tests and non-Godot hosts.

1.3 Core service locator (optional)

  • InventoryServiceLocator.Current
    • Static property holding the game-owned IInventoryService instance.
    • Host game responsibility: set this during bootstrap.
    • This is a convenience for small games and adapters; advanced games may use a DI container and still optionally assign this property.

2. Optional Godot Adapter Node

File: plugins/inventory/ItemVault/Godot/Nodes/InventoryServiceNode.cs Namespace: ItemVault.Godot.Nodes

2.1 Purpose

  • Optional, thin Godot adapter for games that:
    • Do not use a full service architecture or DI container, and/or
    • Prefer simple scene-graph + signal wiring (GDScript, basic C#).
  • It:
    • Resolves IInventoryService from InventoryServiceLocator.Current.
    • Exposes methods:
      • GiveItem(string ownerId, string itemId, int quantity)
      • ConsumeItem(string ownerId, string itemId, int quantity)
      • GetItemCount(string ownerId, string itemId)
    • Emits a Godot signal:
      • InventoryChanged(string ownerId, string inventoryId).

2.2 Architectural rules

  • Node is a thin adapter only:
    • No domain logic.
    • No inventory state ownership.
    • All state and behavior live in ItemVault.Core.
  • XML summary explicitly documents that:
    • This node is an optional convenience.
    • Production games with a service layer should prefer resolving IInventoryService directly.

2.3 Usage in non-DI Godot games

  1. At startup, create the core service:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    using ItemVault.Core;
    
    public partial class GameBootstrap : Node
    {
        public override void _Ready()
        {
            InventoryServiceLocator.Current = new InventoryService();
        }
    }
    
  2. Add InventoryServiceNode to your scene:

    • Attach the C# script ItemVault.Godot.Nodes.InventoryServiceNode.
    • Optionally make it an autoload/singleton.
  3. Connect InventoryChanged to your UI or gameplay scripts.

  4. Call GiveItem, ConsumeItem, GetItemCount from GDScript or C#.


For games with a composition root (like Thistletide):

  1. Register the service in the game layer

    • Use your DI container or manual composition to create a single InventoryService (or custom IInventoryService implementation).
  2. Optionally assign the locator

    • If you want to use InventoryServiceNode anywhere:
      1
      
      InventoryServiceLocator.Current = container.GetRequiredService<IInventoryService>();
      
    • This keeps the adapter working without making it the primary pattern.
  3. Resolve IInventoryService directly in systems

    • Player controllers, HUD, save systems, etc. should:
      • Resolve IInventoryService from DI.
      • Subscribe to InventoryChanged as needed.
      • Avoid relying on InventoryServiceNode for core behavior.
  4. Use adapter nodes only where they simplify UI wiring

    • Example: a small GDScript-based debug panel that wants to call into the service without touching DI.

4. Thistletide Integration (Design Sketch)

This section captures the intended integration; actual wiring should happen after tests and build health are restored.

4.1 Where ItemVault fits

  • Thistletide already owns:
    • Session models (PlayerSession, GameSession).
    • Game-level DI / composition logic for networking and services.
  • ItemVault should be treated as a pure backend service:
    • IInventoryService is registered in Thistletide’s composition root.
    • Thistletide decides:
      • How many inventories exist.
      • How owners (OwnerId) map to players/sessions.

4.2 Planned wiring steps (no code yet)

  1. Add ItemVault as a dependency to the Thistletide solution.
  2. In Thistletide’s bootstrap/composition root:
    • Create or register a single IInventoryService instance.
    • Optionally assign InventoryServiceLocator.Current from DI so InventoryServiceNode works for any Godot-only integrations.
  3. Update player/session logic to:
    • Use OwnerId or an adapter from Thistletide’s player identifiers.
    • Use IInventoryService for item grants/consumption instead of a bespoke inventory implementation (where appropriate).
  4. Optionally add a small ItemVault-backed HUD component that resolves IInventoryService and subscribes to InventoryChanged.

Until Thistletide’s build is green and dependencies are restored, this remains a design sketch rather than implemented wiring.


For small demos it is acceptable for individual entities (e.g. PlayerEntity) to call:

  • GetOrCreateInventory(new OwnerId("some-id"))

directly in their own setup code.

For production games (like Thistletide), we recommend a central inventory bootstrap/manager instead:

  1. Bootstrap phase (pure C# or early Godot node)

    • Create a single IInventoryService instance.
    • Decide which OwnerIds should exist at startup (players, NPCs, shops, chests, etc.).
    • Call GetOrCreateInventory(ownerId) for each of them.
  2. Entity adapters (Player/NPC/etc.)

    • Each character/owner only needs to:
      • Know its OwnerId.
      • Call TryGiveItem, TryConsumeItem, GetItemCount as needed.
    • They do not decide when or how inventories are created.
  3. Sessions vs ownership

    • Session/user systems (GameUserSessions) work with UserId.
    • Inventory systems (ItemVault) work with OwnerId.
    • Game glue is responsible for mapping UserIdOwnerId when needed, keeping ItemVault independent of any particular user framework.

This pattern keeps ItemVault focused on ownership and storage, while sessions and gameplay systems decide who the owners are.