API
Generation by code
When declaring isn't enough, your code builds the world as chunks are made: features, whole structures, the shape of the land, which biome goes where, caves and the blocks of the ground.
What you can write
| What | Kotlin | CraftScript |
|---|---|---|
| Features: spikes, plants on walls, boulders, anything placed in a chunk | feature("…") { place { } } |
No |
| Structures built by code, across several chunks | structure("…") { plan { }; build { } } |
Yes (plan, build) |
| The land of a dimension | terrain("minecraft:the_end") { } |
Yes (terrain) |
| Which biome goes where | biomes("minecraft:the_end") { } |
Yes (biome_map) |
| Carvers: caves and tunnels | carver("…", chance) { } |
Yes (carver) |
| A biome's ground, block by block | biome("…") { surface { } } |
Yes (surface: fun(at)) |
| Tags of blocks, items and biomes | tag(TagKind.BIOME, "…", …) |
No |
Everything on this page runs while chunks are being made, on several threads at once. Use the random numbers you are given (or the world's seed) rather than your own: the same seed then makes the same world. In Kotlin, keep anything your code shares between calls safe for threads. In CraftScript, the random members aren't available; take variety from the coordinates, the seed or the structure's site.
Features
A feature is something your code builds in a chunk being made, in the biomes you list. Crafting picks the places, as the game does for its own features; your place function builds there and returns true when it built something. Features are written in Kotlin.
object CrystalSpikes {
private val CRYSTAL = Identifier.parse("mymod:crystal")
fun register(content: ContentBuilder) {
content.feature("crystal_spike") {
biome("#minecraft:is_overworld")
count = 2..5
heights = -54..30
on = FeatureOn.FLOOR
underground = true
step = FeatureStep.UNDERGROUND
place { world ->
val (x, y, z) = Triple(world.x, world.y, world.z)
if (!world.isIn(x, y - 1, z, "#minecraft:base_stone_overworld")) return@place false
val height = 2 + world.random.nextInt(4)
for (dy in 0 until height) {
if (!world.isAir(x, y + dy, z)) break
world.set(x, y + dy, z, CRYSTAL)
}
true
}
}
}
}| Option | Values | Default |
|---|---|---|
biome(…) |
biomes where it is placed, by id or #tag; at least one |
— |
count |
tries per chunk, a range: 2..5 |
1..1 |
chance |
only in one of every this many chunks | 1 |
heights |
each try at a random height in this range | null: the world's bottom (your code finds its place) |
spread |
true: each try at a random column of the chunk; false: at the chunk's corner, and your code picks from there |
true |
on |
FeatureOn.FLOOR or CEILING: from its height, it moves to the air beside the nearest floor or ceiling, up to 32 blocks away; with none, it isn't placed |
null |
underground |
true: only under the surface, at least 2 blocks below it (in caves) |
false |
everyFloor |
true: on every floor of each column picked, the surface and each cave's floor; count is the columns per floor |
false |
tries |
with everyFloor, how many times the floors are gone through: each time count more columns on each floor, so a deep land is reached by more tries |
1..1 |
step |
when it is made, among the steps that fill a chunk: LAKES, SURFACE, ORES, UNDERGROUND (what decorates caves), VEGETATION |
VEGETATION |
With everyFloor, chance, heights and spread aren't used: the places are the floors themselves.
content.feature("glow_tufts") {
biome("mymod:crystal_glade")
everyFloor = true
count = 4..8
tries = 1..3
place { world ->
val tuft = Identifier.parse("mymod:glow_tuft")
if (world.structures(world.x, world.z).isNotEmpty()) return@place false
world.canStay(world.x, world.y, world.z, tuft) && world.set(world.x, world.y, world.z, tuft)
}
}What a feature sees
place gets the world being made, as seen from where the feature is placed. It may read and set the chunk being made and the chunks around it; setting outside them is skipped. In CraftScript (in a structure's build) the same members are written in snake_case.
| Member | What it is |
|---|---|
x, y, z |
where it is placed |
random |
its own random numbers: the same seed makes the same feature |
seed |
the world's seed, for a noise that is the same over the whole world |
minY, maxY |
the world's heights; blocks go from minY up to below maxY |
block(x, y, z), state(x, y, z, name), isAir(x, y, z) |
the block there, the value of one of its states, whether it is air |
isIn(x, y, z, tag) |
whether the block there is in a tag ("#minecraft:leaves") or is that block ("minecraft:end_stone") |
canStay(x, y, z, block, states) |
whether a block, with those states, could stay there: a plant on its ground, a moss on a wall facing away from it. states can be left out |
set(x, y, z, block, states) |
sets a block, with states such as mapOf("facing" to "north"); false when it is outside what may be set, or isn't a block here |
inside(x, z) |
whether the column is one it may read and set |
surface(x, z) |
the first block above the highest one that isn't air, as the land was before features |
top(x, z) |
the same, now, with what features have set since |
flow(x, y, z) |
lets the water (or lava, or a block holding water) there flow once the chunk is in the world. Water set while a chunk is made stays still, and would hang beside air without this |
structures(x, z) |
the structures (ids, like minecraft:end_city) whose area takes in that column's chunk, to keep a feature out of them |
placeFile(file, x, y, z, rotation, mirror, pivotX, pivotZ) |
places a structure file; see Structure files from code |
A block your feature names that doesn't exist (from a mod that isn't installed) is left out, with one warning in the log; set returns false for it.
Structures built by code
For a structure that isn't made of files, or that has to follow the land, your code decides where it is and builds it. It takes two functions:
planis asked, in each chunk where the world would place one (bybiomes,spacingandseparation, as for any structure), whether it is there, before the chunk's blocks are made. It returns its site: its middle, how far it reaches and whatever the build needs; or nothing, and there is none there.buildbuilds the part of the site in each chunk it covers, one chunk at a time. Every chunk gets the same site and the same random numbers, so each builds its piece of the same structure. The site is kept with the world, so chunks made after a restart still match.
structure "stone_ring" {
biomes: ["#minecraft:is_overworld"]
spacing: 32
separation: 12
plan: fun(p) {
let x = p.chunk_x * 16 + 8
let z = p.chunk_z * 16 + 8
let y = p.height(x, z)
return { x: x, y: y, z: z, radius: 10, min_y: y - 8, max_y: y + 12 }
}
build: fun(world, site) {
for i in 0..15 {
let a = i * pi() / 8
let x = site.x + round(cos(a) * 10)
let z = site.z + round(sin(a) * 10)
if world.inside(x, z) {
let ground = world.surface(x, z)
for dy in 0..2 { world.set(x, ground + dy, z, "minecraft:mossy_cobblestone") }
}
}
}
}content.structure("stone_ring") {
biome("#minecraft:is_overworld")
spacing = 32
separation = 12
plan { p ->
val x = p.chunkX * 16 + 8
val z = p.chunkZ * 16 + 8
val y = p.height(x, z)
StructureSite(x, y, z, radius = 10, minY = y - 8, maxY = y + 12,
data = mapOf("tall" to p.random.nextBoolean()))
}
build { world, site ->
val stone = Identifier.parse("minecraft:mossy_cobblestone")
val top = if (site.data["tall"] == true) 4 else 2
for (i in 0 until 16) {
val x = site.x + (cos(i * PI / 8) * 10).roundToInt()
val z = site.z + (sin(i * PI / 8) * 10).roundToInt()
if (!world.inside(x, z)) continue
val ground = world.surface(x, z)
for (dy in 0..top) world.set(x, ground + dy, z, stone)
}
}
}The site (StructureSite in Kotlin, a map in CraftScript):
| Field | What it is | Default in CraftScript |
|---|---|---|
x, y, z |
its middle | 0 |
radius |
how many blocks around the middle it reaches: the chunks it covers | 8 |
min_y, max_y (minY, maxY) |
the heights it covers | y minus and plus radius |
data |
what the build needs to build the same in each chunk: numbers, text, true or false | empty |
In Kotlin, a site also has a seed made from its position. Numbers in data come back as decimals after a restart: read them with (site.data["n"] as Number).toInt().
What plan gets, before any block of the chunk is made:
| Member | What it is |
|---|---|
chunkX, chunkZ |
the chunk it is asked for (block / 16) |
seed, random |
the world's seed, and this place's own random numbers |
height(x, z) |
the height the surface will have there (the first block above it) |
biome(x, y, z) |
the biome there |
column(x, z), minY |
the blocks the land will have in that column, from minY up to the world's top, before its ground, caves and anything built (in the End: end stone and air) |
fileSize(file) |
the size of a structure file (x, y, z), or null if there is none |
build gets the same world as a feature, but it may only set blocks of the chunk being built, and its random comes from the site. The other options of a structure that apply here are biomes, spacing, separation and, in Kotlin, salt. A structure of code needs both plan and build. Find it with /locate structure, like any other.
Structure files from code
placeFile places a structure file where your code says, turned and mirrored: a tower from your files inside a structure of code, or a ruin a feature drops in a cave. The file is the mod's structures/<path>.nbt ("mymod:watchtower") or one of the game's. Only the part inside what the world may set is placed, so a structure that covers several chunks places its file from each of them. Structure blocks in the file aren't placed. It returns false if there is no such file.
val TOWER = Identifier.parse("mymod:watchtower")
content.structure("watchtower") {
biome("minecraft:plains")
spacing = 40
separation = 10
plan { p ->
val size = p.fileSize(TOWER) ?: return@plan null
val x = p.chunkX * 16 + 8
val z = p.chunkZ * 16 + 8
val y = p.height(x, z)
StructureSite(x, y, z, radius = maxOf(size.x, size.z), minY = y, maxY = y + size.y,
data = mapOf("turns" to p.random.nextInt(4), "half_x" to size.x / 2, "half_z" to size.z / 2))
}
build { world, site ->
val hx = (site.data["half_x"] as Number).toInt()
val hz = (site.data["half_z"] as Number).toInt()
// Its middle on the site, turned about its middle.
world.placeFile(TOWER, site.x - hx, site.y, site.z - hz,
rotation = (site.data["turns"] as Number).toInt(), pivotX = hx, pivotZ = hz)
}
}| Parameter | Values | Default |
|---|---|---|
file |
the file's id | — |
x, y, z |
where its corner goes, before turning | — |
rotation |
quarter turns clockwise | 0 |
mirror |
Mirror.NONE, LEFT_RIGHT (along z) or FRONT_BACK (along x) |
NONE |
pivotX, pivotZ |
the block of the file it turns about | 0, 0 |
The land of a dimension
You can give the shape of the land of one of the game's dimensions (minecraft:overworld, minecraft:the_nether or minecraft:the_end): floating islands, rolling hills, a world of pillars. Your shape says how solid each spot is: above 0 is land, below 0 is air. The land is made of the dimension's usual stone, and its biomes' ground goes on top.
// Rolling hills over the whole overworld.
terrain "minecraft:overworld" {
column: fun(x, z, ys, world) {
let ground = 72 + 14 * sin(x / 60) * cos(z / 60)
return ys.map(fun(y) { return (ground - y) / 8 })
}
has_land: fun(x, z, world) { return true }
}// Round, flattened islands in the End, one every 96 blocks.
content.terrain("minecraft:the_end") { world ->
val shift = kotlin.random.Random(world.seed).nextInt(96)
object : TerrainShape {
fun across(x: Int, z: Int): Double {
val dx = (x + shift).mod(96) - 48
val dz = (z + shift).mod(96) - 48
return sqrt((dx * dx + dz * dz).toDouble())
}
override fun column(x: Int, z: Int, ys: IntArray, out: DoubleArray) {
val flat = across(x, z)
for (i in ys.indices) {
val up = (ys[i] - 64) * 2.5
out[i] = (24 - sqrt(flat * flat + up * up)) / 24
}
}
override fun hasLand(x: Int, z: Int) = across(x, z) < 24
}
}| Member | What it is |
|---|---|
column(x, z, ys, out) |
how solid column x, z is at each height of ys, into out in the same order. In CraftScript, column(x, z, ys, world) returns the list |
hasLand(x, z) (has_land) |
whether the column has any land. In the End it decides between land biomes and the void's |
world.seed |
the world's seed |
world.minY, world.height |
the heights the land is made in: from minY, height blocks up |
world.biome(x, y, z) |
the biome at a block |
The game asks your shape at the corners of cells a few blocks wide and blends between them, so it doesn't need to be exact block by block. In Kotlin your function is called once per world with its seed, and returns the shape. Your shape makes all of the land: the game's own noise caves aren't made (add a carver), and in the End the central island is yours to make too. Only one mod may shape a dimension. A shape that fails is told once in the log and leaves no land there, not a crash.
Dimensions of your own are declared on the Dimensions page with one of the game's terrains.
Which biome goes where
By default, a biome takes a share of the places of the one it replaces (see World generation). For a map of your own, your code picks the biome of each spot. For now this is for the End (minecraft:the_end).
biome_map "minecraft:the_end" {
biome: fun(x, y, z, land, world) {
if x * x + z * z < 65536 { return "minecraft:the_end" } // the central island
if land == "minecraft:end_highlands" and (floor(x / 64) + floor(z / 64)) % 2 == 0 {
return "mymod:crystal_glade"
}
return land
}
}content.tag(TagKind.BIOME, "glades", "crystal_glade", "ember_glade")
content.biomes("minecraft:the_end") { world ->
val glades = world.tagged("mymod:glades")
val theEnd = Identifier.parse("minecraft:the_end")
BiomeMap { x, y, z, land ->
when {
x.toLong() * x + z.toLong() * z < 256L * 256 -> theEnd // within 1024 blocks
land.path != "end_highlands" || glades.isEmpty() -> land
else -> glades[Math.floorMod((x shr 6) * 31 + (z shr 6), glades.size)]
}
}
}The function gets x, y, z in quarts (the game keeps one biome per 4×4×4 blocks: a quart is block / 4) and land, the kind of land the game's noise makes there, as a biome: minecraft:end_highlands, end_midlands, end_barrens or small_end_islands. With a shape of your own, end_highlands on its land and small_end_islands off it. You also get land on the central island, which your map places itself: the game puts minecraft:the_end within 1024 blocks of the middle.
The answer must be a biome of the End: the game's, or one of yours declared with replaces set to an End biome. Any other answer, or a map that fails, falls back to the game's pick (told once in the log). The map's world has the seed and tagged(tag), the biomes in a biome tag. Only one mod may pick a dimension's biomes.
Carvers
A carver hollows caves out of the land once it is made, before its ground and features. For each chunk being made, the game goes through the chunks around it (8 to each side); in each, every carver of that chunk's biome starts with its chance (0–1), and your function marks what it hollows of the chunk being made. Hollowed blocks become air (or water under an aquifer), and the ground is made again over them. A biome uses a carver by naming it in carvers.
// A round room, 6 blocks across, in some chunks.
carver "round_caves" {
chance: 0.05
carve: fun(ctx) {
let cx = ctx.start_x * 16 + 8
let cz = ctx.start_z * 16 + 8
let cy = 20
for x in cx - 6..cx + 6 {
for y in cy - 6..cy + 6 {
for z in cz - 6..cz + 6 {
if (x - cx) * (x - cx) + (y - cy) * (y - cy) + (z - cz) * (z - cz) <= 36 { ctx.carve(x, y, z) }
}
}
}
}
}
biome "hollow_hills" {
replaces: "minecraft:meadow"
carvers: ["round_caves"]
}content.carver("round_caves", chance = 0.05) { ctx ->
val r = ctx.random
val cx = ctx.startX * 16 + r.nextInt(16)
val cz = ctx.startZ * 16 + r.nextInt(16)
val cy = r.nextInt(ctx.minY + 10, 40)
val radius = r.nextInt(4, 9)
for (x in cx - radius..cx + radius) for (y in cy - radius..cy + radius) for (z in cz - radius..cz + radius) {
val (dx, dy, dz) = Triple(x - cx, y - cy, z - cz)
if (dx * dx + dy * dy * 2 + dz * dz <= radius * radius) ctx.carve(x, y, z)
}
}
content.biome("hollow_hills") {
replaces = "minecraft:meadow"
carver("round_caves")
}| Member | What it is |
|---|---|
startX, startZ |
the chunk where this cave starts (chunk coordinates: block / 16) |
chunkX, chunkZ |
the chunk being made |
minY, maxY |
the heights it may hollow, both included |
seed, random |
the world's seed, and the random numbers of this start: they draw the same in every chunk the start is asked for, so a cave crossing chunks lines up |
carve(x, y, z) |
hollows that block, in world coordinates; blocks outside the chunk being made are skipped |
A cave can reach at most 8 chunks from where it starts. In CraftScript, chance defaults to 1. A carver that fails hollows nothing there, told once in the log.
A biome's ground
surface covers a biome's ground with one block. For layers, patches or slopes, give it a function instead: while a chunk is made, the game asks it, for each block of the biome's ground from top to bottom, which block goes there. No answer leaves that block to the dimension (its stone).
biome "crystal_glade" {
replaces: "minecraft:end_highlands"
share: 0.3
surface: fun(at) {
if at.depth_above == 1 { return "mymod:crystal_moss" }
if at.depth_above <= 3 { return "mymod:crystal_soil" }
return null
}
}content.biome("crystal_glade") {
replaces = "minecraft:end_highlands"
share = 0.3
surface { at ->
val patch = at.noise("minecraft:surface", at.x.toDouble(), 0.0, at.z.toDouble()) > 0.2
when {
at.depthAbove == 1 && patch -> Identifier.parse("mymod:glow_moss")
at.depthAbove == 1 -> Identifier.parse("mymod:crystal_moss")
at.depthAbove <= 3 -> Identifier.parse("mymod:crystal_soil")
else -> null
}
}
}Member of at |
What it is |
|---|---|
x, y, z |
the block asked about |
depthAbove (depth_above) |
blocks of ground from the open air above down to this one: 1 for the top block of the ground or of a cave's floor |
depthBelow (depth_below) |
blocks of ground from this one down to the open air under it: 1 for the lowest one over a cave or the void |
surfaceDepth (surface_depth) |
how deep this column's surface layers go, as the game varies it |
steep |
whether the ground slopes as much as where the game's mountains show bare stone |
noise(id, x, y, z) |
one of the world's noises by name ("minecraft:surface"), as the seed makes it |
The function comes before surface: where it answers nothing, a surface block given too still covers the top. A function that fails is told once, and the dimension's ground is used where it fails.
Tags
Tags group blocks, items or biomes under one name, so a feature can test isIn(x, y, z, "#mymod:crystal_ground") and a list of biomes can say "#mymod:glades". A mod adds to its own tags or to anyone's, such as the game's:
// Blocks a crystal can grow on, yours and the game's.
content.tag(TagKind.BLOCK, "crystal_ground", "crystal_moss", "crystal_soil", "minecraft:end_stone")
// Biomes a feature or structure can name as "#mymod:glades".
content.tag(TagKind.BIOME, "glades", "crystal_glade", "ember_glade")
// Adding to the game's: a pickaxe mines it faster.
content.tag(TagKind.BLOCK, "minecraft:mineable/pickaxe", "crystal_soil")TagKind is BLOCK, ITEM or BIOME. Values are ids or #tags; names without a namespace are the mod's. Several mods may add to the same tag, and a value that doesn't exist is left out, not an error.
Good to know
- Only new chunks get what your code makes, as with everything in World generation.
- Errors don't stop the world. Code that throws leaves out what it was making there, and the error is written to the log.
- Keep it quick. This code runs for every chunk, thousands of times as players explore; avoid heavy work in it, and in CraftScript keep in mind the steps limit of the reference.