tModLoader Tutorial: [2] Recipes

Jofairden

Duke Fishron
tModLoader
Recipes
Last update: 9th of June, 2016
kdcROYP.png


Table of contents:

1. Prerequisites
2. Introduction
3. How do recipes work?
4. Adding your own recipes
5. Working with vanilla recipes
6. Other places

kdcROYP.png

Prerequisites
Make sure you have....
  • ...the latest tModLoader installed (offical thread)
  • ...the latest Microsoft .NET Framework installed (offical .net site)
  • ...the Microsoft XNA Framework installed (you have this if you are able to play Terraria)
  • ...Microsoft Visual Studio (community = free) installed with the C# workspace (official MVS site)
  • .. actually, MVS isn't required but it's a very nice IDE to work with. You can also use notepad or notepadd++ or alike, but MVS will be super useful for noobs that makes simple mistakes because the IDE will help you program
  • ...C# knowledge, all tML mods currently are programmed with the C# language
  • ...followed the tutorial prior to this one http://forums.terraria.org/index.php?threads/tutorial-1-your-first-mod.44817
kdcROYP.png


Introduction
In this tutorial we're going to look at recipes in-depth!
Recipes are by far the easiest to start with, and they will not require you to add anything to your source files for now, apart from some code in your main .cs file (the one that derives from 'Mod')


Before we begin, add the following line to the top of your file:
Code:
using Terraria.ID;
kdcROYP.png


How do recipes work?
Recipes consist of several basic parts: required ingredients, required tiles to be around, and the result.
We're going to start with the easiest possible: a recipe that allows the player to craft a gold coin out of a dirt block. It's midas magic!
First of all, for this tutorial everything will be done in MyFirstMod.cs , the class that derives from 'Mod'. This is your main mod file.
You'll need to use the following hook to work with recipes:
Code:
        public override void AddRecipes()
        {

        }
You can add this hook below public MyFirstMod()

Initialising a new ModRecipe

To create a new ModRecipe (ModRecipe == a custom recipe), you need to initialise it as follows:
Code:
ModRecipe recipe = new ModRecipe(this);
This creates a new recipe we can now work with, using the recipe variable.
It creates a new ModRecipe, a class from tML. We need to pass "this" which refers to our file, which is our mod file (it derives from 'Mod') since the ModRecipe needs to know which mod is adding this new recipe. In various other places, a 'mod' variable has been added to use where you can not pass "this".

Adding an ingredient
To add our dirt block ingredient to our recipe, we need to use recipe.addIngredient like as follows:
Code:
recipe.AddIngredient(ItemID.DirtBlock);
This method uses the following parameters: the first one is the ID of the ingredient, the second one is the required amount.
The default amount is set to 1, so we don't need to do anything with this parameter for our recipe.

Adding the ingredient could also be done like the following:
Code:
recipe.AddIngredient("Dirt Block");

Adding ModItems as ingredients
The easiest (coolest :D) way to do this is as follows:
Code:
recipe.AddIngredient(this.GetItem("MyFirstItem"));
In our current case, we could omit 'this' as we are in our mod file. In other situations, such as being in a ModItem class, you'll most likely need to use mod to access GetItem() and functions alike. An alternative would be as follows:
Code:
recipe.AddIngredient(this, "MyFirstItem");
The difference is that in the first example, we use a function to get a ModItem class of "MyFirstItem" and pass that. In the second example we pass our mod and the item's name seperately. (so not as a ModItem) Both ways are correct, you should use whichever you prefer

Adding a required tile
We only want this recipe to work while near any workbench, because workbenches are baws.
We can do this as follows:
Code:
recipe.AddTile(TileID.WorkBenches);
This follows the same principle as adding an ingredient, only now we need to pass the ID of the tile, in this case any workbench.
There's no second paremeter in this case.

Adding ModTiles as required tile
The same principle of adding a modded item applies here. Here you should use GetTile() instead of GetItem(). Again, both ways of doing are correct.

Setting the result
Again, setting our result of the recipe follows the same principle as adding an ingredient.
We can do it as follows:
Code:
recipe.SetResult(ItemID.GoldCoin);
The first parameter is the ID of the item of our result, and the second parameter is the result amount which defaults to 1. Again, we do not need to pass to the second parameter. Just like for required tiles and ingredients, you can pass ModItem here the same way as explained above.

