API

Geração por código

Quando declarar não basta, o seu código constrói o mundo conforme os chunks são criados: features, estruturas inteiras, o formato do terreno, qual bioma vai onde, cavernas e os blocos do chão.

O que você pode escrever

O quê Kotlin CraftScript
Features: espetos, plantas em paredes, rochas, qualquer coisa colocada em um chunk feature("…") { place { } } Não
Estruturas construídas por código, espalhadas por vários chunks structure("…") { plan { }; build { } } Sim (plan, build)
O terreno de uma dimensão terrain("minecraft:the_end") { } Sim (terrain)
Qual bioma vai onde biomes("minecraft:the_end") { } Sim (biome_map)
Carvers: cavernas e túneis carver("…", chance) { } Sim (carver)
O chão de um bioma, bloco a bloco biome("…") { surface { } } Sim (surface: fun(at))
Tags de blocos, itens e biomas tag(TagKind.BIOME, "…", …) Não

Tudo nesta página roda enquanto os chunks são criados, em várias threads ao mesmo tempo. Use os números aleatórios que você recebe (ou a seed do mundo) em vez dos seus: assim a mesma seed cria o mesmo mundo. Em Kotlin, mantenha seguro para threads tudo o que o seu código compartilha entre chamadas. Em CraftScript, os membros de random não estão disponíveis; tire a variedade das coordenadas, da seed ou do local da estrutura.

Features

Uma feature é algo que o seu código constrói em um chunk que está sendo criado, nos biomas que você listar. O Crafting escolhe os lugares, como o jogo faz com as suas próprias features; a sua função place constrói ali e retorna true quando construiu algo. As features são escritas em 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
            }
        }
    }
}
Opção Valores Padrão
biome(…) biomas onde ela é colocada, por id ou #tag; pelo menos um —
count tentativas por chunk, um intervalo: 2..5 1..1
chance só em um a cada tantos chunks 1
heights cada tentativa a uma altura aleatória dentro deste intervalo null: o fundo do mundo (o seu código encontra o lugar)
spread true: cada tentativa em uma coluna aleatória do chunk; false: no canto do chunk, e o seu código escolhe a partir dali true
on FeatureOn.FLOOR ou CEILING: a partir da sua altura, ela vai para o ar ao lado do chão ou teto mais próximo, até 32 blocos de distância; se não houver nenhum, não é colocada null
underground true: só abaixo da superfície, pelo menos 2 blocos abaixo dela (em cavernas) false
everyFloor true: em cada chão de cada coluna escolhida, a superfície e o chão de cada caverna; count são as colunas por chão false
tries com everyFloor, quantas vezes os chãos são percorridos: a cada vez, mais count colunas em cada chão, de modo que um terreno profundo é alcançado com mais tentativas 1..1
step quando ela é feita, entre as etapas que preenchem um chunk: LAKES, SURFACE, ORES, UNDERGROUND (o que decora as cavernas), VEGETATION VEGETATION

Com everyFloor, chance, heights e spread não são usados: os lugares são os próprios chãos.

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

O que uma feature enxerga

place recebe o mundo que está sendo criado, visto de onde a feature é colocada. Ela pode ler e escrever o chunk que está sendo criado e os chunks ao redor; o que for escrito fora deles é ignorado. Em CraftScript (no build de uma estrutura), os mesmos membros são escritos em snake_case.

Membro O que é
x, y, z onde ela é colocada
random os seus próprios números aleatórios: a mesma seed cria a mesma feature
seed a seed do mundo, para um ruído que é o mesmo em todo o mundo
minY, maxY as alturas do mundo; os blocos vão de minY até logo abaixo de maxY
block(x, y, z), state(x, y, z, name), isAir(x, y, z) o bloco que está ali, o valor de um dos seus estados, se é ar
isIn(x, y, z, tag) se o bloco que está ali pertence a uma tag ("#minecraft:leaves") ou é esse bloco ("minecraft:end_stone")
canStay(x, y, z, block, states) se um bloco, com esses estados, poderia ficar ali: uma planta sobre o seu chão, um musgo numa parede voltado para longe dela. states pode ser omitido
set(x, y, z, block, states) coloca um bloco, com estados como mapOf("facing" to "north"); false quando está fora do que pode ser escrito, ou não é um bloco aqui
inside(x, z) se a coluna é uma que ela pode ler e escrever
surface(x, z) o primeiro bloco acima do mais alto que não é ar, como o terreno estava antes das features
top(x, z) o mesmo, agora, com o que as features colocaram desde então
flow(x, y, z) faz a água (ou a lava, ou um bloco que contém água) dali fluir quando o chunk estiver no mundo. A água colocada enquanto um chunk é criado fica parada, e sem isso ficaria pendurada ao lado do ar
structures(x, z) as estruturas (ids, como minecraft:end_city) cuja área abrange o chunk dessa coluna, para manter uma feature fora delas
placeFile(file, x, y, z, rotation, mirror, pivotX, pivotZ) coloca um arquivo de estrutura; veja Arquivos de estrutura a partir do código

