Skip to content

Crafting System

The Crafting system in Evoker Engine provides a flexible recipe system supporting multiple recipe types including shapeless crafting, shaped crafting (grid-based), smelting, and custom recipe types.

Overview

The crafting system includes:

  • Recipes: Define how items are created from ingredients
  • Recipe Types: Shapeless, Shaped, Smelting, and custom types
  • Recipe Registry: Central management for all recipes
  • Ingredients: Required items with quantities
  • Results: Output items with quantities

Recipe Types

ShapelessRecipe

Order-independent crafting (ingredients can be in any arrangement).

var mushroomStew = new ShapelessRecipe
{
    Id = new ResourceKey("game", "mushroom_stew"),
    Type = new ResourceKey("evoker", "shapeless_crafting"),
    Name = "Mushroom Stew",
    Ingredients = new List<Ingredient>
    {
        new Ingredient("game:red_mushroom", 1),
        new Ingredient("game:brown_mushroom", 1),
        new Ingredient("game:bowl", 1)
    },
    Result = new RecipeResult("game:mushroom_stew", 1)
};

ShapedRecipe

Pattern-based crafting in a 3x3 grid.

var pickaxe = new ShapedRecipe
{
    Id = new ResourceKey("game", "iron_pickaxe"),
    Type = new ResourceKey("evoker", "shaped_crafting"),
    Name = "Iron Pickaxe",
    Width = 3,
    Height = 3,
    Pattern = new[]
    {
        "III",  // Top row: 3 iron ingots
        " S ",  // Middle row: 1 stick (center)
        " S "   // Bottom row: 1 stick (center)
    },
    IngredientMap = new Dictionary<char, ResourceKey>
    {
        { 'I', new ResourceKey("game", "iron_ingot") },
        { 'S', new ResourceKey("game", "stick") },
        { ' ', ResourceKey.Empty }  // Empty space
    },
    Result = new RecipeResult("game:iron_pickaxe", 1)
};

SmeltingRecipe

Furnace/smelting recipes.

var ironIngot = new SmeltingRecipe
{
    Id = new ResourceKey("game", "iron_ingot_smelting"),
    Type = new ResourceKey("evoker", "smelting"),
    Name = "Iron Ingot",
    Ingredients = new List<Ingredient>
    {
        new Ingredient("game:iron_ore", 1)
    },
    Result = new RecipeResult("game:iron_ingot", 1),
    CookTime = 10f,  // seconds
    Experience = 0.7f
};

Recipe Components

Ingredient

public class Ingredient
{
    public ResourceKey ItemId { get; set; }
    public int Quantity { get; set; } = 1;
    public Dictionary<string, object>? RequiredProperties { get; set; }
}

// Simple ingredient
var ingredient = new Ingredient("game:wood", 1);

// With required properties
var enchantedBook = new Ingredient("game:enchanted_book", 1)
{
    RequiredProperties = new Dictionary<string, object>
    {
        { "enchantment", "sharpness" },
        { "level", 5 }
    }
};

RecipeResult

public class RecipeResult
{
    public ResourceKey ItemId { get; set; }
    public int Quantity { get; set; } = 1;
    public Dictionary<string, object>? Properties { get; set; }
}

// Simple result
var result = new RecipeResult("game:sword", 1);

// With properties
var enchantedSword = new RecipeResult("game:diamond_sword", 1)
{
    Properties = new Dictionary<string, object>
    {
        { "enchantment", "sharpness" },
        { "durability", 100 }
    }
};

Recipe Registry

public class RecipeRegistry
{
    public static RecipeRegistry Instance { get; }

    public void RegisterRecipeType(ResourceKey typeId, Type recipeType);
    public void Register(Recipe recipe);
    public void RegisterAll(params Recipe[] recipes);
    public Recipe? Get(ResourceKey id);
    public Recipe? Get(string id);
    public IEnumerable<Recipe> GetRecipesByType(ResourceKey typeId);
    public IEnumerable<Recipe> GetAll();
    public Recipe? FindMatchingRecipe(List<Ingredient> ingredients, ResourceKey? recipeType = null);
}

Registering Recipes

using EvokerEngine.Crafting;

// Register single recipe
var recipe = new ShapelessRecipe { /* ... */ };
RecipeRegistry.Instance.Register(recipe);

// Register multiple recipes
RecipeRegistry.Instance.RegisterAll(
    mushroomStew,
    ironPickaxe,
    ironIngotSmelting
);

// Get recipe
var recipe = RecipeRegistry.Instance.Get("game:iron_pickaxe");

Custom Recipe Types

Create your own recipe types:

public class BrewingRecipe : Recipe
{
    public ResourceKey PotionBase { get; set; }  // Base potion
    public ResourceKey BrewingIngredient { get; set; }  // Added ingredient
    public float BrewTime { get; set; } = 20f;