Adding the final recipe
To add your recipe to the game, all you need to run is the following:
Code:
recipe.AddRecipe();

Your total code should now look like this:
Code:
        public override void AddRecipes()
        {
            ModRecipe recipe = new ModRecipe(this);
            recipe.AddIngredient(ItemID.DirtBlock);
            recipe.AddTile(TileID.WorkBenches);
            recipe.SetResult(ItemID.GoldCoin);
            recipe.AddRecipe();
        }

Everytime you create a new recipe...
... you will need to initialise it as a new ModRecipe. You can use the same variable be used before, as follows:
Code:
            recipe = new ModRecipe(this);
            recipe.AddIngredient(ItemID.DirtBlock, 10);
            recipe.AddTile(TileID.WorkBenches);
            recipe.SetResult(ItemID.GoldCoin, 10);
            recipe.AddRecipe();
This is no different than the code we created before, only this one requires 10 dirt to create 10 gold.

Extras
recipe.anyFragment
recipe.anyIronBar
recipe.anyPressurePlate
recipe.anySand
recipe.anyWood


These booleans can be set to true to allow any of their variants to count as a proper ingredient for the recipe.
The following example uses wood instead of dirt to create gold, any wood will work for this recipe:
Code:
            recipe = new ModRecipe(this);
            recipe.AddIngredient(ItemID.Wood);
            recipe.anyWood = true;
            recipe.AddTile(TileID.WorkBenches);
            recipe.SetResult(ItemID.GoldCoin);
            recipe.AddRecipe();
The same principle can be applied for the other variants.
You must still add the wood ingredient to have it consume it and work properly.

recipe.needWater
recipe.needHoney
recipe.needLava


These booleans can be set to true to require their specific type of liquid.
The best example is the recipe for bottled water, you need a bottle as well as water.
For that recipe, needWater would be true.

The following example shows a recipe that allows you to convert your gold coin back to wood while near water, magically!
Code:
            recipe = new ModRecipe(this);
            recipe.AddIngredient(ItemID.GoldCoin);
            recipe.needWater = true;
            recipe.SetResult(ItemID.Wood);
            recipe.AddRecipe();


kdcROYP.png


Working with vanilla recipes

Before we begin, add the following to the top of your file:
Code:
using System;
using System.Collections.Generic;
using System.Linq;
using Terraria;

Removing vanilla recipes
Beware, the following example is taken from the ExampleMod which may be confusing for new modders. It's decently advanced, especially compared to our basic code for creating a recipe. Feel free to skip this part if it's confusing.

Code:
            // Removing Recipes.
            List<Recipe> rec = Main.recipe.ToList();
            int numberRecipesRemoved = 0;
            // The Recipes to remove.
            numberRecipesRemoved += rec.RemoveAll(x => x.createItem.type == ItemID.AlphabetStatueT);
            Main.recipe = rec.ToArray();
            Array.Resize(ref Main.recipe, Recipe.maxRecipes);
            Recipe.numRecipes -= numberRecipesRemoved;

Brief explanation
The first line transforms the main recipe array to a list, which is more 'dynamic' than an array, as an array has a fixed size whereas a list does not. The second line creates a variable that will hold the total amount of removed recipes that will then later be deducted from the total amount of recipes. The third line adds one to this variable per recipe that creates the specified item. The fourth line sets Main.recipe to our altered (converted) recipe list which is recreated to an array. The fifth line resizes the array, as I said: arrays a fixed size. The sixth line deducts the amount of removed recipes from the total amount of available recipes.

The important part:
Code:
numberRecipesRemoved += rec.RemoveAll(x => x.createItem.type == ItemID.AlphabetStatueT);
Especially what comes after += matters, this is the code that removes all recipes that CREATE the Alphabet Statue letter T from the recipe list.

