Economy

Crafting

Validated recipe execution (opaque in/out, all-or-nothing) over Inventory; catalog is a game SPI.

Server
services.AddCrossplayCrafting();
Unity package
com.crossplay.crafting
Depends on
container

Seams you implement 2

Crossplay calls these; your game supplies them. Each ships an inert or permissive default, so register yours before services.AddCrossplayCrafting(); and it wins.

ICraftingCatalog game SPI

SPI the game implements to supply its recipe book — the piece is inert (every craft returns UnknownRecipe) until a non-default catalog is registered. Register your implementation BEFORE AddCrossplayCrafting (a TryAdd seam) so it wins over the empty default. The framework calls TryGet once per craft request to resolve the recipe; keep it allocation-free and fast (it runs on the poll thread) — typically a lookup in a prebuilt dictionary of your game's recipes.

sealed class GameRecipes : ICraftingCatalog
{
    readonly Dictionary<ushort, CraftingRecipe> _byId = Build();
    public bool TryGet(ushort recipeId, out CraftingRecipe recipe) => _byId.TryGetValue(recipeId, out recipe!);
}
// services.AddSingleton<ICraftingCatalog, GameRecipes>(); // BEFORE AddCrossplayCrafting()
  • bool TryGet(ushort recipeId, out CraftingRecipe recipe)

    Resolves a recipe by its game-defined id.

ICraftingRules game SPI

Optional SPI the game implements to gate crafts beyond "holds the ingredients" — skill/level checks, a required crafting station in range, per-recipe cooldowns, quest prerequisites, and the rest of the game's vocabulary. Default: allow every craft whose ingredients are present. Register your own BEFORE AddCrossplayCrafting (a TryAdd seam). Called AFTER the recipe resolved and BEFORE any ingredient is touched, on the poll thread.

sealed class StationRules : ICraftingRules
{
    public bool CanCraft(ISession session, long characterId, CraftingRecipe recipe)
        => _stations.IsNearForge(characterId); // false => CraftRefused
}
  • bool CanCraft(ISession session, long characterId, CraftingRecipe recipe)

    Decide whether this character may craft this recipe right now.

Services you call 1

Crossplay implements these. Resolve them from DI and call them from your own systems.

ICraftingService

Validated recipe execution over an item container (all-or-nothing) — the server-side seam a game calls to craft on a player's behalf (the wire handler calls it too). Resolves the recipe via ICraftingCatalog, gates it via ICraftingRules, then atomically consumes every ingredient and grants the output through the generic container engine. If the output can't be delivered (bag full) the consumed inputs are returned — nothing is ever lost or duplicated.

  • bool TryCraft(ISession session, ushort recipeId, out ushort reason)

    Crafts ONE instance of a recipe for the session's selected character (all-or-nothing).

Wire messages 2

The protocol this piece speaks. Ids are allocated per piece so they can never collide.

3701 CraftRequest

Client → server: craft ONE instance of a game-defined recipe.

3702 CraftResponse

Server → client: the craft's outcome (reason codes are notification codes).

Unity services you inject 1

Crossplay binds these in the client context. Inject and call them from your own MonoBehaviours and presenters.

ICraftingClient

The Crafting piece, client side: ask the server to craft a game-defined recipe. What a recipe id means — and what the ingredients/output look like — is entirely the game's.

  • UniTask<CraftResponse> CraftAsync(ushort recipeId, CancellationToken ct = default)

    Crafts one instance of a recipe (reason codes are notification codes).

UI views you can replace 1

Each ships a working uGUI panel you can drag into a scene. Substitute your own view to keep the logic and change the look — the presenter never knows.

ICrossplayCraftingView

Narrow contract the presenter drives. UI-only: render calls in, user intents out.