API

Generowanie kodem

Gdy deklarowanie nie wystarcza, twój kod buduje świat podczas tworzenia chunków: featury, całe struktury, kształt lądu, to, który biom leży gdzie, jaskinie i bloki gruntu.

Co możesz napisać

Co Kotlin CraftScript
Featury: kolce, rośliny na ścianach, głazy, cokolwiek stawianego w chunku feature("…") { place { } } Nie
Struktury budowane kodem, na kilku chunkach structure("…") { plan { }; build { } } Tak (plan, build)
Ląd wymiaru terrain("minecraft:the_end") { } Tak (terrain)
Który biom leży gdzie biomes("minecraft:the_end") { } Tak (biome_map)
Carvery: jaskinie i tunele carver("…", chance) { } Tak (carver)
Grunt biomu, blok po bloku biome("…") { surface { } } Tak (surface: fun(at))
Tagi bloków, przedmiotów i biomów tag(TagKind.BIOME, "…", …) Nie

Wszystko na tej stronie działa podczas tworzenia chunków, na kilku wątkach naraz. Używaj liczb losowych, które dostajesz (albo ziarna świata), zamiast własnych: to samo ziarno tworzy wtedy ten sam świat. W Kotlinie to, co twój kod współdzieli między wywołaniami, musi być bezpieczne dla wątków. W CraftScript składowe random nie są dostępne; różnorodność bierz ze współrzędnych, ziarna lub miejsca struktury.

Featury

Feature to coś, co twój kod buduje w tworzonym chunku, w wymienionych przez ciebie biomach. Crafting wybiera miejsca, tak jak gra dla własnych featurów; twoja funkcja place buduje tam i zwraca true, gdy coś zbudowała. Featury pisze się w Kotlinie.

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
            }
        }
    }
}
Opcja Wartości Domyślnie
biome(…) biomy, w których jest stawiany, przez id lub #tag; co najmniej jeden —
count prób na chunk, zakres: 2..5 1..1
chance tylko w jednym na tyle chunków 1
heights każda próba na losowej wysokości z tego zakresu null: dno świata (twój kod sam znajduje miejsce)
spread true: każda próba w losowej kolumnie chunku; false: w rogu chunku, a twój kod wybiera stamtąd true
on FeatureOn.FLOOR lub CEILING: z jego wysokości przesuwa się do powietrza obok najbliższej podłogi lub sufitu, do 32 bloków dalej; gdy żadnej nie ma, nie jest stawiany null
underground true: tylko pod powierzchnią, co najmniej 2 bloki pod nią (w jaskiniach) false
everyFloor true: na każdej podłodze każdej wybranej kolumny, na powierzchni i na podłodze każdej jaskini; count to kolumny na podłogę false
tries z everyFloor, ile razy przechodzi się przez podłogi: za każdym razem count kolumn więcej na każdej podłodze, więc głęboki ląd osiąga się większą liczbą prób 1..1
step kiedy jest tworzony, wśród kroków wypełniających chunk: LAKES, SURFACE, ORES, UNDERGROUND (to, co zdobi jaskinie), VEGETATION VEGETATION

Z everyFloor opcje chance, heights i spread nie są używane: miejscami są same podłogi.

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

Co widzi feature

place dostaje tworzony świat, widziany z miejsca, w którym feature jest stawiany. Może czytać i ustawiać tworzony chunk oraz chunki wokół niego; ustawianie poza nimi jest pomijane. W CraftScript (w build struktury) te same składowe zapisuje się w snake_case.

