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 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 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
55pub 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>, ) -> 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
369fn ident_path(ident: &str) -> &str {
371 match ident.rsplit_once(':') {
372 Some((_, after)) => after,
373 None => ident,
374 }
375}
376
377const fn bit_width(n: usize) -> usize {
379 (usize::BITS - n.leading_zeros()) as usize
380}