API

Generación por código

Cuando declarar no basta, tu código construye el mundo a medida que se crean los chunks: features, estructuras enteras, la forma del terreno, qué bioma va dónde, cuevas y los bloques del suelo.

Qué puedes escribir

Qué Kotlin CraftScript
Features: pinchos, plantas en paredes, rocas, cualquier cosa colocada en un chunk feature("…") { place { } } No
Estructuras construidas por código, repartidas en varios chunks structure("…") { plan { }; build { } } Sí (plan, build)
El terreno de una dimensión terrain("minecraft:the_end") { } Sí (terrain)
Qué bioma va dónde biomes("minecraft:the_end") { } Sí (biome_map)
Carvers: cuevas y túneles carver("…", chance) { } Sí (carver)
El suelo de un bioma, bloque a bloque biome("…") { surface { } } Sí (surface: fun(at))
Etiquetas de bloques, ítems y biomas tag(TagKind.BIOME, "…", …) No

Todo lo de esta página se ejecuta mientras se crean los chunks, en varios hilos a la vez. Usa los números aleatorios que te dan (o la semilla del mundo) en lugar de los tuyos: así la misma semilla crea el mismo mundo. En Kotlin, mantén seguro para hilos todo lo que tu código comparta entre llamadas. En CraftScript, los miembros de random no están disponibles; saca la variedad de las coordenadas, de la semilla o del sitio de la estructura.

Features

Una feature es algo que tu código construye en un chunk que se está creando, en los biomas que indiques. Crafting elige los lugares, como hace el juego con sus propias features; tu función place construye allí y devuelve true cuando construyó algo. Las features se escriben en 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
            }
        }
    }
}
Opción Valores Por defecto
biome(…) biomas donde se coloca, por id o #etiqueta; al menos uno —
count intentos por chunk, un rango: 2..5 1..1
chance solo en uno de cada tantos chunks 1
heights cada intento a una altura al azar dentro de este rango null: el fondo del mundo (tu código encuentra su lugar)
spread true: cada intento en una columna al azar del chunk; false: en la esquina del chunk, y tu código elige desde ahí true
on FeatureOn.FLOOR o CEILING: desde su altura, se mueve al aire junto al suelo o techo más cercano, hasta 32 bloques; si no hay ninguno, no se coloca null
underground true: solo bajo la superficie, al menos 2 bloques por debajo (en cuevas) false
everyFloor true: en cada suelo de cada columna elegida, la superficie y el suelo de cada cueva; count son las columnas por suelo false
tries con everyFloor, cuántas veces se recorren los suelos: cada vez count columnas más en cada suelo, así un terreno profundo se alcanza con más intentos 1..1
step cuándo se hace, entre los pasos que llenan un chunk: LAKES, SURFACE, ORES, UNDERGROUND (lo que decora las cuevas), VEGETATION VEGETATION

Con everyFloor, chance, heights y spread no se usan: los lugares son los propios suelos.

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)
    }
}

Lo que ve una feature

place recibe el mundo que se está creando, visto desde donde se coloca la feature. Puede leer y escribir el chunk que se está creando y los chunks de alrededor; lo que se escriba fuera de ellos se omite. En CraftScript (en el build de una estructura) los mismos miembros se escriben en snake_case.

Miembro Qué es
x, y, z dónde se coloca
random sus propios números aleatorios: la misma semilla crea la misma feature
seed la semilla del mundo, para un ruido que es el mismo en todo el mundo
minY, maxY las alturas del mundo; los bloques van desde minY hasta justo debajo de maxY
block(x, y, z), state(x, y, z, name), isAir(x, y, z) el bloque que hay allí, el valor de uno de sus estados, si es aire
isIn(x, y, z, tag) si el bloque que hay allí está en una etiqueta ("#minecraft:leaves") o es ese bloque ("minecraft:end_stone")
canStay(x, y, z, block, states) si un bloque, con esos estados, podría quedarse allí: una planta sobre su suelo, un musgo en una pared mirando hacia fuera de ella. states se puede omitir
set(x, y, z, block, states) coloca un bloque, con estados como mapOf("facing" to "north"); false cuando está fuera de lo que se puede escribir, o no es un bloque aquí
inside(x, z) si la columna es una que puede leer y escribir
surface(x, z) el primer bloque sobre el más alto que no es aire, tal como estaba el terreno antes de las features
top(x, z) lo mismo, ahora, con lo que han colocado las features desde entonces
flow(x, y, z) hace que el agua (o la lava, o un bloque que contiene agua) de allí fluya cuando el chunk esté en el mundo. El agua colocada mientras se crea un chunk se queda quieta, y sin esto quedaría colgando junto al aire
structures(x, z) las estructuras (ids, como minecraft:end_city) cuya área abarca el chunk de esa columna, para mantener una feature fuera de ellas
placeFile(file, x, y, z, rotation, mirror, pivotX, pivotZ) coloca un archivo de estructura; ver Archivos de estructura desde el código

