API
Generazione tramite codice
Quando dichiarare non basta, il tuo codice costruisce il mondo man mano che i chunk vengono creati: feature, strutture intere, la forma del terreno, quale bioma va dove, grotte e i blocchi del suolo.
Che cosa puoi scrivere
| Che cosa | Kotlin | CraftScript |
|---|---|---|
| Feature: spuntoni, piante sui muri, massi, qualsiasi cosa piazzata in un chunk | feature("…") { place { } } |
No |
| Strutture costruite dal codice, estese su più chunk | structure("…") { plan { }; build { } } |
Sì (plan, build) |
| Il terreno di una dimensione | terrain("minecraft:the_end") { } |
Sì (terrain) |
| Quale bioma va dove | biomes("minecraft:the_end") { } |
Sì (biome_map) |
| Carver: grotte e tunnel | carver("…", chance) { } |
Sì (carver) |
| Il suolo di un bioma, blocco per blocco | biome("…") { surface { } } |
Sì (surface: fun(at)) |
| Tag di blocchi, oggetti e biomi | tag(TagKind.BIOME, "…", …) |
No |
Tutto ciò che è in questa pagina viene eseguito mentre i chunk vengono creati, su più thread contemporaneamente. Usa i numeri casuali che ti vengono dati (o il seed del mondo) invece dei tuoi: così lo stesso seed crea lo stesso mondo. In Kotlin, mantieni sicuro per i thread tutto ciò che il tuo codice condivide tra le chiamate. In CraftScript i membri di random non sono disponibili; prendi la varietà dalle coordinate, dal seed o dal sito della struttura.
Feature
Una feature è qualcosa che il tuo codice costruisce in un chunk in via di creazione, nei biomi che indichi. Crafting sceglie i punti, come fa il gioco per le sue feature; la tua funzione place costruisce lì e restituisce true quando ha costruito qualcosa. Le feature si scrivono 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
}
}
}
}| Opzione | Valori | Predefinito |
|---|---|---|
biome(…) |
biomi in cui viene piazzata, per id o #tag; almeno uno |
— |
count |
tentativi per chunk, un intervallo: 2..5 |
1..1 |
chance |
solo in un chunk ogni tanti | 1 |
heights |
ogni tentativo a un’altezza casuale in questo intervallo | null: il fondo del mondo (il tuo codice trova il suo posto) |
spread |
true: ogni tentativo in una colonna casuale del chunk; false: nell’angolo del chunk, e il tuo codice sceglie da lì |
true |
on |
FeatureOn.FLOOR o CEILING: dalla sua altezza, si sposta nell’aria accanto al pavimento o al soffitto più vicino, fino a 32 blocchi di distanza; se non ce ne sono, non viene piazzata |
null |
underground |
true: solo sotto la superficie, ad almeno 2 blocchi sotto di essa (nelle grotte) |
false |
everyFloor |
true: su ogni pavimento di ogni colonna scelta, la superficie e il pavimento di ogni grotta; count sono le colonne per pavimento |
false |
tries |
con everyFloor, quante volte si passano in rassegna i pavimenti: ogni volta count colonne in più su ogni pavimento, così un terreno profondo si raggiunge con più tentativi |
1..1 |
step |
quando viene creata, tra le fasi che riempiono un chunk: LAKES, SURFACE, ORES, UNDERGROUND (ciò che decora le grotte), VEGETATION |
VEGETATION |
Con everyFloor, chance, heights e spread non vengono usati: i punti sono i pavimenti stessi.
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)
}
}Che cosa vede una feature
place riceve il mondo in creazione, visto da dove la feature viene piazzata. Può leggere e scrivere il chunk in creazione e i chunk intorno; ciò che viene scritto fuori da essi viene saltato. In CraftScript (nel build di una struttura) gli stessi membri si scrivono in snake_case.
| Membro | Che cos’è |
|---|---|
x, y, z |
dove viene piazzata |
random |
i suoi numeri casuali: lo stesso seed crea la stessa feature |
seed |
il seed del mondo, per un rumore che è lo stesso in tutto il mondo |
minY, maxY |
le altezze del mondo; i blocchi vanno da minY fino a subito sotto maxY |
block(x, y, z), state(x, y, z, name), isAir(x, y, z) |
il blocco che c’è lì, il valore di uno dei suoi stati, se è aria |
isIn(x, y, z, tag) |
se il blocco che c’è lì è in un tag ("#minecraft:leaves") o è quel blocco ("minecraft:end_stone") |
canStay(x, y, z, block, states) |
se un blocco, con quegli stati, potrebbe stare lì: una pianta sul suo terreno, un muschio su un muro rivolto lontano da esso. states si può omettere |
set(x, y, z, block, states) |
piazza un blocco, con stati come mapOf("facing" to "north"); false quando è fuori da ciò che si può scrivere, o non è un blocco qui |
inside(x, z) |
se la colonna è una che può leggere e scrivere |
surface(x, z) |
il primo blocco sopra il più alto che non è aria, come era il terreno prima delle feature |
top(x, z) |
lo stesso, ora, con ciò che le feature hanno piazzato da allora |
flow(x, y, z) |
fa scorrere l’acqua (o la lava, o un blocco che contiene acqua) lì quando il chunk è nel mondo. L’acqua piazzata mentre un chunk viene creato resta ferma, e senza questo resterebbe sospesa accanto all’aria |
structures(x, z) |
le strutture (id, come minecraft:end_city) la cui area comprende il chunk di quella colonna, per tenere una feature fuori da esse |
placeFile(file, x, y, z, rotation, mirror, pivotX, pivotZ) |
piazza un file di struttura; vedi File di struttura dal codice |
Un blocco che la tua feature nomina e che non esiste (di una mod non installata) viene saltato, con un avviso nel log; set restituisce false per quel blocco.
Strutture costruite dal codice
Per una struttura che non è fatta di file, o che deve seguire il terreno, il tuo codice decide dove si trova e la costruisce. Usa due funzioni:
planviene interpellata, in ogni chunk dove il mondo ne piazzerebbe una (in base abiomes,spacingeseparation, come per qualsiasi struttura), per sapere se è lì, prima che i blocchi del chunk vengano creati. Restituisce il suo sito (site): il suo centro, fin dove arriva e ciò che serve alla costruzione; oppure niente, e lì non ce n’è nessuna.buildcostruisce la parte del sito in ogni chunk che esso copre, un chunk alla volta. Ogni chunk riceve lo stesso sito e gli stessi numeri casuali, così ognuno costruisce il proprio pezzo della stessa struttura. Il sito viene conservato insieme al mondo, quindi i chunk creati dopo un riavvio combaciano ancora.
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)
}
}
}Il sito (StructureSite in Kotlin, una mappa in CraftScript):
| Campo | Che cos’è | Predefinito in CraftScript |
|---|---|---|
x, y, z |
il suo centro | 0 |
radius |
quanti blocchi intorno al centro raggiunge: i chunk che copre | 8 |
min_y, max_y (minY, maxY) |
le altezze che copre | y meno e più radius |
data |
ciò che serve alla costruzione per costruire lo stesso in ogni chunk: numeri, testo, vero o falso | vuoto |
In Kotlin, un sito ha anche un seed ricavato dalla sua posizione. I numeri in data tornano come decimali dopo un riavvio: leggili con (site.data["n"] as Number).toInt().
Ciò che riceve plan, prima che venga creato qualsiasi blocco del chunk:
| Membro | Che cos’è |
|---|---|
chunkX, chunkZ |
il chunk per cui viene interpellata (blocco / 16) |
seed, random |
il seed del mondo, e i numeri casuali propri di questo punto |
height(x, z) |
l’altezza che avrà la superficie lì (il primo blocco sopra di essa) |
biome(x, y, z) |
il bioma che c’è lì |
column(x, z), minY |
i blocchi che avrà il terreno in quella colonna, da minY fino alla cima del mondo, prima del suo suolo, delle sue grotte e di qualsiasi costruzione (nell’End: pietra dell’End e aria) |
fileSize(file) |
la dimensione di un file di struttura (x, y, z), o null se non esiste |
build riceve lo stesso mondo di una feature, ma può piazzare solo blocchi del chunk in costruzione, e il suo random deriva dal sito. Le altre opzioni di una struttura che valgono qui sono biomes, spacing, separation e, in Kotlin, salt. Una struttura di codice richiede sia plan sia build. Trovala con /locate structure, come qualsiasi altra.
File di struttura dal codice
placeFile piazza un file di struttura dove dice il tuo codice, ruotato e specchiato: una torre dei tuoi file dentro una struttura di codice, o una rovina che una feature lascia in una grotta. Il file è il structures/<percorso>.nbt della mod ("mymod:watchtower") o uno del gioco. Viene piazzata solo la parte che sta dentro ciò che il mondo permette di scrivere, quindi una struttura che copre più chunk piazza il suo file da ciascuno di essi. I blocchi di struttura nel file non vengono piazzati. Restituisce false se quel file non esiste.
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)
}
}| Parametro | Valori | Predefinito |
|---|---|---|
file |
l’id del file | — |
x, y, z |
dove va il suo angolo, prima della rotazione | — |
rotation |
quarti di giro in senso orario | 0 |
mirror |
Mirror.NONE, LEFT_RIGHT (lungo z) o FRONT_BACK (lungo x) |
NONE |
pivotX, pivotZ |
il blocco del file attorno al quale ruota | 0, 0 |
Il terreno di una dimensione
Puoi dare la forma del terreno di una delle dimensioni del gioco (minecraft:overworld, minecraft:the_nether o minecraft:the_end): isole fluttuanti, colline dolci, un mondo di pilastri. La tua forma dice quanto è solido ogni punto: sopra 0 è terra, sotto 0 è aria. Il terreno è fatto della pietra abituale della dimensione, e il suolo dei suoi biomi ci va sopra.
// 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 | Che cos’è |
|---|---|
column(x, z, ys, out) |
quanto è solida la colonna x, z a ogni altezza di ys, in out nello stesso ordine. In CraftScript, column(x, z, ys, world) restituisce l’elenco |
hasLand(x, z) (has_land) |
se la colonna ha della terra. Nell’End decide tra i biomi di terra e quelli del vuoto |
world.seed |
il seed del mondo |
world.minY, world.height |
le altezze in cui viene creato il terreno: da minY, height blocchi verso l’alto |
world.biome(x, y, z) |
il bioma di un blocco |
Il gioco interpella la tua forma negli angoli di celle larghe pochi blocchi e sfuma tra di esse, quindi non deve essere esatta blocco per blocco. In Kotlin la tua funzione viene chiamata una volta per mondo con il suo seed, e restituisce la forma. La tua forma crea tutto il terreno: le grotte di rumore del gioco non vengono create (aggiungi un carver), e nell’End anche l’isola centrale tocca a te crearla. Solo una mod può dare forma a una dimensione. Una forma che fallisce viene segnalata una volta nel log e non lascia terra lì, senza far crashare il gioco.
Le dimensioni tutte tue si dichiarano nella pagina Dimensioni con uno dei terreni del gioco.
Quale bioma va dove
Per impostazione predefinita, un bioma prende una parte (share) dei punti di quello che sostituisce (vedi Generazione del mondo). Per una mappa tutta tua, il tuo codice sceglie il bioma di ogni punto. Per ora questo vale per l’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)]
}
}
}La funzione riceve x, y, z in quarti (il gioco conserva un bioma ogni 4×4×4 blocchi: un quarto è blocco / 4) e land, il tipo di terra che il rumore del gioco crea lì, come bioma: minecraft:end_highlands, end_midlands, end_barrens o small_end_islands. Con una forma tua, end_highlands sulla sua terra e small_end_islands fuori da essa. Ricevi land anche sull’isola centrale, che la tua mappa piazza da sé: il gioco mette minecraft:the_end entro 1024 blocchi dal centro.
La risposta deve essere un bioma dell’End: del gioco, o uno tuo dichiarato con replaces impostato su un bioma dell’End. Qualsiasi altra risposta, o una mappa che fallisce, ripiega sulla scelta del gioco (segnalato una volta nel log). Il world della mappa ha il seed e tagged(tag), i biomi di un tag di biomi. Solo una mod può scegliere i biomi di una dimensione.
Carver
Un carver scava grotte nel terreno una volta creato, prima del suo suolo e delle feature. Per ogni chunk in creazione, il gioco passa in rassegna i chunk intorno (8 per lato); in ciascuno, ogni carver del bioma di quel chunk parte con la sua chance (0–1), e la tua funzione segna ciò che scava del chunk in creazione. I blocchi scavati diventano aria (o acqua sotto una falda), e il suolo viene rifatto sopra di essi. Un bioma usa un carver nominandolo 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")
}| Membro | Che cos’è |
|---|---|
startX, startZ |
il chunk da cui parte questa grotta (coordinate di chunk: blocco / 16) |
chunkX, chunkZ |
il chunk in creazione |
minY, maxY |
le altezze che può scavare, entrambe incluse |
seed, random |
il seed del mondo, e i numeri casuali di questa partenza: si ripetono uguali in ogni chunk per cui viene interpellata la partenza, così una grotta che attraversa più chunk combacia |
carve(x, y, z) |
scava quel blocco, in coordinate del mondo; i blocchi fuori dal chunk in creazione vengono saltati |
Una grotta può arrivare al massimo a 8 chunk da dove parte. In CraftScript, chance vale 1 per impostazione predefinita. Un carver che fallisce non scava nulla lì, segnalato una volta nel log.
Il suolo di un bioma
surface copre il suolo di un bioma con un blocco. Per strati, macchie o pendii, dagli invece una funzione: mentre un chunk viene creato, il gioco gliela chiede, per ogni blocco del suolo del bioma dall’alto verso il basso, quale blocco va lì. Nessuna risposta lascia quel blocco alla dimensione (la sua pietra).
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 di at |
Che cos’è |
|---|---|
x, y, z |
il blocco su cui si chiede |
depthAbove (depth_above) |
blocchi di suolo dall’aria aperta sopra fino a questo: 1 per il blocco in cima al suolo o al pavimento di una grotta |
depthBelow (depth_below) |
blocchi di suolo da questo fino all’aria aperta sotto: 1 per il più basso sopra una grotta o il vuoto |
surfaceDepth (surface_depth) |
quanto in profondità arrivano gli strati di superficie di questa colonna, come li fa variare il gioco |
steep |
se il suolo è inclinato quanto dove le montagne del gioco mostrano la pietra nuda |
noise(id, x, y, z) |
uno dei rumori del mondo per nome ("minecraft:surface"), come lo crea il seed |
La funzione ha la precedenza su surface: dove non risponde nulla, un blocco di surface indicato comunque continua a coprire la cima. Una funzione che fallisce viene segnalata una volta, e dove fallisce si usa il suolo della dimensione.
Tag
I tag raggruppano blocchi, oggetti o biomi sotto un solo nome, così una feature può verificare isIn(x, y, z, "#mymod:crystal_ground") e un elenco di biomi può dire "#mymod:glades". Una mod aggiunge ai propri tag o a quelli di chiunque, come quelli del gioco:
// 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 o BIOME. I valori sono id o #tag; i nomi senza namespace sono della mod. Più mod possono aggiungere allo stesso tag, e un valore che non esiste viene saltato, non è un errore.
Buono a sapersi
- Solo i chunk nuovi ricevono ciò che crea il tuo codice, come tutto in Generazione del mondo.
- Gli errori non fermano il mondo. Il codice che lancia un’eccezione lascia fuori ciò che stava creando lì, e l’errore viene scritto nel log.
- Sii veloce. Questo codice viene eseguito per ogni chunk, migliaia di volte mentre i giocatori esplorano; evita lavori pesanti al suo interno, e in CraftScript tieni presente il limite di passi del riferimento.