Składowa Czym jest
x, y, z gdzie jest stawiany
random jego własne liczby losowe: to samo ziarno tworzy ten sam feature
seed ziarno świata, dla szumu takiego samego w całym świecie
minY, maxY wysokości świata; bloki sięgają od minY w górę do poniżej maxY
block(x, y, z), state(x, y, z, name), isAir(x, y, z) blok w tym miejscu, wartość jednego z jego stanów, czy jest powietrzem
isIn(x, y, z, tag) czy blok w tym miejscu należy do tagu ("#minecraft:leaves") albo jest tym blokiem ("minecraft:end_stone")
canStay(x, y, z, block, states) czy blok z tymi stanami mógłby tam stać: roślina na swoim gruncie, mech na ścianie zwrócony od niej. states można pominąć
set(x, y, z, block, states) ustawia blok, ze stanami takimi jak mapOf("facing" to "north"); false, gdy jest poza tym, co można ustawiać, albo nie jest tu blokiem
inside(x, z) czy kolumna jest taką, którą wolno czytać i ustawiać
surface(x, z) pierwszy blok nad najwyższym, który nie jest powietrzem, takim, jaki był ląd przed featurami
top(x, z) to samo, teraz, z tym, co od tamtej pory ustawiły featury
flow(x, y, z) pozwala wodzie (lub lawie, albo blokowi z wodą) w tym miejscu płynąć, gdy chunk trafi do świata. Woda ustawiona podczas tworzenia chunku stoi nieruchomo i bez tego wisiałaby obok powietrza
structures(x, z) struktury (id, jak minecraft:end_city), których obszar obejmuje chunk tej kolumny, żeby trzymać feature z dala od nich
placeFile(file, x, y, z, rotation, mirror, pivotX, pivotZ) stawia plik struktury; zobacz Pliki struktur z kodu

Blok, który twój feature nazywa, a który nie istnieje (z moda, który nie jest zainstalowany), jest pomijany, z jednym ostrzeżeniem w logu; set zwraca dla niego false.

Struktury budowane kodem

Dla struktury, która nie składa się z plików albo która musi podążać za lądem, to twój kod decyduje, gdzie jest, i ją buduje. Potrzebuje dwóch funkcji:

  • plan jest pytana, w każdym chunku, w którym świat postawiłby strukturę (według biomes, spacing i separation, jak dla każdej struktury), czy tam jest, zanim powstaną bloki chunku. Zwraca jej miejsce (site): jej środek, jak daleko sięga i wszystko, czego potrzebuje budowa; albo nic, i wtedy jej tam nie ma.
  • build buduje część miejsca w każdym chunku, który ono obejmuje, po jednym chunku naraz. Każdy chunk dostaje to samo miejsce i te same liczby losowe, więc każdy buduje swój kawałek tej samej struktury. Miejsce jest zapisywane ze światem, więc chunki tworzone po restarcie nadal do niego pasują.
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)
        }
    }
}

Miejsce (StructureSite w Kotlinie, mapa w CraftScript):

Pole Czym jest Domyślnie w CraftScript
x, y, z jego środek 0
radius ile bloków wokół środka sięga: chunki, które obejmuje 8
min_y, max_y (minY, maxY) wysokości, które obejmuje y minus i plus radius
data to, czego potrzebuje budowa, żeby w każdym chunku zbudować to samo: liczby, tekst, prawda lub fałsz puste

W Kotlinie miejsce ma też seed utworzone z jego położenia. Liczby w data wracają po restarcie jako liczby dziesiętne: odczytuj je przez (site.data["n"] as Number).toInt().

Co dostaje plan, zanim powstanie jakikolwiek blok chunku:

Składowa Czym jest
chunkX, chunkZ chunk, o który jest pytana (blok / 16)
seed, random ziarno świata i liczby losowe tego miejsca
height(x, z) wysokość, jaką będzie tam miała powierzchnia (pierwszy blok nad nią)
biome(x, y, z) biom w tym miejscu
column(x, z), minY bloki, które ląd będzie miał w tej kolumnie, od minY w górę do szczytu świata, przed jego gruntem, jaskiniami i czymkolwiek zbudowanym (w Endzie: kamień endu i powietrze)
fileSize(file) rozmiar pliku struktury (x, y, z) albo null, jeśli go nie ma