Um bloco que a sua feature nomeia e que não existe (de um mod que não está instalado) é ignorado, com um aviso no log; set retorna false para ele.

Estruturas construídas por código

Para uma estrutura que não é feita de arquivos, ou que precisa seguir o terreno, o seu código decide onde ela está e a constrói. Ela usa duas funções:

  • plan é consultada, em cada chunk onde o mundo colocaria uma (por biomes, spacing e separation, como em qualquer estrutura), sobre se ela está ali, antes de os blocos do chunk serem criados. Ela retorna o seu local (site): o seu meio, até onde alcança e o que a construção precisar; ou nada, e não há nenhuma ali.
  • build constrói a parte do local em cada chunk que ele cobre, um chunk de cada vez. Cada chunk recebe o mesmo local e os mesmos números aleatórios, então cada um constrói o seu pedaço da mesma estrutura. O local é guardado com o mundo, então os chunks criados depois de uma reinicialização continuam combinando.
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)
        }
    }
}

O local (StructureSite em Kotlin, um mapa em CraftScript):

Campo O que é Padrão em CraftScript
x, y, z o seu meio 0
radius quantos blocos ao redor do meio ele alcança: os chunks que cobre 8
min_y, max_y (minY, maxY) as alturas que cobre y menos e mais radius
data o que a construção precisa para construir o mesmo em cada chunk: números, texto, verdadeiro ou falso vazio

Em Kotlin, um local também tem uma seed feita a partir da sua posição. Os números em data voltam como decimais depois de uma reinicialização: leia-os com (site.data["n"] as Number).toInt().

O que plan recebe, antes de qualquer bloco do chunk ser criado:

Membro O que é
chunkX, chunkZ o chunk sobre o qual se pergunta (bloco / 16)
seed, random a seed do mundo, e os números aleatórios próprios deste lugar
height(x, z) a altura que a superfície terá ali (o primeiro bloco acima dela)
biome(x, y, z) o bioma que está ali
column(x, z), minY os blocos que o terreno terá nessa coluna, de minY até o topo do mundo, antes do seu chão, das suas cavernas e de qualquer coisa construída (no End: end stone e ar)
fileSize(file) o tamanho de um arquivo de estrutura (x, y, z), ou null se ele não existir

build recebe o mesmo mundo que uma feature, mas só pode colocar blocos do chunk que está sendo construído, e o seu random vem do local. As outras opções de uma estrutura que valem aqui são biomes, spacing, separation e, em Kotlin, salt. Uma estrutura de código precisa de plan e de build. Encontre-a com /locate structure, como qualquer outra.

Arquivos de estrutura a partir do código

placeFile coloca um arquivo de estrutura onde o seu código mandar, girado e espelhado: uma torre dos seus arquivos dentro de uma estrutura de código, ou uma ruína que uma feature deixa numa caverna. O arquivo é o structures/<caminho>.nbt do mod ("mymod:watchtower") ou um do jogo. Só é colocada a parte que fica dentro do que o mundo permite escrever, então uma estrutura que cobre vários chunks coloca o seu arquivo a partir de cada um deles. Os blocos de estrutura do arquivo não são colocados. Retorna false se não houver esse arquivo.

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 Padrão
file o id do arquivo —
x, y, z onde vai o seu canto, antes de girar —
rotation quartos de volta no sentido horário 0
mirror Mirror.NONE, LEFT_RIGHT (ao longo de z) ou FRONT_BACK (ao longo de x) NONE
pivotX, pivotZ o bloco do arquivo em torno do qual ele gira 0, 0

O terreno de uma dimensão

Você pode dar o formato do terreno de uma das dimensões do jogo (minecraft:overworld, minecraft:the_nether ou minecraft:the_end): ilhas flutuantes, colinas suaves, um mundo de pilares. A sua forma diz quão sólido é cada ponto: acima de 0 é terra, abaixo de 0 é ar. O terreno é feito da pedra habitual da dimensão, e o chão dos seus biomas vai por cima.

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
    }
}
Membro O que é
column(x, z, ys, out) quão sólida é a coluna x, z em cada altura de ys, em out na mesma ordem. Em CraftScript, column(x, z, ys, world) retorna a lista
hasLand(x, z) (has_land) se a coluna tem alguma terra. No End, decide entre os biomas de terra e os do vazio
world.seed a seed do mundo
world.minY, world.height as alturas em que o terreno é feito: a partir de minY, height blocos para cima
world.biome(x, y, z) o bioma em um bloco

O jogo consulta a sua forma nos cantos de células de poucos blocos de largura e mistura entre elas, então ela não precisa ser exata bloco a bloco. Em Kotlin, a sua função é chamada uma vez por mundo com a sua seed, e retorna a forma. A sua forma faz todo o terreno: as cavernas de ruído do próprio jogo não são criadas (adicione um carver), e no End a ilha central também fica por sua conta. Só um mod pode dar forma a uma dimensão. Uma forma que falha é avisada uma vez no log e não deixa terra ali, sem derrubar o jogo.

