Custom Crafting Processors

The crafting system comes with two crafting processors out of the box: Simple Crafting Processor and Simple Crafting Processor with Currency. These two processors will cover many use cases but can be expanded upon to fit any use case. The examples below explain how the system can be extended.

Crafting level constraint

In some games, recipes can only be crafted if the character has a certain level. This can be implemented in one of two different ways:

  1. Split the recipes by crafting categories and map the character level to crafting categories using a custom component, Scriptable Object or directly on the crafting processor.
  2. Create a custom Crafting Recipe type and add a field for a character level.

Both of these options can be implemented by creating a new processor and overriding the Can Craft function.

public class CustomCraftingProcessor : SimpleCraftingProcessor
{
    /// <summary>
    /// Check if the parameters are valid to craft an item.
    /// </summary>
    /// <param name="recipe">The recipe.</param>
    /// <param name="inventory">The inventory containing the items.</param>
    /// <param name="quantity">The quantity to craft.</param>
    /// <param name="selectedIngredients">The item infos selected.</param>
    /// <returns>True if you can craft.</returns>
    protected override bool CanCraftInternal(CraftingRecipe recipe, IInventory inventory, int quantity, ListSlice<ItemInfo> selectedIngredients)
    {
        // Get the character level from your custom character script. 
        var myCharacterLevel = inventory.gameObject.GetComponent<MyCharacter>().level;
        
        // Find the level required by the recipe.
        var levelRequired = 0;
        
        // Option 1: create the GetLevelFor function that return the level required for crafting recipes in that category.
        levelRequired = GetLevelFor(recipe.Category);
        
        // Option 2: create a custom recipe with a level field and use that field as the constraint.
        if (recipe is MyCustomRecipe customRecipe) {
            levelRequired = customRecipe.level;
        }

        if (myCharacterLevel < levelRequired) { return false; }

        return base.CanCraftInternal(recipe, inventory, quantity, selectedIngredients);
    }
}

Change processor output with external sources

In some games the crafting result can change depending on external sources, such as a mini game score, the player stats or the items quantity/quality. This can be implemented with one of the following methods:

  1. Use the recipe item amounts output to make a list of different possible result. This method is limited as you would only be able to return a single output per craft.
  2. Make the crafted Item mutable such that its attribute values can be changed when it is crafted.
  3. Create a custom recipe type with multiple output possibilities.

These options can be implemented by creating a new processor and overriding the Craft Internal method:

public class CustomCraftingProcessor : SimpleCraftingProcessor
{
    // Your custom code that returns a crafting quality. Examples include a character or a mini game.
    [SerializeField] protected MyCraftringQualityProvider m_CraftingQualityProvider;

    // Optional value for testing.
    [SerializeField] protected int m_Option;
    
    /// <summary>
    /// Craft the items.
    /// </summary>
    /// <param name="recipe">The recipe.</param>
    /// <param name="inventory">The inventory containing the items.</param>
    /// <param name="quantity">The quantity to craft.</param>
    /// <param name="selectedIngredients">The item infos selected.</param>
    /// <returns>True if the character can craft.</returns>
    protected override CraftingResult CraftInternal(CraftingRecipe recipe, IInventory inventory, int quantity,
        ListSlice<ItemInfo> selectedIngredients)
    {
        // Check that the recipe can be crafted.
        if (CanCraftInternal(recipe, inventory, quantity, selectedIngredients) == false) {
            return new CraftingResult(null, false);
        }

        // Remove all the ingredient items from the inventory.
        if (RemoveIngredients(inventory, selectedIngredients) == false) {
            return new CraftingResult(null, false);
        }
        
        // Get the crafting quality from an external source.
        var craftingQuality = m_CraftingQualityProvider.CraftingQuality;

        // Get the result of the craft.
        ItemAmount[] resultItemAmounts;

        // Option 1 : choose one of the outputs in the list of output. This solution is limited to one output.
        if (m_Option == 1) {
            resultItemAmounts = new ItemAmount[1];

            var selectedIndex = 0;
            if (recipe.DefaultOutput.ItemAmounts.Count < craftingQuality) {
                selectedIndex = recipe.DefaultOutput.ItemAmounts.Count - 1;
            }
        
            var itemAmount = recipe.DefaultOutput.ItemAmounts[selectedIndex];
            var craftedItemAmount = new ItemAmount(InventorySystemManager.CreateItem(itemAmount.Item), itemAmount.Amount * quantity);
            resultItemAmounts[0] = craftedItemAmount;
        }

        // Option 2 : Set the quality of the mutable item directly by changing the attribute value.
        else if (m_Option == 2) {
            resultItemAmounts = new ItemAmount[recipe.DefaultOutput.ItemAmounts.Count];
            for (int i = 0; i < resultItemAmounts.Length; i++) {
                var itemAmount = recipe.DefaultOutput.ItemAmounts[i];
                var craftedItem = InventorySystemManager.CreateItem(itemAmount.Item);

                // The attribute can be any type and does not need to be an integer.
                var qualityAttribute = craftedItem.GetAttribute<Attribute<int>>("Quality");
                if (qualityAttribute != null) {
                    qualityAttribute.SetOverrideValue(craftingQuality);
                }
                
                var craftedItemAmount = new ItemAmount(craftedItem, itemAmount.Amount * quantity);
                resultItemAmounts[i] = craftedItemAmount;
            }
        } 
        
        // Option 3 : create a custom recipe type with multiple crafting output possibilities.
        else {
            if (!(recipe is MyCustomQualityRecipe qualityRecipe)) { return new CraftingResult(null, false); }

            ItemAmounts itemOutputAmounts = qualityRecipe.GetOutputForQuality(craftingQuality);

            resultItemAmounts = new ItemAmount[itemOutputAmounts.Count];
            
            for (int i = 0; i < resultItemAmounts.Length; i++) {
                var itemAmount = itemOutputAmounts[i];
                var craftedItem = InventorySystemManager.CreateItem(itemAmount.Item);
                var craftedItemAmount = new ItemAmount(craftedItem, itemAmount.Amount * quantity);
                resultItemAmounts[i] = craftedItemAmount;
            }
        }

        // Add the crafted items in the inventory.
        for (int i = 0; i < resultItemAmounts.Length; i++) {
            inventory.AddItem((ItemInfo)resultItemAmounts[i]);
        }
        
        // Convert the crafted items into an crafting output.
        var output = new CraftingOutput(resultItemAmounts);

        // Return a success crafting result.
        return new CraftingResult(output, true);
    }
}