World: Add GZip compression
This commit is contained in:
parent
94a7a60423
commit
231bdb5614
@ -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.
|
||||||
|
|||||||
@ -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()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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(®ion_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!(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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(),
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user