Dimensões próprias são declaradas na página Dimensões com um dos terrenos do jogo.

Qual bioma vai onde

Por padrão, um bioma fica com uma parte (share) dos lugares daquele que ele substitui (veja Geração de mundo). Para um mapa próprio, o seu código escolhe o bioma de cada ponto. Por enquanto isso vale para o 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)]
        }
    }
}

A função recebe x, y, z em quartos (o jogo guarda um bioma a cada 4×4×4 blocos: um quarto é bloco / 4) e land, o tipo de terra que o ruído do jogo faz ali, como um bioma: minecraft:end_highlands, end_midlands, end_barrens ou small_end_islands. Com uma forma própria, end_highlands sobre a sua terra e small_end_islands fora dela. Você também recebe land na ilha central, que o seu mapa coloca por conta própria: o jogo põe minecraft:the_end a até 1024 blocos do meio.

A resposta precisa ser um bioma do End: do jogo, ou um seu declarado com replaces apontando para um bioma do End. Qualquer outra resposta, ou um mapa que falha, volta à escolha do jogo (avisado uma vez no log). O world do mapa tem a seed e tagged(tag), os biomas de uma tag de biomas. Só um mod pode escolher os biomas de uma dimensão.

Carvers

Um carver escava cavernas no terreno depois de ele ser criado, antes do seu chão e das features. Para cada chunk que está sendo criado, o jogo percorre os chunks ao redor (8 de cada lado); em cada um, cada carver do bioma desse chunk começa com a sua chance (0–1), e a sua função marca o que ele escava do chunk que está sendo criado. Os blocos escavados viram ar (ou água sob um aquífero), e o chão é feito de novo por cima deles. Um bioma usa um carver nomeando-o em 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")
}
Membro O que é
startX, startZ o chunk onde esta caverna começa (coordenadas de chunk: bloco / 16)
chunkX, chunkZ o chunk que está sendo criado
minY, maxY as alturas que ele pode escavar, ambas incluídas
seed, random a seed do mundo, e os números aleatórios deste começo: eles saem iguais em cada chunk para o qual o começo é consultado, então uma caverna que cruza chunks se alinha
carve(x, y, z) escava esse bloco, em coordenadas do mundo; blocos fora do chunk que está sendo criado são ignorados

Uma caverna pode alcançar no máximo 8 chunks de onde começa. Em CraftScript, chance vale 1 por padrão. Um carver que falha não escava nada ali, avisado uma vez no log.

O chão de um bioma

surface cobre o chão de um bioma com um bloco. Para camadas, manchas ou encostas, dê a ele uma função: enquanto um chunk é criado, o jogo pergunta a ela, para cada bloco do chão do bioma de cima para baixo, qual bloco vai ali. Se ela não responder nada, aquele bloco fica para a dimensão (a sua pedra).

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
        }
    }
}
Membro de at O que é
x, y, z o bloco sobre o qual se pergunta
depthAbove (depth_above) blocos de chão desde o ar livre acima até este: 1 para o bloco do topo do chão ou do chão de uma caverna
depthBelow (depth_below) blocos de chão deste até o ar livre embaixo: 1 para o mais baixo sobre uma caverna ou o vazio
surfaceDepth (surface_depth) quão fundo vão as camadas de superfície desta coluna, conforme o jogo as varia
steep se o chão tem tanta inclinação quanto onde as montanhas do jogo mostram pedra nua
noise(id, x, y, z) um dos ruídos do mundo pelo nome ("minecraft:surface"), como a seed o faz

A função vem antes de surface: onde ela não responde nada, um bloco de surface também informado continua cobrindo o topo. Uma função que falha é avisada uma vez, e onde ela falha é usado o chão da dimensão.

Tags

As tags agrupam blocos, itens ou biomas sob um só nome, para que uma feature possa testar isIn(x, y, z, "#mymod:crystal_ground") e uma lista de biomas possa dizer "#mymod:glades". Um mod adiciona às suas próprias tags ou às de qualquer um, como as do jogo:

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 é BLOCK, ITEM ou BIOME. Os valores são ids ou #tags; nomes sem namespace são do mod. Vários mods podem adicionar à mesma tag, e um valor que não existe é ignorado, não é um erro.

Bom saber

  • Só os chunks novos recebem o que o seu código cria, como tudo em Geração de mundo.
  • Erros não param o mundo. O código que lança uma exceção deixa de fora o que estava criando ali, e o erro é escrito no log.
  • Seja rápido. Este código roda para cada chunk, milhares de vezes enquanto os jogadores exploram; evite trabalho pesado nele, e em CraftScript lembre-se do limite de passos da referência.