Placed Features
A placed feature determines where and how a configured feature will be placed. Placed features work via placement modifiers that can be applied to a configured feature.
You can create placed feature files in the data/worldgen/placed_feature
directory or register them in the FeatureRegistry
in code.
Structure
feature
-
The configured feature's id
placement
-
A list of placement modifiers. See Placement Modifiers for more information.
Here's an example of Minecraft's large diamond ore placed feature:
@OptIn(ExperimentalWorldGen::class)
@Init(stage = InitStage.POST_PACK_PRE_WORLD)
object PlacedFeatures : FeatureRegistry by ExampleAddon.registry {
val ORE_DIAMOND_LARGE_PLACEMENT = placedFeature("ore_diamond_large", OreFeatures.ORE_DIAMOND_LARGE) // (1)!
.rarityFilter(9) // (2)!
.inSquareSpread() // (3)!
.heightRangeTriangle(VerticalAnchor.aboveBottom(-80), VerticalAnchor.aboveBottom(80)) // (4)!
.biomeFilter() // (5)!
.register()
}
- The configured feature to place
- Only give the feature a chance of \({{}^{1}\!/_{9}}\) to generate
- Adds a random integer in the range \([0;15]\) to the x- and z-coordinates of the initial position
- Sets the y-coordinate to a value provided by the trapezoid height provider.
triangle
is a shortcut for the trapezoid height provider with a plateau of width 0. This provider provides a y-coordinate in the range \([-80;80]\) below/above the bedrock layer via an isosceles trapezoidal distribution. Since blocks can't be placed under the bedrock layer, this again halves the chance of the feature generating. - Only generates the feature in biomes that contain this feature (the
in_square
placement modifiers might have generated a position in a different biome).
{
"feature": "minecraft:ore_diamond_large",
"placement": [
{
"type": "minecraft:rarity_filter", // (1)!
"chance": 9
},
{
"type": "minecraft:in_square" // (2)!
},
{
"type": "minecraft:height_range", // (3)!
"height": {
"type": "minecraft:trapezoid", // (4)!
"max_inclusive": {
"above_bottom": 80
},
"min_inclusive": {
"above_bottom": -80
}
}
},
{
"type": "minecraft:biome" // (5)!
}
]
}
- Only give the feature a chance of \({{}^{1}\!/_{9}}\) to generate
- Adds a random integer in the range \([0;15]\) to the x- and z-coordinates of the initial position
- Sets the y-coordinate to a value provided by the trapezoid height provider
- Provides a y-coordinate in the range \([-80;80]\) below/above the bedrock layer via an isosceles trapezoidal distribution. Since blocks can't be placed under the bedrock layer, this again halves the chance of the feature generating.
- Only generates the feature in biomes that contain this feature (the
in_square
placement modifiers might have generated a position in a different biome).
Placement Modifiers
A Placement modifier takes an initial position and returns empty, one or more block positions. These modifiers are chained,
and pretty much act like a lot of flatMap
calls. In fact, that's exactly what Minecraft does internally:
private boolean placeWithContext(PlacementContext ctx, RandomSource random, BlockPos pos) {
Stream<BlockPos> stream = Stream.of(pos);
for (PlacementModifier placementmodifier : this.placement) {
stream = stream.flatMap((blockPos) -> {
return placementmodifier.getPositions(ctx, random, blockPos);
});
}
// ...
}
So you can also think of these positions as attempts to place the configured feature. A list of vanilla placement modifiers can be found below.
minecraft:biome
Returns the position if the configured feature is registered in the biome's feature
list at the given position. Empty
otherwise.
minecraft:block_predicate_filter
Returns the position if the block predicate matches the block at the given position. Empty otherwise.
Name | Description |
---|---|
predicate |
The BlockPredicate |
minecraft:carving_mask
Returns all positions in the given position's chunk that were carved out by a carver.
Name | Description |
---|---|
step |
The carver step. Can be air or liquid |
minecraft:count
Returns the given position count
times.
Name | Description |
---|---|
count |
An IntProvider (Range limit in Json is \([0;256]\)). The provided value is the number of times the position is returned |
minecraft:count_on_every_layer
Deprecated. For more information, check out the Minecraft Wiki
minecraft:environment_scan
Scans for blocks matching the given block predicate up/down until it finds a matching block or the max number of steps is reached. If no matching block is found, empty is returned.
Name | Description |
---|---|
direction_of_search |
The direction of the scan. Can be up or down |
target_condition |
The BlockPredicate to match |
allowed_search_condition (optional) |
A BlockPredicate that each scanned block must match to allow further scanning. If not provided, no condition is applied. |
max_steps |
An int that determines the max number of steps. (Range limit in Json is \([1;32]\)) |
minecraft:height_range
Takes the input position and sets the y coordinate to a value provided by the given height provider.
Name | Description |
---|---|
height |
The HeightProvider providing the y-coordinate |
minecraft:heightmap
Takes the input position and sets the y coordinate to one block above the heightmap at the given position.
Check out the heightmap gist page for image examples.
Name | Description |
---|---|
heightmap |
The heightmap type to use. Can be WORLD_SURFACE_WG , WORLD_SURFACE , OCEAN_FLOOR_WG , OCEAN_FLOOR , MOTION_BLOCKING or MOTION_BLOCKING_NO_LEAVES . |
minecraft:in_square
Adds a random integer in the range \([0;15]\) to the x- and z-coordinates of the given position.
minecraft:noise_based_count
Gets the noise value at the given position and, if the value is positive, returns the given position multiple times. The amount of times the position is returned is determined by the following code:
double noise = Biome.BIOME_INFO_NOISE.getValue((double)pos.getX() / noiseFactor, (double)pos.getZ() / noiseFactor, false);
int count = (int)Math.ceil((noise + noiseOffset) * noiseToCountRatio);
Name | Description |
---|---|
noise_to_count_ratio |
An int that defines the ratio of noise to count. |
noise_factor |
A double that scales the noise horizontally. The higher the value, the wider the peaks. |
noise_offset (optional in Json) |
A double that offsets the noise vertically. |
minecraft:noise_threshold_count
Returns the given position multiple times. If the noise value at the given position is below the given threshold, the
position is returned below_noise
times. Otherwise, it is returned above_noise
times. Or, in code:
Name | Description |
---|---|
noise_level |
A double value of the threshold that determines whether the position is returned below_noise or above_noise times. |
below_noise |
An int that determines how often the position is returned if the noise value is below the threshold. |
above_noise |
An int that determines how often the position is returned if the noise value is above/equal to the threshold. |
minecraft:random_offset
Offsets the given position by the provided IntProvider's
values.
Name | Description |
---|---|
xz_spread |
An IntProvider . (Range limit in Json is \([-16;16]\)). x and z are sampled separately! |
y_spread |
An IntProvider . (Range limit in Json is \([-16;16]\)). |
minecraft:rarity_filter
Either returns the given position or empty. The chance of returning the position is determined by the given chance and
calculated via 1 / chance
.
Name | Description |
---|---|
chance |
A positive int that determines the average amount of tries between a success. |
minecraft:surface_relative_threshold_filter
Returns the given position if the surface height at the given position is inside the specified range. Otherwise, returns empty.
Check out the heightmap gist page for image examples.
Name | Description |
---|---|
heightmap |
The heightmap to use. Can be WORLD_SURFACE_WG , WORLD_SURFACE , OCEAN_FLOOR_WG , OCEAN_FLOOR , MOTION_BLOCKING or MOTION_BLOCKING_NO_LEAVES . |
min_inclusive (Optional in Json, defaults to \(-2^{31}\)) |
A double value that defines the minimum surface level. |
max_inclusive (Optional in Json, defaults to \(2^{31} - 1\)) |
A double value that defines the maximum surface level. |
minecraft:surface_water_depth_filter
If the amount of motion-blocking blocks under the surface is less than/equal to max_water_depth
, returns the given position. Otherwise, returns empty.
Name | Description |
---|---|
max_water_depth |
An int defining the maximum allowed depth. |
Custom PlacementModifiers
You can also implement your own custom PlacementModifiers
by extending Minecraft's PlacementModifier
class. You can
then register your custom PlacementModifier
via the FeatureRegistry
either by creating a PlacementModifierType
or
by providing the Codec
directly and thus creating an inline PlacementModifierType
. Check out the Codecs
page for more information on Mojang's serialization system.
Here's how you'd implement the minecraft:count
PlacementModifier
as an example:
@OptIn(ExperimentalWorldGen::class)
@Init(stage = InitStage.POST_PACK_PRE_WORLD)
object PlacementModifiers : FeatureRegistry by ExampleAddon.registry {
val COUNT_PLACEMENT = registerPlacementModifierType("count", CountPlacement.CODEC)
}
class CountPlacement(val count: IntProvider) : PlacementModifier() {
override fun getPositions(ctx: PlacementContext, random: RandomSource, pos: BlockPos): Stream<BlockPos> =
Stream.generate { pos }.limit(count.sample(random).toLong())
override fun type(): PlacementModifierType<*> = PlacementModifiers.COUNT_PLACEMENT
companion object {
@JvmField // (1)!
val CODEC: Codec<CountPlacement> = IntProvider
.codec(0, 256)
.fieldOf("count")
.xmap(::CountPlacement, CountPlacement::count)
.codec()
@JvmStatic
fun of(count: Int) = CountPlacement(ConstantInt.of(count))
@JvmStatic
fun of(count: IntProvider) = CountPlacement(count)
}
}
- This allows
CODEC
to be accessed as a field from Java code instead of having to callgetCODEC()
object CountPlacementType : PlacementModifierType<CountPlacement> {
private val CODEC: Codec<CountPlacement> = IntProvider
.codec(0, 256)
.fieldOf("count")
.xmap(::CountPlacement, CountPlacement::count)
.codec()
override fun codec() = CODEC
}
@OptIn(ExperimentalWorldGen::class)
@Init(stage = InitStage.POST_PACK_PRE_WORLD)
object PlacementModifiers : FeatureRegistry by ExampleAddon.registry {
val COUNT_PLACEMENT = registerPlacementModifierType("count", CountPlacementType)
}
class CountPlacement(val count: IntProvider) : PlacementModifier() {
override fun getPositions(ctx: PlacementContext, random: RandomSource, pos: BlockPos): Stream<BlockPos> =
Stream.generate { pos }.limit(count.sample(random).toLong())
override fun type(): PlacementModifierType<*> = CountPlacementType
companion object {
@JvmStatic
fun of(count: Int) = CountPlacement(ConstantInt.of(count))
@JvmStatic
fun of(count: IntProvider) = CountPlacement(count)
}
}
Minecraft also offers further abstraction via the RepeatingPlacement
and PlacementFilter
classes. They both override
the getPositions
method and provide the count
and shouldPlace
methods respectively.
Inlined
Some placed features might not be worth registering in the Registry
(e.g. fill_layer
features for flat worlds). In
such cases, PlacementUtils.inlinePlaced
can be used to get a Holder
that contains a PlacedFeature
constructed
from the ConfiguredFeature
and PlacementModifiers
provided.
As an example, here's how fill_layer
placed features are inlined in Minecraft's flat level generator:
/* ... */
for (layer = 0; layer < layers.size(); ++layer) {
BlockState blockstate = layers.get(layer);
if (!Heightmap.Types.MOTION_BLOCKING.isOpaque().test(blockstate)) {
layers.set(layer, null);
builder.addFeature(GenerationStep.Decoration.TOP_LAYER_MODIFICATION, PlacementUtils.inlinePlaced(Feature.FILL_LAYER, new LayerConfiguration(layer, blockstate)));
}
}
/* ... */