From 231bdb5614a90b5689e48384aabfae7c4bdf06e0 Mon Sep 17 00:00:00 2001 From: Snowiiii Date: Fri, 16 Aug 2024 11:10:16 +0200 Subject: [PATCH] World: Add GZip compression --- README.md | 6 +- .../client/play/c_disguised_chat_message.rs | 14 +-- pumpkin-text/src/lib.rs | 4 +- pumpkin-world/{ => assets}/blocks.json | 0 pumpkin-world/src/block_registry.rs | 2 +- pumpkin-world/src/lib.rs | 1 + pumpkin-world/src/world.rs | 96 +++++++++++++------ pumpkin/src/client/player_packet.rs | 31 +++--- 8 files changed, 90 insertions(+), 64 deletions(-) rename pumpkin-world/{ => assets}/blocks.json (100%) diff --git a/README.md b/README.md index 7dc693518..817d42850 100644 --- a/README.md +++ b/README.md @@ -21,9 +21,9 @@ and customizable experience. It prioritizes performance and player enjoyment whi - **Flexibility**: Highly configurable with the ability to disable unnecessary features. - **Extensibility**: Provides a foundation for plugin development. -### What Pumpkin will not be: -- A direct replacement for Vanilla or Bukkit servers. -- A framework for building a server from scratch. +### What Pumpkin will not: +- Provide compatibility with Vanilla or Bukkit servers (including configs and plugins). +- Function as a framework for building a server from scratch. > [!IMPORTANT] > Pumpkin is currently under heavy development. diff --git a/pumpkin-protocol/src/client/play/c_disguised_chat_message.rs b/pumpkin-protocol/src/client/play/c_disguised_chat_message.rs index fab48f175..c1228080c 100644 --- a/pumpkin-protocol/src/client/play/c_disguised_chat_message.rs +++ b/pumpkin-protocol/src/client/play/c_disguised_chat_message.rs @@ -1,9 +1,10 @@ use pumpkin_macros::packet; use pumpkin_text::TextComponent; +use serde::Serialize; -use crate::{ClientPacket, VarInt}; +use crate::VarInt; -#[derive(Clone)] +#[derive(Serialize)] #[packet(0x1E)] pub struct CDisguisedChatMessage { 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())); - } -} diff --git a/pumpkin-text/src/lib.rs b/pumpkin-text/src/lib.rs index 9134e863b..6a6f1ddcb 100644 --- a/pumpkin-text/src/lib.rs +++ b/pumpkin-text/src/lib.rs @@ -100,13 +100,13 @@ impl TextComponent { pub fn encode(&self) -> Vec { // TODO: Somehow fix this ugly mess - #[derive(serde::Serialize, Debug)] + #[derive(serde::Serialize)] #[serde(rename_all = "camelCase")] struct TempStruct<'a> { #[serde(flatten)] text: &'a TextContent, #[serde(flatten)] - pub style: &'a Style, + style: &'a Style, } let astruct = TempStruct { text: &self.content, diff --git a/pumpkin-world/blocks.json b/pumpkin-world/assets/blocks.json similarity index 100% rename from pumpkin-world/blocks.json rename to pumpkin-world/assets/blocks.json diff --git a/pumpkin-world/src/block_registry.rs b/pumpkin-world/src/block_registry.rs index 600642a64..3c1774f65 100644 --- a/pumpkin-world/src/block_registry.rs +++ b/pumpkin-world/src/block_registry.rs @@ -4,7 +4,7 @@ use lazy_static::lazy_static; 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)] struct BlockDefinition { diff --git a/pumpkin-world/src/lib.rs b/pumpkin-world/src/lib.rs index 6198796ed..b2ce15dc3 100644 --- a/pumpkin-world/src/lib.rs +++ b/pumpkin-world/src/lib.rs @@ -1,5 +1,6 @@ pub mod chunk; pub mod dimension; +mod gen; pub const WORLD_HEIGHT: usize = 384; pub const WORLD_Y_START_AT: i32 = -64; pub const DIRECT_PALETTE_BITS: u32 = 15; diff --git a/pumpkin-world/src/world.rs b/pumpkin-world/src/world.rs index 919a83393..274ef7857 100644 --- a/pumpkin-world/src/world.rs +++ b/pumpkin-world/src/world.rs @@ -1,9 +1,10 @@ use std::{ + fs::OpenOptions, io::{Read, Seek}, path::PathBuf, }; -use flate2::bufread::ZlibDecoder; +use flate2::{bufread::ZlibDecoder, read::GzDecoder}; use itertools::Itertools; use rayon::prelude::*; use thiserror::Error; @@ -16,6 +17,7 @@ use crate::chunk::ChunkData; pub struct Level { root_folder: PathBuf, + region_folder: PathBuf, } #[derive(Error, Debug)] @@ -29,16 +31,24 @@ pub enum WorldError { RegionIsInvalid, #[error("Chunk not found")] ChunkNotFound, - #[error("Compression scheme not recognised")] - UnknownCompression, - #[error("Error while working with zlib compression: {0}")] - ZlibError(std::io::Error), + #[error("Compression Error")] + Compression(CompressionError), #[error("Error deserializing chunk: {0}")] ErrorDeserializingChunk(String), #[error("The requested block state id does not exist")] 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)] pub enum Compression { Gzip, @@ -49,7 +59,12 @@ pub enum Compression { impl Level { 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 @@ -80,16 +95,12 @@ impl Level { ((chunk.1 as f32) / 32.0).floor() as i32, ); 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) - let mut region_file = match std::fs::File::options() - .read(true) - .write(false) - .open(®ion_file_path) - { + let mut region_file = match OpenOptions::new().read(true).open( + self.region_folder + .join(format!("r.{}.{}.mca", region.0, region.1)), + ) { Ok(f) => f, Err(err) => match err.kind() { std::io::ErrorKind::NotFound => { @@ -172,34 +183,63 @@ impl Level { 3 => Compression::None, 4 => Compression::LZ4, _ => { - let _ = - channel.send(((chunk.0, chunk.1), Err(WorldError::RegionIsInvalid))); + let _ = channel.send(( + (chunk.0, chunk.1), + Err(WorldError::Compression( + CompressionError::UnknownCompression, + )), + )); 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()); // 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 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 _ = channel + .blocking_send((chunk, ChunkData::from_bytes(decompressed_chunk, chunk))); + }) + .collect::>(); + } - let mut z = ZlibDecoder::new(&chunk_data[..]); + fn decompress_data( + compression: Compression, + compressed_data: Vec, + ) -> Result, CompressionError> { + match compression { + Compression::Gzip => { + let mut z = GzDecoder::new(&compressed_data[..]); let mut chunk_data = Vec::new(); match z.read_to_end(&mut chunk_data) { Ok(_) => {} Err(e) => { - let _ = channel.blocking_send((chunk, Err(WorldError::ZlibError(e)))); - return; + return Err(CompressionError::GZipError(e)); } } - - let _ = channel.blocking_send((chunk, ChunkData::from_bytes(chunk_data, chunk))); - }) - .collect::>(); + Ok(chunk_data) + } + Compression::Zlib => { + 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!(), + } } } diff --git a/pumpkin/src/client/player_packet.rs b/pumpkin/src/client/player_packet.rs index 579ed460a..47fa3e44f 100644 --- a/pumpkin/src/client/player_packet.rs +++ b/pumpkin/src/client/player_packet.rs @@ -1,23 +1,21 @@ use num_traits::FromPrimitive; use pumpkin_entity::EntityId; -use pumpkin_inventory::WindowType; use pumpkin_protocol::{ client::play::{ - Animation, CEntityAnimation, CEntityVelocity, CHeadRot, CHurtAnimation, COpenScreen, - CSetEntityMetadata, CUpdateEntityPos, CUpdateEntityPosRot, CUpdateEntityRot, Metadata, + Animation, CEntityAnimation, CEntityVelocity, CHeadRot, CHurtAnimation, CSystemChatMessge, + CUpdateEntityPos, CUpdateEntityPosRot, CUpdateEntityRot, }, server::play::{ Action, SChatCommand, SChatMessage, SClientInformationPlay, SConfirmTeleport, SInteract, SPlayerAction, SPlayerCommand, SPlayerPosition, SPlayerPositionRotation, SPlayerRotation, SSwingArm, }, - VarInt, }; use pumpkin_text::TextComponent; use crate::{ commands::{handle_command, CommandSender}, - entity::player::{ChatMode, GameMode, Hand, Player}, + entity::player::{ChatMode, GameMode, Hand}, server::Server, util::math::wrap_degrees, }; @@ -176,7 +174,7 @@ impl Client { 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 { pumpkin_protocol::server::play::Action::StartSneaking => player.sneaking = true, 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) { 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 let gameprofile = self.gameprofile.as_ref().unwrap(); dbg!("got message"); // yeah a "raw system message", the ugly way to do that, but it works - // server.broadcast_packet( - // self, - // CSystemChatMessge::new( - // TextComponent::from(format!("{}: {}", gameprofile.name, message)), - // false, - // ), - // ); + server.broadcast_packet( + self, + &CSystemChatMessge::new( + TextComponent::from(format!("{}: {}", gameprofile.name, message)), + false, + ), + ); /* server.broadcast_packet( self, CPlayerChatMessage::new( @@ -245,7 +238,7 @@ impl Client { */ /* server.broadcast_packet( self, - CDisguisedChatMessage::new( + &CDisguisedChatMessage::new( TextComponent::from(message.clone()), VarInt(0), gameprofile.name.clone().into(),