World: Add GZip compression

This commit is contained in:
Snowiiii 2024-08-16 11:10:16 +02:00
parent 94a7a60423
commit 231bdb5614
8 changed files with 90 additions and 64 deletions

View File

@ -21,9 +21,9 @@ and customizable experience. It prioritizes performance and player enjoyment whi
- **Flexibility**: Highly configurable with the ability to disable unnecessary features. - **Flexibility**: Highly configurable with the ability to disable unnecessary features.
- **Extensibility**: Provides a foundation for plugin development. - **Extensibility**: Provides a foundation for plugin development.
### What Pumpkin will not be: ### What Pumpkin will not:
- A direct replacement for Vanilla or Bukkit servers. - Provide compatibility with Vanilla or Bukkit servers (including configs and plugins).
- A framework for building a server from scratch. - Function as a framework for building a server from scratch.
> [!IMPORTANT] > [!IMPORTANT]
> Pumpkin is currently under heavy development. > Pumpkin is currently under heavy development.

View File

@ -1,9 +1,10 @@
use pumpkin_macros::packet; use pumpkin_macros::packet;
use pumpkin_text::TextComponent; use pumpkin_text::TextComponent;
use serde::Serialize;
use crate::{ClientPacket, VarInt}; use crate::VarInt;
#[derive(Clone)] #[derive(Serialize)]
#[packet(0x1E)] #[packet(0x1E)]
pub struct CDisguisedChatMessage { pub struct CDisguisedChatMessage {
message: TextComponent, message: TextComponent,
@ -27,12 +28,3 @@ impl CDisguisedChatMessage {
} }
} }
} }
impl ClientPacket for CDisguisedChatMessage {
fn write(&self, bytebuf: &mut crate::bytebuf::ByteBuffer) {
bytebuf.put_slice(&self.message.encode());
bytebuf.put_var_int(&self.chat_type);
bytebuf.put_slice(&self.sender_name.encode());
bytebuf.put_option(&self.target_name, |p, v| p.put_slice(&v.encode()));
}
}

View File

