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 UserId → OwnerId 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.