Altering existing vanilla recipes
If you understand the above code, we can also use that way of programming to change existing recipes.
The following snippet doesn't really 'remove' the recipe, it instead changes the alphabet T statue recipe to require 10 wood and instead create 10 clentaminator. It basically became a new recipe. If you understand the above code, you should understand the following as well.
Code:
            // Removing Recipes.
            List<Recipe> rec = Main.recipe.ToList();
            int numberRecipesRemoved = 0;
            // The Recipes to remove.
            numberRecipesRemoved += rec.RemoveAll(x => x.createItem.type == ItemID.AlphabetStatueU);
            //Change AlphabetStatueT's recipe to create 10 clentaminator from 10 wood
            rec.Where(x => x.createItem.type == ItemID.AlphabetStatueT).ToList().ForEach(s =>
            {
                for (int i = 0; i < s.requiredItem.Length; i++)
                {
                    s.requiredItem[i] = new Item();
                }
                s.requiredItem[0].SetDefaults(ItemID.Wood, false);
                s.requiredItem[0].stack = 10;

                s.createItem.SetDefaults(ItemID.Clentaminator, false);
                s.createItem.stack = 10;
            });
            Main.recipe = rec.ToArray();
            Array.Resize(ref Main.recipe, Recipe.maxRecipes);
            Recipe.numRecipes -= numberRecipesRemoved;
