Modding:Content Tutorial Complex Grid Recipes: Difference between revisions

From Vintage Story Wiki
mNo edit summary
(Finished tutorial.)
 
(One intermediate revision by the same user not shown)
Line 3: Line 3:
{{GameVersion|1.19.7}}
{{GameVersion|1.19.7}}
__FORCETOC__
__FORCETOC__
In progress.


== Introduction ==
== Introduction ==


=== Objective ===
=== Objective ===
The variant system has an extensive amount of uses. In this tutorial, we will create a set of blocks with independent textures, using two separate variant groups. These blocks will show you how you can mix variant groups together to create a huge number of new assets.
Recipes in Vintage Story are very versatile, and the grid recipe system has a number of features that have not yet been talked about. In this tutorial, you will find out how to create more complex recipes, including those with variants, different itemstack quantities, returned itemstacks, and some other smaller features.


=== Assets ===
=== Assets ===
Before starting, it is recommended you [https://github.com/Nateonus/vsmodexamples/releases/tag/BlockVariantsTutorial download the workspace and assets] for this tutorial. The completed files can also be found here.  
Before starting, it is recommended you [https://github.com/Nateonus/vsmodexamples/releases/tag/ComplexGridRecipesTutorial download the workspace and assets] for this tutorial. The completed files can also be found here.  


This tutorial starts with the following assets:
This tutorial starts with the following assets:


* Mod Setup & Folder Structure
* Mod Setup & Folder Structure
* Template advanced shiny block file
* Simple 'Sleek Door' recipe
* Completed lang file
* Block texture files


=== Prerequisites ===
=== Prerequisites ===
Line 25: Line 21:


* [[Modding:Developing a Content Mod|Setting up a content mod and using an IDE.]]
* [[Modding:Developing a Content Mod|Setting up a content mod and using an IDE.]]
* [[Modding:Content Tutorial Basics|The functions of shape, texture and lang files.]]


It is recommended to have completed the following tutorials:
It is recommended to have completed the following tutorial:


* [[Modding:Content Tutorial Simple Block|3. Simple Block]] - The simple block made in this tutorial is the basis for this tutorial.
* [[Modding:Content Tutorial Simple Recipe|4. Simple Recipes]] - This tutorial begins with a basic recipe.
* [[Modding:Content Tutorial Item Variants|5. Item Variants]] - This tutorial gives a good beginning stage for creating variants. These tutorials will also be formatted very similarly.


It is recommended, but not necessary, to understand the following concept:
It is recommended, but not necessary, to understand the following concept:
Line 37: Line 31:


== Navigating Assets ==
== Navigating Assets ==
Using the downloaded workspace, have a look at the mod assets that currently exist.
This tutorial is simply to create new recipes - The only file is ''recipes/grid/sleekdoor.json''. This is a simple recipe, which inputs an oak solid door and outputs an oak sleek door.<syntaxhighlight lang="json">
 
{
* ''blocktypes/advancedshinyblock.json'' - This blocktype file is from the finished [[Modding:Content Tutorial Simple Block|Simple Block]] tutorial.
"ingredientPattern": "D",
* ''lang/en.json'' - This already contains the entries needed for the tutorial.
"width": 1,
 
"height": 1,
* ''textures/block/shiny...'' - The four texture files for our blocks. Notice that we are using gold and iron textures, and both have 'damaged' states.
"ingredients": {
 
"D": {
== Defining Variants ==
"type": "block",
All work in this tutorial will be done in the ''blocktypes''/''advancedshinyblock.json'' file. Open it in your IDE and you can get started.
"code": "game:door-solid-oak"
 
}
Firstly, the item's code is still set to "''simplegoldblock"''. Change this to "''advancedshinyblock"''.
 
Before you can utilize any variant systems, you will need to define the variants for your block. To do this, use the ''"variantGroups"'' property. This is ''usually'' placed directly below the ''code'' property, but it would work anywhere in the object.
 
For this block, we are going to create two variant groups.<syntaxhighlight lang="json">
"variantgroups": [
{
"code": "type",
"states": [ "gold", "iron" ]
},
},
{
"output": {
"code": "condition",
"type": "block",
"states": [ "good", "used" ]
"code": "game:door-sleek-windowed-oak"
}
}
],
}
</syntaxhighlight>This example creates two variant groups - Named "''type''" and "''condition''". When you use more than one variant group, the game will create block codes using every permutation of those two groups.
</syntaxhighlight>


If you were to test the mod now, you will see that there exists four block objects with the codes:
== Variant-Based Recipes ==
The code that is given within the recipe file works to create an oak sleek door from an oak solid door. However, there exists a sleek door for every solid door, ''so let's go ahead and create 13 or so other recipes for the other wood types''.


* ''advancedshinyblock-gold-good''
Of course, there's a much easier way of doing that, and it uses the variant system.
* ''advancedshinyblock-gold-used''
* ''advancedshinyblock-iron-good''
* ''advancedshinyblock-iron-used''
Notice that the order your variant groups are defined affects the order they appear in the ID. When using variants, especially when using wildcards, this is important to remember.  


As you probably expected by now, these blocks do not have the appropriate textures.  
Firstly, let's add a wildcard into out ingredient's code to allow us to use a solid door of any type.<syntaxhighlight lang="json">
"D": {
"type": "block",
"code": "game:door-solid-*"
}
</syntaxhighlight>So, the recipe now inputs any type of solid door, but will always return an ''oak'' sleek door. There needs to be a way of copying the text from the wildcard ( '''*''' ) and using it within the output.


[[File:BlockVariantsContentTutorial Block all with same textures..png|frameless]]
By using the "''name''" property in the ingredient, you can achieve exactly that.<syntaxhighlight lang="json">
"D": {
"type": "block",
"code": "game:door-solid-*",
"name": "wood"
}
</syntaxhighlight>This now marks the recipe as containing a variant called "''wood''". Note that this name can be anything, it does not have to match with the variant code used when the item or block was defined.  


== Variant-Based Textures ==
To use this in the output, you can use a ''variant substitution'' ('''{ }''') in the output code.<syntaxhighlight lang="json">
As explained in the [[Modding:Content Tutorial Item Variants|Item Variants]] tutorial, there are two ways of using variants - ''ByType'' and ''Substitution''. In this tutorial, you're going to use both methods. Before continuing, it may be a good idea to refresh yourself about how both methods work in the item variants tutorial.
"output": {
"type": "block",
"code": "game:door-sleek-windowed-{wood}"
}
</syntaxhighlight>Make sure that you use the variant code you defined earlier (using "''name''") is the same as the substitution in your output.


This is the current texture property:<syntaxhighlight lang="json">
'''Important:''' You cannot name more than one variant per ingredient.
"textures": {
 
"all": { "base": "block/shinygoldtexture" }
If you test your recipe now, you'll find that you can create any sleek door from its appropriate solid door type.
},
</syntaxhighlight>This blocktype has a total of four variants - That's not many. Although you could create a ''byType'' entry for each variant code, it is a good idea to understand how you can use a mixture of the two methods. Keep in mind, similar to the item variants, there are a number of ways to achieve the final result. Feel free to experiment with other methods, variants are a complex but beautiful subject when used effectively.


Using the ''ByType'' suffix, you can split the texture by the condition variant:<syntaxhighlight lang="json">
== More Tool-Usage ==
"texturesbytype": {
In the previous recipes tutorial, you used a hammer to create gold brick blocks. Add another ingredient to the sleek door recipe to make the recipe require a saw of any type.<syntaxhighlight lang="json">
"*-good": {
{
"all": { "base": "block/shinygoldtexture" }
"ingredientPattern": "S,D",
"width": 1,
"height": 2,
"ingredients": {
"S": {
"type": "item",
"code": "game:saw-*",
"isTool": true
},
"D": {
"type": "block",
"code": "game:door-solid-*",
"name": "wood"
}
},
},
"*-used": {
"output": {
"all": { "base": "block/shinygoldtexture" }
"type": "block",
"code": "game:door-sleek-windowed-{wood}"
}
}
}
</syntaxhighlight>This is a fairly simple modification, it just changes the pattern, height, and adds the extra saw ingredient.
The saw is marked as a tool, so it will lose a single durability point when the recipe is created. However, you may be aware that some recipes use more tool durability than others. To achieve this, you can add a "toolDurabilityCost" property to the tool ingredient.<syntaxhighlight lang="json">
"S": {
"type": "item",
"code": "game:saw-*",
"isTool": true,
"toolDurabilityCost": 10
},
},
</syntaxhighlight>A quick reminder of wildcards may be in order. When using the ''ByType'' suffix, each object's resolved code will start at the first key (''"*-good")'' listed and go down the list. If the resolved code matches with the given string, if will then use that key's property. Using an asterisk ( '''*''' ) in a key tells the game that ''any'' string can go here. So, in this example, here is the list of matching objects:
</syntaxhighlight>If you test this, you will find that the door now requires a saw to make, which will lose 10 durability when the recipe is complete.
{| class="wikitable"
 
!"*-good"
[[File:ContentTutorialComplexGridRecipe Sleek Door With Saw Shaped.png|frameless]]
|"advancedshinyblock-gold-good", "advancedshinyblock-iron-good"
 
|-
== Shapeless Recipes ==
!"*-used"
All the examples given so far have been ''shaped'' recipes, meaning that the ingredients must be placed in the order defined in the "''ingredientPattern''" property. However, there are some instances where we might now want to worry about the layout of the ingredients. To now make the sleek door recipe shapeless, add the "''shapeless"'' property.
|"advancedshinyblock-gold-used", "advancedshinyblock-iron-used"
|}
Although not used in this example, you can simply use an asterisk as a byType key to match everything. This is useful when you wish to specify a value for a single object, but have every other object use a different value.
{| class="wikitable mw-collapsible mw-collapsed"
{| class="wikitable mw-collapsible mw-collapsed"
|Why can't you use "'''*-gold'''" as a key?
|Shapeless sleek door recipe...
|-
|-
|Remember that the order of the variant groups is important when using wildcards. In every example where wildcards have been used, it has always been to specify the final variant. The ID of, for example, "advancedshinyblock-gold-good" does ''not'' fit within the wildcard "*-gold", as "gold" is not the final part of the code.
|<syntaxhighlight lang="json">
As such, you would have to use "'''*-gold-*'''". This will accept any string before "'''-gold-'''" as well as any string after it.
{
|}
"ingredientPattern": "S,D",
''Anyway, back to the code.''
"width": 1,
 
"height": 2,
The given code sample doesn't actually specify a different texture for each type. Replace each texture property with a specific variant substitution.<syntaxhighlight lang="json">
"shapeless": true,
"texturesbytype": {
"ingredients": {
"*-good": {
"S": {
"all": { "base": "block/shiny{type}texture" }
"type": "item",
"code": "game:saw-*",
"isTool": true,
"toolDurabilityCost": 10
},
"D": {
"type": "block",
"code": "game:door-solid-*",
"name": "wood"
}
},
},
"*-used": {
"output": {
"all": { "base": "block/shiny{type}texture-damaged" }
"type": "block",
"code": "game:door-sleek-windowed-{wood}"
}
}
},
}
</syntaxhighlight>As you can see, this uses the '''{type}''' to substitute "gold" or "iron" into the texture path. If you follow the code, you end up with these resolved texture paths:
</syntaxhighlight>
{| class="wikitable"
!Block ID
!Resolved Texture Path
|-
|advancedshinyblock-gold-good
|block/shinygoldtexture
|-
|advancedshinyblock-gold-used
|block/shinygoldtexture-damaged
|-
|advancedshinyblock-iron-good
|block/shinyirontexture
|-
|advancedshinyblock-iron-used
|block/shinyirontexture-damaged
|}
|}
If you are having issues getting any textures to work, it is often a good idea to create a table like this. For every ID, work out the resolved texture path, and ensure it exists.
Now, the saw and door can be arranged in any order in the crafting grid.
 
[[File:ContentTutorialComplexGridRecipes Shapeless Recipe.png|frameless]]
 
== Extra-Outputs & Returned Stacks ==
In some recipes, ingredients can be turned into a different itemstack instead of being consumed.


If you now test your mod, you'll see your blocks working as expected!
The sleek door looks like a solid door with a couple of planks removed for a window, so you should return two wooden planks to the player when they create it.


[[File:ContentTutorialBlockVariants All Block Variants Working With Textures.png|frameless]]
To do this, you can use the "''returnedStack"'' property on an ingredient.<syntaxhighlight lang="json">
"D": {
"type": "block",
"code": "game:door-solid-*",
"name": "wood",
"returnedStack": {
"type": "item",
"code": "game:plank-{wood}",
"quantity": 2
}
}
</syntaxhighlight>Notice that you can still use variant substitutions here, as well as a quantity value. If you test your recipe now, your recipe will give you two wooden planks when the sleek door is created. Note that you have to create the door for the returnedStacks to be created.
 
You can only have one returned stack per ingredient.


== Conclusion ==
== Conclusion ==
Congratulations, you now have four variants of a single block, all within one file! After this tutorial, you should have more of an understanding of how variants can be grouped together, and how to get the most out of them.  
Congratulations, you now have an advanced recipe for every type of sleek door, all within one file! After this tutorial, you should have more of an understanding of how variants can be used within grid recipes, and how grid recipes have a lot more functionality.  


=== Next Steps... ===
=== Next Steps... ===
If you want to test your knowledge, consider doing the tasks under the ''Going Further'' section below.
If you want to test your knowledge, consider doing the tasks under the ''Going Further'' section below.


Variants are pretty nifty, but they can get even better. Take a look at the next tutorial, [[Modding:Content Tutorial Complex Grid Recipes|Complex Grid Recipes]] for a demonstration on how to use variants inside recipes.
Grid recipes are only one type of recipe in Vintage Story. Move on to the next tutorial, [[Modding:Content Tutorial Further Recipes|Further Recipes]], for information on barrel, clayforming, cooking, knapping, and smithing recipes!


== Going Further ==
== Going Further ==
Want to make some additional changes to this mod? Try and achieve the following things!
Want to make some additional changes to this mod? Try and achieve the following things!


Make the '<nowiki/>''used''<nowiki/>' blocks destroy faster than the '<nowiki/>''good''' blocks.
Create a new recipe that allows you to change any sleek door back into a solid door. What gameplay problem would arise from this, and how could you solve it?
{| class="wikitable mw-collapsible mw-collapsed"
{| class="wikitable mw-collapsible mw-collapsed"
|To achieve this...
|To achieve this...
|-
|-
|Replace the ''resistance'' property with ''resistanceByType.'' Specify two wildcard keys, namely "'''*-good'''" and "'''*-used'''" to specify values of 3.5 and 2 respectively.
|Copy the appropriate recipe, however make the input a sleek door with a wood variant, and the output code a solid door using the defined wood variant.
{| class="wikitable mw-collapsible mw-collapsed"
The issue with this is the returned two planks when creating a sleek door - Which could result in an infinite number of wooden planks if the player kept exchanging between solid and sleek doors. To fix this, you could add another ingredient of two planks for the sleek to solid door recipe.
|Example...
|-
|<syntaxhighlight lang="json">
"resistanceByType": {
"*-good": 3.5,
"*-used": 2
},
</syntaxhighlight>
|}
|}
|}
Change the original recipe to stop the player from using a copper saw to make sleek doors. (Hint: The "''skipVariants"'' property (which accepts an array) in an ingredient allows you to control what variants cannot be used in a wildcard.)
Remove texturesByType, and achieve the same result using just variant substitution. (Hint: You will have to rename texture files)
{| class="wikitable mw-collapsible mw-collapsed"
{| class="wikitable mw-collapsible mw-collapsed"
|To achieve this...
|To achieve this...
|-
|-
|There are a number of similar methods to achieve this. Here is my method:
|Add the "''skipVariants''" property to the saw ingredient, with an array containing just "''copper''".<syntaxhighlight lang="json">
Firstly, revert the "texturesByType" to a "textures" property. Change the base property to be "''block/shiny{type}texture-{condition}".'' This will result in the following ID to Texture table:
"S": {
{| class="wikitable"
"type": "item",
!Block ID
"code": "game:saw-*",
!Resolved Texture Path
"skipVariants": [ "copper" ],
|-
"isTool": true,
|advancedshinyblock-gold-good
"toolDurabilityCost": 10
|block/shinygoldtexture-good
|-
|advancedshinyblock-gold-used
|block/shinygoldtexture-used
|-
|advancedshinyblock-iron-good
|block/shinyirontexture-good
|-
|advancedshinyblock-iron-used
|block/shinyirontexture-used
|}
You will now have to rename the texture files to match those in the resolved texture path column.
{| class="wikitable mw-collapsible mw-collapsed"
|Example...
|-
|<syntaxhighlight lang="json">
"textures": {
"all": { "base": "block/shiny{type}texture-{condition}" }
},
},
</syntaxhighlight>
</syntaxhighlight>
|}
|}
|}


{{Navbox/contentmodding}}
{{Navbox/contentmodding}}
</translate>
</translate>

Latest revision as of 15:45, 9 April 2024

This page was last verified for Vintage Story version 1.19.7.


Introduction

Objective

Recipes in Vintage Story are very versatile, and the grid recipe system has a number of features that have not yet been talked about. In this tutorial, you will find out how to create more complex recipes, including those with variants, different itemstack quantities, returned itemstacks, and some other smaller features.

Assets

Before starting, it is recommended you download the workspace and assets for this tutorial. The completed files can also be found here.

This tutorial starts with the following assets:

  • Mod Setup & Folder Structure
  • Simple 'Sleek Door' recipe

Prerequisites

This tutorial will assume you understand the following topics:

It is recommended to have completed the following tutorial:

It is recommended, but not necessary, to understand the following concept:

Navigating Assets

This tutorial is simply to create new recipes - The only file is recipes/grid/sleekdoor.json. This is a simple recipe, which inputs an oak solid door and outputs an oak sleek door.

{
	"ingredientPattern": "D",
	"width": 1,
	"height": 1,	
	"ingredients": {
		"D": {
			"type": "block",
			"code": "game:door-solid-oak"
		}
	},
	"output": {
		"type": "block",
		"code": "game:door-sleek-windowed-oak"
	}
}

Variant-Based Recipes

The code that is given within the recipe file works to create an oak sleek door from an oak solid door. However, there exists a sleek door for every solid door, so let's go ahead and create 13 or so other recipes for the other wood types.

Of course, there's a much easier way of doing that, and it uses the variant system.

Firstly, let's add a wildcard into out ingredient's code to allow us to use a solid door of any type.

"D": {
	"type": "block",
	"code": "game:door-solid-*"
}

So, the recipe now inputs any type of solid door, but will always return an oak sleek door. There needs to be a way of copying the text from the wildcard ( * ) and using it within the output. By using the "name" property in the ingredient, you can achieve exactly that.

"D": {
	"type": "block",
	"code": "game:door-solid-*",
	"name": "wood"
}

This now marks the recipe as containing a variant called "wood". Note that this name can be anything, it does not have to match with the variant code used when the item or block was defined. To use this in the output, you can use a variant substitution ({ }) in the output code.

"output": {
	"type": "block",
	"code": "game:door-sleek-windowed-{wood}"
}

Make sure that you use the variant code you defined earlier (using "name") is the same as the substitution in your output.

Important: You cannot name more than one variant per ingredient.

If you test your recipe now, you'll find that you can create any sleek door from its appropriate solid door type.

More Tool-Usage

In the previous recipes tutorial, you used a hammer to create gold brick blocks. Add another ingredient to the sleek door recipe to make the recipe require a saw of any type.

{
	"ingredientPattern": "S,D",
	"width": 1,
	"height": 2,	
	"ingredients": {
		"S": {
			"type": "item",
			"code": "game:saw-*",
			"isTool": true
		},
		"D": {
			"type": "block",
			"code": "game:door-solid-*",
			"name": "wood"
		}
	},
	"output": {
		"type": "block",
		"code": "game:door-sleek-windowed-{wood}"
	}
}

This is a fairly simple modification, it just changes the pattern, height, and adds the extra saw ingredient. The saw is marked as a tool, so it will lose a single durability point when the recipe is created. However, you may be aware that some recipes use more tool durability than others. To achieve this, you can add a "toolDurabilityCost" property to the tool ingredient.

"S": {
	"type": "item",
	"code": "game:saw-*",
	"isTool": true,
	"toolDurabilityCost": 10
},

If you test this, you will find that the door now requires a saw to make, which will lose 10 durability when the recipe is complete.

ContentTutorialComplexGridRecipe Sleek Door With Saw Shaped.png

Shapeless Recipes

All the examples given so far have been shaped recipes, meaning that the ingredients must be placed in the order defined in the "ingredientPattern" property. However, there are some instances where we might now want to worry about the layout of the ingredients. To now make the sleek door recipe shapeless, add the "shapeless" property.

Shapeless sleek door recipe...
{
	"ingredientPattern": "S,D",
	"width": 1,
	"height": 2,
	"shapeless": true,
	"ingredients": {
		"S": {
			"type": "item",
			"code": "game:saw-*",
			"isTool": true,
			"toolDurabilityCost": 10
		},
		"D": {
			"type": "block",
			"code": "game:door-solid-*",
			"name": "wood"
		}
	},
	"output": {
		"type": "block",
		"code": "game:door-sleek-windowed-{wood}"
	}
}

Now, the saw and door can be arranged in any order in the crafting grid.

ContentTutorialComplexGridRecipes Shapeless Recipe.png

Extra-Outputs & Returned Stacks

In some recipes, ingredients can be turned into a different itemstack instead of being consumed.

The sleek door looks like a solid door with a couple of planks removed for a window, so you should return two wooden planks to the player when they create it.

To do this, you can use the "returnedStack" property on an ingredient.

"D": {
	"type": "block",
	"code": "game:door-solid-*",
	"name": "wood",
	"returnedStack": {
		"type": "item",
		"code": "game:plank-{wood}",
		"quantity": 2
	}
}

Notice that you can still use variant substitutions here, as well as a quantity value. If you test your recipe now, your recipe will give you two wooden planks when the sleek door is created. Note that you have to create the door for the returnedStacks to be created.

You can only have one returned stack per ingredient.

Conclusion

Congratulations, you now have an advanced recipe for every type of sleek door, all within one file! After this tutorial, you should have more of an understanding of how variants can be used within grid recipes, and how grid recipes have a lot more functionality.

Next Steps...

If you want to test your knowledge, consider doing the tasks under the Going Further section below.

Grid recipes are only one type of recipe in Vintage Story. Move on to the next tutorial, Further Recipes, for information on barrel, clayforming, cooking, knapping, and smithing recipes!

Going Further

Want to make some additional changes to this mod? Try and achieve the following things!

Create a new recipe that allows you to change any sleek door back into a solid door. What gameplay problem would arise from this, and how could you solve it?

To achieve this...
Copy the appropriate recipe, however make the input a sleek door with a wood variant, and the output code a solid door using the defined wood variant.

The issue with this is the returned two planks when creating a sleek door - Which could result in an infinite number of wooden planks if the player kept exchanging between solid and sleek doors. To fix this, you could add another ingredient of two planks for the sleek to solid door recipe.

Change the original recipe to stop the player from using a copper saw to make sleek doors. (Hint: The "skipVariants" property (which accepts an array) in an ingredient allows you to control what variants cannot be used in a wildcard.)

To achieve this...
Add the "skipVariants" property to the saw ingredient, with an array containing just "copper".
"S": {
	"type": "item",
	"code": "game:saw-*",
	"skipVariants": [ "copper" ],
	"isTool": true,
	"toolDurabilityCost": 10
},


Content Modding
Basics Content Mods Developing a Content Mod
Tutorials
Concepts Modding Concepts Variants Domains Patching Remapping World Properties
Uncategorized
Icon Sign.png

Wondering where some links have gone?
The modding navbox is going through some changes! Check out Navigation Box Updates for more info and help finding specific pages.

Modding
Modding Introduction Getting Started Theme Pack
Content Modding Content Mods Developing a Content Mod Basic Tutorials Intermediate Tutorials Advanced Tutorials Content Mod Concepts
Code Modding Code Mods Setting up your Development Environment
Property Overview ItemEntityBlockBlock BehaviorsBlock ClassesBlock EntitiesBlock Entity BehaviorsWorld properties
Workflows & Infrastructure Modding Efficiency TipsMod-engine compatibilityMod ExtensibilityVS Engine
Additional Resources Community Resources Modding API Updates Programming Languages List of server commandsList of client commandsClient startup parametersServer startup parameters
Example ModsAPI DocsGitHub Repository