fix: working chunk parsing in 1.16.5
basically refactored it a lot and rewrote almost from scratch and needs a lot of cleanup but holy shit it just worked and it's insane and i wasted my night on this so i better commit it before i fuck it up
This commit is contained in:
parent
1712826942
commit
c1f9f7f262
4 changed files with 350 additions and 284 deletions
307
src/chunk.rs
307
src/chunk.rs
|
@ -1,194 +1,21 @@
|
||||||
use std::collections::HashMap;
|
use std::io::{Cursor, Read, Seek};
|
||||||
use std::io::{Cursor, Read};
|
|
||||||
use log::{info, warn};
|
|
||||||
|
|
||||||
use pyo3::{exceptions::PyValueError, prelude::*};
|
use pyo3::prelude::*;
|
||||||
|
|
||||||
fn abs(v:i32, modulo:i32) -> i32 {
|
use crate::section::{ChunkSectionModern, ChunkSection};
|
||||||
if v < 0 {
|
|
||||||
return (modulo + (v % modulo)) % modulo;
|
|
||||||
} else {
|
|
||||||
return v % modulo;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[pyfunction]
|
|
||||||
pub fn bit_pack(data: Vec<i32>, bits: i32, size: i32) -> PyResult<Vec<i32>> {
|
|
||||||
if size <= bits {
|
|
||||||
return Err(PyValueError::new_err(
|
|
||||||
"Cannot pack into chunks smaller than bits per block",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
let mut out = vec![0; 0];
|
|
||||||
let mut cursor = 0;
|
|
||||||
let mut buffer = 0;
|
|
||||||
for el in data {
|
|
||||||
if cursor + bits > size {
|
|
||||||
let delta = (cursor + bits) - size;
|
|
||||||
buffer |= (el & (2 << (bits - delta) - 1)) << cursor;
|
|
||||||
out.push(buffer);
|
|
||||||
buffer = 0 | ((el >> (bits - delta)) & (2 << delta - 1));
|
|
||||||
cursor = delta;
|
|
||||||
} else {
|
|
||||||
buffer |= (el & (2 << bits - 1)) << cursor;
|
|
||||||
cursor += bits;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Ok(out);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait ChunkSection {
|
|
||||||
fn new() -> Self;
|
|
||||||
fn read<R: Read>(&mut self, chunk_data: &mut R) -> PyResult<()>;
|
|
||||||
fn get_states(&self) -> [[[u16; 16]; 16]; 16];
|
|
||||||
fn get_light(&self) -> [[[u16; 16]; 16]; 16];
|
|
||||||
fn get_sky_light(&self) -> Option<[[[u16; 16]; 16]; 16]>;
|
|
||||||
|
|
||||||
fn read_varint<R: Read>(buffer: &mut R) -> PyResult<i32> {
|
|
||||||
let mut num_read = 0;
|
|
||||||
let mut result: i32 = 0;
|
|
||||||
loop {
|
|
||||||
let mut data: [u8; 1] = [0u8; 1];
|
|
||||||
buffer.read_exact(&mut data)?;
|
|
||||||
result |= ((data[0] & 0b01111111) as i32) << (7 * num_read);
|
|
||||||
num_read += 1;
|
|
||||||
if num_read > 4 {
|
|
||||||
return Err(PyValueError::new_err("VarInt too big"));
|
|
||||||
}
|
|
||||||
if data[0] & 0b10000000 == 0 {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Ok(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_paletted_container<R: Read>(buffer: &mut R) -> PyResult<[[[u16; 16]; 16]; 16]> {
|
|
||||||
let mut data: [u8; 1] = [0u8; 1];
|
|
||||||
buffer.read_exact(&mut data)?;
|
|
||||||
// bits = UnsignedByte.read(buffer, ctx=ctx) # FIXME if bits > 4 it reads trash
|
|
||||||
// #logging.debug("[%d|%d@%d] Bits per block : %d", ctx.x, ctx.z, ctx.sec, bits)
|
|
||||||
let bits;
|
|
||||||
if data[0] < 4 {
|
|
||||||
bits = 4
|
|
||||||
} else if data[0] >= 9 {
|
|
||||||
bits = 13
|
|
||||||
}
|
|
||||||
// this should not be hardcoded but we have no way to calculate all possible block states
|
|
||||||
else {
|
|
||||||
bits = data[0]
|
|
||||||
}
|
|
||||||
let palette_len = ChunkFormat340::read_varint(buffer)?;
|
|
||||||
let mut palette = vec![0; palette_len as usize];
|
|
||||||
for p in 0..palette_len as usize {
|
|
||||||
palette[p] = ChunkFormat340::read_varint(buffer)?;
|
|
||||||
}
|
|
||||||
// # logging.debug("[%d|%d@%d] Palette section : [%d] %s", ctx.x, ctx.z, ctx.sec, palette_len, str(palette))
|
|
||||||
let container_size = ChunkFormat340::read_varint(buffer)?;
|
|
||||||
let mut block_data = vec![0u64; container_size as usize];
|
|
||||||
let mut recv_buf: [u8; 8] = [0u8; 8];
|
|
||||||
for i in 0..container_size as usize {
|
|
||||||
buffer.read_exact(&mut recv_buf)?;
|
|
||||||
let mut tmp: u64 = 0;
|
|
||||||
for j in 0..8 {
|
|
||||||
tmp |= (recv_buf[j] as u64) << ((7-j) * 8);
|
|
||||||
}
|
|
||||||
block_data[i] = tmp;
|
|
||||||
}
|
|
||||||
let mut section = [[[0u16; 16]; 16]; 16];
|
|
||||||
let max_val: u16 = (1 << bits) - 1;
|
|
||||||
for y in 0..16 {
|
|
||||||
for z in 0..16 {
|
|
||||||
for x in 0..16 {
|
|
||||||
let i = (((y * 16) + z) * 16) + x;
|
|
||||||
let start_byte = (i * bits as usize) / 64;
|
|
||||||
let start_offset = (i * bits as usize) % 64;
|
|
||||||
let end_byte = ((i + 1) * bits as usize - 1) / 64;
|
|
||||||
let value: u16;
|
|
||||||
if start_byte == end_byte {
|
|
||||||
value = ((block_data[start_byte as usize] //FIXME out of bounds?
|
|
||||||
>> start_offset) & max_val as u64) as u16;
|
|
||||||
} else {
|
|
||||||
let end_offset = 64 - start_offset;
|
|
||||||
value = (((block_data[start_byte as usize] as usize)
|
|
||||||
>> start_offset
|
|
||||||
| (block_data[end_byte as usize] as usize) << end_offset)
|
|
||||||
& max_val as usize) as u16;
|
|
||||||
}
|
|
||||||
if bits == 13 {
|
|
||||||
section[x][y][z] = value;
|
|
||||||
} else {
|
|
||||||
if value as i32 >= palette_len {
|
|
||||||
warn!("index out of palette bounds : {}/{} (bits {})", value, palette_len, bits);
|
|
||||||
section[x][y][z] = value;
|
|
||||||
} else {
|
|
||||||
section[x][y][z] = palette[value as usize] as u16;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Ok(section);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ChunkFormat340 {
|
|
||||||
block_states: [[[u16; 16]; 16]; 16],
|
|
||||||
block_light: [[[u16; 16]; 16]; 16],
|
|
||||||
sky_light: Option<[[[u16; 16]; 16]; 16]>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ChunkFormat340 {
|
|
||||||
fn read_half_byte_array<R: Read>(buffer: &mut R) -> PyResult<[[[u16; 16]; 16]; 16]> {
|
|
||||||
let mut buf: [u8; (16 * 16 * 16) / 2] = [0u8; (16 * 16 * 16) / 2];
|
|
||||||
buffer.read_exact(&mut buf)?;
|
|
||||||
let mut out = [[[0u16; 16]; 16]; 16];
|
|
||||||
for y in 0..16 {
|
|
||||||
for z in 0..16 {
|
|
||||||
for x in 0..16 {
|
|
||||||
let index: usize = (((y * 16) + z) * 16) + x;
|
|
||||||
let tmp = buf[index / 2];
|
|
||||||
out[x][y][z] = if index / 2 != 0 { tmp >> 4 } else { tmp & 0xF } as u16
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Ok(out);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ChunkSection for ChunkFormat340 {
|
|
||||||
fn new() -> Self {
|
|
||||||
return Self { block_states: [[[0;16];16];16], block_light: [[[0;16];16];16], sky_light: Some([[[0;16];16];16]) };
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read<R: Read>(&mut self, chunk_data: &mut R) -> PyResult<()> {
|
|
||||||
self.block_states = ChunkFormat340::read_paletted_container(chunk_data)?; // TODO! Handle error
|
|
||||||
self.block_light = ChunkFormat340::read_half_byte_array(chunk_data)?;
|
|
||||||
self.sky_light = Some(ChunkFormat340::read_half_byte_array(chunk_data)?); // TODO are we in overworld?
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_states(&self) -> [[[u16; 16]; 16]; 16] {
|
|
||||||
self.block_states
|
|
||||||
}
|
|
||||||
fn get_light(&self) -> [[[u16; 16]; 16]; 16] {
|
|
||||||
self.block_light
|
|
||||||
}
|
|
||||||
fn get_sky_light(&self) -> Option<[[[u16; 16]; 16]; 16]> {
|
|
||||||
self.sky_light
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[pyclass]
|
#[pyclass]
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
pub struct Chunk {
|
pub struct Chunk {
|
||||||
pub x: i32,
|
pub x: i32,
|
||||||
pub z: i32,
|
pub z: i32,
|
||||||
pub bitmask: u16,
|
pub bitmask: u16,
|
||||||
pub ground_up_continuous: bool,
|
pub ground_up_continuous: bool,
|
||||||
block_states: [[[u16; 16]; 256]; 16],
|
pub(crate) block_states: [[[u16; 16]; 256]; 16],
|
||||||
block_light: [[[u16; 16]; 256]; 16],
|
pub(crate) block_light: [[[u16; 16]; 256]; 16],
|
||||||
sky_light: [[[u16; 16]; 256]; 16],
|
pub(crate) sky_light: [[[u16; 16]; 256]; 16],
|
||||||
biomes: [u8; 256],
|
// pub(crate) biomes: [u16; 256],
|
||||||
block_entities: String, // TODO less jank way to store this NBT/JSON/PyDict ...
|
pub(crate) block_entities: String, // TODO less jank way to store this NBT/JSON/PyDict ...
|
||||||
}
|
}
|
||||||
// Biomes
|
// Biomes
|
||||||
// The biomes array is only present when ground-up continuous is set to true. Biomes cannot be changed unless a chunk is re-sent.
|
// The biomes array is only present when ground-up continuous is set to true. Biomes cannot be changed unless a chunk is re-sent.
|
||||||
|
@ -199,44 +26,35 @@ impl Chunk {
|
||||||
#[new]
|
#[new]
|
||||||
pub fn new(x: i32, z: i32, bitmask: u16, ground_up_continuous: bool, block_entities: String) -> Self {
|
pub fn new(x: i32, z: i32, bitmask: u16, ground_up_continuous: bool, block_entities: String) -> Self {
|
||||||
Self {
|
Self {
|
||||||
x: x,
|
x, z, bitmask, ground_up_continuous, block_entities,
|
||||||
z: z,
|
|
||||||
bitmask: bitmask,
|
|
||||||
ground_up_continuous: ground_up_continuous,
|
|
||||||
block_states: [[[0u16; 16]; 256]; 16],
|
block_states: [[[0u16; 16]; 256]; 16],
|
||||||
block_light: [[[0u16; 16]; 256]; 16],
|
block_light: [[[0u16; 16]; 256]; 16],
|
||||||
sky_light: [[[0u16; 16]; 256]; 16],
|
sky_light: [[[0u16; 16]; 256]; 16],
|
||||||
biomes: [0u8; 256],
|
// biomes: [0u16; 256],
|
||||||
block_entities: block_entities,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read(&mut self, chunk_data: Vec<u8>) -> PyResult<()> {
|
pub fn read(&mut self, chunk_data: Vec<u8>) -> PyResult<()> {
|
||||||
let mut c = Cursor::new(chunk_data);
|
let mut c = Cursor::new(chunk_data);
|
||||||
|
c.seek(std::io::SeekFrom::Start(0)).unwrap(); // just in case
|
||||||
|
let mut count = 0;
|
||||||
|
for i in 0..16 {
|
||||||
|
if ((self.bitmask >> i) & 1) != 0 { count += 1}
|
||||||
|
}
|
||||||
|
log::info!("chunk {}:{} contains {} sections", self.x, self.z, count);
|
||||||
for i in 0..16 {
|
for i in 0..16 {
|
||||||
if ((self.bitmask >> i) & 1) != 0 {
|
if ((self.bitmask >> i) & 1) != 0 {
|
||||||
let mut section: ChunkFormat340 = ChunkFormat340::new();
|
log::info!("reading section #{} of chunk {}:{}", i, self.x, self.z);
|
||||||
|
let mut section = ChunkSectionModern::default();
|
||||||
section.read(&mut c)?;
|
section.read(&mut c)?;
|
||||||
for x in 0..16 {
|
for ((x, y, z), state) in section {
|
||||||
for y in 0..16 {
|
self.block_states[x][y + (i*16)][z] = state;
|
||||||
for z in 0..16 {
|
|
||||||
self.block_states[x][(i * 16) + y][z] = section.block_states[x][y][z];
|
|
||||||
self.block_light[x][(i * 16) + y][z] = section.block_states[x][y][z];
|
|
||||||
self.sky_light[x][(i * 16) + y][z] = section.block_states[x][y][z];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
log::info!("updated block states");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.ground_up_continuous {
|
Ok(())
|
||||||
c.read_exact(&mut self.biomes)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if buffer.read() {
|
|
||||||
// logging.warning("Leftover data in chunk buffer")
|
|
||||||
// }
|
|
||||||
return Ok(());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn merge(&mut self, other: Chunk) -> Option<Chunk> {
|
pub fn merge(&mut self, other: Chunk) -> Option<Chunk> {
|
||||||
|
@ -267,83 +85,6 @@ impl Chunk {
|
||||||
slice[x][z] = self.block_states[x][y as usize][z];
|
slice[x][z] = self.block_states[x][y as usize][z];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return slice;
|
slice
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Clone for Chunk {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
Chunk {
|
|
||||||
x: self.x,
|
|
||||||
z: self.z,
|
|
||||||
bitmask: self.bitmask,
|
|
||||||
ground_up_continuous: self.ground_up_continuous,
|
|
||||||
block_states: self.block_states.clone(),
|
|
||||||
block_light: self.block_light.clone(),
|
|
||||||
sky_light: self.sky_light.clone(),
|
|
||||||
biomes: self.biomes.clone(),
|
|
||||||
block_entities: self.block_entities.clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[pyclass]
|
|
||||||
pub struct World {
|
|
||||||
chunks: HashMap<(i32, i32), Chunk>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[pymethods]
|
|
||||||
impl World {
|
|
||||||
#[new]
|
|
||||||
pub fn new() -> Self {
|
|
||||||
info!("Initializing world from Rust");
|
|
||||||
World {
|
|
||||||
chunks: HashMap::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn __getitem__(&self, item: (i32, i32)) -> Option<Chunk> {
|
|
||||||
return self.get(item.0, item.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_block(&self, x: i32, y: i32, z: i32) -> Option<u16> {
|
|
||||||
if y < 0 {
|
|
||||||
return Some(0); // TODO no longer the case after 1.17
|
|
||||||
}
|
|
||||||
let mut chunk_x = x / 16;
|
|
||||||
let mut chunk_z = z / 16;
|
|
||||||
if chunk_x < 0 && chunk_x % 16 != 0 { chunk_x-=1; }
|
|
||||||
if chunk_z < 0 && chunk_z % 16 != 0 { chunk_z-=1; }
|
|
||||||
if let Some(chunk) = self.chunks.get(&(chunk_x, chunk_z)) {
|
|
||||||
return Some(chunk.block_states[abs(x, 16) as usize][y as usize][abs(z, 16) as usize]);
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn put_block(&mut self, x: i32, y:i32, z:i32, id:u16) -> Option<u16> {
|
|
||||||
if y < 0 {
|
|
||||||
return Some(0);
|
|
||||||
}
|
|
||||||
let mut chunk_x = x / 16;
|
|
||||||
let mut chunk_z = z / 16;
|
|
||||||
if chunk_x < 0 && chunk_x % 16 != 0 { chunk_x-=1; }
|
|
||||||
if chunk_z < 0 && chunk_z % 16 != 0 { chunk_z-=1; }
|
|
||||||
let x_off = abs(x, 16) as usize; let z_off = abs(z, 16) as usize;
|
|
||||||
let c = self.chunks.get_mut(&(chunk_x, chunk_z))?;
|
|
||||||
let old_block = c.block_states[x_off][y as usize][z_off];
|
|
||||||
c.block_states[x_off][y as usize][z_off] = id;
|
|
||||||
return Some(old_block);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get(&self, x: i32, z: i32) -> Option<Chunk> {
|
|
||||||
return Some((self.chunks.get(&(x, z))?).clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn put(&mut self, chunk: Chunk, x: i32, z: i32, merge: bool) -> Option<Chunk> {
|
|
||||||
if merge && self.chunks.contains_key(&(x, z)) {
|
|
||||||
return self.chunks.get_mut(&(x, z)).unwrap().merge(chunk);
|
|
||||||
} else {
|
|
||||||
return self.chunks.insert((x, z), chunk);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
38
src/lib.rs
38
src/lib.rs
|
@ -1,6 +1,9 @@
|
||||||
mod chunk;
|
mod chunk;
|
||||||
|
mod world;
|
||||||
|
mod section;
|
||||||
|
|
||||||
use chunk::{Chunk,World,bit_pack};
|
use chunk::Chunk;
|
||||||
|
use world::World;
|
||||||
|
|
||||||
use pyo3::prelude::*;
|
use pyo3::prelude::*;
|
||||||
|
|
||||||
|
@ -17,3 +20,36 @@ fn aiocraft(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
|
||||||
// m.add_submodule(native)?;
|
// m.add_submodule(native)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn abs(v:i32, modulo:i32) -> i32 {
|
||||||
|
if v < 0 {
|
||||||
|
return (modulo + (v % modulo)) % modulo;
|
||||||
|
} else {
|
||||||
|
return v % modulo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pyfunction]
|
||||||
|
pub fn bit_pack(data: Vec<i32>, bits: i32, size: i32) -> PyResult<Vec<i32>> {
|
||||||
|
if size <= bits {
|
||||||
|
return Err(pyo3::exceptions::PyValueError::new_err(
|
||||||
|
"Cannot pack into chunks smaller than bits per block",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let mut out = vec![0; 0];
|
||||||
|
let mut cursor = 0;
|
||||||
|
let mut buffer = 0;
|
||||||
|
for el in data {
|
||||||
|
if cursor + bits > size {
|
||||||
|
let delta = (cursor + bits) - size;
|
||||||
|
buffer |= (el & (2 << (bits - delta) - 1)) << cursor;
|
||||||
|
out.push(buffer);
|
||||||
|
buffer = 0 | ((el >> (bits - delta)) & (2 << delta - 1));
|
||||||
|
cursor = delta;
|
||||||
|
} else {
|
||||||
|
buffer |= (el & (2 << bits - 1)) << cursor;
|
||||||
|
cursor += bits;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Ok(out);
|
||||||
|
}
|
||||||
|
|
223
src/section.rs
Normal file
223
src/section.rs
Normal file
|
@ -0,0 +1,223 @@
|
||||||
|
use std::io::{Read, ErrorKind};
|
||||||
|
|
||||||
|
use pyo3::prelude::*;
|
||||||
|
|
||||||
|
fn read_varint<R: Read>(buffer: &mut R) -> PyResult<i32> {
|
||||||
|
let mut num_read = 0;
|
||||||
|
let mut result: i32 = 0;
|
||||||
|
loop {
|
||||||
|
let mut data: [u8; 1] = [0u8; 1];
|
||||||
|
buffer.read_exact(&mut data)?;
|
||||||
|
result |= ((data[0] & 0b01111111) as i32) << (7 * num_read);
|
||||||
|
num_read += 1;
|
||||||
|
if num_read > 4 {
|
||||||
|
return Err(pyo3::exceptions::PyValueError::new_err("VarInt too big"));
|
||||||
|
}
|
||||||
|
if data[0] & 0b10000000 == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait ChunkSection : Default + Iterator {
|
||||||
|
fn read<R: Read>(&mut self, chunk_data: &mut R) -> PyResult<()>;
|
||||||
|
fn max_bits() -> u8;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default)]
|
||||||
|
pub struct ChunkSectionModern {
|
||||||
|
pub block_count: i16,
|
||||||
|
pub bits_per_block: u8,
|
||||||
|
pub palette: Vec<i32>,
|
||||||
|
pub data: Vec<u64>,
|
||||||
|
count: usize,
|
||||||
|
bits: usize,
|
||||||
|
x: usize,
|
||||||
|
z: usize,
|
||||||
|
y: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ChunkSectionModern {
|
||||||
|
fn count_up_coordinates(&mut self) {
|
||||||
|
self.x += 1;
|
||||||
|
if self.x >= 16 {
|
||||||
|
self.z += 1;
|
||||||
|
self.x = 0;
|
||||||
|
}
|
||||||
|
if self.z >= 16 {
|
||||||
|
self.y += 1;
|
||||||
|
self.z = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Iterator for ChunkSectionModern {
|
||||||
|
type Item = ((usize, usize, usize), u16);
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
if self.count >= 4096 { return None };
|
||||||
|
if self.bits % 64 > 64 - self.bits_per_block as usize {
|
||||||
|
self.bits += 64 - (self.bits % 64);
|
||||||
|
}
|
||||||
|
let start_byte = self.bits / 64;
|
||||||
|
let start_offset = self.bits % 64;
|
||||||
|
let end_byte = (self.bits + self.bits_per_block as usize - 1) / 64;
|
||||||
|
let max_val = (1_u64 << self.bits_per_block) - 1_u64;
|
||||||
|
let value = if start_byte == end_byte { // just discard from start and end
|
||||||
|
(self.data[start_byte] >> start_offset) & max_val // FIXME out of bounds?
|
||||||
|
} else {
|
||||||
|
log::warn!("block spread across two longs, this shouldn't happen");
|
||||||
|
let end_offset = 64 - start_offset;
|
||||||
|
(self.data[start_byte] >> start_offset) | (self.data[end_byte] << end_offset) & max_val // FIXME: out of bounds?
|
||||||
|
};
|
||||||
|
|
||||||
|
self.bits += self.bits_per_block as usize;
|
||||||
|
self.count += 1;
|
||||||
|
|
||||||
|
let coord = (self.x, self.y, self.z);
|
||||||
|
self.count_up_coordinates();
|
||||||
|
|
||||||
|
let state = if self.bits_per_block >= 9 {
|
||||||
|
value as u16
|
||||||
|
} else {
|
||||||
|
self.palette[value as usize] as u16
|
||||||
|
};
|
||||||
|
|
||||||
|
Some((coord, state))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ChunkSection for ChunkSectionModern {
|
||||||
|
fn read<R: Read>(&mut self, buffer: &mut R) -> PyResult<()> {
|
||||||
|
let mut block_count_buf: [u8; 2] = [0; 2];
|
||||||
|
buffer.read_exact(&mut block_count_buf)?;
|
||||||
|
self.block_count = i16::from_be_bytes(block_count_buf);
|
||||||
|
log::info!("block count is {}", self.block_count);
|
||||||
|
|
||||||
|
let mut bits_per_block_buf: [u8; 1] = [0u8; 1];
|
||||||
|
buffer.read_exact(&mut bits_per_block_buf)?;
|
||||||
|
self.bits_per_block = u8::from_be_bytes(bits_per_block_buf);
|
||||||
|
log::info!("bits per block is {}", self.bits_per_block);
|
||||||
|
self.bits_per_block = match self.bits_per_block {
|
||||||
|
0..=4 => 4,
|
||||||
|
5..=8 => self.bits_per_block,
|
||||||
|
9.. => ChunkSectionModern::max_bits(),
|
||||||
|
};
|
||||||
|
|
||||||
|
self.palette = Vec::new();
|
||||||
|
if self.bits_per_block < 9 {
|
||||||
|
let palette_len_buf = read_varint(buffer)?;
|
||||||
|
for _ in 0..palette_len_buf {
|
||||||
|
self.palette.push(read_varint(buffer)?);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log::info!("palette of len {}: {:?}", self.palette.len(), self.palette);
|
||||||
|
|
||||||
|
let content_len = read_varint(buffer)? as usize;
|
||||||
|
log::info!("reading {} longs from buffer ({} bytes)", content_len, content_len * 8);
|
||||||
|
|
||||||
|
let mut data_buf = vec![0u8; content_len * 8];
|
||||||
|
buffer.read_exact(&mut data_buf)?;
|
||||||
|
|
||||||
|
self.data = data_buf.chunks(8)
|
||||||
|
.map(|x| u64::from_be_bytes(x.try_into().unwrap())) // wtf rust!!!
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
self.bits = 0;
|
||||||
|
self.count = 0;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn max_bits() -> u8 {
|
||||||
|
15
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
pub struct ChunkSection340 {
|
||||||
|
pub block_states: [[[u16; 16]; 16]; 16],
|
||||||
|
pub block_light: [[[u16; 16]; 16]; 16],
|
||||||
|
pub sky_light: Option<[[[u16; 16]; 16]; 16]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
fn read_paletted_container<R: Read>(buffer: &mut R) -> PyResult<[[[u16; 16]; 16]; 16]> {
|
||||||
|
let mut data: [u8; 1] = [0u8; 1];
|
||||||
|
buffer.read_exact(&mut data)?;
|
||||||
|
// bits = UnsignedByte.read(buffer, ctx=ctx) # FIXME if bits > 4 it reads trash
|
||||||
|
// #logging.debug("[%d|%d@%d] Bits per block : %d", ctx.x, ctx.z, ctx.sec, bits)
|
||||||
|
let bits_raw = u8::from_be_bytes(data);
|
||||||
|
let bits = match bits_raw {
|
||||||
|
0 => 0,
|
||||||
|
1..=4 => 4,
|
||||||
|
5..=8 => bits_raw,
|
||||||
|
9.. => 13, // this should not be hardcoded but we have no way to calculate all possible block states
|
||||||
|
};
|
||||||
|
log::info!("bits per block: {} -> {}", bits_raw, bits);
|
||||||
|
let palette_len = read_varint(buffer)?;
|
||||||
|
log::info!("palette len: {}", palette_len);
|
||||||
|
if bits == 0 { // single value palette: the length is the actual value and it fills the chunk
|
||||||
|
let _container_size = read_varint(buffer)? as usize; // this is still sent
|
||||||
|
assert_eq!(_container_size, 0);
|
||||||
|
return Ok([[[palette_len as u16; 16]; 16]; 16]);
|
||||||
|
}
|
||||||
|
let mut palette = vec![0; palette_len as usize];
|
||||||
|
for p in 0..palette_len as usize {
|
||||||
|
palette[p] = read_varint(buffer)?;
|
||||||
|
}
|
||||||
|
// # logging.debug("[%d|%d@%d] Palette section : [%d] %s", ctx.x, ctx.z, ctx.sec, palette_len, str(palette))
|
||||||
|
let container_size = read_varint(buffer)? as usize;
|
||||||
|
log::info!("reading off socket {}x8 = {} bytes (palette of {} bits)", container_size, container_size * 8, bits);
|
||||||
|
let mut block_data_buffer = vec![0u8; container_size * 8];
|
||||||
|
buffer.read_exact(&mut block_data_buffer)?;
|
||||||
|
let block_data : Vec<u64> = block_data_buffer
|
||||||
|
.chunks_exact(8)
|
||||||
|
.map(|x| u64::from_be_bytes(x.try_into().unwrap())) // wtf rust!!!
|
||||||
|
.collect();
|
||||||
|
let mut section = [[[0u16; 16]; 16]; 16];
|
||||||
|
let max_val: u16 = (1 << bits) - 1;
|
||||||
|
let mut i = 0;
|
||||||
|
for y in 0..16 {
|
||||||
|
for z in 0..16 {
|
||||||
|
for x in 0..16 {
|
||||||
|
// let i = (y * 16 * 16) + (z * 16) + x;
|
||||||
|
let start_byte = (i * bits as usize) / 64;
|
||||||
|
let start_offset = (i * bits as usize) % 64;
|
||||||
|
let end_byte = ((i + 1) * bits as usize) / 64;
|
||||||
|
if start_byte >= container_size || end_byte >= container_size {
|
||||||
|
log::warn!("early exit? is this ok?");
|
||||||
|
return Ok(section); // early exit? is this OK?
|
||||||
|
}
|
||||||
|
let value: u16;
|
||||||
|
if start_byte == end_byte {
|
||||||
|
value = ((block_data[start_byte as usize] //FIXME out of bounds?
|
||||||
|
>> start_offset) & max_val as u64) as u16;
|
||||||
|
} else {
|
||||||
|
let end_offset = 64 - start_offset;
|
||||||
|
value = (((block_data[start_byte as usize] as usize)
|
||||||
|
>> start_offset
|
||||||
|
| (block_data[end_byte as usize] as usize) << end_offset) // FIXME: out of bounds?
|
||||||
|
& max_val as usize) as u16;
|
||||||
|
}
|
||||||
|
if bits == 13 {
|
||||||
|
section[x][y][z] = value;
|
||||||
|
} else if value as i32 >= palette_len {
|
||||||
|
log::warn!("index out of palette bounds : {}/{} (bits {})", value, palette_len, bits);
|
||||||
|
section[x][y][z] = value;
|
||||||
|
} else {
|
||||||
|
section[x][y][z] = palette[value as usize] as u16;
|
||||||
|
}
|
||||||
|
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Ok(section);
|
||||||
|
}
|
66
src/world.rs
Normal file
66
src/world.rs
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use pyo3::prelude::*;
|
||||||
|
|
||||||
|
use crate::{chunk::Chunk, abs};
|
||||||
|
|
||||||
|
#[pyclass]
|
||||||
|
pub struct World {
|
||||||
|
chunks: HashMap<(i32, i32), Chunk>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl World {
|
||||||
|
#[new]
|
||||||
|
pub fn new() -> Self {
|
||||||
|
log::info!("Initializing world from Rust");
|
||||||
|
World {
|
||||||
|
chunks: HashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn __getitem__(&self, item: (i32, i32)) -> Option<Chunk> {
|
||||||
|
return self.get(item.0, item.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_block(&self, x: i32, y: i32, z: i32) -> Option<u16> {
|
||||||
|
if y < 0 {
|
||||||
|
return Some(0); // TODO no longer the case after 1.17
|
||||||
|
}
|
||||||
|
let mut chunk_x = x / 16;
|
||||||
|
let mut chunk_z = z / 16;
|
||||||
|
if chunk_x < 0 && chunk_x % 16 != 0 { chunk_x-=1; }
|
||||||
|
if chunk_z < 0 && chunk_z % 16 != 0 { chunk_z-=1; }
|
||||||
|
if let Some(chunk) = self.chunks.get(&(chunk_x, chunk_z)) {
|
||||||
|
return Some(chunk.block_states[abs(x, 16) as usize][y as usize][abs(z, 16) as usize]);
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn put_block(&mut self, x: i32, y:i32, z:i32, id:u16) -> Option<u16> {
|
||||||
|
if y < 0 {
|
||||||
|
return Some(0);
|
||||||
|
}
|
||||||
|
let mut chunk_x = x / 16;
|
||||||
|
let mut chunk_z = z / 16;
|
||||||
|
if chunk_x < 0 && chunk_x % 16 != 0 { chunk_x-=1; }
|
||||||
|
if chunk_z < 0 && chunk_z % 16 != 0 { chunk_z-=1; }
|
||||||
|
let x_off = abs(x, 16) as usize; let z_off = abs(z, 16) as usize;
|
||||||
|
let c = self.chunks.get_mut(&(chunk_x, chunk_z))?;
|
||||||
|
let old_block = c.block_states[x_off][y as usize][z_off];
|
||||||
|
c.block_states[x_off][y as usize][z_off] = id;
|
||||||
|
return Some(old_block);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&self, x: i32, z: i32) -> Option<Chunk> {
|
||||||
|
return Some((self.chunks.get(&(x, z))?).clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn put(&mut self, chunk: Chunk, x: i32, z: i32, merge: bool) -> Option<Chunk> {
|
||||||
|
if merge && self.chunks.contains_key(&(x, z)) {
|
||||||
|
return self.chunks.get_mut(&(x, z)).unwrap().merge(chunk);
|
||||||
|
} else {
|
||||||
|
return self.chunks.insert((x, z), chunk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue