mirror of
https://github.com/Pumpkin-MC/Pumpkin.git
synced 2026-08-30 20:14:23 +00:00
244 lines
8.0 KiB
Rust
244 lines
8.0 KiB
Rust
use std::io::{Error, Write};
|
|
use xxhash_rust::xxh64::xxh64;
|
|
|
|
use pumpkin_macros::packet;
|
|
use pumpkin_world::chunk::{ChunkData, palette::NetworkPalette};
|
|
|
|
use crate::{
|
|
codec::{var_int::VarInt, var_uint::VarUInt},
|
|
serial::PacketWrite,
|
|
};
|
|
|
|
const VERSION: u8 = 9;
|
|
|
|
#[packet(58)]
|
|
pub struct CLevelChunk<'a> {
|
|
// https://mojang.github.io/bedrock-protocol-docs/html/LevelChunkPacket.html
|
|
pub dimension: i32,
|
|
pub cache_enabled: bool,
|
|
|
|
// https://gist.github.com/Tomcc/a96af509e275b1af483b25c543cfbf37
|
|
// https://github.com/Mojang/bedrock-protocol-docs/blob/main/additional_docs/SubChunk%20Request%20System%20v1.18.10.md
|
|
pub chunk: &'a ChunkData,
|
|
}
|
|
|
|
pub type ChunkBlob = (u64, Vec<u8>);
|
|
pub type EncodedChunk = (Vec<u8>, Vec<ChunkBlob>);
|
|
|
|
impl CLevelChunk<'_> {
|
|
pub fn encode_chunk(
|
|
chunk: &ChunkData,
|
|
dimension: i32,
|
|
cache_enabled: bool,
|
|
) -> Result<EncodedChunk, Error> {
|
|
let mut writer = Vec::new();
|
|
|
|
VarInt(chunk.x).write(&mut writer)?;
|
|
VarInt(chunk.z).write(&mut writer)?;
|
|
|
|
VarInt(dimension).write(&mut writer)?;
|
|
let sub_chunk_count = chunk.section.count as u32;
|
|
VarUInt(sub_chunk_count).write(&mut writer)?;
|
|
// Optional sub-chunk request limit. Pumpkin sends complete chunks.
|
|
false.write(&mut writer)?;
|
|
cache_enabled.write(&mut writer)?;
|
|
|
|
let mut blobs = Vec::new();
|
|
|
|
let block_sections = chunk
|
|
.section
|
|
.block_sections
|
|
.read()
|
|
.map_err(|_| Error::other("block_sections read lock poisoned"))?;
|
|
let min_y_section = (chunk.section.min_y >> 4) as i8;
|
|
|
|
let mut subchunk_bytes_list = Vec::with_capacity(block_sections.len());
|
|
|
|
for (i, block_palette) in block_sections.iter().enumerate() {
|
|
let mut subchunk_buf = Vec::new();
|
|
// Version 9: [version:byte][num_storages:byte][sub_chunk_index:byte]
|
|
let y = (i as i8) + min_y_section;
|
|
let num_storages = 1;
|
|
subchunk_buf.write_all(&[VERSION, num_storages, y as u8])?;
|
|
|
|
let network_repr = block_palette.convert_be_network();
|
|
|
|
(network_repr.bits_per_entry << 1 | 1).write(&mut subchunk_buf)?;
|
|
|
|
for data in network_repr.packed_data {
|
|
data.write(&mut subchunk_buf)?;
|
|
}
|
|
|
|
match network_repr.palette {
|
|
NetworkPalette::Single(id) => {
|
|
VarInt(i32::from(id)).write(&mut subchunk_buf)?;
|
|
}
|
|
NetworkPalette::Indirect(palette) => {
|
|
VarInt(palette.len() as i32).write(&mut subchunk_buf)?;
|
|
for id in palette {
|
|
VarInt(i32::from(id)).write(&mut subchunk_buf)?;
|
|
}
|
|
}
|
|
NetworkPalette::Direct => (),
|
|
}
|
|
|
|
subchunk_bytes_list.push(subchunk_buf);
|
|
}
|
|
|
|
let biome_sections = chunk
|
|
.section
|
|
.biome_sections
|
|
.read()
|
|
.map_err(|_| Error::other("biome_sections read lock poisoned"))?;
|
|
|
|
let mut biome_buf = Vec::new();
|
|
for biome_palette in biome_sections.iter() {
|
|
let network_repr = biome_palette.convert_be_network();
|
|
|
|
(network_repr.bits_per_entry << 1 | 1).write(&mut biome_buf)?;
|
|
|
|
for data in network_repr.packed_data {
|
|
data.write(&mut biome_buf)?;
|
|
}
|
|
|
|
match network_repr.palette {
|
|
NetworkPalette::Single(id) => {
|
|
VarInt(i32::from(id)).write(&mut biome_buf)?;
|
|
}
|
|
NetworkPalette::Indirect(palette) => {
|
|
VarInt(palette.len() as i32).write(&mut biome_buf)?;
|
|
for id in palette {
|
|
VarInt(i32::from(id)).write(&mut biome_buf)?;
|
|
}
|
|
}
|
|
NetworkPalette::Direct => (),
|
|
}
|
|
}
|
|
|
|
if cache_enabled {
|
|
for subchunk_buf in subchunk_bytes_list {
|
|
let hash = xxh64(&subchunk_buf, 0);
|
|
blobs.push((hash, subchunk_buf));
|
|
}
|
|
let biome_hash = xxh64(&biome_buf, 0);
|
|
blobs.push((biome_hash, biome_buf));
|
|
|
|
VarUInt(blobs.len() as u32).write(&mut writer)?;
|
|
for (hash, _) in &blobs {
|
|
writer.write_all(&hash.to_le_bytes())?;
|
|
}
|
|
|
|
// Chunk data payload when cache_enabled: only border block count byte (0).
|
|
VarUInt(1).write(&mut writer)?;
|
|
writer.write_all(&[0])?;
|
|
} else {
|
|
VarUInt(0).write(&mut writer)?;
|
|
|
|
let mut chunk_data = Vec::new();
|
|
for subchunk_buf in subchunk_bytes_list {
|
|
chunk_data.write_all(&subchunk_buf)?;
|
|
}
|
|
chunk_data.write_all(&biome_buf)?;
|
|
chunk_data.write_all(&[0])?;
|
|
|
|
VarUInt(chunk_data.len() as u32).write(&mut writer)?;
|
|
writer.write_all(&chunk_data)?;
|
|
}
|
|
|
|
Ok((writer, blobs))
|
|
}
|
|
}
|
|
|
|
impl PacketWrite for CLevelChunk<'_> {
|
|
fn write<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
|
|
let (encoded, _) = Self::encode_chunk(self.chunk, self.dimension, self.cache_enabled)?;
|
|
writer.write_all(&encoded)
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use std::sync::{
|
|
Mutex,
|
|
atomic::{AtomicBool, AtomicU64},
|
|
};
|
|
|
|
use pumpkin_data::chunk::ChunkStatus;
|
|
use pumpkin_world::{
|
|
chunk::{ChunkData, ChunkHeightmaps, ChunkLight, ChunkSections},
|
|
tick::scheduler::ChunkTickScheduler,
|
|
};
|
|
|
|
use super::CLevelChunk;
|
|
use crate::serial::PacketWrite;
|
|
|
|
fn read_var_uint(data: &[u8], offset: &mut usize) -> u32 {
|
|
let mut value = 0;
|
|
for shift in (0..35).step_by(7) {
|
|
let byte = data[*offset];
|
|
*offset += 1;
|
|
value |= u32::from(byte & 0x7f) << shift;
|
|
if byte & 0x80 == 0 {
|
|
return value;
|
|
}
|
|
}
|
|
panic!("VarUInt is too long");
|
|
}
|
|
|
|
#[test]
|
|
fn biomes_follow_subchunks_without_subchunk_headers() {
|
|
let chunk = ChunkData {
|
|
section: ChunkSections::new(24, -64),
|
|
heightmap: Mutex::new(ChunkHeightmaps::default()),
|
|
x: 0,
|
|
z: 0,
|
|
block_ticks: ChunkTickScheduler::default(),
|
|
fluid_ticks: ChunkTickScheduler::default(),
|
|
pending_block_entities: Mutex::default(),
|
|
light_engine: Mutex::new(ChunkLight::default()),
|
|
light_populated: AtomicBool::new(false),
|
|
status: ChunkStatus::Full,
|
|
blending_data: None,
|
|
dirty: AtomicBool::new(false),
|
|
inhabited_time: AtomicU64::new(0),
|
|
};
|
|
let mut encoded = Vec::new();
|
|
CLevelChunk {
|
|
dimension: 0,
|
|
cache_enabled: false,
|
|
chunk: &chunk,
|
|
}
|
|
.write(&mut encoded)
|
|
.unwrap();
|
|
|
|
let mut offset = 0;
|
|
for _ in 0..3 {
|
|
read_var_uint(&encoded, &mut offset);
|
|
}
|
|
assert_eq!(read_var_uint(&encoded, &mut offset), 24);
|
|
assert_eq!(encoded[offset], 0); // No sub-chunk request limit.
|
|
assert_eq!(encoded[offset + 1], 0); // Cache disabled.
|
|
offset += 2;
|
|
assert_eq!(read_var_uint(&encoded, &mut offset), 0);
|
|
let raw_len = read_var_uint(&encoded, &mut offset) as usize;
|
|
let raw = &encoded[offset..];
|
|
assert_eq!(raw.len(), raw_len);
|
|
|
|
let mut raw_offset = 0;
|
|
for y in -4i8..20 {
|
|
assert_eq!(&raw[raw_offset..raw_offset + 3], &[9, 1, y as u8]);
|
|
raw_offset += 3;
|
|
assert_eq!(raw[raw_offset], 1); // Single-value block palette.
|
|
raw_offset += 1;
|
|
read_var_uint(raw, &mut raw_offset);
|
|
}
|
|
for _ in 0..24 {
|
|
assert_eq!(raw[raw_offset], 1); // No version/storage/Y prefix.
|
|
raw_offset += 1;
|
|
read_var_uint(raw, &mut raw_offset);
|
|
}
|
|
assert_eq!(raw[raw_offset], 0); // Border block count.
|
|
assert_eq!(raw_offset + 1, raw.len());
|
|
}
|
|
}
|