Item Drops

ItemDrops 2.0: ItemVault & Inventory Integration

ItemDrops 2.0: Inventory & ItemVault Integration

Goals

  • Stay inventory-agnostic: ItemDrops Core only knows about drops and contexts.
  • First-class ItemVault support: Provide a thin, testable bridge so games using ItemVault can plug in easily.
  • Encourage cross-plugin compatibility: The same patterns work for other inventory systems.

This guide assumes you are using ItemDrops 2.0 (C#) and, optionally, ItemVault.Core for inventories.


This page is the canonical integration guide for ItemDrops 2.0 and ItemVault. Internal docs and tests should point back here as the primary source.

1. Core Concepts

DropService + LootContext

At the core of ItemDrops 2.0 is DropService, which wraps a DropTableRegistry and the DropCalculator:

var registry = new DropTableRegistry();
var dropService = new DropService(registry);

// Register a table
registry.RegisterDropTable("default_enemy", enemyTable);

// Build a loot context from game state
var context = new LootContext
{
    Level = 10,
    LuckModifier = 1.25f, // game-derived luck scalar
    EntityType = "enemy"
};

// Generate drops
var results = dropService.GenerateDrops("default_enemy", context);

Key points:

  • LootContext is immutable and used by public APIs.
  • DropContext is a mutable core context used internally and by some legacy/engine APIs.
  • DropCalculator bridges between them:
    • Overloads accept both LootContext and DropContext.
    • Internally, conditions and tables run on DropContext; metadata uses LootContext.

LuckModifier behavior

LootContext.LuckModifier (and DropContext.LuckModifier) scales table drop chance:

// Effective chance = table.DropChance * LuckModifier, clamped [0,1]
var effectiveChance = dropTable.DropChance * context.LuckModifier;
  • LuckModifier = 0 → table never drops.
  • LuckModifier = 1 → table uses its configured DropChance.
  • LuckModifier > 1 (with low base chance) → chance is boosted up to 100% (capped).

This gives you a single scalar hook the game can compute from stats, world events, difficulty, etc.

Global conditions & modifiers (legacy)

  • DropTableCollection.GlobalConditions and GlobalModifiers still exist as config fields.
  • DropCalculator 2.0 does not apply them at runtime.
  • All behavior should be expressed via:
    • Per-drop conditions/modifiers.
    • The calling code (by choosing tables, building context, or using a separate effects pipeline).

This is the “Option B” simplification: no hidden global effects in the core calculator.


2. Inventory-Agnostic Integration via IInventoryIntegration

ItemDrops 2.0 continues to use the IInventoryIntegration abstraction to talk to any inventory system:

public interface IInventoryIntegration
{
    string Id { get; }
    string Name { get; }
    string Description { get; }

    int Capacity { get; }
    int CurrentCount { get; }
    bool IsFull { get; }
    bool IsEmpty { get; }

    bool CanAcceptDrops(IEnumerable<DropInstance> drops);
    InventoryAddResult AddDrops(IEnumerable<DropInstance> drops);
    InventoryAddResult AddDrop(DropInstance drop);

    int GetItemQuantity(string itemId);
    bool HasItem(string itemId);
    IEnumerable<DropInstance> GetAllItems();

    bool IsValid();
}

Typical flow:

  1. DropService generates DropResult / DropInstance collections.
  2. Game code (or a small adapter) converts those into final DropInstances for inventory.
  3. IInventoryIntegration adds them to your inventory system.

This keeps ItemDrops Core free of inventory concerns.


For games that use ItemVault.Core, we recommend a thin bridge in an infra project that references both cores.

Conceptually:

internal sealed class ItemVaultInventoryIntegration : IInventoryIntegration
{
    private readonly IInventoryService _inventoryService;
    private readonly OwnerId _ownerId;

    public ItemVaultInventoryIntegration(IInventoryService inventoryService, OwnerId ownerId) { ... }

    public InventoryAddResult AddDrops(IEnumerable<DropInstance> drops)
    {
        var result = new InventoryAddResult();

        foreach (var drop in drops)
        {
            var granted = _inventoryService.TryGiveItem(_ownerId, drop.ItemId, drop.Quantity);
            if (granted)
            {
                result.ItemsAdded += drop.Quantity;
                result.AddedItems.Add(drop);
            }
            else
            {
                result.ItemsRejected += drop.Quantity;
                result.RejectedItems.Add(drop);
            }
        }

        result.Success = result.ItemsRejected == 0;
        return result;
    }

    // CanAcceptDrops, GetItemQuantity, HasItem, GetAllItems, etc.
}

Notes:

  • Lives in a separate infra layer (e.g. ItemDropsItemVault.Infra) so neither plugin depends on the other.
  • Uses only:
    • ItemDrops.Core types (DropInstance, IInventoryIntegration).
    • ItemVault.Core types (IInventoryService, OwnerId, Inventory).
  • Fully testable in isolation (see ItemDropsItemVault.Infra.Tests).

Example test scenario

var inventoryService = new InventoryService();
var ownerId = new OwnerId("owner-1");
var bridge = new ItemVaultInventoryIntegration(inventoryService, ownerId);

var drop = new DropInstance(
    itemId: "test_potion",
    itemName: "Test Potion",
    itemType: "Item",
    quantity: 3,
    rarity: ItemRarity.Common,
    weight: 1.0f);

var result = bridge.AddDrop(drop);

result.Success.ShouldBeTrue();
bridge.GetItemQuantity("test_potion").ShouldBe(3);

This demonstrates the Option A mapping from ItemDrops-style drops into an ItemVault inventory.


4. Thistletide & Per-Session Services (Overview)

In Thistletide, game systems are wired around a per-session spine (GameUserSessions, WorldTime, ItemDrops, ItemVault, etc.). A common pattern is:

  • A fixture or factory that knows the current GameSessionId/UserId.
  • Helper methods like CreateTimeServiceForCurrentSession() or CreateDropServiceForCurrentSession().

Going forward, you can generalize this into a small service factory interface, for example:

public interface IGameSessionServiceFactory
{
    TService GetServiceForCurrentSession<TService>() where TService : class;
}
  • Implementation details (dictionary keyed by session + type, composition root wiring, etc.) are left to the game.
  • ItemDrops and ItemVault stay pure core; they do not depend on the factory.
  • Thistletide (or other games) can adopt this pattern to keep wiring consistent across services.

5. Versioning Notes (2.0)

  • The ItemDrops C# core used in this guide is considered 2.0 for documentation and integration purposes.
  • GDScript plugin version numbers may differ (e.g., plugin.cfg version="0.4.x") and are treated as a separate track during the porting phase.
  • Data-driven drop table configuration remains schema version "1.0" unless otherwise noted; future schema versions will be documented separately.

For an overview of the architecture and porting status, see:

  • docs/item-drops/content/item_drops/architecture.md
  • docs/PORTING_GDSCRIPT_TO_CSHARP_WORLD_TIME_AND_ITEM_DROPS.md

Last Updated: December 2025
ItemDrops C# Core: 2.0 (documentation target)