The following code is our removal code and the altering code combined. (it now removes the alphabet U statue recipe)
Code:
rec.Where(x => x.createItem.type == ItemID.AlphabetStatueT).ToList().ForEach(s =>
This line will run through our Main.recipe list, and get all elements where the result is the statue T, then creates a list out of those elements and does the code in brackets "ForEach" element. This is Linq for ya. Let's take a closer look at what's actually happening.
Code:
                for (int i = 0; i < s.requiredItem.Length; i++)
                {
                    s.requiredItem[i] = new Item();
                }
                s.requiredItem[0].SetDefaults(ItemID.Wood, false);
                s.requiredItem[0].stack = 10;

                s.createItem.SetDefaults(ItemID.Clentaminator, false);
                s.createItem.stack = 10;
The first for loop is very important. When you go into src, you'll find that every recipe can have a maximum of 15 ingredients (Recipe.maxRequirements)
Non added ingredients are actually empty items!
a3ea695841884a089341e58f1e1f6bb3.png
Running this for loop, we are resetting each original ingredient to become an empty item (basically, nothing)
Code:
                s.requiredItem[0].SetDefaults(ItemID.Wood, false);
                s.requiredItem[0].stack = 10;
Here we set our first ingredient to be wood, and having it require 10. (the amount of ingredients required is held in the stack value)

Code:
                s.createItem.SetDefaults(ItemID.Clentaminator, false);
                s.createItem.stack = 10;
Here we reset the recipe to create a total of 10 Clentaminators, the same principle as changing the ingredient(s).

Fully resetting a vanilla recipe
I've went again and wrote the following function you can start using right off the bat. This should recompletely reset a recipe's settings. I hope it ins't too hard to understand. All booleans are false by default. As explained before, every empty ingredient slot is just an empty item. The same somewhat counts for tiles, the difference here is that the array stores only the ID and not the Tile itself, so we need to set all of them to -1 which equals an empty slot. The acceptedGroups is a list that should contain all recipe groups accepted by the recipe, which we are clearing. Finally we are setting our result to be an empty item. You can for example use this function to reset a particular recipe you've filtered, and then add certain settings back to your likings.
Code:
        public static void ResetRecipe(Recipe s)
        {
            for (int i = 0; i < Recipe.maxRequirements; i++)
            {
                s.requiredItem[i] = new Item();
            }
            for (int i = 0; i < Recipe.maxRequirements; i++)
            {
                s.requiredTile[i] = -1;
            }
            s.anyFragment = false;
            s.anyIronBar = false;
            s.anyPressurePlate = false;
            s.anySand = false;
            s.anyWood = false;
            s.needHoney = false;
            s.needLava = false;
            s.needWater = false;
            s.alchemy = false;
            s.acceptedGroups.Clear();
            s.createItem = new Item();
        }


More filtering options
Of course, there are a lot more ways for you to filter the recipes that you want to be removed. The following code will add to numberRecipesRemoved and removes all recipes that have a wood ingredient with a requirement of 10 or higher. This for example removes the recipe for Work Bench.
Code:
numberRecipesRemoved += rec.RemoveAll(x => x.requiredItem.Any(i => i.type == ItemID.Wood && i.stack >= 10));

Transforming ingredients
You can use the examples I've given you so far to transform ingredients in recipes. For example: I want every recipe that uses Stone Block instead to use Gold Coin, so long as that item isn't already in the recipe.
Code:
            // Filter our recipe list for recipes that use StoneBlock as an ingredient and does not have GoldCoin as ingredient
            // Then proceed to change the Stone Block ingredient to gold coin.
            rec.Where(x => x.requiredItem.Any(i => i.type == ItemID.StoneBlock)).Select((a, b) => new { b, a }).Where(x => !x.a.requiredItem.Any(item => item.type == ItemID.GoldCoin)).ToList().ForEach(s =>
            {
                s.a.requiredItem.Where(x => x.type == ItemID.StoneBlock).Select((a,b) => new { a, b }).ToList().ForEach(y =>
                {
                    var refStack = (object)y.a.stack;
                    y.a.SetDefaults(ItemID.GoldCoin, false);
                    y.a.stack = (int)refStack;
                });
            });

This code may seem somewhat complicated, if you have questions feel free to ask.

kdcROYP.png


Other places

Your main.cs file is not the only place you can add recipes. For example: there are AddRecipes() hooks in ModItems. This allows you to add the recipes for the specific items in their own file. More examples on this later.

kdcROYP.png


Final code

Code:
using System;
using System.Collections.Generic;
using System.Linq;
using Terraria;
using Terraria.ID;
using Terraria.ModLoader;

namespace MyFirstMod
{
    class MyFirstMod : Mod
    {
        public MyFirstMod()
        {
            // Make sure Autoload is true, it saves a lot of code in the Load() hook
            Properties = new ModProperties()
            {
                Autoload = true,
                AutoloadGores = true,
                AutoloadSounds = true
            };
        }

        public override void AddRecipes()
        {
            // Our first recipe, transforming a dirtblock to a gold coin while near any workbench.
            ModRecipe recipe = new ModRecipe(this);
            recipe.AddIngredient(ItemID.Wood);
            recipe.AddTile(TileID.WorkBenches);
            recipe.SetResult(ItemID.GoldCoin);
            recipe.AddRecipe();

            // Our second recipe, the same as above, but this time 10 times as fast
            recipe = new ModRecipe(this);
            recipe.AddIngredient(ItemID.DirtBlock, 10);
            recipe.AddTile(TileID.WorkBenches);
            recipe.SetResult(ItemID.GoldCoin, 10);
            recipe.AddRecipe();

            // Removing Recipes.
            List<Recipe> rec = Main.recipe.ToList();
            int numberRecipesRemoved = 0;
            // The Recipes to remove.
            numberRecipesRemoved += rec.RemoveAll(x => x.createItem.type == ItemID.AlphabetStatueU); // Remove AlphabetStatueU
            //Change AlphabetStatueT's recipe to create 10 clentaminator from 10 wood
            rec.Where(x => x.createItem.type == ItemID.AlphabetStatueT).ToList().ForEach(s =>
            {
                // Loop the ingredients and reset them
                for (int i = 0; i < s.requiredItem.Length; i++)
                {
                    s.requiredItem[i] = new Item();
                }
                // Change the first ingredient to wood and require 10
                s.requiredItem[0].SetDefaults(ItemID.Wood, false);
                s.requiredItem[0].stack = 10;

                // Set the result as Clentaminator and aquire 10
                s.createItem.SetDefaults(ItemID.Clentaminator, false);
                s.createItem.stack = 10;

                //alternatively, we could use our ResetRecipe() function
            });

            // Filter our recipe list for recipes that use StoneBlock as an ingredient and does not have GoldCoin as ingredient
            // Then proceed to change the Stone Block ingredient to gold coin.
            rec.Where(x => x.requiredItem.Any(i => i.type == ItemID.StoneBlock)).Select((a, b) => new { b, a }).Where(x => !x.a.requiredItem.Any(item => item.type == ItemID.GoldCoin)).ToList().ForEach(s =>
            {
                s.a.requiredItem.Where(x => x.type == ItemID.StoneBlock).Select((a,b) => new { a, b }).ToList().ForEach(y =>
                {
                    var refStack = (object)y.a.stack;
                    y.a.SetDefaults(ItemID.GoldCoin, false);
                    y.a.stack = (int)refStack;
                });
            });

            // Remove all recipes that use Wood as an ingredient and require 10 or more.
            numberRecipesRemoved += rec.RemoveAll(x => x.requiredItem.Any(i => i.type == ItemID.Wood && i.stack >= 10));

            Main.recipe = rec.ToArray(); // recreate array
            Array.Resize(ref Main.recipe, Recipe.maxRecipes); // resize the array
            Recipe.numRecipes -= numberRecipesRemoved; // remove amount of removed recipes

            // Convert any wood at a workbench for a gold coin!
            recipe = new ModRecipe(this);
            recipe.AddIngredient(ItemID.Wood);
            recipe.anyWood = true;
            recipe.AddTile(TileID.WorkBenches);
            recipe.SetResult(ItemID.GoldCoin);
            recipe.AddRecipe();

            // While near water, you can magically convert gold coins back to wood!
            recipe = new ModRecipe(this);
            recipe.AddIngredient(ItemID.GoldCoin);
            recipe.needWater = true;
            recipe.SetResult(ItemID.Wood);
            recipe.AddRecipe();

            recipe = new ModRecipe(this);
            recipe.AddIngredient(this, "MyFirstItem");
            recipe.needWater = true;
            recipe.SetResult(ItemID.Wood);
            recipe.AddRecipe();
        }

        // Fully reset a recipe.
        public static void ResetRecipe(Recipe s)
        {
            for (int i = 0; i < Recipe.maxRequirements; i++)
            {
                s.requiredItem[i] = new Item();
            }
            for (int i = 0; i < Recipe.maxRequirements; i++)
            {
                s.requiredTile[i] = -1;
            }
            s.anyFragment = false;
            s.anyIronBar = false;
            s.anyPressurePlate = false;
            s.anySand = false;
            s.anyWood = false;
            s.needHoney = false;
            s.needLava = false;
            s.needWater = false;
            s.alchemy = false;
            s.acceptedGroups.Clear();
            s.createItem = new Item();
        }

        // Log stuff for testing
        public static void Log(Array array)
        {
            foreach (var item in array)
            {
                ErrorLogger.Log(item.ToString());
            }
        }
    }
}



Go to next tutorial: Tutorial [3] Items
 
Last edited:
How to make it so that it uses either gold or platinum?
Code:
recipe.anyGoldBar = true;
doesnt work
and also how to use either evil bar
 
What if I wanted to make an item from my mod craftable with an item from another mod?
You need to get the mod instance and then call ItemType("Internal name") although I believe the latest updates may have introduced an easier way to get the ID, but I haven't fiddled around with it yet.
 
You need to get the mod instance and then call ItemType("Internal name") although I believe the latest updates may have introduced an easier way to get the ID, but I haven't fiddled around with it yet.

Yeah I kinda already figured that out thanks to fargowilta sharing his mod's source code with me.
Thanks anyway!
 
recipe.AddIngredient(this.GetItem("MyFirstItem"));

What if I need to get a mod item to use in a recipe that's in another folder, considering "this.GetItem (etc...)" would only try to find the item from this folder you're working in.

I've tried "((Items.Crafting).GetItem("CatFragment")"
 
What if I need to get a mod item to use in a recipe that's in another folder, considering "this.GetItem (etc...)" would only try to find the item from this folder you're working in.

I've tried "((Items.Crafting).GetItem("CatFragment")"
It goes by class name, not namespace. You can just do mod.GetItem("name") or mod.ItemType("name")
 
I typed the sample code in.

Code:
public override void AddRecipes()
        {
            ModRecipe recipe = new ModRecipe(this);
            recipe.AddIngredient(ItemID.DirtBlock);
            recipe.AddTile(TileID.WorkBenches);
            recipe.SetResult(ItemID.GoldCoin);
            recipe.AddRecipe();
        }

And I get these errors.

Code:
Random.cs(1,18) : error CS1518: Expected class, delegate, enum, interface, or struct

Random.cs(3,36) : error CS1518: Expected class, delegate, enum, interface, or struct

Random.cs(8,9) error CS1022: Type or namespace definition, or end-of-file expected

What am I doing wrong?
 
i dont know where to put the first code it just says add the following line to the top of your file but the thing is theres like 20 files already lol is it the MyFirstMod.cs????
[doublepost=1486053360,1486053091][/doublepost]ive havent yet been able to find a software that i can open .cs files with
 
Back
Top Bottom