Un bloque que tu feature nombra y que no existe (de un mod que no está instalado) se omite, con un aviso en el log; set devuelve false para él.

Estructuras construidas por código

Para una estructura que no está hecha de archivos, o que tiene que seguir el terreno, tu código decide dónde está y la construye. Usa dos funciones:

  • plan se consulta, en cada chunk donde el mundo colocaría una (según biomes, spacing y separation, como en cualquier estructura), si está allí, antes de que se creen los bloques del chunk. Devuelve su sitio: su centro, hasta dónde llega y lo que necesite la construcción; o nada, y allí no hay ninguna.
  • build construye la parte del sitio en cada chunk que cubre, un chunk cada vez. Cada chunk recibe el mismo sitio y los mismos números aleatorios, así que cada uno construye su trozo de la misma estructura. El sitio se guarda con el mundo, así que los chunks creados tras un reinicio siguen encajando.
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)
        }
    }
}

El sitio (StructureSite en Kotlin, un mapa en CraftScript):

Campo Qué es Por defecto en CraftScript
x, y, z su centro 0
radius cuántos bloques alrededor del centro alcanza: los chunks que cubre 8
min_y, max_y (minY, maxY) las alturas que cubre y menos y más radius
data lo que necesita la construcción para construir lo mismo en cada chunk: números, texto, verdadero o falso vacío

En Kotlin, un sitio también tiene una seed hecha a partir de su posición. Los números de data vuelven como decimales tras un reinicio: léelos con (site.data["n"] as Number).toInt().

Lo que recibe plan, antes de que se cree ningún bloque del chunk:

Miembro Qué es
chunkX, chunkZ el chunk por el que se pregunta (bloque / 16)
seed, random la semilla del mundo, y los números aleatorios propios de este lugar
height(x, z) la altura que tendrá la superficie allí (el primer bloque sobre ella)
biome(x, y, z) el bioma que hay allí
column(x, z), minY los bloques que tendrá el terreno en esa columna, desde minY hasta lo más alto del mundo, antes de su suelo, sus cuevas y lo que se construya (en el End: piedra del End y aire)
fileSize(file) el tamaño de un archivo de estructura (x, y, z), o null si no existe

build recibe el mismo mundo que una feature, pero solo puede colocar bloques del chunk que se está construyendo, y su random sale del sitio. Las demás opciones de una estructura que se aplican aquí son biomes, spacing, separation y, en Kotlin, salt. Una estructura de código necesita tanto plan como build. Encuéntrala con /locate structure, como cualquier otra.

Archivos de estructura desde el código

placeFile coloca un archivo de estructura donde tu código diga, girado y reflejado: una torre de tus archivos dentro de una estructura de código, o unas ruinas que una feature deja en una cueva. El archivo es el structures/<ruta>.nbt del mod ("mymod:watchtower") o uno del juego. Solo se coloca la parte que queda dentro de lo que el mundo permite escribir, así que una estructura que cubre varios chunks coloca su archivo desde cada uno de ellos. Los bloques de estructura del archivo no se colocan. Devuelve false si no existe ese archivo.

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)
    }
}
Parámetro Valores Por defecto
file el id del archivo —
x, y, z dónde va su esquina, antes de girarlo —
rotation cuartos de vuelta en sentido horario 0
mirror Mirror.NONE, LEFT_RIGHT (a lo largo de z) o FRONT_BACK (a lo largo de x) NONE
pivotX, pivotZ el bloque del archivo alrededor del cual gira 0, 0

El terreno de una dimensión

Puedes dar la forma del terreno de una de las dimensiones del juego (minecraft:overworld, minecraft:the_nether o minecraft:the_end): islas flotantes, colinas suaves, un mundo de pilares. Tu forma dice cuán sólido es cada punto: por encima de 0 es tierra, por debajo de 0 es aire. El terreno se hace con la piedra habitual de la dimensión, y el suelo de sus biomas va encima.

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
    }
}
Miembro Qué es
column(x, z, ys, out) cuán sólida es la columna x, z a cada altura de ys, en out en el mismo orden. En CraftScript, column(x, z, ys, world) devuelve la lista
hasLand(x, z) (has_land) si la columna tiene algo de tierra. En el End decide entre los biomas de tierra y los del vacío
world.seed la semilla del mundo
world.minY, world.height las alturas en que se hace el terreno: desde minY, height bloques hacia arriba
world.biome(x, y, z) el bioma en un bloque

El juego pregunta tu forma en las esquinas de celdas de unos pocos bloques de ancho y mezcla entre ellas, así que no necesita ser exacta bloque a bloque. En Kotlin tu función se llama una vez por mundo con su semilla, y devuelve la forma. Tu forma crea todo el terreno: las cuevas de ruido del propio juego no se crean (añade un carver), y en el End la isla central también te toca crearla a ti. Solo un mod puede dar forma a una dimensión. Una forma que falla se avisa una vez en el log y no deja tierra allí, sin cerrar el juego.