build dostaje ten sam świat co feature, ale może ustawiać tylko bloki budowanego chunku, a jego random pochodzi z miejsca. Pozostałe opcje struktury, które tu obowiązują, to biomes, spacing, separation i, w Kotlinie, salt. Struktura z kodu potrzebuje zarówno plan, jak i build. Znajdziesz ją przez /locate structure, jak każdą inną.

Pliki struktur z kodu

placeFile stawia plik struktury tam, gdzie każe twój kod, obrócony i odbity: wieżę z twoich plików wewnątrz struktury z kodu albo ruinę, którą feature zostawia w jaskini. Plik to structures/<ścieżka>.nbt moda ("mymod:watchtower") albo jeden z plików gry. Stawiana jest tylko ta część, która mieści się w tym, co świat może ustawiać, więc struktura obejmująca kilka chunków stawia swój plik z każdego z nich. Bloki strukturalne w pliku nie są stawiane. Zwraca false, jeśli takiego pliku nie ma.

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)
    }
}
Parametr Wartości Domyślnie
file id pliku —
x, y, z gdzie trafia jego róg, przed obrotem —
rotation ćwierćobroty zgodnie z ruchem wskazówek zegara 0
mirror Mirror.NONE, LEFT_RIGHT (wzdłuż z) lub FRONT_BACK (wzdłuż x) NONE
pivotX, pivotZ blok pliku, wokół którego się obraca 0, 0

Ląd wymiaru

Możesz nadać kształt lądowi jednego z wymiarów gry (minecraft:overworld, minecraft:the_nether lub minecraft:the_end): latające wyspy, faliste wzgórza, świat filarów. Twój kształt mówi, jak lity jest każdy punkt: powyżej 0 to ląd, poniżej 0 to powietrze. Ląd jest zrobiony ze zwykłego kamienia wymiaru, a grunt jego biomów leży na wierzchu.

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
    }
}
Składowa Czym jest
column(x, z, ys, out) jak lita jest kolumna x, z na każdej wysokości z ys, do out w tej samej kolejności. W CraftScript column(x, z, ys, world) zwraca listę
hasLand(x, z) (has_land) czy kolumna ma jakikolwiek ląd. W Endzie decyduje między biomami lądu a biomami pustki
world.seed ziarno świata
world.minY, world.height wysokości, na których tworzony jest ląd: od minY, height bloków w górę
world.biome(x, y, z) biom w danym bloku

Gra pyta o twój kształt w rogach komórek szerokich na kilka bloków i płynnie je łączy, więc nie musi być dokładny co do bloku. W Kotlinie twoja funkcja jest wywoływana raz na świat z jego ziarnem i zwraca kształt. Twój kształt tworzy cały ląd: własne jaskinie szumowe gry nie powstają (dodaj carver), a w Endzie centralną wyspę też tworzysz ty. Wymiar może kształtować tylko jeden mod. Kształt, który zawiedzie, jest zgłaszany raz w logu i nie zostawia tam lądu, zamiast powodować awarię.

Własne wymiary deklaruje się na stronie Wymiary z jednym z terenów gry.

Który biom leży gdzie

Domyślnie biom zajmuje część (share) miejsc biomu, który zastępuje (zobacz Generowanie świata). Dla własnej mapy twój kod wybiera biom każdego punktu. Na razie dotyczy to Endu (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)]
        }
    }
}

Funkcja dostaje x, y, z w kwartach (gra trzyma jeden biom na 4×4×4 bloków: kwart to blok / 4) oraz land, rodzaj lądu, który tam tworzy szum gry, jako biom: minecraft:end_highlands, end_midlands, end_barrens lub small_end_islands. Przy własnym kształcie jest to end_highlands na jego lądzie i small_end_islands poza nim. Dostajesz też land na centralnej wyspie, którą twoja mapa stawia sama: gra umieszcza minecraft:the_end w promieniu 1024 bloków od środka.

Odpowiedź musi być biomem Endu: z gry albo jednym z twoich, zadeklarowanym z replaces ustawionym na biom Endu. Każda inna odpowiedź, albo mapa, która zawiedzie, wraca do wyboru gry (zgłaszane raz w logu). world mapy ma seed i tagged(tag), czyli biomy z tagu biomów. Biomy wymiaru może wybierać tylko jeden mod.

