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.

content/CrystalSpikes.ktKotlin
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/GlowTufts.ktKotlin
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:

  • plan is asked, in each chunk where the world would place one (by biomes, spacing and separation, 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.
  • build builds 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.
content/stone_ring.craftCraftScript
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/StoneRing.ktKotlin
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.

content/Watchtower.ktKotlin
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.

content/hills.craftCraftScript
// 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 }
}
content/FloatingIslands.ktKotlin
// 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).

content/end_map.craftCraftScript
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/EndMap.ktKotlin
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.

content/round_caves.craftCraftScript
// 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/RoundCaves.ktKotlin
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).

content/crystal_glade.craftCraftScript
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/CrystalGlade.ktKotlin
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:

content/Tags.ktKotlin
// 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.