    public override bool Matches(List<Ingredient> ingredients)
    {
        // Custom matching logic
        return ingredients.Any(i => i.ItemId == PotionBase) &&
               ingredients.Any(i => i.ItemId == BrewingIngredient);
    }
}

// Register the custom recipe type
RecipeRegistry.Instance.RegisterRecipeType(
    new ResourceKey("game", "brewing"),
    typeof(BrewingRecipe)
);

// Create brewing recipe
var strengthPotion = new BrewingRecipe
{
    Id = new ResourceKey("game", "strength_potion"),
    Type = new ResourceKey("game", "brewing"),
    PotionBase = new ResourceKey("game", "awkward_potion"),
    BrewingIngredient = new ResourceKey("game", "blaze_powder"),
    Result = new RecipeResult("game:strength_potion", 1),
    BrewTime = 20f
};

Complete Examples

Example 1: Crafting Table System

public class CraftingSystem
{
    private List<Ingredient> _craftingGrid = new();

    public bool TryCraft()
    {
        var recipe = RecipeRegistry.Instance.FindMatchingRecipe(
            _craftingGrid,
            new ResourceKey("evoker", "shaped_crafting")
        );

        if (recipe != null)
        {
            // Remove ingredients
            RemoveIngredients(recipe.Ingredients);

            // Give result
            GiveItem(recipe.Result.ItemId, recipe.Result.Quantity);

            Logger.Info($"Crafted: {recipe.Name}");
            return true;
        }

        return false;
    }

    private void RemoveIngredients(List<Ingredient> ingredients)
    {
        foreach (var ingredient in ingredients)
        {
            // Remove from inventory
        }
    }

    private void GiveItem(ResourceKey itemId, int quantity)
    {
        // Add to inventory
    }
}

Example 2: Complete Recipe Set

public static class GameRecipes
{
    public static void RegisterAll()
    {
        // Tools
        RegisterToolRecipes();

        // Weapons
        RegisterWeaponRecipes();

        // Food
        RegisterFoodRecipes();

        // Smelting
        RegisterSmeltingRecipes();
    }

    private static void RegisterToolRecipes()
    {
        // Wooden Pickaxe
        RecipeRegistry.Instance.Register(new ShapedRecipe
        {
            Id = new ResourceKey("game", "wooden_pickaxe"),
            Type = new ResourceKey("evoker", "shaped_crafting"),
            Pattern = new[] { "PPP", " S ", " S " },
            IngredientMap = new Dictionary<char, ResourceKey>
            {
                { 'P', new ResourceKey("game", "planks") },
                { 'S', new ResourceKey("game", "stick") }
            },
            Result = new RecipeResult("game:wooden_pickaxe", 1)
        });

        // Add more tools...
    }

    private static void RegisterFoodRecipes()
    {
        // Bread
        RecipeRegistry.Instance.Register(new ShapelessRecipe
        {
            Id = new ResourceKey("game", "bread"),
            Type = new ResourceKey("evoker", "shapeless_crafting"),
            Ingredients = new List<Ingredient>
            {
                new Ingredient("game:wheat", 3)
            },
            Result = new RecipeResult("game:bread", 1)
        });
    }

    private static void RegisterSmeltingRecipes()
    {
        // Iron Ingot
        RecipeRegistry.Instance.Register(new SmeltingRecipe
        {
            Id = new ResourceKey("game", "iron_ingot"),
            Type = new ResourceKey("evoker", "smelting"),
            Ingredients = new List<Ingredient>
            {
                new Ingredient("game:iron_ore", 1)
            },
            Result = new RecipeResult("game:iron_ingot", 1),
            CookTime = 10f,
            Experience = 0.7f
        });

        // Gold Ingot
        RecipeRegistry.Instance.Register(new SmeltingRecipe
        {
            Id = new ResourceKey("game", "gold_ingot"),
            Type = new ResourceKey("evoker", "smelting"),
            Ingredients = new List<Ingredient>
            {
                new Ingredient("game:gold_ore", 1)
            },
            Result = new RecipeResult("game:gold_ingot", 1),
            CookTime = 10f,
            Experience = 1.0f
        });
    }
}

Best Practices

✅ Do's

  • Use ResourceKey format for all IDs
  • Group related recipes together
  • Set appropriate cook times for smelting
  • Use ShapelessRecipe when order doesn't matter
  • Register all recipes at startup

❌ Don'ts

  • Don't create recipes with impossible ingredient requirements
  • Don't forget to register custom recipe types first
  • Don't duplicate recipe IDs
  • Don't make recipes that create more resources than inputs without balance

See Also

  • Inventory - Item system
  • Blocks - Block system for crafting blocks as ingredients