use std::convert::TryFrom;
use std::fmt;
use std::ops::Range;
use std::sync::RwLock;
use once_cell::sync::OnceCell;
use serde::Deserialize;
use fastnbt::ByteArray;
use crate::{biome::Biome, Block, Chunk, HeightMode};
use crate::{expand_heightmap, Heightmaps, SectionLike, SectionTower};
mod pre13_block_names;
static BLOCK_LIST: [OnceCell<Block>; 256 * 16] = {
#[allow(clippy::declare_interior_mutable_const)]
const BLOCK: OnceCell<Block> = OnceCell::new();
[BLOCK; 256 * 16]
};
pub fn init_block(block_id: u16, data_value: u8, block: Block) -> Result<(), Block> {
assert!(block_id < (1u16 << 12));
assert!(data_value < (1u8 << 4));
let block_list_index = ((block_id as usize) << 4) + data_value as usize;
BLOCK_LIST[block_list_index].set(block)
}
pub type CustomBlockCallback = Box<dyn Send + Sync + Fn(u16, u8) -> Option<&'static Block>>;
static CUSTOM_BLOCK_CALLBACK: OnceCell<RwLock<CustomBlockCallback>> = OnceCell::new();
pub fn set_custom_block_callback(f: CustomBlockCallback) -> CustomBlockCallback {
std::mem::replace(
&mut *CUSTOM_BLOCK_CALLBACK
.get_or_init(|| RwLock::new(Box::new(|_block_id, _data_value| None)))
.write()
.unwrap(),
f,
)
}
fn custom_block_callback(block_id: u16, data_value: u8) -> Option<&'static Block> {
(CUSTOM_BLOCK_CALLBACK
.get_or_init(|| RwLock::new(Box::new(|_block_id, _data_value| None)))
.read()
.unwrap())(block_id, data_value)
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]
pub struct JavaChunk {
pub data_version: Option<i32>,
pub level: Level,
}
impl Chunk for JavaChunk {
fn status(&self) -> String {
"full".to_string()
}
fn surface_height(&self, x: usize, z: usize, mode: HeightMode) -> isize {
let mut heightmap = self.level.lazy_heightmap.read().unwrap();
if heightmap.is_none() {
drop(heightmap);
self.recalculate_heightmap(mode);
heightmap = self.level.lazy_heightmap.read().unwrap();
}
heightmap.unwrap()[z * 16 + x] as isize
}
fn biome(&self, x: usize, _y: isize, z: usize) -> Option<Biome> {
let biomes = self.level.biomes.as_ref()?;
let i = z * 16 + x;
let biome = biomes[i];
Biome::try_from(biome as i32).ok()
}
fn block(&self, x: usize, y: isize, z: usize) -> Option<&Block> {
let sec = self.level.sections.as_ref()?.get_section_for_y(y)?;
let sec_y = (y - sec.y as isize * 16) as usize;
let raw_block = sec.block(x, sec_y, z);
if (raw_block.0 as usize) < BLOCK_LIST.len() {
Some(BLOCK_LIST[raw_block.0 as usize].get_or_init(|| {
pre13_block_names::init_default_block(raw_block.block_id(), raw_block.data_value())
}))
} else {
Some(
custom_block_callback(raw_block.block_id(), raw_block.data_value())
.unwrap_or_else(|| panic!("Unknown raw block index {:?}. Use `set_custom_block_callback` to support this block id", raw_block)),
)
}
}
fn y_range(&self) -> std::ops::Range<isize> {
match &self.level.sections {
Some(sections) => Range {
start: sections.y_min(),
end: sections.y_max(),
},
None => Range { start: 0, end: 0 },
}
}
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]
pub struct Level {
#[serde(rename = "xPos")]
pub x_pos: i32,
#[serde(rename = "zPos")]
pub z_pos: i32,
pub biomes: Option<ByteArray>,
pub sections: Option<SectionTower<Pre13Section>>,
pub heightmaps: Option<Heightmaps>,
#[serde(skip)]
pub(crate) lazy_heightmap: RwLock<Option<[i16; 256]>>,
}
impl JavaChunk {
pub fn recalculate_heightmap(&self, mode: HeightMode) {
let mut map = [0; 256];
match mode {
HeightMode::Trust => {
let updated = self
.level
.heightmaps
.as_ref()
.and_then(|hm| hm.motion_blocking.as_ref())
.map(|hm| {
let y_min = self.level.sections.as_ref().unwrap().y_min();
expand_heightmap(hm, y_min, self.data_version.unwrap_or(0))
})
.map(|hm| map.copy_from_slice(hm.as_slice()))
.is_some();
if updated {
*self.level.lazy_heightmap.write().unwrap() = Some(map);
return;
}
}
HeightMode::Calculate => {} }
let y_range = self.y_range();
let y_end = y_range.end;
for z in 0..16 {
for x in 0..16 {
for i in y_range.clone() {
let y = y_end - i;
let block = self.block(x, y - 1, z);
if block.is_none() {
continue;
}
if !["minecraft:air", "minecraft:cave_air"]
.as_ref()
.contains(&block.unwrap().name())
{
map[z * 16 + x] = y as i16;
break;
}
}
}
}
*self.level.lazy_heightmap.write().unwrap() = Some(map);
}
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]
pub struct Pre13Section {
pub y: i8,
pub blocks: ByteArray,
pub add: Option<ByteArray>,
pub data: ByteArray,
}
impl Pre13Section {
pub(crate) fn block(&self, x: usize, sec_y: usize, z: usize) -> RawBlock {
let idx: usize = (sec_y << 8) + (z << 4) + x;
let mut block_id = self.blocks[idx] as u8 as usize;
if let Some(add) = &self.add {
let mut add_id = add[idx / 2] as u8;
if idx % 2 == 0 {
add_id &= 0x0F;
} else {
add_id = (add_id & 0xF0) >> 4;
}
block_id += (add_id as usize) << 8;
}
let block_data = {
let mut add_id = self.data[idx / 2] as u8;
if idx % 2 == 0 {
add_id &= 0x0F;
} else {
add_id = (add_id & 0xF0) >> 4;
}
add_id
};
let block_list_index = (block_id << 4) + block_data as usize;
RawBlock(block_list_index as u16)
}
}
impl SectionLike for Pre13Section {
fn is_terminator(&self) -> bool {
false
}
fn y(&self) -> i8 {
self.y
}
}
#[derive(Default, PartialEq, Eq)]
pub struct RawBlock(u16);
impl fmt::Debug for RawBlock {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "RawBlock({}:{})", self.block_id(), self.data_value())
}
}
impl RawBlock {
pub fn block_id(&self) -> u16 {
self.0 >> 4
}
pub fn data_value(&self) -> u8 {
(self.0 & 0x0F) as u8
}
}