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.
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.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 (porbiomes,spacingeseparation, 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.buildconstró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.
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)
}
}
}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.
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.
// 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
}
}| 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).
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)]
}
}
}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.
// 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")
}| 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).
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
}
}
}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:
// 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.