Tutorial: Adding Recipes with CP (1.6)

From Stardew Modding Wiki
Jump to navigation Jump to search


Introduction

Hello! Rokugin here with another tutorial on how to make some simple stuff with Content Patcher. If you need any help with this or any of the stuff on my Git, feel free to try to reach me on the SDV Discord, there are also a lot of other super helpful and knowledgeable people there that are willing to help.


The focus of this guide will be adding crafting and cooking recipes to the game using Content Patcher with tailoring recipes coming at a later date.

Getting Started

I'll assume you've already installed SMAPI and Content Patcher. Pictures of this process can be found in my first tutorial.


Make sure you have file name extensions enabled.

Windows 10
Open File Explorer, click on the View tab, then check the File Name Extensions box.
Windows 11
Open File Explorer, click on View > Show > File Name Extensions.
Mac
Open Finder, and in the menu bar click on Finder > Settings (or Preferences) > Advanced, then check the Show all filename extensions box.


The first thing you'll want to do is create a new folder to contain your mod, located inside your Mods folder.

Naming convention for the folder is to start it with [CP] to indicate that it's a Content Patcher mod.


Inside the folder, you'll create your manifest.json and content.json.


Manifest

More info about the manifest can be found on the wiki.


Here's my manifest as an example:

{
    "Name": "Recipe Tutorial",
    "Author": "rokugin",
    "Version": "1.0.0",
    "Description": "Tutorial mod for adding recipes to the game.",
    "UniqueID": "rokugin.recipetutorial",
    "UpdateKeys": [],
    "ContentPackFor": {
        "UniqueID": "Pathoschild.ContentPatcher"
    }
}


Name
Can be anything you want.
Author
This is usually you, whatever name you like to use is fine.
Version
What version of your mod this is.
Description
A description of what your mod does or is for.
UniqueID
This is usually Author.NameWithoutSpaces, but just has to be unique from other mods you have installed.
UpdateKeys
Used if/when you upload the mod to places like Nexus to indicate when there's a version change to users.
ContentPackFor
Indicates which mod this is a content pack for, leave this as shown.


Content

The initial content.json:

{
	"Format": "2.0.0",
	"Changes": [
		
	]
}


Format
Indicates which version of Content Patcher is being used, generally it's best to use the latest, the version as of the writing of this tutorial is: 2.0.0.
Changes
Our main block where all of our different patches will be contained.

Crafting Recipes

New Crafting Recipe for an Existing Item

We'll start by making a new recipe for an item that's already in the game.

While playing I noticed that when you upgrade your Chest to a Big Chest, you then have a leftover Chest.

Not a problem if you have somewhere to put it, but instead of having a bunch of regular Chests potentially lying around, I created a recipe that uses the Chest to offset the wood cost of the Big Chests.

{
	"Format": "2.0.0",
	"Changes": [
		{
			"LogName": "Create new big chest crafting recipe",
			"Action": "EditData",
			"Target": "Data/CraftingRecipes",
			"When": {
				"HasCraftingRecipe": "Big Chest"
			},
			"Entries": {
				"{{ModId}}_BigChest": "(BC)130 1 388 70 334 2/Home/BigChest/true/default/"
			}
		}
	]
}


LogName
Shows up in SMAPI's console if there's a problem with this patch block, making it easier to locate when a problem arises.
This should be unique per block in order to make troubleshooting easier.
Action
Indicates what kind of patch we're attempting to perform, in this case we're doing EditData.
Target
The asset we're attempting to patch, for crafting recipes that's Data/CraftingRecipes.
When
Sets a condition for the patch to be performed.
HasCraftingRecipe
The condition we're looking for, because the Big Chest recipe is sold by Robin, we only want this recipe to appear after buying that recipe.
Entries
Where the body of our patch will go, in this case it's where the actual recipe will go.


Breaking Down the Crafting Recipe

Now that we have an example to compare to, we can go over how the table on the wiki relates to the format of the crafting recipe.

