use std::ops::Deref;
use miniz_oxide::inflate;
use crate::{bigendian::BigEndian, nbt, positive_mod, Result};
#[repr(u8)]
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum CompressionType {
GZip = 1,
Zlib = 2,
Uncompressed = 3,
LZ4 = 4,
Custom = 127,
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
#[repr(C)]
pub(crate) struct Location {
pub offset: BigEndian<3>,
pub sector_count: u8,
}
impl Location {
pub const fn is_empty(&self) -> bool {
self.offset.as_u32() == 0 && self.sector_count == 0
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct ParsedChunk {
nbt: nbt::ChunkNbt,
}
impl Deref for ParsedChunk {
type Target = nbt::ChunkNbt;
fn deref(&self) -> &Self::Target {
&self.nbt
}
}
#[derive(Debug, Eq, PartialEq)]
#[repr(C)]
pub struct Chunk {
pub compression_type: CompressionType,
compressed_data: [u8],
}
impl Chunk {
pub fn boxed(&self) -> Box<Self> {
let mut b = vec![0u8; std::mem::size_of_val(self)];
unsafe { b.set_len(b.len() - 1) };
let b = b.into_boxed_slice();
let mut b: Box<Self> = unsafe { std::mem::transmute(b) };
b.as_mut().compression_type = self.compression_type;
b.compressed_data.copy_from_slice(&self.compressed_data);
b
}
pub fn parse(&self) -> Result<ParsedChunk> {
match self.compression_type {
CompressionType::GZip => todo!(),
CompressionType::Zlib => {
let data = &self.compressed_data;
let uncompressed = inflate::decompress_to_vec_zlib(data)?;
Ok(ParsedChunk {
nbt: fastnbt::from_bytes(&uncompressed)?,
})
}
CompressionType::Uncompressed => todo!(),
CompressionType::LZ4 => todo!(),
CompressionType::Custom => todo!(),
}
}
pub fn len(&self) -> usize {
self.compressed_data.len()
}
}
impl ParsedChunk {
pub fn get_chunk_section_at(&self, block_y: i32) -> Option<&nbt::ChunkSection> {
let subchunk_y = (block_y / 16) as i8;
self.sections.iter().find(|s| s.y == subchunk_y)
}
pub fn get_block(&self, block_x: u32, block_y: i32, block_z: u32) -> Option<&nbt::BlockState> {
let subchunk = self.get_chunk_section_at(block_y)?;
assert!(block_x < 16);
assert!(block_z < 16);
let block_y: u32 = positive_mod!(block_y, 16) as u32;
let bs = subchunk.block_states.as_ref()?;
let block_states = if let Some(data) = &bs.data {
data
} else {
return None;
};
let bits = std::cmp::max((bs.palette.len() as f32).log2().ceil() as u32, 4);
let block_index = block_y * 16 * 16 + block_z * 16 + block_x;
let block = get_item_in_packed_slice(&block_states, block_index as usize, bits);
Some(&bs.palette[block as usize])
}
pub fn get_block_from_absolute_coords(
&self,
block_x: u32,
block_y: i32,
block_z: u32,
) -> Option<&nbt::BlockState> {
self.get_block(block_x % 16, block_y, block_z % 16)
}
}
fn get_item_in_packed_slice(slice: &[i64], index: usize, bits: u32) -> u64 {
let nums_per_u64 = u64::BITS / bits;
assert_eq!(
(slice.len() as u32),
((4096. / nums_per_u64 as f32).ceil() as u32)
);
let index_in_num = index as u32 % nums_per_u64;
let shifted_num = slice[index / nums_per_u64 as usize] as u64 >> bits * index_in_num;
shifted_num & (2u64.pow(bits) - 1)
}
#[test]
fn test_get_item_in_packed_slice() {
let slice = &[0; 128];
assert_eq!(get_item_in_packed_slice(slice, 15, 2), 0);
let slice = &[0; 456];
assert_eq!(get_item_in_packed_slice(slice, 15, 7), 0);
}