1use crate::{
6 BLOCKS_PER_REGION, Coords,
7 chunk::ChunkData,
8 config::Config,
9 error::{Error, Result},
10 nbt::Block,
11};
12use ahash::AHashMap;
13use dashmap::{
14 DashMap,
15 mapref::one::{Ref, RefMut},
16};
17use mca::{CompressionType, RegionIter, RegionReader, RegionWriter};
18use simdnbt::owned::{BaseNbt, Nbt, NbtCompound, NbtList, NbtTag};
19use std::{
20 fmt::Debug,
21 io::{Cursor, Read, Write},
22 ops::{Deref, Range},
23};
24
25#[derive(Clone)]
27pub struct Region {
28 pub chunks: DashMap<(u8, u8), ChunkData>,
30 pub(crate) config: Config,
32 pub region_coords: (i32, i32),
34}
35
36#[derive(Debug, Clone, PartialEq, Eq)]
38pub struct BlockWithCoordinate {
39 pub coordinates: Coords,
41 pub block: Block,
43}
44
45impl Region {
46 pub(crate) const REQUIRED_STATUS: &'static str = "minecraft:full";
48 pub const MIN_DATA_VERSION: i32 = 2860;
52
53 pub fn get_config(&self) -> &Config {
55 &self.config
56 }
57
58 pub fn set_config(&mut self, config: Config) -> Result<bool> {
62 let changed_world_height = if self.config.world_height != config.world_height {
63 self.set_world_height(config.world_height.clone())?;
64 true
65 } else {
66 false
67 };
68
69 self.config = config;
70
71 Ok(changed_world_height)
72 }
73
74 pub fn set_world_height(&mut self, range: Range<isize>) -> Result<()> {
91 let world_height_count = range.clone().count();
93 for x in 0..32 {
94 for z in 0..32 {
95 let mut chunk = self.get_chunk_mut(x, z)?;
96 chunk.pending_blocks = AHashMap::new();
97 chunk.pending_biomes = AHashMap::new();
98 chunk.seen_blocks = ChunkData::block_bitset(world_height_count);
99 chunk.seen_biomes = ChunkData::biome_bitset(world_height_count);
100 }
101 }
102
103 self.config.world_height = range;
104
105 Ok(())
106 }
107
108 pub fn empty(region_coords: (i32, i32)) -> Self {
112 let config = Config {
113 create_chunk_if_missing: true,
114 ..Default::default()
115 };
116
117 Self {
118 chunks: DashMap::new(),
119 region_coords,
120 config,
121 }
122 }
123
124 pub fn full_empty(region_coords: (i32, i32)) -> Self {
126 let mut chunks = AHashMap::new();
127
128 for x in 0..mca::REGION_SIZE as u8 {
129 for z in 0..mca::REGION_SIZE as u8 {
130 chunks.insert(
131 (x, z),
132 get_empty_chunk((x, z), region_coords, Config::DEFAULT_WORLD_HEIGHT),
133 );
134 }
135 }
136
137 Self::from_nbt(chunks, region_coords)
138 }
139
140 pub fn from_nbt(chunks: AHashMap<(u8, u8), NbtCompound>, region_coords: (i32, i32)) -> Self {
142 let config = Config::default();
143
144 let chunks = chunks
145 .into_iter()
146 .map(|(k, v)| (k, ChunkData::new(v, config.world_height.clone())))
147 .collect();
148
149 Self {
150 chunks,
151 region_coords,
152 config,
153 }
154 }
155
156 pub fn from_region<R: Read>(reader: &mut R, region_coords: (i32, i32)) -> Result<Self> {
166 let mut bytes = Vec::with_capacity(4_194_304); reader.read_to_end(&mut bytes)?;
168 let region_reader = RegionReader::new(&bytes)?;
169
170 let mut chunks = AHashMap::new();
171 for (i, chunk) in region_reader.iter().enumerate() {
172 let chunk = chunk?;
173 let chunk = match chunk {
174 Some(c) => c.decompress()?,
175 None => continue,
176 };
177
178 let chunk_nbt = match simdnbt::owned::read(&mut Cursor::new(&chunk))? {
179 Nbt::Some(nbt) => nbt.as_compound(),
180 Nbt::None => return Err(Error::InvalidNbtType("base_nbt")),
181 };
182 let (x, z) = RegionIter::get_chunk_coordinate(i);
183
184 chunks.insert((x as u8, z as u8), chunk_nbt);
185 }
186
187 Ok(Self::from_nbt(chunks, region_coords))
188 }
189
190 pub fn write<W: Write>(self, writer: &mut W) -> Result<()> {
203 let mut region_writer = RegionWriter::new();
204
205 for ((x, z), chunk_data) in self.chunks {
206 let mut raw_nbt = vec![];
207 let wrapped = Nbt::Some(BaseNbt::new("", chunk_data.nbt));
208 wrapped.write(&mut raw_nbt);
209 region_writer.push_chunk_with_compression(&raw_nbt, (x, z), CompressionType::Zlib)?;
210 }
211
212 region_writer.write(writer)?;
213
214 Ok(())
215 }
216
217 pub fn get_chunk(&self, x: u8, z: u8) -> Result<Option<Ref<'_, (u8, u8), ChunkData>>> {
228 if x >= mca::REGION_SIZE as u8 || z >= mca::REGION_SIZE as u8 {
229 return Err(Error::ChunkOutOfRegionBounds(x, z));
230 }
231
232 Ok(self.chunks.get(&(x, z)))
233 }
234
235 #[cfg(test)]
237 pub(crate) fn get_raw_chunk(
238 &self,
239 x: u8,
240 z: u8,
241 ) -> Result<Option<Ref<'_, (u8, u8), ChunkData>>> {
242 Ok(self.chunks.get(&(x, z)))
243 }
244
245 pub fn get_chunk_mut<'a>(&'a self, x: u8, z: u8) -> Result<RefMut<'a, (u8, u8), ChunkData>> {
257 if x >= mca::REGION_SIZE as u8 || z >= mca::REGION_SIZE as u8 {
258 return Err(Error::ChunkOutOfRegionBounds(x, z));
259 }
260
261 match self.chunks.get_mut(&(x, z)) {
262 Some(ch) => return Ok(ch),
263 None if self.config.create_chunk_if_missing => {
264 self.chunks.insert(
265 (x, z),
266 ChunkData::new(
267 get_empty_chunk(
268 (x, z),
269 self.region_coords,
270 self.config.world_height.clone(),
271 ),
272 self.config.world_height.clone(),
273 ),
274 );
275 return Ok(self.chunks.get_mut(&(x, z)).unwrap());
276 }
277 None => return Err(Error::TriedToModifyMissingChunk(x, z)),
278 };
279 }
280
281 pub fn is_region_generated(&self) -> Result<bool> {
283 for x in 0..mca::REGION_SIZE as u8 {
284 for z in 0..mca::REGION_SIZE as u8 {
285 let chunk = self.get_chunk(x, z)?;
286 match chunk {
287 Some(ch) => match ch.nbt.string("Status") {
288 Some(status) => {
289 if status.to_str() != Region::REQUIRED_STATUS {
290 return Ok(false);
291 }
292 }
293 None => return Ok(false),
294 },
295 None => return Ok(false),
296 };
297 }
298 }
299
300 Ok(true)
301 }
302}
303
304impl Debug for Region {
305 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
306 write!(
307 f,
308 "Region({}, {})\n > chunks: {}\n > {:?}",
309 self.region_coords.0,
310 self.region_coords.1,
311 self.chunks.len(),
312 self.config
313 )
314 }
315}
316
317impl Into<(Coords, Block)> for BlockWithCoordinate {
318 fn into(self) -> (Coords, Block) {
319 (self.coordinates, self.block)
320 }
321}
322
323impl<'a> Into<(&'a Coords, &'a Block)> for &'a BlockWithCoordinate {
324 fn into(self) -> (&'a Coords, &'a Block) {
325 (&self.coordinates, &self.block)
326 }
327}
328
329pub(crate) fn get_block_bit_count(len: usize) -> u32 {
333 match len {
334 0..=16 => 4, 17..=32 => 5,
336 33..=64 => 6,
337 65..=128 => 7,
338 129..=256 => 8,
339 257..=512 => 9,
340 513..=1024 => 10,
341 1025..=2048 => 11,
342 2049..=4096 => 12,
343 _ => 13,
344 }
345}
346
347pub(crate) fn get_biome_bit_count(len: usize) -> u32 {
348 match len {
349 0 | 1 => 0,
350 2 => 1,
351 3 | 4 => 2,
352 5..=8 => 3,
353 9..=16 => 4,
354 17..=32 => 5,
355 _ => 6,
356 }
357}
358
359pub fn get_empty_chunk(
363 coords: (u8, u8),
364 region_coords: (i32, i32),
365 world_height: Range<isize>,
366) -> NbtCompound {
367 let mut sections: Vec<NbtCompound> =
368 Vec::with_capacity(Config::DEFAULT_WORLD_HEIGHT.clone().count() / ChunkData::WIDTH);
369 let (section_start, section_end) = (
370 (world_height.start / ChunkData::WIDTH as isize) as i8,
371 (world_height.end / ChunkData::WIDTH as isize) as i8,
372 );
373
374 for y in section_start..section_end {
377 let biomes = NbtCompound::from_values(vec![(
378 "palette".into(),
379 NbtTag::List(NbtList::String(vec!["minecraft:plains".into()])),
380 )]);
381 let block_states = NbtCompound::from_values(vec![(
382 "palette".into(),
383 NbtTag::List(NbtList::Compound(vec![NbtCompound::from_values(vec![(
384 "Name".into(),
385 NbtTag::String("minecraft:air".into()),
386 )])])),
387 )]);
388
389 sections.push(NbtCompound::from_values(vec![
390 ("Y".into(), NbtTag::Byte(y)),
391 ("biomes".into(), NbtTag::Compound(biomes)),
392 ("block_states".into(), NbtTag::Compound(block_states)),
393 ]));
394 }
395
396 let chunk = NbtCompound::from_values(vec![
397 (
398 "Status".into(),
399 NbtTag::String(Region::REQUIRED_STATUS.into()),
400 ),
401 ("DataVersion".into(), NbtTag::Int(Region::MIN_DATA_VERSION)),
402 ("sections".into(), NbtTag::List(NbtList::Compound(sections))),
403 ("block_entities".into(), NbtTag::List(NbtList::Empty)),
404 ("isLightOn".into(), NbtTag::Byte(0)),
405 (
406 "xPos".into(),
407 NbtTag::Int((region_coords.0 * mca::REGION_SIZE as i32) + coords.0 as i32),
408 ),
409 (
410 "zPos".into(),
411 NbtTag::Int((region_coords.1 * mca::REGION_SIZE as i32) + coords.1 as i32),
412 ),
413 ]);
414
415 chunk
416}
417
418pub fn to_region_local(coords: (i32, i32, i32)) -> Coords {
428 (
429 (coords.0 as i32 & (BLOCKS_PER_REGION - 1) as i32) as u32,
430 coords.1,
431 (coords.2 as i32 & (BLOCKS_PER_REGION - 1) as i32) as u32,
432 )
433 .into()
434}
435
436pub(crate) fn is_valid_chunk(chunk: &NbtCompound, coordinate: (u8, u8)) -> Result<()> {
438 let status = chunk
439 .string("Status")
440 .ok_or(Error::MissingNbtTag("Status"))?
441 .to_str();
442 if status != Region::REQUIRED_STATUS {
443 return Err(Error::NotFullyGenerated {
444 chunk: coordinate,
445 status: status.into_owned(),
446 });
447 }
448
449 let data_version = chunk
450 .int("DataVersion")
451 .ok_or(Error::MissingNbtTag("DataVersion"))?;
452 if data_version < Region::MIN_DATA_VERSION {
453 return Err(Error::UnsupportedVersion {
454 chunk: coordinate,
455 data_version,
456 });
457 }
458
459 Ok(())
460}
461
462pub(crate) fn clean_palette<T>(data: &mut [i64], data_len: usize, palette: &mut Vec<T>) {
464 let mut palette_count: Vec<i32> = vec![0; palette.len()];
465 for index in data.deref() {
466 palette_count[*index as usize] += 1;
467 }
468
469 let mut palette_offsets: Vec<i64> = vec![0; palette.len()];
470
471 let mut len = palette.len();
472 let mut i = len as i32 - 1;
473 while i >= 0 {
474 if palette_count[i as usize] == 0 {
475 palette.remove(i as usize);
476 len -= 1;
477
478 for j in (i as usize)..palette_count.len() {
479 palette_offsets[j as usize] += 1;
480 }
481 }
482 i -= 1;
483 }
484
485 for block in 0..data_len {
486 data[block] -= palette_offsets[data[block] as usize];
487 }
488}
489
490impl Default for Region {
491 fn default() -> Self {
492 Region::full_empty((0, 0))
493 }
494}
495
496#[cfg(test)]
497mod test {
498 use super::*;
499 use std::io::BufReader;
500
501 #[test]
502 fn same_region_local_coordinates() {
503 let coords = (52, -81, 381);
504 let local = to_region_local(coords);
505 assert_eq!((52, -81, 381), local);
506 }
507
508 #[test]
509 fn region_local_coordinates() {
510 let coords = (851, 85, -481);
511 let local = to_region_local(coords);
512 assert_eq!((339, 85, 31), local);
513 }
514
515 #[test]
516 fn empty_chunk() -> Result<()> {
517 let chunk = get_empty_chunk((15, 9), (2, -5), Config::DEFAULT_WORLD_HEIGHT);
518 let data_version = chunk
519 .int("DataVersion")
520 .ok_or(Error::MissingNbtTag("DataVersion"))?;
521 let x_pos = chunk.int("xPos").ok_or(Error::MissingNbtTag("xPos"))?;
522 let z_pos = chunk.int("zPos").ok_or(Error::MissingNbtTag("zPos"))?;
523 let sections = chunk
524 .list("sections")
525 .ok_or(Error::MissingNbtTag("sections"))?
526 .compounds()
527 .ok_or(Error::InvalidNbtList("!= compounds"))?;
528
529 assert_eq!(data_version, Region::MIN_DATA_VERSION);
530 assert_eq!(x_pos, 79);
531 assert_eq!(z_pos, -151);
532 assert_eq!(sections.len(), 24);
533
534 Ok(())
535 }
536
537 #[test]
538 fn block_bit_count() {
539 assert_eq!(get_block_bit_count(0), 4);
540 assert_eq!(get_block_bit_count(58), 6);
541 assert_eq!(get_block_bit_count(1754), 11);
542 assert_eq!(get_block_bit_count(8572728), 13);
543 }
544
545 #[test]
546 fn biome_bit_count() {
547 assert_eq!(get_biome_bit_count(0), 0);
548 assert_eq!(get_biome_bit_count(4), 2);
549 assert_eq!(get_biome_bit_count(7), 3);
550 assert_eq!(get_biome_bit_count(25), 5);
551 assert_eq!(get_biome_bit_count(8572728), 6);
552 }
553
554 #[test]
555 fn empty_region() -> Result<()> {
556 let region = Region::empty((0, 0));
557 assert_eq!(region.chunks.len(), 0);
558 assert!(region.get_chunk(0, 0)?.is_none());
559 assert_eq!(region.region_coords, (0, 0));
560 Ok(())
561 }
562
563 #[test]
564 fn full_empty_region() {
565 let region = Region::default();
566 assert_eq!(region.chunks.len(), 1024);
567 }
568
569 #[test]
570 fn empty_from_nbt_region() {
571 let chunks = AHashMap::new();
572 let region = Region::from_nbt(chunks, (0, 0));
573 assert_eq!(region.chunks.len(), 0);
574 }
575
576 #[test]
577 fn from_nbt_region() -> Result<()> {
578 let mut chunks = AHashMap::new();
579 chunks.insert(
580 (4, 8),
581 get_empty_chunk((4, 8), (0, 0), Config::DEFAULT_WORLD_HEIGHT),
582 );
583
584 let region = Region::from_nbt(chunks, (0, 0));
585 assert_eq!(region.chunks.len(), 1);
586 assert_eq!(region.get_raw_chunk(4, 8)?.unwrap().pending_blocks.len(), 0);
587 assert_eq!(
588 region
589 .get_raw_chunk(4, 8)?
590 .unwrap()
591 .seen_blocks
592 .count_ones(..),
593 0
594 );
595 assert_eq!(region.region_coords, (0, 0));
596
597 Ok(())
598 }
599
600 const TEST_REGION: &[u8] = include_bytes!("../tests/full_region.mca");
601
602 #[test]
603 fn from_region_region() -> Result<()> {
604 let mut bytes = BufReader::new(TEST_REGION);
605 let region = Region::from_region(&mut bytes, (0, 0))?;
606 assert_eq!(region.chunks.len(), 1024);
607 Ok(())
608 }
609
610 const EMPTY_REGION: &[u8] = include_bytes!("../tests/empty_region.mca");
611
612 #[test]
613 fn write_region() -> Result<()> {
614 let mut bytes = BufReader::new(EMPTY_REGION);
615 let region = Region::from_region(&mut bytes, (0, 0))?;
616 let mut new_region_buf = vec![];
617 region.write(&mut new_region_buf)?;
618
619 assert_eq!(new_region_buf, EMPTY_REGION);
620
621 Ok(())
622 }
623
624 #[test]
625 fn get_chunk() -> Result<()> {
626 let mut chunks = AHashMap::new();
627 chunks.insert(
628 (9, 1),
629 get_empty_chunk((9, 1), (0, 0), Config::DEFAULT_WORLD_HEIGHT),
630 );
631
632 let region = Region::from_nbt(chunks, (0, 0));
633
634 assert!(region.get_chunk(9, 1)?.is_some());
635 assert!(region.get_chunk(1, 9)?.is_none());
636
637 Ok(())
638 }
639
640 #[test]
641 fn fully_generated() -> Result<()> {
642 let region = Region::default();
643 assert!(region.is_region_generated()?);
644 Ok(())
645 }
646}