Godot Integration Guide

This guide covers adding InventoryFramework to a Godot 4 project using C#.


Requirements

  • Godot 4.x with .NET enabled (the Mono / C# build, not GDScript-only)
  • .NET runtime: Godot 4.0–4.2 ships with .NET 6; Godot 4.3+ ships with .NET 8. Either works with InventoryFramework.GodotAdapter.
  • A running InventoryFramework server (see server-configuration.md)

Installation

Add the NuGet packages

From the terminal inside your Godot project directory (where the .csproj lives):

dotnet add package InventoryFramework.GodotAdapter

This pulls in InventoryFramework.SDK as a transitive dependency. You do not need to copy any DLLs manually.


Autoload Setup

Create an autoload node InventoryManager.cs:

using Godot;
using InventoryFramework.GodotAdapter.Models;
using InventoryFramework.GodotAdapter.Services;
using System.Threading.Tasks;

public partial class InventoryManager : Node
{
    public GodotInventoryFacade Facade { get; private set; }
    public GodotInventorySnapshot Snapshot { get; private set; }

    public override async void _Ready()
    {
        var config = new GodotInventoryConfiguration
        {
            ServerAddress = "https://localhost:7289",
            ActorId       = OS.GetUniqueId(),
            ApiKey        = "sk-game-your-key"
        };

        Facade = new GodotInventoryFacade(config);

        var created = await Facade.CreateDefaultInventoryAsync();
        if (!created)
        {
            GD.PrintErr("Failed to create inventory.");
            return;
        }

        Snapshot = await Facade.RefreshAsync();
        GD.Print($"Inventory loaded. Aggregate ID: {Facade.CurrentInventoryAggregateId}");
    }

    protected override void Dispose(bool disposing)
    {
        Facade?.Dispose();
        base.Dispose(disposing);
    }
}

Register InventoryManager as an autoload in Project > Project Settings > Autoload.


Accessing from Other Nodes

var manager = GetNode<InventoryManager>("/root/InventoryManager");
var snapshot = await manager.Facade.RefreshAsync();

Common Operations

Grant items

var result = await manager.Facade.GrantItemsAsync(
    targetContainerId: "source-container-id",
    itemDefinitionId: "wood",
    quantity: 10);

if (!result.Succeeded)
    GD.PrintErr($"Grant failed: {result.ErrorMessage}");

Transfer between containers

await manager.Facade.TransferItemsAsync(
    sourceContainerId: "src-id",
    sourceSlotIndex: 0,
    targetContainerId: "dst-id",
    quantity: 5);

Quick-store

await manager.Facade.QuickStoreToTargetContainerAsync(
    sourceContainerId: "src-id",
    targetContainerId: "dst-id",
    options: new GodotQuickStoreOptions
    {
        OnlyMatchExistingStacks = false,
        AllowCreatingNewStacks  = true
    });

Lock / unlock a slot

await manager.Facade.LockSlotAsync("src-id", slotIndex: 2, lockSlot: true);
// Slot 2 is now skipped by QuickStore and SortContainer

// Check lock state from snapshot
bool isLocked = snapshot.Containers[0].Slots[2].IsLocked;

Split a stack

var result = await manager.Facade.SplitStackAsync(
    containerId: "src-id",
    sourceSlotIndex: 0,
    amount: 5);

GD.Print($"Split landed in slot {result.DestinationSlotIndex}");

Drop items

var result = await manager.Facade.DropItemsAsync(
    containerId: "src-id",
    slotIndex: 0,
    amount: 3);

if (result.Succeeded)
    SpawnPickupNode(result.DroppedItemDefinitionId, result.DroppedQuantity);

Sort container

// sortMode: 0 = ByNameAscending, 1 = ByWeightDescending, 2 = ByTagThenName
await manager.Facade.SortContainerAsync("src-id", sortMode: 0);

Craft preview + craft

var preview = await manager.Facade.PreviewCraftItemsAsync(
    recipeId: "plank_recipe",
    sourceContainerId: "src-id",
    targetContainerId: "dst-id",
    requestedCraftCount: 2,
    requiredStation: "workbench");

GD.Print($"Max craftable: {preview.MaximumCraftableCount}");

if (preview.CanCraftRequestedCount)
{
    await manager.Facade.CraftItemsAsync(
        recipeId: "plank_recipe",
        sourceContainerId: "src-id",
        targetContainerId: "dst-id",
        craftCount: 2,
        allowPartial: false,
        requiredStation: "workbench");
}

Displaying Inventory in UI

var snapshot = await manager.Facade.RefreshAsync();

foreach (var container in snapshot.Containers)
{
    GD.Print($"Container: {container.ContainerId}");

    foreach (var slot in container.Slots)
    {
        if (slot.IsEmpty) continue;

        var durabilityText = slot.CurrentDurability.HasValue
            ? $" (dur: {slot.CurrentDurability.Value:F0})"
            : string.Empty;

        GD.Print($"  Slot {slot.Index}: {slot.ItemDefinitionId} x{slot.Quantity}{durabilityText}");
    }

    if (container.WeightCapacity.HasValue)
        GD.Print($"  Weight: {container.CurrentWeight:F1} / {container.WeightCapacity.Value:F1}");
}

Recipe Browsing

// All recipes in a category
var browse = await manager.Facade.BrowseRecipesAsync(
    craftingCategory: "woodworking",
    requiredStation: "workbench");

foreach (var recipe in browse.Recipes)
    GD.Print($"  {recipe.Id}: {recipe.DisplayName}");

// Available recipes (filtered by what the player can currently craft)
var available = await manager.Facade.GetAvailableRecipesAsync(
    sourceContainerId: "src-id",
    targetContainerId: "dst-id",
    requestedCraftCount: 1,
    craftingCategory: "woodworking",
    requiredStation: "workbench");

foreach (var r in available.Recipes)
    GD.Print($"  {r.DisplayName}: can craft = {r.CanCraftRequestedCount}");

Player Progression

// Unlock a recipe tier (admin operation)
await manager.Facade.UnlockRecipeKeyAsync(
    targetActorId: "player-456",
    unlockKey: "tier2_crafting");

// Read progression
var progression = await manager.Facade.GetPlayerProgressionAsync("player-456");
foreach (var key in progression.UnlockedKeys)
    GD.Print($"Unlocked: {key.UnlockKey} at {key.UnlockedAtUtc}");

Error Codes

Code Meaning
ITEM_NOT_FOUND itemDefinitionId does not exist in the item registry
CONTAINER_NOT_FOUND Container ID is not part of this inventory
INSUFFICIENT_ITEMS Not enough items in source slot
CONTAINER_FULL Target container has no available capacity
WEIGHT_LIMIT_EXCEEDED Adding items would exceed the container weight limit
RECIPE_NOT_FOUND recipeId does not exist
MISSING_INGREDIENTS Not enough ingredients to craft
RECIPE_LOCKED Actor has not unlocked the required recipe key
UNAUTHORIZED API key is missing or invalid