Index Field Example Value
Cooking Crafting Cooking Crafting
Key Name Salad Stone Fence
0 Ingredients 20 1 22 1 419 1 390 2
1 (Unused) 25 5 Field
2 Yield 196 323
3 Big craftable? false
3 4 Unlock conditions f Emily 3 Farming 2
4 5 Display name
Tokenizable String. Defaults to the display name of the first product if not given.
Ensalada Valla de piedra
The Crafting Recipe
"{{ModId}}_BigChest": "(BC)130 1 388 70 334 2/Home/BigChest/true/default/"
Each section of the recipe is divided by a /.
{{ModId}}_BigChest
The key of the entry, since we're adding a new recipe this needs to be unique so we use the ModId token to tell CP to insert our UniqueID from our manifest here.
Index 0
This field is the ingredients for the recipe:
(BC)130 1 388 70 334 2
(BC) is the qualifier for big craftables, since we're using a Chest this is important and must be used. (Without the qualifier, this recipe would instead call for the object ID 130, which is a Tuna.)
130 is the item ID for Chests.
1 is the amount required.
388 70 is the unqualified item ID for Wood and the amount of 70, the difference between what the original crafting recipe calls for and the amount of Wood it takes to craft a Chest.
334 2 is the unqualified item ID for Copper Bar and the unmodified amount from the original recipe, as regular Chests only use wood, I've only opted to change the amount of wood required to use this alternate recipe.
So put plainly, this recipe calls for 1 Chest, 70 Wood, and 2 Copper Bars.
Index 1
This field isn't used, however it is still required to fill with something or our recipe will be invalid.
Home
Index 2
This field is what the recipe gives, which must be an unqualified item ID and can optionally be followed by the amount you want to receive.
BigChest
Because Big Chests are new, they make full use of the plain name ID's of 1.6 and their ID isn't an arbitrary number.
Index 3
This field determines whether the yield from index 2 is a big craftable or not, accepts only true or false.
true
Index 4
This field determines how the recipe is learned.
default
Tells the game that this recipe should be known from the very beginning. (Our When condition keeps the recipe from appearing until you've unlocked the Big Chest recipe.)
The only other accepted value here for crafting recipes is <skill> <level> which adds the recipe to the level up rewards. (ex. Foraging 2)
Any other value here will tell the game that you intend to unlock the recipe via some other means, such as sending mail yourself or through a shop.
Index 5
This field is for translations, though it isn't necessary if your yield is a vanilla item or if your custom item's data is set up with translations correctly.


Changing a Crafting Recipe for an Existing Item

If you happen to disagree with the balance of the game and think the recipe for something should be different, changing it is really easy.

For this example we'll adjust the materials required to make the regular Chest.


Here is my content.json in full:

{
	"Format": "2.0.0",
	"Changes": [
		{
			"LogName": "Create new big chest crafting recipe",
			"Action": "EditData",
			"Target": "Data/CraftingRecipes",
			"When": {
				"HasCraftingRecipe": "Big Chest"
			},
			"Entries": {
				"{{ModId}}_BigChest": "(BC)130 1 388 70 334 2/Home/BigChest/true/default/"
			}
		},
		{
			"LogName": "Adjust regular chest crafting recipe",
			"Action": "EditData",
			"Target": "Data/CraftingRecipes",
			"Entries": {
				"Chest": "388 10/Home/130/true/default/"
			}
		}
	]
}


The first Action block is our previous patch adding a new recipe for an existing item, you can see that for the entry in that Action my key is {{ModId}}_BigChest which creates a new entry as there isn't one already named that in the data asset.

The second Action block is our new patch changing the recipe for Chests. In order to target an existing recipe, I find it in the Data/CraftingRecipes and make sure my entry key matches.

Normally we could combine these two blocks into one Entries field, however because our Big Chest recipe change is conditional and our Chest recipe change isn't, we have to break them up into separate blocks.


Here is the block on its own:

{
  "LogName": "Adjust regular chest crafting recipe",
  "Action": "EditData",
  "Target": "Data/CraftingRecipes",
  "Entries": {
    "Chest": "388 10/Home/130/true/default/"
  }
}

The original ingredients for a Chest in Data/CraftingRecipes is 388 50 which plainly written is 50 Wood. By changing the 50 to 10, we change the recipe to only require 10 Wood.


We could also fully replace the ingredients with whatever we want instead:

{
  "LogName": "Adjust regular chest crafting recipe",
  "Action": "EditData",
  "Target": "Data/CraftingRecipes",
  "Entries": {
    "Chest": "771 1/Home/130/true/default/"
  }
}

Here we've replaced 388 10 with 771 1 , which if we look in the Data/Objects.json, or using an online resource, we can see is the unqualified ID for Fiber.

Now when we check in game, we can see that a Chest only costs a single Fiber to craft.


When using an unqualified item ID in the ingredients field, the game will search through the various data assets to try to find a match, starting with objects, and choose the first match it finds.
Because we know in this example that we want an object type ingredient, we don't have to qualify our item ID, but it's a good habit to qualify item ID's whenever the field accepts them.


Most of the time, you can find what you need to put into a field on the wiki on the appropriate page or on the migration guide if the individual page still hasn't been updated.

Cooking Recipes

New Cooking Recipe for an Existing Item

We'll start by making an alternative recipe for an existing recipe.

For this example we'll be creating an additional/alternative recipe for the Fried Egg:

{
  "Format": "2.0.0",
  "Changes": [
    {
      "LogName": "Create new fried egg cooking recipe",
      "Action": "EditData",
      "Target": "Data/CookingRecipes",
      "Entries": {
		"{{ModId}}_FriedEgg": "(O)771 1/10 10/194/default/"
      }
    }
  ]
}
LogName
Shows up in SMAPI's console if there's a problem with this patch block, making it easier to locate when a problem arises.
This should be unique per block in order to make troubleshooting easier.
Action
Indicates what kind of patch we're attempting to perform, in this case we're doing EditData.
Target
The asset we're attempting to patch, for cooking recipes that's Data/CookingRecipes.
Entries
Where the body of our patch will go, in this case it's where the actual recipe will go.

Breaking Down the Cooking Recipe

The same table as before can be used as reference.

Index Field Example Value
Cooking Crafting Cooking Crafting
Key Name Salad Stone Fence
0 Ingredients 20 1 22 1 419 1 390 2
1 (Unused) 25 5 Field
2 Yield 196 323
3 Big craftable? false
3 4 Unlock conditions f Emily 3 Farming 2
4 5 Display name
Tokenizable String. Defaults to the display name of the first product if not given.
Ensalada Valla de piedra
The Cooking Recipe
"{{ModId}}_FriedEgg": "(O)771 1/10 10/194/default/"
Each section of the recipe is divided by a /.
{{ModId}}_FriedEgg
The key of the entry, since we're adding a new recipe this needs to be unique so we use the ModId token to tell CP to insert our UniqueID from our manifest here.
Index 0
This field is the ingredients for the recipe:
(O)771 1
(O) is the qualifier for objects. (This isn't necessary in this case but ties in nicely as an example that it can be included here.)
771 is the item ID for Fiber.
1 is the amount required.
So put plainly, this recipe calls for 1 Fiber.
Index 1
This field isn't used, however it is still required to fill with something or our recipe will be invalid.
10 10
Index 2
This field is what the recipe gives, which must be an unqualified item ID and can optionally be followed by the amount you want to receive.
194
You can see here that Fried Egg's item ID is the old number-style as it's an item from before 1.6.
Index 3
This field determines how the recipe is learned.
default
Tells the game that this recipe should be known from the very beginning.
The other accepted values here for cooking recipes are:
<skill> <level> which adds the recipe to the level up rewards. (ex. Foraging 2)
f <NPC> <hearts> which adds the recipe to a letter that will be delivered the day after getting that heart level. (ex. f Abigail 3)
Any other value here will tell the game that you intend to unlock the recipe via some other means, such as sending mail yourself or through a shop.
Index 4
This field is for translations, though it isn't necessary if your yield is a vanilla item or if your custom item's data is set up with translations correctly.


Note that cooking recipes don't use the field that determines if the recipe yield should be a big craftable, it should be omitted entirely.

Changing a Cooking Recipe for an Existing Item

If you happen to disagree with the balance of the game and think the recipe for something should be different, changing it is really easy.

For this example we'll adjust the materials required to cook Baked Fish.


In order to target an existing recipe, I find it in the Data/CookingRecipes and make sure my entry key matches. Here is my content.json in full:

{
  "Format": "2.0.0",
  "Changes": [
    {
      "LogName": "Create new fried egg cooking recipe",
      "Action": "EditData",
      "Target": "Data/CookingRecipes",
      "Entries": {
		"{{ModId}}_FriedEgg": "(O)771 1/10 10/194/default/",
        "Baked Fish": "137 1 246 1/6 5/198/l 12/"
      }
    }
  ]
}

Because we aren't doing any conditional patches and we're editing the same asset, we can do both of these changes in the same Entries field.


The original ingredients field for Baked Fish in Data/CookingRecipes is 145 1 132 1 246 1.

145 1 is 1 Sunfish, 132 1 is 1 Bream, and 246 1 is 1 Wheat Flour.


We changed the ingredients to 137 1 246 1 which is 1 Smallmouth Bass and 1 Wheat Flour.


When using an unqualified item ID in the ingredients field, the game will search through the various data assets to try to find a match, starting with objects, and choose the first match it finds.
Because we know in this example that we want an object type ingredient, we don't have to qualify our item ID, but it's a good habit to qualify item ID's whenever the field accepts them.

Bonus: Adding Recipes to Shops

Adding a recipe to a shop is like adding an object to a shop but with an extra field. Additional info about shop data can be found on the wiki.


Make sure the unlock condition of the recipe is null or none:

"Entries": {
  "{{ModId}}_FriedEgg": "(O)771 1/10 10/194/null/"
}


Add the recipe to the shop like so:

{
  "LogName": "Add alternate fried egg recipe to saloon",
  "Action": "EditData",
  "Target": "Data/Shops",
  "TargetField": [
    "Saloon",
    "Items"
  ],
  "Entries": {
    "{{ModId}}_FriedEgg_AltRecipe": {
      "Condition": "PLAYER_HAS_CRAFTING_RECIPE Current Fried Egg",
      "Id": "{{ModId}}_FriedEgg_AltRecipe",
      "ItemId": "(O)194",
      "IsRecipe": true,
      "ObjectInternalName": "{{ModId}}_FriedEgg"
    }
  },
  "MoveEntries": [
    {
      "ID": "{{ModId}}_FriedEgg_AltRecipe",
      "ToPosition": "Top"
    }
  ]
}
TargetField
Used to target a specific field or entry, in order to avoid completely overwriting the whole entry.
Condition
A Game State Query, in this context it checks if the item should be included in the shop stock.
Because shop stock is only built when you open a shop, if your Condition requires another recipe from the same shop then you'll have to reopen the shop in order for your recipe to appear.
Id
The ID used to identify this entry, also used when moving the entry.
ItemId
This is the qualified or unqualified item ID of whatever you're trying to add to the shop, in order to make sure you get the right item I suggest qualifying the ID.
IsRecipe
This tells the shop whether it should be trying to sell the recipe for the item, rather than the item itself.
In order for this to work there has to be a recipe for the item.
ObjectInternalName
This is used when your object's internal name differs from your ItemId or when you need to target a different recipe for some reason.
This is important here because there isn't technically a {{ModId}}_FriedEgg object that can be added to shop stock.
Setting this to {{ModId}}_FriedEgg tells the game where it can find the recipe it's looking for instead.
MoveEntries
Used to move entries around when that's allowed, in shop stock this lets you organize the shop however you see fit.
ID
The Id of the entry we're trying to move.
ToPosition
A specific position to move the entry to, in this example we move it to the top to make it easier to find for testing purposes.