PREREQUISITES
Make sure you've completed the datagen setup process first.
The generation for features of Minecraft worlds is broken down into 3 parts:
- Configured Features: this defines what a feature is; for example, a single tree
- Placement Features: this defines how the features should be laid out, in which direction, relative location, and so on; for example, the placement of trees in a forest
- Biome Modifications: this defines where the features are placed in the world; for example, the coordinates of the whole forest
INFO
Features in Minecraft are natural or generated patterns in the world, like trees, flowers, ores, or lakes. Features are different from structures (for example villages, temples...), which can be found with the /locate command.
Setup
First, we need to make our provider. Create a class that extends FabricDynamicRegistryProvider and fill out the base methods:
java
public class ExampleModWorldgenProvider extends FabricDynamicRegistryProvider {
public ExampleModWorldgenProvider(FabricDataOutput output, CompletableFuture<HolderLookup.Provider> registriesFuture) {
super(output, registriesFuture);
}
@Override
protected void configure(HolderLookup.Provider registries, Entries entries) {
}
@Override
public String getName() {
return "";
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
Then add this provider to your DataGeneratorEntrypoint class within the onInitializeDataGenerator method:
java
pack.addProvider(ExampleModWorldgenProvider::new);1
Next, make a class for your configured features and a class for your placed features. These don't need to extend anything.
The configured feature class and placed feature class should both have a public method to register and define your features. Its argument, that we called context, should be a BootstrapContext<ConfiguredFeature<?, ?>> for the configured feature, or a BootstrapContext<PlacedFeature> for the placed feature.
In your DataGeneratorEntrypoint class, add the lines below to your buildRegistry method, replacing the method name with what you chose:
java
registryBuilder.add(Registries.CONFIGURED_FEATURE, ExampleModWorldConfiguredFeatures::configure);
registryBuilder.add(Registries.PLACED_FEATURE, ExampleModWorldPlacedFeatures::configure);1
2
2
If you don't already have the buildRegistry method, create it and annotate it with an @Override.
Configured Features
To make a feature naturally spawn in our world, we should start by defining a configured feature in our configured features class. Let's add a custom configured feature for a Diamond Ore vein.
First, register the key for the ConfiguredFeature in your configured feature class:
java
public static final ResourceKey<ConfiguredFeature<?, ?>> DIAMOND_BLOCK_VEIN_CONFIGURED_KEY =
ResourceKey.create(
Registries.CONFIGURED_FEATURE,
Identifier.fromNamespaceAndPath(ExampleMod.MOD_ID, "diamond_block_vein")
);1
2
3
4
5
2
3
4
5
TIP
The second argument to the Identifier (diamond_block_vein in this example) is what you would use to spawn in the structure with the /place command, which is helpful for debugging.
Ores
Next, we'll make a RuleTest that controls which blocks your feature can replace. For example, this RuleTest allows the replacement of every block with the tag DEEPSLATE_ORE_REPLACEABLES:
java
RuleTest deepslateReplaceableRule = new TagMatchTest(BlockTags.DEEPSLATE_ORE_REPLACEABLES);1
Next, we need to create the OreConfiguration, which tells the game what to replace blocks with.
java
List<OreConfiguration.TargetBlockState> diamondBlockOreConfig =
List.of(
OreConfiguration.target(deepslateReplaceableRule, Blocks.DIAMOND_BLOCK.defaultBlockState())
);1
2
3
4
2
3
4
You can have multiple cases in the list for different variants. For example, let's set a different variant for stone and deepslate:
java
List<OreConfiguration.TargetBlockState> ironAndDiamondBlockOreConfig =
List.of(
OreConfiguration.target(deepslateReplaceableRule, Blocks.DIAMOND_BLOCK.defaultBlockState()),
OreConfiguration.target(stoneReplaceableRule, Blocks.IRON_BLOCK.defaultBlockState())
);1
2
3
4
5
2
3
4
5
Lastly, we need to register our configured feature to our game!
java
context.register(
DIAMOND_BLOCK_VEIN_CONFIGURED_KEY,
new ConfiguredFeature<>(
Feature.ORE,
new OreConfiguration(diamondBlockOreConfig, 10)) // 10 is the blocks per vein
);1
2
3
4
5
6
2
3
4
5
6
Trees
To make a custom tree, you need to first create a TreeConfiguration:
java
TreeConfiguration diamondTree = new TreeConfiguration.TreeConfigurationBuilder(
// Trunk / Logs
BlockStateProvider.simple(Blocks.DIAMOND_BLOCK),
new StraightTrunkPlacer(4, 2, 0),
// Leaves
BlockStateProvider.simple(Blocks.GOLD_BLOCK),
new BlobFoliagePlacer(ConstantInt.of(2), ConstantInt.of(0), 3),
new TwoLayersFeatureSize(0, 0, 0)
).build();1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
This is what each argument does:
- Specifies the block type for the tree trunk; for example, diamond blocks
- Configures the trunk shape and height behavior using a trunk placer
- Specifies the block type for the tree leaves; for example, gold blocks
- Defines the foliage's shape and size using a foliage placer
- Controls how the tree trunk tapers at different heights, primarily for larger trunks
TIP
We highly recommend that you play around with these values to create a custom tree that you are happy with!
You can use the built-in placers for the Trunk and Foliage from the vanilla trees as a reference.
Next, we need to register our tree by adding the following line to the configure method of ExampleModWorldConfiguredFeatures.
java
context.register(DIAMOND_TREE_CONFIGURED_KEY, new ConfiguredFeature<>(Feature.TREE, diamondTree));1
Placement Features
The next step in adding a feature to the game is creating its Placement Feature.
In your placed features class's configure method, create a variable like the one below:
java
HolderGetter<ConfiguredFeature<?, ?>> configuredFeatures = context.lookup(Registries.CONFIGURED_FEATURE);1
In your placed features class, define the key for your placed feature.
java
public static final ResourceKey<PlacedFeature> DIAMOND_BLOCK_ORE_PLACED_KEY =
ResourceKey.create(
Registries.PLACED_FEATURE,
Identifier.fromNamespaceAndPath(ExampleMod.MOD_ID, "diamond_block_ore_placed")
);1
2
3
4
5
2
3
4
5
Placement Modifiers
Next, we need to define our Placement Modifiers, which are attributes that you set when spawning the feature. These can be anything: from the spawn frequency, to the starting y level. You can have as few or as many modifiers as you like.
java
List<PlacementModifier> diamondBlockVeinModifiers = List.of(
CountPlacement.of(6),
BiomeFilter.biome(),
InSquarePlacement.spread(),
HeightRangePlacement.of(BiasedToBottomHeight.of(VerticalAnchor.BOTTOM, VerticalAnchor.absolute(0), 3))
);1
2
3
4
5
6
2
3
4
5
6
The function of each modifier listed is as follows:
- CountPlacement: Roughly the amount of instances of this feature (in this case veins) per chunk
- BiomeFilter: Allows us to control what biomes/dimensions it spawns (we'll do more with this later)
- InSquarePlacement: Spreads the features more pseudo-randomly
- HeightRangePlacement: Specifies the range of
ycoordinates where a feature can spawn; it supports three main types of distributions:Uniform: All
yvalues within the range are equally likely to contain the feature. If you're unsure, just use this one.Trapezoid:
yvalues closer to the medianyvalue have a higher probability of containing the feature.Biased-Bottom: Uses a logarithmic scale where lower
yvalues are more likely to get the feature. It receives a startingycoordinate, below which the feature never spawns. The second argument is the maximum height where the feature can spawn. The third argument defines a range in blocks over which the maximum probability is extended.
TIP
Trees and other surface structures should include the modifier PlacedFeatures.WORLD_SURFACE_WG_HEIGHTMAP instead of HeightRangePlacement, to make sure the tree spawns on the surface.
Now that we have the modifiers, we can register our placed feature:
java
context.register(
DIAMOND_BLOCK_ORE_PLACED_KEY,
new PlacedFeature(
configuredFeatures.getOrThrow(ExampleModWorldConfiguredFeatures.DIAMOND_BLOCK_VEIN_CONFIGURED_KEY),
diamondBlockVeinModifiers
)
);1
2
3
4
5
6
7
2
3
4
5
6
7
Biome Modifications
Lastly, we need to add our placed feature to BiomeModifications during mod initialization. We can do this by adding the following to our mod initiializer:
java
// Spawns everywhere in the overworld
BiomeModifications.addFeature(
BiomeSelectors.foundInOverworld(),
GenerationStep.Decoration.UNDERGROUND_ORES,
ExampleModWorldPlacedFeatures.DIAMOND_BLOCK_ORE_PLACED_KEY
);1
2
3
4
5
6
2
3
4
5
6
TIP
For trees, the second parameter should be set to GenerationStep.Decoration.VEGETAL_DECORATION,
Biome Specific Generation
By changing the BiomeSelectors argument, we can have our feature spawn only in a specific type of biome:
java
// Spawns in forest biomes only
BiomeModifications.addFeature(
BiomeSelectors.tag(BiomeTags.IS_FOREST),
GenerationStep.Decoration.VEGETAL_DECORATION,
ExampleModWorldPlacedFeatures.DIAMOND_TREE_PLACED_KEY
);1
2
3
4
5
6
2
3
4
5
6
This would only spawn in biomes tagged with the minecraft:is_forest biome tag.
Running Datagen
Now, when you run datagen, you should see a .json file under src/main/generated/data/example-mod/worldgen/configured_feature for each configured feature you added, and a file under src/main/generated/data/example-mod/worldgen/placed_feature for each placed feature as well!
Generated File for the Configured Feature
json
{
"type": "minecraft:ore",
"config": {
"discard_chance_on_air_exposure": 0.0,
"size": 10,
"targets": [
{
"state": {
"Name": "minecraft:diamond_block"
},
"target": {
"predicate_type": "minecraft:tag_match",
"tag": "minecraft:deepslate_ore_replaceables"
}
}
]
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Generated File for the Placed Feature
json
{
"feature": "example-mod:diamond_block_vein",
"placement": [
{
"type": "minecraft:count",
"count": 6
},
{
"type": "minecraft:biome"
},
{
"type": "minecraft:in_square"
},
{
"type": "minecraft:height_range",
"height": {
"type": "minecraft:biased_to_bottom",
"inner": 3,
"max_inclusive": {
"absolute": 0
},
"min_inclusive": {
"above_bottom": 0
}
}
}
]
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28



