mirror of
https://github.com/Pumpkin-MC/Pumpkin.git
synced 2026-08-30 20:14:23 +00:00
World: Add GZip compression
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,13 +100,13 @@ impl TextComponent {
|
||||
|
||||
pub fn encode(&self) -> Vec<u8> {
|
||||
// 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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 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();
|
||||
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::<Vec<()>>();
|
||||
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!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
|
||||
Reference in New Issue
Block a user