mca_parser/nbt.rs
1//! This module contains all structs related to the nbt data in the chunks
2//!
3//! Note: Many of the descriptions for the fields/structs in this module come directly from the
4//! Minecraft Wiki, with some occasional modifications to make them make more sense (at least in
5//! this context). The latest update to these descriptions was on 5 March 2024, and I'll try to
6//! keep them updated.
7//!
8//! <rant>
9//! Mojang has the worst naming conventions ever! Sometimes they use snake_case, sometimes they use
10//! PascalCase, other times they use camelCase, sometimes it's SCREAMING_SNAKE_CASE! This is so
11//! annoying when dealing with Mojang things! Feel free to look at the name changes on _almost
12//! every field in this module_ just to make it happy and you'll be just as annoyed as I am!
13//! </rant>
14
15use fastnbt::{self, LongArray, Value};
16use serde::Deserialize;
17
18/// Represents a namespace that can show up in the game
19#[derive(Debug, Clone, Eq, PartialEq)]
20pub enum Namespace {
21 /// Default namespace for every vanilla item/block/etc
22 Minecraft,
23 /// Custom namespace, used in mods/datapacks/etc
24 Custom(String),
25}
26
27impl From<&str> for Namespace {
28 fn from(value: &str) -> Self {
29 if value == "minecraft" {
30 Self::Minecraft
31 } else {
32 Self::Custom(value.into())
33 }
34 }
35}
36
37/// A struct which represents a key with a namespace
38#[derive(Debug, Clone, Eq, PartialEq)]
39pub struct NamespacedKey {
40 /// The namespace of this key
41 pub namespace: Namespace,
42 /// The key itself
43 pub key: String,
44}
45
46impl NamespacedKey {
47 /// Create a new NamespacedKey from a namsepace and key
48 pub fn new(namespace: impl AsRef<str>, key: String) -> Self {
49 Self {
50 namespace: Namespace::from(namespace.as_ref()),
51 key,
52 }
53 }
54
55 /// Create a new NamespacedKey using the `minecraft` namespace
56 pub const fn minecraft(key: String) -> Self {
57 Self {
58 namespace: Namespace::Minecraft,
59 key,
60 }
61 }
62}
63
64impl From<&str> for NamespacedKey {
65 fn from(value: &str) -> Self {
66 if let Some((ns, k)) = value.split_once(':') {
67 Self::new(ns, k.into())
68 } else {
69 Self::minecraft(value.into())
70 }
71 }
72}
73
74impl<'de> serde::Deserialize<'de> for NamespacedKey {
75 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
76 where
77 D: serde::Deserializer<'de>,
78 {
79 Ok(NamespacedKey::from(<&str>::deserialize(deserializer)?))
80 }
81}
82
83/// The represents that chunk's nbt data stored in the region file
84///
85/// - See <https://minecraft.wiki/w/Chunk_format#NBT_structure>
86#[derive(Deserialize, Debug, Clone, PartialEq)]
87pub struct ChunkNbt {
88 /// Version of the chunk NBT structure.
89 #[serde(rename = "DataVersion")]
90 pub data_version: i32,
91 /// `x` position of the chunk (in absolute chunks from world `x`, `z` origin, __not__ relative to the region).
92 #[serde(rename = "xPos")]
93 pub x_pos: i32,
94 /// `z` position of the chunk (in absolute chunks from world `x`, `z` origin, __not__ relative to the region).
95 #[serde(rename = "zPos")]
96 pub z_pos: i32,
97 /// Lowest Y section in chunk
98 #[serde(rename = "yPos")]
99 pub y_pos: i32,
100 /// Defines the world generation status of this chunk
101 ///
102 /// All status except [`Status::Full`] are used for chunks called proto-chunks, in other words,
103 /// for chunks with incomplete generation.
104 #[serde(rename = "Status")]
105 pub status: NamespacedKey,
106 /// Tick when the chunk was last saved.
107 #[serde(rename = "LastUpdate")]
108 pub last_update: i64,
109 /// List of block entities in this chunk
110 pub block_entities: Vec<Value>, // TODO: Can probably be replaced with an enum
111 /// Several different heightmaps corresponding to 256 values compacted at 9 bits per value
112 /// (lowest being 0, highest being 384, both values inclusive).
113 #[serde(rename = "Heightmaps")]
114 pub height_maps: HeightMaps,
115 /// List of "active" liquids in this chunk waiting to be updated
116 pub fluid_ticks: Vec<Value>, // - See Tile Tick Format
117 /// List of "active" blocks in this chunk waiting to be updated. These are used to save the
118 /// state of redstone machines or falling sand, and other activity
119 pub block_ticks: Vec<Value>,
120 /// The cumulative number of ticks players have been in this chunk. Note that this value
121 /// increases faster when more players are in the chunk. Used for Regional Difficulty.
122 #[serde(rename = "InhabitedTime")]
123 pub inhabited_time: i64,
124 /// This appears to be biome blending data, although more testing is needed to confirm.
125 pub blending_data: Option<BlendingData>,
126 /// A List of 24 Lists that store the positions of blocks that need to receive an update when
127 /// a proto-chunk turns into a full chunk, packed in Shorts. Each list corresponds to specific
128 /// section in the height of the chunk
129 #[serde(rename = "PostProcessing")]
130 pub post_processing: [Vec<Value>; 24],
131 /// Structure data in this chunk
132 pub structures: Value,
133 /// A list of the sections in this chunk
134 ///
135 /// All sections in the world's height are present in this list, even those who are empty (filled with air).
136 pub sections: Vec<ChunkSection>,
137}
138
139/// From the wiki: This appears to be biome blending data, although more testing is needed to confirm.
140///
141/// - See <https://minecraft.wiki/w/Chunk_format#NBT_structure>
142#[derive(Deserialize, Debug, Clone, PartialEq)]
143pub struct BlendingData {
144 /// [More information needed]
145 pub min_section: i32,
146 /// [More information needed]
147 pub max_section: i32,
148}
149
150/// Several different heightmaps corresponding to 256 values compacted at 9 bits per value (lowest
151/// being 0, highest being 384, both values inclusive).
152///
153/// - See <https://minecraft.wiki/w/Chunk_format#NBT_structure>
154/// - See <https://minecraft.wiki/w/Heightmap>
155#[derive(Deserialize, Debug, Clone, PartialEq)]
156#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
157pub struct HeightMaps {
158 /// Stores the Y-level of the highest block whose material blocks motion (i.e. has a collision
159 /// box) or blocks that contains a fluid (water, lava, or waterlogging blocks).
160 pub motion_blocking: Option<HeightMap>,
161 /// Stores the Y-level of the highest block whose material blocks motion (i.e. has a collision
162 /// box), or blocks that contains a fluid (water, lava, or waterlogging blocks), except various
163 /// leaves. Used only on the server side.
164 pub motion_blocking_no_leaves: Option<HeightMap>,
165 /// Stores the Y-level of the highest block whose material blocks motion (i.e. has a collision
166 /// box). One exception is carpets, which are considered to not have a collision box to
167 /// heightmaps. Used only on the server side.
168 pub ocean_floor: Option<HeightMap>,
169 /// Stores the Y-level of the highest block whose material blocks motion (i.e. has a collision
170 /// box). Used only during world generation, and automatically deleted after carvers are
171 /// generated.
172 pub ocean_floor_wg: Option<HeightMap>,
173 /// Stores the Y-level of the highest non-air (all types of air) block.
174 pub world_surface: Option<HeightMap>,
175 /// Stores the Y-level of the highest non-air (all types of air) block. Used only during world
176 /// generation, and automatically deleted after carvers are generated.
177 pub world_surface_wg: Option<HeightMap>,
178}
179
180/// Wrapper type around a [`LongArray`] to abstract away the details of how the HeightMaps store
181/// their data
182///
183/// Several different heightmaps corresponding to 256 values compacted at 9 bits per value (lowest
184/// being 0, highest being 384, both values inclusive).
185///
186/// - See <https://minecraft.wiki/w/Chunk_format#NBT_structure>
187/// - See <https://minecraft.wiki/w/Heightmap>
188#[derive(Deserialize, Debug, Clone, PartialEq)]
189#[serde(transparent)]
190pub struct HeightMap {
191 /// The 9-bit values are stored in an array of 37 Longs ([`u64`]), each containing 7 values (7×9 =
192 /// 63; the last bit is unused). The 9-bit values are unsigned, and indicate the amount of blocks
193 /// above the bottom of the world (y = -64).
194 raw: LongArray,
195}
196
197impl HeightMap {
198 /// Get the height of a chunk using this heightmap at a positon (relative to the chunk)
199 pub fn get_height(&self, block_x: u32, block_z: u32) -> i32 {
200 assert!(block_x < 16);
201 assert!(block_z < 16);
202
203 let index = (block_z * 16 + block_x) as usize;
204
205 let num = self.raw[index / 7] as u64 >> ((index % 7) * 9) & (2u64.pow(9) - 1);
206
207 num as i32 - 65
208 }
209}
210
211/// - See <https://minecraft.wiki/w/Chunk_format#NBT_structure>
212#[derive(Deserialize, Debug, PartialEq, Clone)]
213pub struct BlockStates {
214 /// Set of different block states used in this particular section.
215 pub palette: Vec<BlockState>,
216 /// A packed array of 4096 indices pointing to the palette
217 ///
218 /// If only one block state is present in the palette, this field is not required and the
219 /// block fills the whole section.
220 ///
221 /// All indices are the same length. This length is set to the minimum amount of bits required
222 /// to represent the largest index in the palette, and then set to a minimum size of 4 bits.
223 ///
224 /// The indices are not packed across multiple elements of the array, meaning that
225 /// if there is no more space in a given 64-bit integer for the whole next index, it starts
226 /// instead at the first (lowest) bit of the next 64-bit integer. Different sections of a
227 /// chunk can have different lengths for the indices.
228 pub data: Option<LongArray>,
229}
230
231/// Data which represents a block in a chunk
232///
233/// - See <https://minecraft.wiki/w/Chunk_format#NBT_structure>
234#[derive(Deserialize, Debug, PartialEq, Clone)]
235pub struct BlockState {
236 /// Block [resource location](https://minecraft.wiki/w/Resource_location)
237 #[serde(rename = "Name")]
238 pub name: NamespacedKey,
239 /// Properties of the block state
240 #[serde(rename = "Properties")]
241 pub properties: Option<Value>,
242}
243
244/// - See <https://minecraft.wiki/w/Chunk_format#NBT_structure>
245#[derive(Deserialize, Debug, PartialEq, Clone)]
246pub struct Biomes {
247 /// Set of different biomes used in this particular section.
248 pub palette: Vec<String>,
249 /// A packed array of 64 indices pointing to the palette
250 ///
251 /// If only one biome is present in the palette, this field is not required and the biome fills
252 /// the whole section.
253 ///
254 /// All indices are the same length: the minimum amount of bits required to represent the
255 /// largest index in the palette. These indices do not have a minimum size. Different chunks
256 /// can have different lengths for the indices.
257 pub data: Option<LongArray>,
258}
259
260/// - See <https://minecraft.wiki/w/Chunk_format#Tile_tick_format>
261#[derive(Deserialize, Debug, PartialEq, Eq, Clone)]
262pub struct TileTick {
263 /// The ID of the block; used to activate the correct block update procedure.
264 #[serde(rename = "i")]
265 pub id: String,
266 /// If multiple tile ticks are scheduled for the same tick, tile ticks with lower priority are
267 /// processed first. If they also have the same priority, the order is unknown.
268 #[serde(rename = "p")]
269 pub priority: i32,
270 /// The number of ticks until processing should occur. May be negative when processing is
271 /// overdue.
272 #[serde(rename = "t")]
273 pub ticks: i32,
274 /// x position
275 pub x: i32,
276 /// y position
277 pub y: i32,
278 /// z position
279 pub z: i32,
280}
281
282/// The represents a section (or subchunk) from a chunk's NBT data stored in the region file
283///
284/// - See <https://minecraft.wiki/w/Chunk_format#NBT_structure>
285#[derive(Deserialize, Debug, PartialEq, Clone)]
286pub struct ChunkSection {
287 /// Block states of all blocks in this section
288 pub block_states: Option<BlockStates>,
289 /// y-value of the section
290 #[serde(rename = "Y")]
291 pub y: i8,
292 /// Biomes used in this chunk
293 pub biomes: Option<Biomes>,
294}