valence_anvil/
parsing.rs

1use std::borrow::Cow;
2use std::collections::BTreeMap;
3use std::path::PathBuf;
4
5use thiserror::Error;
6use valence_server::block::{PropName, PropValue};
7use valence_server::layer::chunk::{Chunk, UnloadedChunk};
8use valence_server::nbt::{Compound, List, Value};
9use valence_server::protocol::BlockKind;
10use valence_server::registry::biome::BiomeId;
11use valence_server::registry::BiomeRegistry;
12use valence_server::{ChunkPos, Ident};
13
14use crate::{RegionError, RegionFolder};
15
16#[derive(Debug)]
17pub struct DimensionFolder {
18    region: RegionFolder,
19    /// Mapping of biome names to their biome ID.
20    biome_to_id: BTreeMap<Ident<String>, BiomeId>,
21}
22
23impl DimensionFolder {
24    pub fn new<R: Into<PathBuf>>(dimension_root: R, biomes: &BiomeRegistry) -> Self {
25        let mut region_root = dimension_root.into();
26        region_root.push("region");
27
28        Self {
29            region: RegionFolder::new(region_root),
30            biome_to_id: biomes
31                .iter()
32                .map(|(id, name, _)| (name.to_string_ident(), id))
33                .collect(),
34        }
35    }
36
37    /// Gets the parsed chunk at the given chunk position.
38    ///
39    /// Returns `Ok(Some(chunk))` if the chunk exists and no errors occurred
40    /// loading it. Returns `Ok(None)` if the chunk does not exist and no
41    /// errors occurred attempting to load it. Returns `Err(_)` if an error
42    /// occurred attempting to load the chunk.
43    pub fn get_chunk(&mut self, pos: ChunkPos) -> Result<Option<ParsedChunk>, ParseChunkError> {
44        let Some(raw_chunk) = self.region.get_chunk(pos.x, pos.z)? else {
45            return Ok(None);
46        };
47        let parsed = parse_chunk(raw_chunk.data, &self.biome_to_id)?;
48        Ok(Some(ParsedChunk {
49            chunk: parsed,
50            timestamp: raw_chunk.timestamp,
51        }))
52    }
53}
54
55/// A chunk parsed to show block information, biome information etc.
56pub struct ParsedChunk {
57    pub chunk: UnloadedChunk,
58    pub timestamp: u32,
59}
60
61#[derive(Debug, Error)]
62#[non_exhaustive]
63pub enum ParseChunkError {
64    #[error("region error: {0}")]
65    Region(#[from] RegionError),
66    #[error("missing chunk sections")]
67    MissingSections,
68    #[error("missing chunk section Y")]
69    MissingSectionY,
70    #[error("section Y is out of bounds")]
71    SectionYOutOfBounds,
72    #[error("missing block states")]
73    MissingBlockStates,
74    #[error("missing block palette")]
75    MissingBlockPalette,
76    #[error("invalid block palette length")]
77    BadBlockPaletteLen,
78    #[error("missing block name in palette")]
79    MissingBlockName,
80    #[error("unknown block name of \"{0}\"")]
81    UnknownBlockName(String),
82    #[error("unknown property name of \"{0}\"")]
83    UnknownPropName(String),
84    #[error("property value of block is not a string")]
85    BadPropValueType,
86    #[error("unknown property value of \"{0}\"")]
87    UnknownPropValue(String),
88    #[error("missing packed block state data in section")]
89    MissingBlockStateData,
90    #[error("unexpected number of longs in block state data")]
91    BadBlockLongCount,
92    #[error("invalid block palette index")]
93    BadBlockPaletteIndex,
94    #[error("missing biomes")]
95    MissingBiomes,
96    #[error("missing biome palette")]
97    MissingBiomePalette,
98    #[error("invalid biome palette length")]
99    BadBiomePaletteLen,
100    #[error("biome name is not a valid resource identifier")]
101    BadBiomeName,
102    #[error("missing packed biome data in section")]
103    MissingBiomeData,
104    #[error("unexpected number of longs in biome data")]
105    BadBiomeLongCount,
106    #[error("invalid biome palette index")]
107    BadBiomePaletteIndex,
108    #[error("missing block entities")]
109    MissingBlockEntities,
110    #[error("missing block entity ident")]
111    MissingBlockEntityIdent,
112    #[error("invalid block entity ident of \"{0}\"")]
113    InvalidBlockEntityName(String),
114    #[error("invalid block entity position")]
115    InvalidBlockEntityPosition,
116}
117
118fn parse_chunk(
119    mut nbt: Compound,
120    biome_map: &BTreeMap<Ident<String>, BiomeId>, // TODO: replace with biome registry arg.
121) -> Result<UnloadedChunk, ParseChunkError> {
122    let Some(Value::List(List::Compound(sections))) = nbt.remove("sections") else {
123        return Err(ParseChunkError::MissingSections);
124    };
125
126    if sections.is_empty() {
127        return Ok(UnloadedChunk::new());
128    }
129
130    let mut chunk =
131        UnloadedChunk::with_height((sections.len() * 16).try_into().unwrap_or(u32::MAX));
132
133    let min_sect_y = i32::from(
134        sections
135            .iter()
136            .filter_map(|sect| {
137                if let Some(Value::Byte(sect_y)) = sect.get("Y") {
138                    Some(*sect_y)
139                } else {
140                    None
141                }
142            })
143            .min()
144            .unwrap(),
145    );
146
147    let mut converted_block_palette = vec![];
148    let mut converted_biome_palette = vec![];
149
150    for mut section in sections {
151        let Some(Value::Byte(sect_y)) = section.remove("Y") else {
152            return Err(ParseChunkError::MissingSectionY);
153        };
154
155        let sect_y = (i32::from(sect_y) - min_sect_y) as u32;
156
157        if sect_y >= chunk.height() / 16 {
158            return Err(ParseChunkError::SectionYOutOfBounds);
159        }
160
161        let Some(Value::Compound(mut block_states)) = section.remove("block_states") else {
162            return Err(ParseChunkError::MissingBlockStates);
163        };
164
165        let Some(Value::List(List::Compound(palette))) = block_states.remove("palette") else {
166            return Err(ParseChunkError::MissingBlockPalette);
167        };
168
169        if !(1..BLOCKS_PER_SECTION).contains(&palette.len()) {
170            return Err(ParseChunkError::BadBlockPaletteLen);
171        }
172
173        converted_block_palette.clear();
174
175        for mut block in palette {
176            let Some(Value::String(name)) = block.remove("Name") else {
177                return Err(ParseChunkError::MissingBlockName);
178            };
179
180            let Some(block_kind) = BlockKind::from_str(ident_path(&name)) else {
181                return Err(ParseChunkError::UnknownBlockName(name));
182            };
183
184            let mut state = block_kind.to_state();
185
186            if let Some(Value::Compound(properties)) = block.remove("Properties") {
187                for (key, value) in properties {
188                    let Value::String(value) = value else {
189                        return Err(ParseChunkError::BadPropValueType);
190                    };
191
192                    let Some(prop_name) = PropName::from_str(&key) else {
193                        return Err(ParseChunkError::UnknownPropName(key));
194                    };
195
196                    let Some(prop_value) = PropValue::from_str(&value) else {
197                        return Err(ParseChunkError::UnknownPropValue(value));
198                    };
199
200                    state = state.set(prop_name, prop_value);
201                }
202            }
203
204            converted_block_palette.push(state);
205        }
206
207        if converted_block_palette.len() == 1 {
208            chunk.fill_block_state_section(sect_y, converted_block_palette[0]);
209        } else {
210            debug_assert!(converted_block_palette.len() > 1);
211
212            let Some(Value::LongArray(data)) = block_states.remove("data") else {
213                return Err(ParseChunkError::MissingBlockStateData);
214            };
215
216            let bits_per_idx = bit_width(converted_block_palette.len() - 1).max(4);
217            let idxs_per_long = 64 / bits_per_idx;
218            let long_count = BLOCKS_PER_SECTION.div_ceil(idxs_per_long);
219            let mask = 2_u64.pow(bits_per_idx as u32) - 1;
220
221            if long_count != data.len() {
222                return Err(ParseChunkError::BadBlockLongCount);
223            };
224
225            let mut i: u32 = 0;
226            for long in data {
227                let u64 = long as u64;
228
229                for j in 0..idxs_per_long {
230                    if i >= BLOCKS_PER_SECTION as u32 {
231                        break;
232                    }
233
234                    let idx = (u64 >> (bits_per_idx * j)) & mask;
235
236                    let Some(block) = converted_block_palette.get(idx as usize).copied() else {
237                        return Err(ParseChunkError::BadBlockPaletteIndex);
238                    };
239
240                    let x = i % 16;
241                    let z = i / 16 % 16;
242                    let y = i / (16 * 16);
243
244                    chunk.set_block_state(x, sect_y * 16 + y, z, block);
245
246                    i += 1;
247                }
248            }
249        }
250
251        let Some(Value::Compound(biomes)) = section.get("biomes") else {
252            return Err(ParseChunkError::MissingBiomes);
253        };
254
255        let Some(Value::List(List::String(palette))) = biomes.get("palette") else {
256            return Err(ParseChunkError::MissingBiomePalette);
257        };
258
259        if !(1..BIOMES_PER_SECTION).contains(&palette.len()) {
260            return Err(ParseChunkError::BadBiomePaletteLen);
261        }
262
263        converted_biome_palette.clear();
264
265        for biome_name in palette {
266            let Ok(ident) = Ident::<Cow<str>>::new(biome_name) else {
267                return Err(ParseChunkError::BadBiomeName);
268            };
269
270            converted_biome_palette
271                .push(biome_map.get(ident.as_str()).copied().unwrap_or_default());
272        }
273
274        if converted_biome_palette.len() == 1 {
275            chunk.fill_biome_section(sect_y, converted_biome_palette[0]);
276        } else {
277            debug_assert!(converted_biome_palette.len() > 1);
278
279            let Some(Value::LongArray(data)) = biomes.get("data") else {
280                return Err(ParseChunkError::MissingBiomeData);
281            };
282
283            let bits_per_idx = bit_width(converted_biome_palette.len() - 1);
284            let idxs_per_long = 64 / bits_per_idx;
285            let long_count = BIOMES_PER_SECTION.div_ceil(idxs_per_long);
286            let mask = 2_u64.pow(bits_per_idx as u32) - 1;
287
288            if long_count != data.len() {
289                return Err(ParseChunkError::BadBiomeLongCount);
290            };
291
292            let mut i: u32 = 0;
293            for &long in data {
294                let u64 = long as u64;
295
296                for j in 0..idxs_per_long {
297                    if i >= BIOMES_PER_SECTION as u32 {
298                        break;
299                    }
300
301                    let idx = (u64 >> (bits_per_idx * j)) & mask;
302
303                    let Some(biome) = converted_biome_palette.get(idx as usize).copied() else {
304                        return Err(ParseChunkError::BadBiomePaletteIndex);
305                    };
306
307                    let x = i % 4;
308                    let z = i / 4 % 4;
309                    let y = i / (4 * 4);
310
311                    chunk.set_biome(x, sect_y * 4 + y, z, biome);
312
313                    i += 1;
314                }
315            }
316        }
317    }
318
319    let Some(Value::List(block_entities)) = nbt.remove("block_entities") else {
320        return Err(ParseChunkError::MissingBlockEntities);
321    };
322
323    if let List::Compound(block_entities) = block_entities {
324        for mut comp in block_entities {
325            let Some(Value::String(ident)) = comp.remove("id") else {
326                return Err(ParseChunkError::MissingBlockEntityIdent);
327            };
328
329            if let Err(e) = Ident::new(ident) {
330                return Err(ParseChunkError::InvalidBlockEntityName(e.0));
331            }
332
333            let Some(Value::Int(x)) = comp.remove("x") else {
334                return Err(ParseChunkError::InvalidBlockEntityPosition);
335            };
336
337            let x = x.rem_euclid(16) as u32;
338
339            let Some(Value::Int(y)) = comp.remove("y") else {
340                return Err(ParseChunkError::InvalidBlockEntityPosition);
341            };
342
343            let Ok(y) = u32::try_from(y.wrapping_sub(min_sect_y * 16)) else {
344                return Err(ParseChunkError::InvalidBlockEntityPosition);
345            };
346
347            if y >= chunk.height() {
348                return Err(ParseChunkError::InvalidBlockEntityPosition);
349            }
350
351            let Some(Value::Int(z)) = comp.remove("z") else {
352                return Err(ParseChunkError::InvalidBlockEntityPosition);
353            };
354
355            let z = z.rem_euclid(16) as u32;
356
357            comp.remove("keepPacked");
358
359            chunk.set_block_entity(x, y, z, Some(comp));
360        }
361    }
362
363    Ok(chunk)
364}
365
366const BLOCKS_PER_SECTION: usize = 16 * 16 * 16;
367const BIOMES_PER_SECTION: usize = 4 * 4 * 4;
368
369/// Gets the path part of a resource identifier.
370fn ident_path(ident: &str) -> &str {
371    match ident.rsplit_once(':') {
372        Some((_, after)) => after,
373        None => ident,
374    }
375}
376
377/// Returns the minimum number of bits needed to represent the integer `n`.
378const fn bit_width(n: usize) -> usize {
379    (usize::BITS - n.leading_zeros()) as usize
380}