Modding:Content Tutorial Simple Worldgen
This page was last verified for Vintage Story version 1.20.7.
Introduction
Welcome to the last intermediate content tutorial! In this tutorial, you'll learn how to generate blocks through world generation.
Objective
Much of Vintage Story's world generation is modifiable and extendable through JSON. In this tutorial, you will be shown two simple ways of adding to the world generation: blockpatches and deposits. You will also learn how to modify these methods to increase or decrease the commodity of them spawning.
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
- Incomplete 'loosesticks' blockpatches file
- Incomplete 'sand' deposits file
Prerequisites
This tutorial will assume you understand the following topics:
It is recommended to understand the following concept:
This tutorial only contains world generation assets. You will be adding in new world generation rules that use existing game content. Take a look at the following files:
- assets/worldgen/blockpatches/loosesticks.json
- assets/worldgen/deposits/sand.json
Block Patches
Block patches refer to a type of world generation where new blocks are added into a world - essentially replacing empty air blocks. In the base game, block patches are used for berry bushes, tall grass, mushrooms, underground gears, and a lot more. Files that define blockpatches must be created in the blockpatches folder.
Loose Sticks
To begin with, you will be creating a JSON file that will generate a number of loose sticks on the surface. Open worldgen/blockpatches/loosesticks.json and you should see the following partially-made script:
[
{
"comment": "Small groups of loose sticks everywhere",
}
]
As you can see, there exists a single property. This comment property has no functionality, it is merely a way of describing what the generator achieves.
Block patches all follow a very similar two-part structure. First you define what you want to generate, including the block code and the quantity, and then you define the rules of it spawning, including things such as temperature, height, fertility, as well as the 'chance' of it spawning in a chunk.
So, firstly you need to define what to generate. Using the blockCodes property, you can define an array of all the potential block types. When the generation process happens, one of these blocks will be picked randomly. In this case, just add the block code for loose sticks underneath the comment property.
"blockCodes": [ "game:loosestick-free" ],
Next, define a quantity. This is a NatFloat (natural float) type - A specific type of random number generator that uses an average and a variable amount.
"quantity": {
"avg": 4,
"var": 2
},
NatFloats can be considerably more complex - However in this example it will return a value between (avg - var) and (avg + var). In effect, this will pick a random number between 2 and 6, and generate that number of sticks in a localized group.
After you have defined what to spawn and how many of them to spawn in a single group, you can add a number of optional criteria for the block spawning.
The following properties will limit loose sticks from spawning in some areas:
"minTemp": -2,
"minForest": 0,
There are many more optional criteria that can be used. These include:
- MinTemp and MaxTemp
- MinRain and MaxRain
- MinForest and MaxForest
- MinShrub and MaxShrub (for how much shrubbery and bushes are in the current area)
- MinFertility and MaxFertility
- MinY and MaxY (for elevation, as a 0-1 decimal relative to the map height. A Y of 0 is the very bottom of the world, a Y of 1 is the world height, and a Y of around 0.43 is sea-level)
After adding in the optional criteria, you need to add in a chance property:
"chance": 3
This value effects how the chance of this rule spawning per chunk. The chance can be a decimal value. For example, a chance of 0.5 means there is a 50% chance per chunk that the blockpatch will be generated. A chance of 3 means that there will be 3 instances of this generating per chunk.
If you run the game with your mod, you will be able to see sticks generating most places in the surface. If you want to increase the spawn rate, increase the value of the chance property.
loosesticks.json |
---|
[
{
"comment": "Single loose sticks everywhere",
"blockCodes": [ "game:loosestick-free" ],
"minTemp": -2,
"minForest": 0,
"quantity": {
"avg": 4,
"var": 2
},
"chance": 3
}
]
|
Underground Bones
There's more to be done with blockpatches. Create a new file in the blockpatches folder called "bones.json" and fill it with this code:
[
{
"comment": "Lots of bones and carcasses underground",
}
]
This file will be very similar to the previous one - Add in the following for the blockcodes, the quantity, and the chance.
"blockCodes": [ "game:bonyremains-cowskull-up", "game:bonyremains-ribcage", "game:drycarcass-humanoid1", "game:drycarcass-humanoid2", "game:drycarcass-tiny", "game:drycarcass-small", "game:drycarcass-medium", "game:drycarcass-large" ],
"quantity": {
"avg": 1,
"var": 0
},
"chance": 120,
So this will add in a random carcass or bony remains. Using a quantity average of 1 with a var of 0 means this will always spawn a single carcass. There exists some further criteria for blockpatches. An additional placement property will allow you to define a more specific criteria on where the worldgen will place things.
"placement": "Underground"
This particular value will allow the blocks to be placed underground. Since there is now a much larger area to work with, the chance property for this entry is considerably higher. The following values can be used for placement:
- ReplaceSurface
- OnSurface (Default)
- NearWater
- Anywhere
- Underground
- UnderWater
- NearSeaWater
- UnderSeaWater
- UnderTrees
- OnTrees
- OnSurfacePlusUnderTrees
When this is specified, you can test the mod. You should begin seeing carcasses and other bones spawning underground.
bones.json |
---|
[
{
"comment": "Lots of bones and carcasses underground",
"blockCodes": [ "game:bonyremains-cowskull-up", "game:bonyremains-ribcage", "game:drycarcass-humanoid1", "game:drycarcass-humanoid2", "game:drycarcass-tiny", "game:drycarcass-small", "game:drycarcass-medium", "game:drycarcass-large" ],
"quantity": {
"avg": 1,
"var": 0
},
"chance": 120,
"placement": "Underground"
}
]
|
Deposits
Deposits are a little more complicated than blockpatches. This type of world generation is used to replace certain blocks with other blocks. Blocks such as ores, clay/peat, and cracked stone all spawn using a deposit generation. Deposits must be defined in the deposits folder in worldgen. Open the tutorial file at worldgen/deposits/sand.json and you'll see this code sample:
[
{
"code": "sand",
}
]
In this instance, you're going to make chunks of sand spawn within rock.
There's three main sections that need to be specified for deposits: The old block being replaced, the new block to replace with, and how the blocks are selected in a chunk.
To start, let's look at how to specify the old block to be replaced.
Inblock - The Old Block Being Replaced
For a deposit to work, we need to specify what block (or blocks) can be replaced. This works similarly to an ingredient in a recipe, and we can specify variants and use wildcards.
Due to how deposits work, a lot of the json properties have to within an 'attibutes' property. To specify what block can be replaced, we will use the 'inblock' property:
"attributes": {
"inblock": {
"code": "game:rock-*",
"name": "rock",
"allowedVariants": [ "andesite", "chalk", "chert", "conglomerate", "limestone", "claystone", "granite", "sandstone", "shale", "basalt", "peridotite", "phyllite", "slate" ]
},
}
The above code is likely to be quite familiar by now - The 'code' property specifies a block code, and can use a wildcard. In this case, it's allowing any type of rock, and storing the variant type within the 'rock' substitute by using the 'name' property. It also specifies the allowedVariants that can be used for the sand, since there exists some rock types that do not have a sand variant.
Placeblock - The New Block Being Placed
The second part of a deposit is specifying what block it should replace with.
This is quite simple - Inside the attributes property, add the following:
"placeblock": { "code": "game:sand-{rock}" },
In this case, the 'rock' substitute that's been used from inblock will determine what type of sand to place.
SurfaceBlockChance
There exists two more properties which are quite useful when using block deposits, which affect what block will be generated on the surface to indicate that this block has spawned below. For example, how copper nuggets on the surface indicate that there is a copper ore under the surface.
In the instance of copper (no need to copy this to your own code):
"surfaceBlock": { "code": "looseores-nativecopper-{rock}-free" },
"surfaceBlockChance": 0.33,
This isn't going to be used in the tutorial, but it's certainly good to know.
Generator - How Blocks Are Selected
The generator is the central beefy part of a deposit, and has a number of properties to do with it.
Firstly, there are two new properties that should be added outside of the attributes:
[
{
"code": "sand",
"triesPerChunk": 8,
"generator": "disc-anywhere",
"attributes": {
...
}
}
]
The 'triesPerChunk' property seems rather self-explanatory, but refers to a chunk column instead of a single chunk. Chunks in Vintage Story are 32x32x32 blocks, but world generation is based on chunk columns, which is all the chunks in a certain X and Z position.
The 'generator' controls what blocks are selected. The different types are out-of-scope for this tutorial, but 'disc-anywhere' means that our desposit will spawn in a disc shape, and will spawn at any Y level.
Generator Settings
The disc generator uses a few attributes to determine exactly where the deposits will be generated:
Most of these attributes use the NatFloat type:
Radius - This controls the radius in blocks of the deposit disc being created. The example is using a gaussian distribution (or bell curve), with a range between 0 and 64, tending towards 32:
"radius": {
"dist": "gaussian",
"avg": 32,
"var": 32
},
YPosRel - This controls the Y position for each placed block, relative to the starting position of the disc. The example uses a uniform distribution from 0 to 1:
"yPosRel": {
"dist": "uniform",
"avg": 0.5,
"var": 0.5
},
Thickness - This controls the thickness in blocks of the generator. Note that the disc generator we're chosen uses the 'radius' property to determine the number of blocks of the X and Z axes, and the 'thickness' property to determine the number of blocks in the Y axis. The example uses an inverse exponential distribution. This gives a value from 1.3 to 4.3, where numbers towards 4.3 will be exponentially less common.
"thickness": {
"dist": "invexp",
"avg": 1.3,
"var": 3
}
Putting it All Together
When put all together, our deposit is complete. With the settings that were used, you should be able to pick any position and dig down until you find some sand. You'll also be able to go on any sand block in the hand block, and see an "Occurs in host rock" message.
If you are having trouble, or a part of your deposit isn't working, double check that all the JSON properties are in the correct place. The code, triesPerChunk, and generator properties are the only ones that should exist outside of the attributes. InBlock, PlaceBlock, Radius, YPosRel and Thickness should all exist inside the attributes property.
The full file should look like this:
sand.json |
---|
[
{
"code": "sand",
"triesPerChunk": 8,
"generator": "disc-anywhere",
"attributes": {
"inblock": {
"code": "game:rock-*",
"name": "rock",
"allowedVariants": [ "andesite", "chalk", "chert", "conglomerate", "limestone", "claystone", "granite", "sandstone", "shale", "basalt", "peridotite", "phyllite", "slate" ]
},
"placeblock": { "code": "game:sand-{rock}" },
"radius": {
"dist": "gaussian",
"avg": 32,
"var": 32
},
"yPosRel": {
"dist": "uniform",
"avg": 0.5,
"var": 0.5
},
"thickness": {
"dist": "invexp",
"avg": 1.3,
"var": 3
}
}
}
]
|
Conclusion
Congratulations, you now have the ability to generate blocks in your world! There's a lot of possibilities with this - From spawning ores, to new plant types.
Next Steps...
If you want to test your knowledge, consider doing the tasks under the Going Further section below.
There's no more tutorials in this series at the moment! Consider moving on to the Advanced Content Tutorials when you're ready. These are going to be more complex, so it's a good idea to ensure you've completed the rest of the tutorials before continuing.
Going Further
Want to make some additional changes to this mod? Try and achieve the following things!
A more complex one... create a blockpatch which will spawn gold bits underneath trees. The gold bits should be appropriate to the rock type, which can be done by using the '{rocktype}' substitution in their code. You will have to use one of the other prementioned placement methods too. The block code for gold bits, in andesite, is 'game:looseores-quartz_nativegold-andesite-free'.
To achieve this... | ||
Copy and rename one of the block patch files, and replace the block codes with 'game:looseores-quartz_nativegold-{rocktype}-free'. Change or add the placement property to UnderTrees, and set the chance to around 25 for an abundance of gold bits. The finished code should look similar to this:
|