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}