Las dimensiones propias se declaran en la página Dimensiones con uno de los terrenos del juego.

Qué bioma va dónde

Por defecto, un bioma se queda con una parte (share) de los lugares del que reemplaza (ver Generación de mundo). Para un mapa propio, tu código elige el bioma de cada punto. Por ahora esto es para el 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)]
        }
    }
}

La función recibe x, y, z en cuartos (el juego guarda un bioma por cada 4×4×4 bloques: un cuarto es bloque / 4) y land, el tipo de tierra que hace allí el ruido del juego, como un bioma: minecraft:end_highlands, end_midlands, end_barrens o small_end_islands. Con una forma propia, end_highlands sobre su tierra y small_end_islands fuera de ella. También recibes land en la isla central, que tu mapa coloca por sí mismo: el juego pone minecraft:the_end a menos de 1024 bloques del centro.

La respuesta debe ser un bioma del End: del juego, o uno tuyo declarado con replaces apuntando a un bioma del End. Cualquier otra respuesta, o un mapa que falla, vuelve a la elección del juego (se avisa una vez en el log). El world del mapa tiene la seed y tagged(tag), los biomas de una etiqueta de biomas. Solo un mod puede elegir los biomas de una dimensión.

Carvers

Un carver excava cuevas en el terreno una vez creado, antes de su suelo y de las features. Para cada chunk que se crea, el juego recorre los chunks de alrededor (8 a cada lado); en cada uno, cada carver del bioma de ese chunk empieza con su chance (0–1), y tu función marca lo que excava del chunk que se está creando. Los bloques excavados pasan a ser aire (o agua bajo un acuífero), y el suelo se vuelve a hacer sobre ellos. Un bioma usa un carver nombrándolo en 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")
}
Miembro Qué es
startX, startZ el chunk donde empieza esta cueva (coordenadas de chunk: bloque / 16)
chunkX, chunkZ el chunk que se está creando
minY, maxY las alturas que puede excavar, ambas incluidas
seed, random la semilla del mundo, y los números aleatorios de este comienzo: salen igual en cada chunk por el que se pregunta el comienzo, así que una cueva que cruza chunks encaja
carve(x, y, z) excava ese bloque, en coordenadas del mundo; los bloques fuera del chunk que se está creando se omiten

Una cueva puede llegar como mucho a 8 chunks de donde empieza. En CraftScript, chance vale 1 por defecto. Un carver que falla no excava nada allí, y se avisa una vez en el log.

El suelo de un bioma

surface cubre el suelo de un bioma con un bloque. Para capas, manchas o pendientes, dale una función en su lugar: mientras se crea un chunk, el juego se la pregunta, para cada bloque del suelo del bioma de arriba abajo, qué bloque va allí. Si no responde nada, ese bloque se deja a la dimensión (su piedra).

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
        }
    }
}
Miembro de at Qué es
x, y, z el bloque por el que se pregunta
depthAbove (depth_above) bloques de suelo desde el aire libre de arriba hasta este: 1 para el bloque superior del suelo o del suelo de una cueva
depthBelow (depth_below) bloques de suelo desde este hasta el aire libre de debajo: 1 para el más bajo sobre una cueva o el vacío
surfaceDepth (surface_depth) cuán profundas llegan las capas de superficie de esta columna, según las varía el juego
steep si el suelo tiene tanta pendiente como donde las montañas del juego muestran piedra desnuda
noise(id, x, y, z) uno de los ruidos del mundo por su nombre ("minecraft:surface"), tal como lo hace la semilla

La función va antes que surface: donde no responde nada, un bloque de surface también dado sigue cubriendo la parte superior. Una función que falla se avisa una vez, y donde falla se usa el suelo de la dimensión.

Etiquetas

Las etiquetas agrupan bloques, ítems o biomas bajo un solo nombre, para que una feature pueda comprobar isIn(x, y, z, "#mymod:crystal_ground") y una lista de biomas pueda decir "#mymod:glades". Un mod añade a sus propias etiquetas o a las de cualquiera, como las del juego:

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 es BLOCK, ITEM o BIOME. Los valores son ids o #etiquetas; los nombres sin espacio de nombres son del mod. Varios mods pueden añadir a la misma etiqueta, y un valor que no existe se omite, no es un error.

Lo que conviene saber

  • Solo los chunks nuevos reciben lo que crea tu código, como todo en Generación de mundo.
  • Los errores no detienen el mundo. El código que lanza una excepción deja fuera lo que estaba creando allí, y el error se escribe en el log.
  • Hazlo rápido. Este código se ejecuta en cada chunk, miles de veces mientras los jugadores exploran; evita el trabajo pesado en él, y en CraftScript ten presente el límite de pasos de la referencia.