API
コードによる生成
宣言だけでは足りないとき、チャンクが作られる際にあなたのコードがワールドを組み立てます。フィーチャー、構造物まるごと、地形の形、どこにどのバイオームを置くか、洞窟、地面のブロックまで扱えます。
書けるもの
| 内容 | Kotlin | CraftScript |
|---|---|---|
| フィーチャー:スパイク、壁に生える植物、岩など、チャンクに置かれるもの何でも | feature("…") { place { } } |
なし |
| 複数のチャンクにまたがる、コードで作る構造物 | structure("…") { plan { }; build { } } |
あり(plan、build) |
| ディメンションの地形 | terrain("minecraft:the_end") { } |
あり(terrain) |
| どこにどのバイオームを置くか | biomes("minecraft:the_end") { } |
あり(biome_map) |
| カーバー:洞窟とトンネル | carver("…", chance) { } |
あり(carver) |
| バイオームの地面をブロック単位で | biome("…") { surface { } } |
あり(surface: fun(at)) |
| ブロック、アイテム、バイオームのタグ | tag(TagKind.BIOME, "…", …) |
なし |
このページのコードはすべて、チャンクが作られている間に、複数のスレッドで同時に実行されます。自前の乱数ではなく、渡される乱数(またはワールドのシード値)を使ってください。そうすれば、同じシード値は同じワールドを作ります。Kotlin では、呼び出しの間で共有するものはスレッドセーフにしてください。CraftScript では random のメンバーは使えません。ばらつきは、座標、シード値、構造物のサイトから得てください。
フィーチャー
フィーチャーは、作られているチャンクの中で、指定したバイオームにあなたのコードが組み立てるものです。ゲーム自身のフィーチャーと同じように、場所は Crafting が選びます。あなたの place 関数がそこに組み立て、何かを作ったら true を返します。フィーチャーは 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
}
}
}
}| オプション | 値 | デフォルト |
|---|---|---|
biome(…) |
置かれるバイオーム(ID または #tag)。少なくとも 1 つ |
— |
count |
チャンクごとの試行回数(範囲):2..5 |
1..1 |
chance |
このチャンク数ごとに 1 つのチャンクだけ | 1 |
heights |
各試行をこの範囲のランダムな高さで行う | null:ワールドの底(場所はコードが見つける) |
spread |
true:各試行をチャンクのランダムな列で行う。false:チャンクの角で行い、そこからコードが選ぶ |
true |
on |
FeatureOn.FLOOR または CEILING:その高さから、最大 32 ブロック離れた最も近い床または天井の隣の空気へ移動する。見つからなければ置かれない |
null |
underground |
true:地表の下、少なくとも 2 ブロック下(洞窟の中)だけ |
false |
everyFloor |
true:選ばれた各列のすべての床、つまり地表と各洞窟の床で行う。count は 1 つの床あたりの列の数 |
false |
tries |
everyFloor と併用:床を何周するか。周ごとに各床で count 本ずつ列が増えるので、深い地形にもより多くの試行が届く |
1..1 |
step |
チャンクを満たす段階のうち、いつ作られるか:LAKES、SURFACE、ORES、UNDERGROUND(洞窟を飾るもの)、VEGETATION |
VEGETATION |
everyFloor では、chance、heights、spread は使われません。場所は床そのものだからです。
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)
}
}フィーチャーに見えるもの
place は、作られているワールドを、フィーチャーが置かれる場所から見た形で受け取ります。作られているチャンクとその周囲のチャンクを読み書きできます。その外への書き込みは無視されます。CraftScript(構造物の build 内)では、同じメンバーを snake_case で書きます。
| メンバー | 内容 |
|---|---|
x、y、z |
置かれる場所 |
random |
専用の乱数:同じシード値なら同じフィーチャーになります |
seed |
ワールドのシード値。ワールド全体で同じになるノイズ用 |
minY、maxY |
ワールドの高さ。ブロックは minY から maxY の手前まで |
block(x, y, z)、state(x, y, z, name)、isAir(x, y, z) |
そこのブロック、そのステートの 1 つの値、空気かどうか |
isIn(x, y, z, tag) |
そこのブロックがタグ("#minecraft:leaves")に含まれる、またはそのブロック("minecraft:end_stone")かどうか |
canStay(x, y, z, block, states) |
そのステートのブロックがそこに留まれるかどうか:地面の上の植物、向きが外向きの壁の苔など。states は省略できます |
set(x, y, z, block, states) |
ブロックを設定します。ステートは mapOf("facing" to "north") のように指定します。設定できる範囲の外、またはここではブロックでない場合は false |
inside(x, z) |
その列が、読み書きできる列かどうか |
surface(x, z) |
空気でない最も高いブロックの上の最初のブロック。フィーチャーが置かれる前の地形による |
top(x, z) |
同じものを、その後フィーチャーが設定したものを含めた現在の状態で |
flow(x, y, z) |
チャンクがワールドに入った後、そこの水(または溶岩、水を含むブロック)を流れさせます。チャンクの生成中に設定した水は静止したままなので、これがないと空気の隣に浮いてしまいます |
structures(x, z) |
その列のチャンクを範囲に含む構造物(minecraft:end_city のような ID)。フィーチャーをそこから遠ざけるのに使います |
placeFile(file, x, y, z, rotation, mirror, pivotX, pivotZ) |
構造物ファイルを置きます。コードから使う構造物ファイルを参照 |
フィーチャーが指定したブロックが存在しない場合(インストールされていない Mod のブロックなど)は省かれ、ログに警告が 1 回出ます。そのブロックでは set は false を返します。
コードで作る構造物
ファイルで作れない構造物や、地形に合わせる必要がある構造物では、どこに置くかをコードが決めて組み立てます。2 つの関数を使います。
planは、ワールドが構造物を置くことになるチャンクごとに(biomes、spacing、separationによる。他の構造物と同じ)、そのチャンクのブロックが作られる前に、そこにあるかどうかを尋ねられます。サイト、つまり中心、届く範囲、組み立てに必要なものを返します。何も返さなければ、そこにはありません。buildは、サイトが覆う各チャンクにある部分を、1 チャンクずつ組み立てます。どのチャンクにも同じサイトと同じ乱数が渡されるので、各チャンクは同じ構造物のそれぞれの部分を作ります。サイトはワールドと一緒に保存されるので、再起動後に作られるチャンクも整合します。
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)
}
}
}サイト(Kotlin では StructureSite、CraftScript ではマップ):
| フィールド | 内容 | CraftScript でのデフォルト |
|---|---|---|
x、y、z |
中心 | 0 |
radius |
中心の周囲のブロック数で表す届く範囲:覆うチャンクが決まる | 8 |
min_y、max_y(minY、maxY) |
覆う高さ | y から radius を引いた値と足した値 |
data |
各チャンクで同じものを組み立てるのに build が必要とするもの:数値、テキスト、true か false |
空 |
Kotlin では、サイトにその位置から作られる seed もあります。data の数値は再起動後に小数として戻ってきます。(site.data["n"] as Number).toInt() で読んでください。
チャンクのブロックがまだ 1 つも作られていない時点で、plan が受け取るもの:
| メンバー | 内容 |
|---|---|
chunkX、chunkZ |
尋ねられているチャンク(ブロック / 16) |
seed、random |
ワールドのシード値と、この場所専用の乱数 |
height(x, z) |
そこで地表がなる高さ(その上の最初のブロック) |
biome(x, y, z) |
そこのバイオーム |
column(x, z)、minY |
その列で地形がなるブロック。minY からワールドの上端まで、地面、洞窟、組み立てられるものより前の状態(エンドでは、エンドストーンと空気) |
fileSize(file) |
構造物ファイルのサイズ(x、y、z)。ファイルがなければ null |
build はフィーチャーと同じワールドを受け取りますが、設定できるのは組み立て中のチャンクのブロックだけで、その random はサイトから来ます。ここで使える構造物のその他のオプションは、biomes、spacing、separation、そして Kotlin では salt です。コードの構造物には plan と build の両方が必要です。他の構造物と同じように /locate structure で探せます。
コードから使う構造物ファイル
placeFile は、コードが指定した場所に、回転と反転をつけて構造物ファイルを置きます。コードの構造物の中に自分のファイルの塔を置いたり、フィーチャーが洞窟に廃墟を落としたりできます。ファイルは Mod の structures/<path>.nbt("mymod:watchtower")か、ゲームのものです。ワールドが設定できる範囲の内側の部分だけが置かれるので、複数のチャンクにまたがる構造物は、各チャンクからファイルを置きます。ファイルの中のストラクチャーブロックは置かれません。そのようなファイルがなければ false を返します。
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)
}
}| パラメーター | 値 | デフォルト |
|---|---|---|
file |
ファイルの ID | — |
x、y、z |
回転前の、その角を置く場所 | — |
rotation |
時計回りの 90 度回転の回数 | 0 |
mirror |
Mirror.NONE、LEFT_RIGHT(z 軸に沿って)または FRONT_BACK(x 軸に沿って) |
NONE |
pivotX、pivotZ |
回転の中心にするファイル内のブロック | 0, 0 |
ディメンションの地形
ゲームのディメンション(minecraft:overworld、minecraft:the_nether、minecraft:the_end)の地形の形を与えられます。浮遊島、なだらかな丘、柱だらけの世界などです。あなたの形は、各地点がどれだけ固いかを返します。0 より大きければ陸、0 より小さければ空気です。陸はそのディメンション通常の石でできていて、その上にバイオームの地面が載ります。
// 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
}
}| メンバー | 内容 |
|---|---|
column(x, z, ys, out) |
列 x、z が ys の各高さでどれだけ固いかを、同じ順序で out に入れます。CraftScript では、column(x, z, ys, world) がリストを返します |
hasLand(x, z)(has_land) |
その列に陸があるかどうか。エンドでは、陸のバイオームと虚空のバイオームのどちらにするかを決めます |
world.seed |
ワールドのシード値 |
world.minY、world.height |
陸が作られる高さ:minY から上へ height ブロック |
world.biome(x, y, z) |
ブロックのバイオーム |
ゲームは、数ブロック幅のセルの角であなたの形を尋ね、その間を補間するので、ブロック単位で正確である必要はありません。Kotlin では、関数はシード値とともにワールドごとに 1 回呼ばれ、形を返します。あなたの形が陸のすべてを作ります。ゲーム自身のノイズ洞窟は作られず(カーバーを追加してください)、エンドでは中央の島もあなたが作ります。1 つのディメンションを形作れる Mod は 1 つだけです。失敗した形はログに 1 回報告され、そこには陸が作られません。クラッシュはしません。
独自のディメンションは、ゲームの地形のどれかを使って、ディメンションのページで宣言します。
どこにどのバイオームを置くか
デフォルトでは、バイオームは置き換えるバイオームの場所の share を引き継ぎます(ワールド生成を参照)。独自のマップにするには、各地点のバイオームをコードが選びます。今のところ、エンド(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)]
}
}
}関数は、クォート単位の x、y、z(ゲームは 4×4×4 ブロックごとに 1 つのバイオームを持ちます。1 クォートはブロック / 4)と、land、つまりそこでゲームのノイズが作る陸の種類をバイオームとして受け取ります:minecraft:end_highlands、end_midlands、end_barrens、small_end_islands のいずれかです。独自の形があるときは、陸の上では end_highlands、陸の外では small_end_islands です。中央の島でも land を受け取ります。そこはマップ自身が置きます。ゲームは、中心から 1024 ブロック以内に minecraft:the_end を置きます。
答えはエンドのバイオームでなければなりません。ゲームのもの、または replaces をエンドのバイオームにして宣言した自分のものです。それ以外の答え、または失敗したマップは、ゲームの選択に戻ります(ログに 1 回報告)。マップの world には、seed と tagged(tag)(バイオームタグに含まれるバイオーム)があります。ディメンションのバイオームを選べる Mod は 1 つだけです。
カーバー
カーバーは、地形が作られた後、地面とフィーチャーより前に、洞窟をくり抜きます。作られているチャンクごとに、ゲームはその周囲のチャンク(各方向に 8 つ)を調べます。それぞれで、そのチャンクのバイオームのすべてのカーバーが chance(0〜1)で開始し、あなたの関数が、作られているチャンクのうちどこをくり抜くかを示します。くり抜かれたブロックは空気(帯水層の下では水)になり、その上に地面が作り直されます。バイオームは、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")
}| メンバー | 内容 |
|---|---|
startX、startZ |
この洞窟が始まるチャンク(チャンク座標:ブロック / 16) |
chunkX、chunkZ |
作られているチャンク |
minY、maxY |
くり抜ける高さ(両端を含む) |
seed、random |
ワールドのシード値と、この開始点の乱数:開始点が尋ねられるどのチャンクでも同じ値を引くので、チャンクをまたぐ洞窟もつながります |
carve(x, y, z) |
そのブロックをワールド座標でくり抜きます。作られているチャンクの外のブロックは無視されます |
洞窟は、始まった場所から最大 8 チャンクまで届きます。CraftScript では、chance のデフォルトは 1 です。失敗したカーバーはそこで何もくり抜かず、ログに 1 回報告されます。
バイオームの地面
surface は、バイオームの地面を 1 種類のブロックで覆います。層、パッチ、斜面にしたいときは、代わりに関数を与えます。チャンクが作られる間、ゲームはバイオームの地面の各ブロックについて、上から下へ、そこにどのブロックを置くかを尋ねます。答えがなければ、そのブロックはディメンション(その石)に任されます。
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
}
}
}at のメンバー |
内容 |
|---|---|
x、y、z |
尋ねられているブロック |
depthAbove(depth_above) |
上の開けた空気からこのブロックまでの地面のブロック数:地面の最上部のブロックや洞窟の床では 1 |
depthBelow(depth_below) |
このブロックから下の開けた空気までの地面のブロック数:洞窟や虚空の上の最も低いブロックでは 1 |
surfaceDepth(surface_depth) |
この列の表層がどこまで深いか。ゲームがばらつかせた値 |
steep |
ゲームの山が岩肌をむき出しにする場所と同じくらい地面が傾いているかどうか |
noise(id, x, y, z) |
ワールドのノイズのうち、名前で指定した 1 つ("minecraft:surface")。シード値から作られたもの |
関数は surface より先に評価されます。関数が何も答えなかった場所では、同時に surface のブロックが指定されていれば、それが最上部を覆います。失敗した関数は 1 回だけ報告され、失敗した場所ではディメンションの地面が使われます。
タグ
タグは、ブロック、アイテム、バイオームを 1 つの名前でまとめます。これにより、フィーチャーは isIn(x, y, z, "#mymod:crystal_ground") を調べられ、バイオームのリストは "#mymod:glades" と書けます。Mod は、自分のタグにも、ゲームのものなど他の誰かのタグにも追加できます。
// 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、BIOME のいずれかです。値は ID または #tags で、名前空間のない名前は Mod のものです。複数の Mod が同じタグに追加でき、存在しない値は、エラーにならず省かれます。