.mca内に格納されるチャンク1つ分のNBT構造。ここから先は形式依存である。
本書は26.1で導入された形式(DataVersion 4786以降) を対象とし、記述はJava版26.2(DataVersion 4903)の実データで確認した。
前提: 20 Anvilリージョン形式 / 31 パレット付きコンテナ
ルートは名前なし(空文字列)のTAG_Compound。
下表はJava版26.2の実ワールド(3,481チャンク)を走査して確認した内容である。
「出現」欄は、走査したチャンク(full/未完成の両方を含む)のすべてに現れたキーを「全部」、一部のチャンクにだけ現れたキーを「一部」と示す。
| キー | 型 | 出現 | 内容 |
|---|---|---|---|
DataVersion |
Int |
全部 | チャンク構造のバージョン。26.1は4786、26.2は4903 |
xPos |
Int |
全部 | 絶対チャンクX座標 |
zPos |
Int |
全部 | 絶対チャンクZ座標 |
yPos |
Int |
全部 | 最下段セクションのY位置。オーバーワールドは-4 |
Status |
String |
全部 | 生成段階(→ 1.1) |
LastUpdate |
Long |
全部 | 最終更新のゲーム内時刻 |
InhabitedTime |
Long |
全部 | プレイヤー滞在時間(tick) |
sections |
List<Compound> |
全部 | セクション(→ 2章) |
block_entities |
List<Compound> |
全部 | ブロックエンティティ |
block_ticks |
List<Compound> |
全部 | 予約されたブロック更新 |
fluid_ticks |
List<Compound> |
全部 | 予約された液体更新 |
Heightmaps |
Compound |
全部 | 高さマップ(→ 3章) |
structures |
Compound |
全部 | startsとReferencesを持つ |
PostProcessing |
List<List> |
全部 | 生成後処理の待ち位置 |
isLightOn |
Byte |
一部 | 光源計算済みフラグ。実データではminecraft:fullのチャンクにのみ現れた |
entities |
List<Compound> |
一部 | 生成途中のチャンクだけが持つ。完成するとentities/のリージョンへ移る |
carving_mask |
ByteArray |
一部 | 洞窟の削り取り状況。minecraft:carvers前後の段階でのみ現れる |
実データに現れた値(生成の進行順)。
| 値 | 意味 |
|---|---|
minecraft:structure_starts |
構造物の開始点だけ決まった段階 |
minecraft:biomes |
バイオームまで決まった段階 |
minecraft:carvers |
洞窟の削り取りまで済んだ段階 |
minecraft:initialize_light |
光源の初期化まで済んだ段階 |
minecraft:full |
生成完了。ブロック操作の対象になるのはこれ |
minecraft:full以外のチャンクは構造が不完全である。
set_blockはminecraft:fullのチャンクに対してのみ行うのが安全である。
本ライブラリは生成段階を検査せず、それ以外のチャンクにもそのまま書き込む。
生成済みかどうかはis_fully_generatedで確かめられる。
本ライブラリは上記以外のキーも含め、読んだ要素をすべて保持する。 未知のキーを落とさないので、将来の追加要素があってもデータはおかしくならない。
Chunk型は解釈済みの値(sectionsなど)と元のNbtCompoundの両方を持ち、書き出し時は「解釈した部分(各セクションのblock_statesとbiomes)を元のCompoundへ書き戻し、解釈していないキーはそのまま残す」方式をとる。
sectionsはList<Compound>。1要素が16×16×16ブロックを表す。
| キー | 型 | 内容 |
|---|---|---|
Y |
Byte |
セクションのY位置。オーバーワールドは-5..20 |
block_states |
Compound |
ブロック状態のパレット付きコンテナ(4096エントリ) |
biomes |
Compound |
バイオームのパレット付きコンテナ(64エントリ) |
BlockLight |
ByteArray |
2048バイト。1ブロックあたり4bit |
SkyLight |
ByteArray |
2048バイト。1ブロックあたり4bit |
YがyPos - 1や最上段+1になっている光源専用セクションが存在しうる。
光源専用セクションはblock_statesを持たないため、ブロックAPIでは空セクションとして扱う。
(26.2の実ワールドでは観測されず、全セクションがblock_statesとbiomesを持っていた。
ただし将来や他の生成条件で現れうるため、block_statesの有無は必ず確認する。)
BlockLight / SkyLightは必要なセクションにしか無い。
実データでは83,544セクションのうちBlockLightが5,103、SkyLightが4,904だった。
sectionsはYの昇順に並んでいるが、並び順に依存してはならない。
読み込み時にYから索引を作る。
{ Name: "minecraft:oak_stairs", Properties: { facing: "north", half: "top", waterlogged: "false" } }
Nameは必須のString。名前空間が省略されていたらminecraft:を補うPropertiesは任意のCompound。値はすべてString(数値や真偽値も文字列)Propertiesが空の場合、Minecraftはキー自体を出力しない。本ライブラリも同様にする
パレット要素は次の形式で文字列化・解析できる(BlockState)。
<名前空間>:<パス>[<キー>=<値>,<キー>=<値>,...]
- プロパティは必ず名前の昇順に並べて出力する。 これにより、同じブロック状態は内部の並び順に関係なく常に同じ文字列になり、全言語で一致する
- プロパティが空のときは角括弧ごと省略する
- 解析時は名前空間の省略を許し、
minecraft:を補う - 解析時のみ、
=の前後と,の直後の空白を読み飛ばす。出力側は空白を入れない
解析の失敗はすべてINVALID_ARGUMENT(呼び出し側が渡した文字列の誤りであり、ファイルの中身の破損ではないため)。
| 入力 | 結果 |
|---|---|
stone |
minecraft:stone |
minecraft:oak_stairs[half=top,facing=north] |
minecraft:oak_stairs[facing=north,half=top] |
minecraft:oak_stairs[] |
minecraft:oak_stairs |
| `` (空文字列) | INVALID_ARGUMENT |
minecraft:oak_stairs[facing=north |
INVALID_ARGUMENT(閉じ括弧が無い) |
minecraft:oak_stairs[]extra |
INVALID_ARGUMENT(]で終わっていない) |
minecraft:oak_stairs[facing] |
INVALID_ARGUMENT(=が無い) |
minecraft:oak_stairs[=north] |
INVALID_ARGUMENT(キーが空) |
minecraft:oak_stairs[facing=north,facing=south] |
INVALID_ARGUMENT(キーの重複) |
キーの重複を後勝ちで受け入れないのは、どちらが採用されたのかが呼び出し側から分からないまま書き込まれてしまうため。
block index = (y & 15) * 256 + (z & 15) * 16 + (x & 15)
biome index = ((y & 15) / 4) * 16 + ((z & 15) / 4) * 4 + ((x & 15) / 4)
& 15により、負の座標でも正しくセクション内の相対値になる。
セクションの選択はsectionY = y >> 4(算術右シフト)で行う。
ブロックそのもの以外に、特定の座標に結びついたデータがチャンクのルート直下に3つある。
| キー | 内容 | 主なキー |
|---|---|---|
block_entities |
チェスト・看板・スポナーなどの中身 | id, x, y, z |
block_ticks |
ブロックのティック予約 | i(ブロックID), x, y, z, t, p |
fluid_ticks |
液体のティック予約 | 同上 |
いずれもList<Compound>で、座標は絶対ワールド座標のIntで持つ。
チャンク内の相対座標ではないので、チャンク座標から換算して比べる必要がある。
Java版26.2の実データの例を示す。
block_entities: {id:"minecraft:mob_spawner", x:-318, y:-40, z:-302, SpawnData:{...}, ...}
block_ticks: {i:"minecraft:acacia_leaves", x:-93, y:75, z:-305, t:0, p:0}
ブロックを別の種類へ置き換えたら、その座標を指す2.3の要素をすべて取り除く。
取り除かないと、たとえばチェストのあった座標に石を置いたとき、block_entitiesにチェストの中身が残る。
ブロックと中身が食い違った状態になり、Minecraft側で警告やアイテムの復活といった予期しない挙動を招く。
しかも、利用者からは見えにくい形でデータがおかしくなる。
| 状況 | 動作 |
|---|---|
| 別の種類のブロックを置いた | その座標の要素を3つのリストすべてから取り除く |
| 同じブロック状態を置き直した | 何もしない(状態が変わっていないので掃除する理由がない) |
要素がx y zを持たない |
対象か判断できないので触らない |
新しいブロックが本来必要とするblock_entity(置いたのがチェストなら空のチェスト)は生成しない。
どのブロックがblock entityを必要とするかの判定にはブロック定義テーブルが要り、それ自体が別の成果物になるため(adr/0004と同じ理由)。
必要なら利用者が生のNBTへ直接足す。
HeightmapsはCompoundで、各値はLongArray。
256エントリ(16×16)を9ビット幅のBitStorage(跨ぎなし)で詰めたもの。
bits = 9
values_per_long = 64 / 9 = 7
long_count = ceil(256 / 7) = 37
index = z * 16 + x
値 = (そのXZ列で条件を満たす最上位ブロックのY) - yPos*16 + 1
| キー | 意味 |
|---|---|
WORLD_SURFACE |
空気でない最上位ブロック |
MOTION_BLOCKING |
移動を妨げるか液体である最上位ブロック |
MOTION_BLOCKING_NO_LEAVES |
上記から葉ブロックを除いたもの |
OCEAN_FLOOR |
移動を妨げる固体の最上位ブロック |
読み書き(そのまま保持)のみ対応し、再計算は行わない。 どのブロックが「移動を妨げる」かの判定にはブロック定義テーブルが必要で、それ自体が別の大きな成果物になるため(→ adr/0004)。
ブロックを改変した後は次のいずれかを推奨する。
chunk.clear_heightmaps()でHeightmapsを削除する。Minecraftが次回読み込み時に再計算する- 何もしない。見た目(草の生成やモブスポーン判定)が一時的にずれる可能性がある
同様にchunk.invalidate_lighting()はisLightOnを0にし、光源の再計算を促す。
entities/とpoi/のリージョンファイルも同じAnvil形式だが、中身が異なる。
entities(dimensions/<ns>/<path>/entities/r.X.Z.mca)
| キー | 型 | 内容 |
|---|---|---|
DataVersion |
Int |
|
Position |
IntArray |
[chunkX, chunkZ]の2要素 |
Entities |
List<Compound> |
エンティティ。各要素がid(String)とPos(List<Double>)を持つ |
poi(dimensions/<ns>/<path>/poi/r.X.Z.mca)
| キー | 型 | 内容 |
|---|---|---|
DataVersion |
Int |
|
Sections |
Compound |
キーがセクションYの10進文字列("-1"など)、値が下記 |
Sectionsの各値は次の形をとる。
{ Valid: 1b, Records: [ { pos: [I; 279, -16, 182], free_tickets: 1, type: "minecraft:home" } ] }
これらは生のNbtCompoundとして読み書きできるところまでを対象とし、型付きのAPIは提供しない。
ブロックの位置と範囲を表す値型を用意する。
BlockPos { x, y, z }
chunk_pos() この座標を含むチャンクの座標(x >> 4, z >> 4)
local_x() local_z() チャンク内での位置 (0..15)
offset(dx, dy, dz) 各軸へずらした座標
Cuboid { min_x, min_y, min_z, max_x, max_y, max_z }
of(x1, y1, z1, x2, y2, z2) 両端から作る。大小の順序は問わない
size_x() size_y() size_z() 各軸の長さ
volume() 含まれるブロックの個数
contains(x, y, z) 範囲に含まれるか
positions() 範囲内の座標を順に返す
Cuboidは両端を含む。of(0, 0, 0, 0, 0, 0)は1ブロックで、volume()は1。
positions()の並びはY、Z、Xの順とし、Xをいちばん内側で動かす。
Xはチャンク内で連続するので、同じセクションに続けてアクセスできる。
チャンク座標への変換は算術右シフトで行う。 論理右シフトでは負の座標が正しく求まらない。
対応の判定はワールドの形式で行う。Minecraftのバージョン番号は条件にしない。
いまの形式は26.1(DataVersion 4786)で入った。
これ以降のバージョンは、形式が変わらない限りそのまま読み書きできる。
MIN_SUPPORTED_DATA_VERSION = 4786 扱える形式の下限
TARGET_DATA_VERSION = 4903 動作を確かめたバージョン(26.2)
- 読み込み時、
DataVersionが4786以上なら何もしない- 対象より新しいバージョンでも警告を出さない
- 形式が同じなら読めるので、知らせる必要がない
4786未満ならChunkReadOptions.on_version_mismatchに従うWarn(既定): 警告コールバックを呼んで続行するError:UNSUPPORTED_DATA_VERSIONIgnore: 何もしない
- 書き込み時、
DataVersionは読んだ値をそのまま残す- 書き換えると、そのワールドを開くゲーム側の判断を誤らせる
4786未満のチャンクは既定でUNSUPPORTED_DATA_VERSIONChunkWriteOptions.allow_foreign_data_version = trueで明示的に許可できる。このときもDataVersionは書き換えない
形式が変わったときだけ下限を上げる。 下限を上げると、そのワールドを扱えなくなる利用者が出るので、バージョン番号のいちばん上の桁も上げる。
詳細は07 バージョンポリシー。