Carvery

Carver (wyrzynacz) drąży jaskinie w lądzie po jego utworzeniu, przed jego gruntem i featurami. Dla każdego tworzonego chunku gra przechodzi przez chunki wokół niego (po 8 w każdą stronę); w każdym z nich każdy carver biomu tego chunku startuje ze swoją szansą chance (0–1), a twoja funkcja zaznacza, co drąży w tworzonym chunku. Wydrążone bloki stają się powietrzem (albo wodą pod warstwą wodonośną), a grunt jest tworzony na nowo nad nimi. Biom używa carvera, nazywając go w 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")
}
Składowa Czym jest
startX, startZ chunk, w którym zaczyna się ta jaskinia (współrzędne chunków: blok / 16)
chunkX, chunkZ tworzony chunk
minY, maxY wysokości, które wolno drążyć, oba włącznie
seed, random ziarno świata i liczby losowe tego startu: losują to samo w każdym chunku, o który start jest pytany, więc jaskinia przecinająca chunki się schodzi
carve(x, y, z) drąży ten blok, we współrzędnych świata; bloki poza tworzonym chunkiem są pomijane

Jaskinia może sięgać najwyżej 8 chunków od miejsca startu. W CraftScript chance domyślnie wynosi 1. Carver, który zawiedzie, nic tam nie drąży, co jest zgłaszane raz w logu.

Grunt biomu

surface pokrywa grunt biomu jednym blokiem. Dla warstw, plam lub zboczy podaj zamiast tego funkcję: podczas tworzenia chunku gra pyta ją, dla każdego bloku gruntu biomu od góry do dołu, jaki blok tam trafia. Brak odpowiedzi zostawia ten blok wymiarowi (jego kamieniowi).

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
        }
    }
}
Składowa at Czym jest
x, y, z blok, o który się pyta
depthAbove (depth_above) bloków gruntu od otwartego powietrza nad nim w dół do tego: 1 dla górnego bloku gruntu albo podłogi jaskini
depthBelow (depth_below) bloków gruntu od tego w dół do otwartego powietrza pod nim: 1 dla najniższego nad jaskinią lub pustką
surfaceDepth (surface_depth) jak głęboko sięgają warstwy powierzchniowe tej kolumny, tak jak gra je zmienia
steep czy grunt opada tak stromo jak tam, gdzie góry z gry pokazują gołą skałę
noise(id, x, y, z) jeden z szumów świata po nazwie ("minecraft:surface"), taki, jaki tworzy ziarno

Funkcja ma pierwszeństwo przed surface: tam, gdzie nic nie odpowiada, podany też blok surface nadal pokrywa wierzch. Funkcja, która zawiedzie, jest zgłaszana raz, a tam, gdzie zawodzi, używany jest grunt wymiaru.

Tagi

Tagi grupują bloki, przedmioty lub biomy pod jedną nazwą, więc feature może sprawdzić isIn(x, y, z, "#mymod:crystal_ground"), a lista biomów może zawierać "#mymod:glades". Mod dodaje do własnych tagów albo do cudzych, takich jak te z gry:

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 to BLOCK, ITEM lub BIOME. Wartości to id lub #tagi; nazwy bez przestrzeni nazw należą do moda. Kilka modów może dodawać do tego samego tagu, a wartość, która nie istnieje, jest pomijana, a nie jest błędem.

Warto wiedzieć

  • Tylko nowe chunki dostają to, co tworzy twój kod, tak jak wszystko w Generowaniu świata.
  • Błędy nie zatrzymują świata. Kod, który rzuci wyjątek, pomija to, co tam tworzył, a błąd trafia do logu.
  • Pisz szybko. Ten kod działa dla każdego chunku, tysiące razy, gdy gracze eksplorują; unikaj w nim ciężkiej pracy, a w CraftScript pamiętaj o limicie kroków z opisu języka.