@ -100,13 +100,13 @@ impl TextComponent {
pub fn encode(&self) -> Vec<u8> { pub fn encode(&self) -> Vec<u8> {
// TODO: Somehow fix this ugly mess // TODO: Somehow fix this ugly mess
#[derive(serde::Serialize, Debug)] #[derive(serde::Serialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
struct TempStruct<'a> { struct TempStruct<'a> {
#[serde(flatten)] #[serde(flatten)]
text: &'a TextContent, text: &'a TextContent,
#[serde(flatten)] #[serde(flatten)]
pub style: &'a Style, style: &'a Style,
} }
let astruct = TempStruct { let astruct = TempStruct {
text: &self.content, text: &self.content,

View File

@ -4,7 +4,7 @@ use lazy_static::lazy_static;
use crate::world::WorldError; use crate::world::WorldError;
const BLOCKS_JSON: &str = include_str!("../blocks.json"); const BLOCKS_JSON: &str = include_str!("../assets/blocks.json");
#[derive(serde::Deserialize, Debug, Clone, PartialEq, Eq)] #[derive(serde::Deserialize, Debug, Clone, PartialEq, Eq)]
struct BlockDefinition { struct BlockDefinition {

View File

@ -1,5 +1,6 @@
pub mod chunk; pub mod chunk;
pub mod dimension; pub mod dimension;
mod gen;
pub const WORLD_HEIGHT: usize = 384; pub const WORLD_HEIGHT: usize = 384;
pub const WORLD_Y_START_AT: i32 = -64; pub const WORLD_Y_START_AT: i32 = -64;
pub const DIRECT_PALETTE_BITS: u32 = 15; pub const DIRECT_PALETTE_BITS: u32 = 15;

View File

@ -1,9 +1,10 @@
use std::{ use std::{
fs::OpenOptions,
io::{Read, Seek}, io::{Read, Seek},
path::PathBuf, path::PathBuf,
}; };
use flate2::bufread::ZlibDecoder; use flate2::{bufread::ZlibDecoder, read::GzDecoder};
use itertools::Itertools; use itertools::Itertools;
use rayon::prelude::*; use rayon::prelude::*;
use thiserror::Error; use thiserror::Error;
@ -16,6 +17,7 @@ use crate::chunk::ChunkData;
pub struct Level { pub struct Level {
root_folder: PathBuf, root_folder: PathBuf,
region_folder: PathBuf,
} }
#[derive(Error, Debug)] #[derive(Error, Debug)]
@ -29,16 +31,24 @@ pub enum WorldError {
RegionIsInvalid, RegionIsInvalid,
#[error("Chunk not found")] #[error("Chunk not found")]
ChunkNotFound, ChunkNotFound,
#[error("Compression scheme not recognised")] #[error("Compression Error")]
UnknownCompression, Compression(CompressionError),
#[error("Error while working with zlib compression: {0}")]
ZlibError(std::io::Error),
#[error("Error deserializing chunk: {0}")] #[error("Error deserializing chunk: {0}")]
ErrorDeserializingChunk(String), ErrorDeserializingChunk(String),
#[error("The requested block state id does not exist")] #[error("The requested block state id does not exist")]
BlockStateIdNotFound, BlockStateIdNotFound,
} }
#[derive(Error, Debug)]
pub enum CompressionError {
#[error("Compression scheme not recognised")]
UnknownCompression,
#[error("Error while working with zlib compression: {0}")]
ZlibError(std::io::Error),
#[error("Error while working with Gzip compression: {0}")]
GZipError(std::io::Error),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Compression { pub enum Compression {
Gzip, Gzip,
@ -49,7 +59,12 @@ pub enum Compression {
impl Level { impl Level {
pub fn from_root_folder(root_folder: PathBuf) -> Self { pub fn from_root_folder(root_folder: PathBuf) -> Self {
Level { root_folder } // TODO: Check if exists
let region_folder = root_folder.join("region");
Level {
root_folder,
region_folder,
}
} }
// /// Read one chunk in the world // /// Read one chunk in the world
@ -80,16 +95,12 @@ impl Level {
((chunk.1 as f32) / 32.0).floor() as i32, ((chunk.1 as f32) / 32.0).floor() as i32,
); );
let channel = channel.clone(); let channel = channel.clone();
let mut region_file_path = self.root_folder.clone();
region_file_path.push("region");
region_file_path.push(format!("r.{}.{}.mca", region.0, region.1));
// return different error when file is not found (because that means that the chunks have just not been generated yet) // return different error when file is not found (because that means that the chunks have just not been generated yet)
let mut region_file = match std::fs::File::options() let mut region_file = match OpenOptions::new().read(true).open(
.read(true) self.region_folder
.write(false) .join(format!("r.{}.{}.mca", region.0, region.1)),
.open(&region_file_path) ) {
{
Ok(f) => f, Ok(f) => f,
Err(err) => match err.kind() { Err(err) => match err.kind() {
std::io::ErrorKind::NotFound => { std::io::ErrorKind::NotFound => {
@ -172,34 +183,63 @@ impl Level {
3 => Compression::None, 3 => Compression::None,
4 => Compression::LZ4, 4 => Compression::LZ4,
_ => { _ => {
let _ = let _ = channel.send((
channel.send(((chunk.0, chunk.1), Err(WorldError::RegionIsInvalid))); (chunk.0, chunk.1),
Err(WorldError::Compression(
CompressionError::UnknownCompression,
)),
));
return; return;
} }
}; };
match compression {
Compression::Zlib => {}
_ => panic!("Compression type is not supported"), // TODO: support other compression types
}
let size = u32::from_be_bytes(header[0..4].try_into().unwrap()); let size = u32::from_be_bytes(header[0..4].try_into().unwrap());
// size includes the compression scheme byte, so we need to subtract 1 // size includes the compression scheme byte, so we need to subtract 1
let chunk_data = file_buf.drain(0..size as usize - 1).collect_vec(); let chunk_data = file_buf.drain(0..size as usize - 1).collect_vec();
let decompressed_chunk = match Self::decompress_data(compression, chunk_data) {
Ok(data) => data,
Err(e) => {
let _ = channel.send(((chunk.0, chunk.1), Err(WorldError::Compression(e))));
return;
}
};
let mut z = ZlibDecoder::new(&chunk_data[..]); let _ = channel
.blocking_send((chunk, ChunkData::from_bytes(decompressed_chunk, chunk)));
})
.collect::<Vec<()>>();
}
fn decompress_data(
compression: Compression,
compressed_data: Vec<u8>,
) -> Result<Vec<u8>, CompressionError> {
match compression {
Compression::Gzip => {
let mut z = GzDecoder::new(&compressed_data[..]);
let mut chunk_data = Vec::new(); let mut chunk_data = Vec::new();
match z.read_to_end(&mut chunk_data) { match z.read_to_end(&mut chunk_data) {
Ok(_) => {} Ok(_) => {}
Err(e) => { Err(e) => {
let _ = channel.blocking_send((chunk, Err(WorldError::ZlibError(e)))); return Err(CompressionError::GZipError(e));
return;
} }
} }
Ok(chunk_data)
let _ = channel.blocking_send((chunk, ChunkData::from_bytes(chunk_data, chunk))); }
}) Compression::Zlib => {
.collect::<Vec<()>>(); let mut z = ZlibDecoder::new(&compressed_data[..]);
let mut chunk_data = Vec::new();
match z.read_to_end(&mut chunk_data) {
Ok(_) => {}
Err(e) => {
return Err(CompressionError::ZlibError(e));
}
}
Ok(chunk_data)
}
Compression::None => Ok(compressed_data),
Compression::LZ4 => todo!(),
}
} }
} }

View File

@ -1,23 +1,21 @@
use num_traits::FromPrimitive; use num_traits::FromPrimitive;
use pumpkin_entity::EntityId; use pumpkin_entity::EntityId;
use pumpkin_inventory::WindowType;
use pumpkin_protocol::{ use pumpkin_protocol::{
client::play::{ client::play::{
Animation, CEntityAnimation, CEntityVelocity, CHeadRot, CHurtAnimation, COpenScreen, Animation, CEntityAnimation, CEntityVelocity, CHeadRot, CHurtAnimation, CSystemChatMessge,
CSetEntityMetadata, CUpdateEntityPos, CUpdateEntityPosRot, CUpdateEntityRot, Metadata, CUpdateEntityPos, CUpdateEntityPosRot, CUpdateEntityRot,
}, },
server::play::{ server::play::{
Action, SChatCommand, SChatMessage, SClientInformationPlay, SConfirmTeleport, SInteract, Action, SChatCommand, SChatMessage, SClientInformationPlay, SConfirmTeleport, SInteract,
SPlayerAction, SPlayerCommand, SPlayerPosition, SPlayerPositionRotation, SPlayerRotation, SPlayerAction, SPlayerCommand, SPlayerPosition, SPlayerPositionRotation, SPlayerRotation,
SSwingArm, SSwingArm,
}, },
VarInt,
}; };
use pumpkin_text::TextComponent; use pumpkin_text::TextComponent;
use crate::{ use crate::{
commands::{handle_command, CommandSender}, commands::{handle_command, CommandSender},
entity::player::{ChatMode, GameMode, Hand, Player}, entity::player::{ChatMode, GameMode, Hand},
server::Server, server::Server,
util::math::wrap_degrees, util::math::wrap_degrees,
}; };
@ -176,7 +174,7 @@ impl Client {
return; return;
} }
if let Some(action) = Action::from_i32(command.action.0 as i32) { if let Some(action) = Action::from_i32(command.action.0) {
match action { match action {
pumpkin_protocol::server::play::Action::StartSneaking => player.sneaking = true, pumpkin_protocol::server::play::Action::StartSneaking => player.sneaking = true,
pumpkin_protocol::server::play::Action::StopSneaking => player.sneaking = false, pumpkin_protocol::server::play::Action::StopSneaking => player.sneaking = false,
@ -208,22 +206,17 @@ impl Client {
pub fn handle_chat_message(&mut self, server: &mut Server, chat_message: SChatMessage) { pub fn handle_chat_message(&mut self, server: &mut Server, chat_message: SChatMessage) {
let message = chat_message.message; let message = chat_message.message;
self.send_packet(&COpenScreen::new(
VarInt(0),
VarInt(WindowType::CraftingTable as i32),
TextComponent::from("Test Crafter"),
));
// TODO: filter message & validation // TODO: filter message & validation
let gameprofile = self.gameprofile.as_ref().unwrap(); let gameprofile = self.gameprofile.as_ref().unwrap();
dbg!("got message"); dbg!("got message");
// yeah a "raw system message", the ugly way to do that, but it works // yeah a "raw system message", the ugly way to do that, but it works
// server.broadcast_packet( server.broadcast_packet(
// self, self,
// CSystemChatMessge::new( &CSystemChatMessge::new(
// TextComponent::from(format!("{}: {}", gameprofile.name, message)), TextComponent::from(format!("{}: {}", gameprofile.name, message)),
// false, false,
// ), ),
// ); );
/* server.broadcast_packet( /* server.broadcast_packet(
self, self,
CPlayerChatMessage::new( CPlayerChatMessage::new(
@ -245,7 +238,7 @@ impl Client {
*/ */
/* server.broadcast_packet( /* server.broadcast_packet(
self, self,
CDisguisedChatMessage::new( &CDisguisedChatMessage::new(
TextComponent::from(message.clone()), TextComponent::from(message.clone()),
VarInt(0), VarInt(0),
gameprofile.name.clone().into(), gameprofile.name.clone().into(),