diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index ccfd6ea8..193c05eb 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -32,7 +32,7 @@ jobs: - uses: actions/checkout@v4 - run: rustup update ${{ matrix.toolchain }} && rustup default ${{ matrix.toolchain }} - uses: Swatinem/rust-cache@v2 - - run: cargo clippy --all-targets --all-features --no-default-features + - run: cargo clippy --all-targets --all-features build_and_test: name: Build project and test runs-on: ${{ matrix.os }} @@ -77,4 +77,4 @@ jobs: - uses: actions/checkout@v4 - run: rustup update ${{ matrix.toolchain }} && rustup default ${{ matrix.toolchain }} - uses: Swatinem/rust-cache@v2 - - run: cargo clippy --release --all-targets --all-features --no-default-features + - run: cargo clippy --release --all-targets --all-features diff --git a/.gitignore b/.gitignore index 9016d354..4b08876e 100644 --- a/.gitignore +++ b/.gitignore @@ -74,7 +74,6 @@ gradle-app.setting *.zip *.tar.gz *.rar - # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* replay_pid* diff --git a/Cargo.toml b/Cargo.toml index dd3e6ba0..7122a354 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,8 +16,6 @@ members = [ version = "0.1.0" edition = "2021" -[profile.dev.package."*"] -opt-level = 3 [profile.dev] opt-level = 1 @@ -61,5 +59,3 @@ uuid = { version = "1.11.0", features = ["serde", "v3", "v4"] } derive_more = { version = "1.0.0", features = ["full"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" - -itertools = "0.13.0" diff --git a/extractor/build.gradle.kts b/extractor/build.gradle.kts index 0fccb5cc..944a30d3 100644 --- a/extractor/build.gradle.kts +++ b/extractor/build.gradle.kts @@ -2,8 +2,8 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { - kotlin("jvm") version "2.0.21" - id("fabric-loom") version "1.8.9" + kotlin("jvm") version "2.1.0" + id("fabric-loom") version "1.9-SNAPSHOT" id("maven-publish") } diff --git a/extractor/gradle.properties b/extractor/gradle.properties index f028ef93..5b4def5d 100644 --- a/extractor/gradle.properties +++ b/extractor/gradle.properties @@ -4,13 +4,11 @@ org.gradle.parallel=true # Fabric Properties # check these on https://modmuss50.me/fabric.html minecraft_version=1.21.4 -yarn_mappings=1.21.4+build.1 +yarn_mappings=1.21.4+build.2 loader_version=0.16.9 -kotlin_loader_version=1.12.3+kotlin.2.0.21 +kotlin_loader_version=1.13.0+kotlin.2.1.0 # Mod Properties mod_version=1.0-SNAPSHOT maven_group=de.snowii archives_base_name=extractor -# Dependencies -# check this on https://modmuss50.me/fabric.html -fabric_version=0.110.5+1.21.4 +fabric_version=0.112.1+1.21.4 diff --git a/pumpkin-config/src/lib.rs b/pumpkin-config/src/lib.rs index a69c5349..5e82ae33 100644 --- a/pumpkin-config/src/lib.rs +++ b/pumpkin-config/src/lib.rs @@ -5,8 +5,9 @@ use query::QueryConfig; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use std::{ - fs, + env, fs, net::{Ipv4Addr, SocketAddr}, + num::NonZeroU8, path::Path, sync::LazyLock, }; @@ -35,6 +36,8 @@ mod server_links; use proxy::ProxyConfig; use resource_pack::ResourcePackConfig; +const CONFIG_ROOT_FOLDER: &str = "config/"; + pub static ADVANCED_CONFIG: LazyLock = LazyLock::new(AdvancedConfiguration::load); @@ -71,9 +74,9 @@ pub struct BasicConfiguration { /// The maximum number of players allowed on the server. Specifying `0` disables the limit. pub max_players: u32, /// The maximum view distance for players. - pub view_distance: u8, + pub view_distance: NonZeroU8, /// The maximum simulated view distance. - pub simulation_distance: u8, + pub simulation_distance: NonZeroU8, /// The default game difficulty. pub default_difficulty: Difficulty, /// Whether the Nether dimension is enabled. @@ -103,8 +106,8 @@ impl Default for BasicConfiguration { server_address: SocketAddr::new(Ipv4Addr::new(0, 0, 0, 0).into(), 25565), seed: "".to_string(), max_players: 100000, - view_distance: 10, - simulation_distance: 10, + view_distance: NonZeroU8::new(10).unwrap(), + simulation_distance: NonZeroU8::new(10).unwrap(), default_difficulty: Difficulty::Normal, allow_nether: true, hardcore: false, @@ -125,26 +128,32 @@ trait LoadConfiguration { where Self: Sized + Default + Serialize + DeserializeOwned, { - let path = Self::get_path(); + let exe_dir = env::current_dir().unwrap(); + let config_dir = exe_dir.join(CONFIG_ROOT_FOLDER); + if !config_dir.exists() { + log::debug!("creating new config root folder"); + fs::create_dir(&config_dir).expect("Failed to create Config root folder"); + } + let path = config_dir.join(Self::get_path()); let config = if path.exists() { - let file_content = fs::read_to_string(path) - .unwrap_or_else(|_| panic!("Couldn't read configuration file at {:?}", path)); + let file_content = fs::read_to_string(&path) + .unwrap_or_else(|_| panic!("Couldn't read configuration file at {:?}", &path)); toml::from_str(&file_content).unwrap_or_else(|err| { panic!( "Couldn't parse config at {:?}. Reason: {}. This is is proberbly caused by an Config update, Just delete the old Config and start Pumpkin again", - path, + &path, err.message() ) }) } else { let content = Self::default(); - if let Err(err) = fs::write(path, toml::to_string(&content).unwrap()) { + if let Err(err) = fs::write(&path, toml::to_string(&content).unwrap()) { warn!( "Couldn't write default config to {:?}. Reason: {}. This is is proberbly caused by an Config update, Just delete the old Config and start Pumpkin again", - path, err + &path, err ); } @@ -176,9 +185,14 @@ impl LoadConfiguration for BasicConfiguration { } fn validate(&self) { - assert!(self.view_distance >= 2, "View distance must be at least 2"); assert!( - self.view_distance <= 32, + self.view_distance + .ge(unsafe { &NonZeroU8::new_unchecked(2) }), + "View distance must be at least 2" + ); + assert!( + self.view_distance + .le(unsafe { &NonZeroU8::new_unchecked(32) }), "View distance must be less than 32" ); if self.online_mode { diff --git a/pumpkin-core/Cargo.toml b/pumpkin-core/Cargo.toml index c56368a9..31fd66e5 100644 --- a/pumpkin-core/Cargo.toml +++ b/pumpkin-core/Cargo.toml @@ -13,5 +13,3 @@ num-derive.workspace = true colored = "2" md5 = "0.7.0" - -enum_dispatch = "0.3.13" diff --git a/pumpkin-core/src/lib.rs b/pumpkin-core/src/lib.rs index 09f36f9b..8ffd25b2 100644 --- a/pumpkin-core/src/lib.rs +++ b/pumpkin-core/src/lib.rs @@ -7,7 +7,7 @@ pub use gamemode::GameMode; use serde::{Deserialize, Serialize}; -#[derive(PartialEq, Serialize, Deserialize)] +#[derive(PartialEq, Serialize, Deserialize, Clone)] pub enum Difficulty { Peaceful, Easy, diff --git a/pumpkin-core/src/random/mod.rs b/pumpkin-core/src/random/mod.rs index 8e147cb1..d52b58fd 100644 --- a/pumpkin-core/src/random/mod.rs +++ b/pumpkin-core/src/random/mod.rs @@ -1,3 +1,8 @@ +use std::{ + sync::atomic::{AtomicU64, Ordering}, + time, +}; + use legacy_rand::{LegacyRand, LegacySplitter}; use xoroshiro128::{Xoroshiro, XoroshiroSplitter}; @@ -5,6 +10,26 @@ mod gaussian; pub mod legacy_rand; pub mod xoroshiro128; +static SEED_UNIQUIFIER: AtomicU64 = AtomicU64::new(8682522807148012u64); + +pub fn get_seed() -> u64 { + let seed = SEED_UNIQUIFIER + .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |val| { + Some(val.wrapping_mul(1181783497276652981u64)) + }) + // We always return Some, so there will always be an Ok result + .unwrap(); + + let nanos = time::SystemTime::now() + .duration_since(time::SystemTime::UNIX_EPOCH) + .unwrap() + .as_nanos(); + + let nano_upper = (nanos >> 8) as u64; + let nano_lower = nanos as u64; + seed ^ nano_upper ^ nano_lower +} + pub enum RandomGenerator { Xoroshiro(Xoroshiro), Legacy(LegacyRand), @@ -186,7 +211,7 @@ pub trait RandomDeriverImpl { fn split_pos(&self, x: i32, y: i32, z: i32) -> impl RandomImpl; } -fn hash_block_pos(x: i32, y: i32, z: i32) -> i64 { +pub fn hash_block_pos(x: i32, y: i32, z: i32) -> i64 { let l = (x.wrapping_mul(3129871) as i64) ^ ((z as i64).wrapping_mul(116129781i64)) ^ (y as i64); let l = l .wrapping_mul(l) @@ -195,7 +220,7 @@ fn hash_block_pos(x: i32, y: i32, z: i32) -> i64 { l >> 16 } -fn java_string_hash(string: &str) -> i32 { +pub fn java_string_hash(string: &str) -> i32 { // All byte values of latin1 align with // the values of U+0000 - U+00FF making this code // equivalent to both java hash implementations diff --git a/pumpkin-inventory/Cargo.toml b/pumpkin-inventory/Cargo.toml index ac680e6d..79ff82f6 100644 --- a/pumpkin-inventory/Cargo.toml +++ b/pumpkin-inventory/Cargo.toml @@ -4,7 +4,8 @@ version.workspace = true edition.workspace = true [dependencies] -# For items +pumpkin-protocol = { path = "../pumpkin-protocol" } + pumpkin-world = { path = "../pumpkin-world" } pumpkin-registry = {path = "../pumpkin-registry"} pumpkin-macros = { path = "../pumpkin-macros" } @@ -12,8 +13,6 @@ pumpkin-core = { path = "../pumpkin-core" } log.workspace = true rayon.workspace = true -itertools.workspace = true -crossbeam.workspace = true tokio.workspace = true thiserror.workspace = true diff --git a/pumpkin-inventory/src/container_click.rs b/pumpkin-inventory/src/container_click.rs index 5d1039a9..d93ba3d3 100644 --- a/pumpkin-inventory/src/container_click.rs +++ b/pumpkin-inventory/src/container_click.rs @@ -1,4 +1,5 @@ use crate::InventoryError; +use pumpkin_protocol::server::play::SlotActionType; use pumpkin_world::item::ItemStack; pub struct Click { @@ -7,23 +8,22 @@ pub struct Click { } impl Click { - pub fn new(mode: u8, button: i8, slot: i16) -> Result { + pub fn new(mode: SlotActionType, button: i8, slot: i16) -> Result { match mode { - 0 => Self::new_normal_click(button, slot), + SlotActionType::Pickup => Self::new_normal_click(button, slot), // Both buttons do the same here, so we omit it - 1 => Self::new_shift_click(slot), - 2 => Self::new_key_click(button, slot), - 3 => Ok(Self { + SlotActionType::QuickMove => Self::new_shift_click(slot), + SlotActionType::Swap => Self::new_key_click(button, slot), + SlotActionType::Clone => Ok(Self { click_type: ClickType::CreativePickItem, slot: Slot::Normal(slot.try_into().or(Err(InventoryError::InvalidSlot))?), }), - 4 => Self::new_drop_item(button), - 5 => Self::new_drag_item(button, slot), - 6 => Ok(Self { + SlotActionType::Throw => Self::new_drop_item(button), + SlotActionType::QuickCraft => Self::new_drag_item(button, slot), + SlotActionType::PickupAll => Ok(Self { click_type: ClickType::DoubleClick, slot: Slot::Normal(slot.try_into().or(Err(InventoryError::InvalidSlot))?), }), - _ => Err(InventoryError::InvalidPacket), } } @@ -31,7 +31,7 @@ impl Click { let slot = match slot { -999 => Slot::OutsideInventory, _ => { - let slot = slot.try_into().or(Err(InventoryError::InvalidSlot))?; + let slot = slot.try_into().unwrap_or(0); Slot::Normal(slot) } }; diff --git a/pumpkin-inventory/src/crafting.rs b/pumpkin-inventory/src/crafting.rs index 61e2a359..643ffbd3 100644 --- a/pumpkin-inventory/src/crafting.rs +++ b/pumpkin-inventory/src/crafting.rs @@ -1,4 +1,3 @@ -use itertools::Itertools; use pumpkin_registry::{ flatten_3x3, get_tag_values, IngredientSlot, IngredientType, RecipeResult, TagCategory, RECIPES, }; @@ -76,13 +75,13 @@ fn shapeless_crafting_match( input: [[Option; 3]; 3], pattern: &[[[Option; 3]; 3]], ) -> bool { - let mut pattern = pattern + let mut pattern: Vec = pattern .iter() .flatten() .flatten() .flatten() .cloned() - .collect_vec(); + .collect(); for item in input.into_iter().flatten().flatten() { if let Some(index) = pattern.iter().enumerate().find_map(|(i, recipe_item)| { if ingredient_slot_check(recipe_item, item) { diff --git a/pumpkin-inventory/src/drag_handler.rs b/pumpkin-inventory/src/drag_handler.rs index 70336c56..fefcd605 100644 --- a/pumpkin-inventory/src/drag_handler.rs +++ b/pumpkin-inventory/src/drag_handler.rs @@ -1,6 +1,5 @@ use crate::container_click::MouseDragType; use crate::{Container, InventoryError}; -use itertools::Itertools; use num_traits::Euclid; use pumpkin_world::item::ItemStack; use std::collections::HashMap; @@ -73,10 +72,10 @@ impl DragHandler { Err(InventoryError::MultiplePlayersDragging)? } let mut slots = container.all_slots(); - let slots_cloned = slots + let slots_cloned: Vec> = slots .iter() .map(|stack| stack.map(|item| item.to_owned())) - .collect_vec(); + .collect(); let Some(carried_item) = maybe_carried_item else { return Ok(()); }; diff --git a/pumpkin-macros/Cargo.toml b/pumpkin-macros/Cargo.toml index 233af3bd..9f242888 100644 --- a/pumpkin-macros/Cargo.toml +++ b/pumpkin-macros/Cargo.toml @@ -9,7 +9,6 @@ proc-macro = true [dependencies] serde.workspace = true serde_json.workspace = true -itertools.workspace = true proc-macro2 = "1.0" quote = "1.0" diff --git a/pumpkin-protocol/Cargo.toml b/pumpkin-protocol/Cargo.toml index b2a4c780..674b290e 100644 --- a/pumpkin-protocol/Cargo.toml +++ b/pumpkin-protocol/Cargo.toml @@ -3,6 +3,13 @@ name = "pumpkin-protocol" version.workspace = true edition.workspace = true +[features] +default = ["packets", "query"] +packets = ["serverbound", "clientbound"] +serverbound = [] +clientbound = [] +query = [] + [dependencies] pumpkin-nbt = { path = "../pumpkin-nbt" } pumpkin-config = { path = "../pumpkin-config" } @@ -13,7 +20,6 @@ pumpkin-core = { path = "../pumpkin-core" } uuid.workspace = true serde.workspace = true thiserror.workspace = true -itertools.workspace = true log.workspace = true tokio.workspace = true num-traits.workspace = true diff --git a/pumpkin-protocol/src/bytebuf/deserializer.rs b/pumpkin-protocol/src/bytebuf/deserializer.rs index 829eb515..c7f574c3 100644 --- a/pumpkin-protocol/src/bytebuf/deserializer.rs +++ b/pumpkin-protocol/src/bytebuf/deserializer.rs @@ -1,34 +1,27 @@ use std::fmt::Display; +use super::{ByteBuf, ReadingError}; +use bytes::Buf; use serde::de::{self, DeserializeSeed, SeqAccess}; -use thiserror::Error; -use super::ByteBuffer; - -pub struct Deserializer<'a> { - inner: &'a mut ByteBuffer, -} - -#[derive(Debug, Error)] -pub enum DeserializerError { - #[error("serializer error {0}")] - Message(String), +pub struct Deserializer<'a, B: Buf> { + inner: &'a mut B, } -impl de::Error for DeserializerError { +impl de::Error for ReadingError { fn custom(msg: T) -> Self { Self::Message(msg.to_string()) } } -impl<'a> Deserializer<'a> { - pub fn new(buf: &'a mut ByteBuffer) -> Self { +impl<'a, B: Buf> Deserializer<'a, B> { + pub fn new(buf: &'a mut B) -> Self { Self { inner: buf } } } -impl<'de> de::Deserializer<'de> for Deserializer<'_> { - type Error = DeserializerError; +impl<'de, B: Buf> de::Deserializer<'de> for Deserializer<'_, B> { + type Error = ReadingError; fn deserialize_any(self, _visitor: V) -> Result where @@ -43,77 +36,77 @@ impl<'de> de::Deserializer<'de> for Deserializer<'_> { where V: de::Visitor<'de>, { - visitor.visit_bool(self.inner.get_bool()?) + visitor.visit_bool(self.inner.try_get_bool()?) } fn deserialize_i8(self, visitor: V) -> Result where V: de::Visitor<'de>, { - visitor.visit_i8(self.inner.get_i8()?) + visitor.visit_i8(self.inner.try_get_i8()?) } fn deserialize_i16(self, visitor: V) -> Result where V: de::Visitor<'de>, { - visitor.visit_i16(self.inner.get_i16()?) + visitor.visit_i16(self.inner.try_get_i16()?) } fn deserialize_i32(self, visitor: V) -> Result where V: de::Visitor<'de>, { - visitor.visit_i32(self.inner.get_i32()?) + visitor.visit_i32(self.inner.try_get_i32()?) } fn deserialize_i64(self, visitor: V) -> Result where V: de::Visitor<'de>, { - visitor.visit_i64(self.inner.get_i64()?) + visitor.visit_i64(self.inner.try_get_i64()?) } fn deserialize_u8(self, visitor: V) -> Result where V: de::Visitor<'de>, { - visitor.visit_u8(self.inner.get_u8()?) + visitor.visit_u8(self.inner.try_get_u8()?) } fn deserialize_u16(self, visitor: V) -> Result where V: de::Visitor<'de>, { - visitor.visit_u16(self.inner.get_u16()?) + visitor.visit_u16(self.inner.try_get_u16()?) } fn deserialize_u32(self, visitor: V) -> Result where V: de::Visitor<'de>, { - visitor.visit_u32(self.inner.get_u32()?) + visitor.visit_u32(self.inner.try_get_u32()?) } fn deserialize_u64(self, visitor: V) -> Result where V: de::Visitor<'de>, { - visitor.visit_u64(self.inner.get_u64()?) + visitor.visit_u64(self.inner.try_get_u64()?) } fn deserialize_f32(self, visitor: V) -> Result where V: de::Visitor<'de>, { - visitor.visit_f32(self.inner.get_f32()?) + visitor.visit_f32(self.inner.try_get_f32()?) } fn deserialize_f64(self, visitor: V) -> Result where V: de::Visitor<'de>, { - visitor.visit_f64(self.inner.get_f64()?) + visitor.visit_f64(self.inner.try_get_f64()?) } fn deserialize_char(self, _visitor: V) -> Result @@ -127,16 +120,14 @@ impl<'de> de::Deserializer<'de> for Deserializer<'_> { where V: de::Visitor<'de>, { - let string = self.inner.get_string()?; - visitor.visit_str(&string) + visitor.visit_str(&self.inner.try_get_string()?) } fn deserialize_string(self, visitor: V) -> Result where V: de::Visitor<'de>, { - let string = self.inner.get_string()?; - visitor.visit_str(&string) + visitor.visit_str(&self.inner.try_get_string()?) } fn deserialize_bytes(self, _visitor: V) -> Result @@ -193,12 +184,12 @@ impl<'de> de::Deserializer<'de> for Deserializer<'_> { where V: de::Visitor<'de>, { - struct Access<'a, 'b> { - deserializer: &'a mut Deserializer<'b>, + struct Access<'a, 'b, B: Buf> { + deserializer: &'a mut Deserializer<'b, B>, } - impl<'de, 'a, 'b: 'a> SeqAccess<'de> for Access<'a, 'b> { - type Error = DeserializerError; + impl<'de, 'a, 'b: 'a, B: Buf> SeqAccess<'de> for Access<'a, 'b, B> { + type Error = ReadingError; fn next_element_seed(&mut self, seed: T) -> Result, Self::Error> where @@ -225,13 +216,13 @@ impl<'de> de::Deserializer<'de> for Deserializer<'_> { where V: de::Visitor<'de>, { - struct Access<'a, 'b> { - deserializer: &'a mut Deserializer<'b>, + struct Access<'a, 'b, B: Buf> { + deserializer: &'a mut Deserializer<'b, B>, len: usize, } - impl<'de, 'a, 'b: 'a> SeqAccess<'de> for Access<'a, 'b> { - type Error = DeserializerError; + impl<'de, 'a, 'b: 'a, B: Buf> SeqAccess<'de> for Access<'a, 'b, B> { + type Error = ReadingError; fn next_element_seed(&mut self, seed: T) -> Result, Self::Error> where diff --git a/pumpkin-protocol/src/bytebuf/mod.rs b/pumpkin-protocol/src/bytebuf/mod.rs index accceb4b..b1ee75ae 100644 --- a/pumpkin-protocol/src/bytebuf/mod.rs +++ b/pumpkin-protocol/src/bytebuf/mod.rs @@ -1,376 +1,372 @@ use core::str; -use crate::{BitSet, FixedBitSet, VarInt, VarLong}; -use bytes::{Buf, BufMut, BytesMut}; +use crate::{ + codec::{ + bit_set::BitSet, identifier::Identifier, var_int::VarInt, var_long::VarLong, Codec, + DecodeError, + }, + FixedBitSet, +}; +use bytes::{Buf, BufMut}; mod deserializer; -pub use deserializer::DeserializerError; +use thiserror::Error; pub mod packet_id; mod serializer; -#[derive(Debug)] -pub struct ByteBuffer { - buffer: BytesMut, +use std::mem::size_of; + +#[derive(Debug, Error)] +pub enum ReadingError { + /// End-of-File + #[error("EOF, Tried to read {0} but No bytes left to consume")] + EOF(String), + #[error("{0} is Incomplete")] + Incomplete(String), + #[error("{0} is too Large")] + TooLarge(String), + #[error("{0}")] + Message(String), } -impl ByteBuffer { - pub fn empty() -> Self { - Self { - buffer: BytesMut::new(), - } - } - pub fn new(buffer: BytesMut) -> Self { - Self { buffer } - } +pub trait ByteBuf: Buf { + fn try_get_bool(&mut self) -> Result; - pub fn get_var_int(&mut self) -> Result { - match VarInt::decode(&mut self.buffer) { - Ok(var_int) => Ok(var_int), - Err(error) => match error { - crate::VarIntDecodeError::Incomplete => Err(DeserializerError::Message( - "VarInt is Incomplete".to_string(), - )), - crate::VarIntDecodeError::TooLarge => { - Err(DeserializerError::Message("VarInt is too big".to_string())) - } - }, - } - } + fn try_get_u8(&mut self) -> Result; - pub fn get_var_long(&mut self) -> Result { - match VarLong::decode(&mut self.buffer) { - Ok(var_long) => Ok(var_long), - Err(error) => match error { - crate::VarLongDecodeError::Incomplete => Err(DeserializerError::Message( - "VarLong is Incomplete".to_string(), - )), - crate::VarLongDecodeError::TooLarge => { - Err(DeserializerError::Message("VarLong is too big".to_string())) - } - }, - } - } + fn try_get_i8(&mut self) -> Result; - pub fn get_string(&mut self) -> Result { - self.get_string_len(i16::MAX as i32) - } + fn try_get_u16(&mut self) -> Result; - pub fn get_string_len(&mut self, max_size: i32) -> Result { - let size = self.get_var_int()?.0; - if size > max_size { - return Err(DeserializerError::Message( - "String length is bigger than max size".to_string(), - )); - } + fn try_get_i16(&mut self) -> Result; - let data = self.copy_to_bytes(size as usize)?; - if data.len() as i32 > max_size { - return Err(DeserializerError::Message( - "String is bigger than max size".to_string(), - )); - } - match str::from_utf8(&data) { - Ok(string_result) => Ok(string_result.to_string()), - Err(e) => Err(DeserializerError::Message(e.to_string())), - } - } + fn try_get_u32(&mut self) -> Result; - pub fn get_bool(&mut self) -> Result { - Ok(self.get_u8()? != 0) - } + fn try_get_i32(&mut self) -> Result; - pub fn get_uuid(&mut self) -> Result { - let mut bytes = [0u8; 16]; - self.copy_to_slice(&mut bytes)?; - Ok(uuid::Uuid::from_slice(&bytes).expect("Failed to parse UUID")) - } + fn try_get_u64(&mut self) -> Result; - pub fn get_fixed_bitset(&mut self, bits: usize) -> Result { - self.copy_to_bytes(bits.div_ceil(8)) - } + fn try_get_i64(&mut self) -> Result; - pub fn put_bool(&mut self, v: bool) { - if v { - self.buffer.put_u8(1); - } else { - self.buffer.put_u8(0); - } - } + fn try_get_f32(&mut self) -> Result; - pub fn put_uuid(&mut self, v: &uuid::Uuid) { - // thats the vanilla way - let pair = v.as_u64_pair(); - self.put_u64(pair.0); - self.put_u64(pair.1); - } + fn try_get_f64(&mut self) -> Result; - pub fn put_string(&mut self, val: &str) { - self.put_string_len(val, i16::MAX as i32); - } + fn try_copy_to_bytes(&mut self, len: usize) -> Result; - pub fn put_string_len(&mut self, val: &str, max_size: i32) { - if val.len() as i32 > max_size { - // Should be panic?, I mean its our fault - panic!("String is too big"); - } - self.put_var_int(&val.len().into()); - self.buffer.put(val.as_bytes()); - } + fn try_copy_to_slice(&mut self, dst: &mut [u8]) -> Result<(), ReadingError>; - pub fn put_string_array(&mut self, array: &[String]) { - for string in array { - self.put_string(string) - } - } + fn try_get_var_int(&mut self) -> Result; - pub fn put_var_int(&mut self, value: &VarInt) { - value.encode(&mut self.buffer); - } + fn try_get_var_long(&mut self) -> Result; - pub fn put_bit_set(&mut self, set: &BitSet) { - self.put_var_int(&set.0); - for b in set.1 { - self.put_i64(*b); - } - } + fn try_get_identifer(&mut self) -> Result; + + fn try_get_string(&mut self) -> Result; + + fn try_get_string_len(&mut self, max_size: usize) -> Result; /// Reads a boolean. If true, the closure is called, and the returned value is /// wrapped in Some. Otherwise, this returns None. - pub fn get_option( + fn try_get_option( &mut self, - val: impl FnOnce(&mut Self) -> Result, - ) -> Result, DeserializerError> { - if self.get_bool()? { - Ok(Some(val(self)?)) - } else { - Ok(None) - } - } - /// Writes `true` if the option is Some, or `false` if None. If the option is - /// some, then it also calls the `write` closure. - pub fn put_option(&mut self, val: &Option, write: impl FnOnce(&mut Self, &T)) { - self.put_bool(val.is_some()); - if let Some(v) = val { - write(self, v) - } - } + val: impl FnOnce(&mut Self) -> Result, + ) -> Result, ReadingError>; - pub fn get_list( + fn get_list( &mut self, - val: impl Fn(&mut Self) -> Result, - ) -> Result, DeserializerError> { - let len = self.get_var_int()?.0 as usize; - let mut list = Vec::with_capacity(len); - for _ in 0..len { - list.push(val(self)?); - } - Ok(list) + val: impl Fn(&mut Self) -> Result, + ) -> Result, ReadingError>; + + fn try_get_uuid(&mut self) -> Result; + + fn try_get_fixed_bitset(&mut self, bits: usize) -> Result; +} + +impl ByteBuf for T { + fn try_get_bool(&mut self) -> Result { + Ok(self.try_get_u8()? != 0) } - /// Writes a list to the buffer. - pub fn put_list(&mut self, list: &[T], write: impl Fn(&mut Self, &T)) { - self.put_var_int(&list.len().into()); - for v in list { - write(self, v); + + fn try_get_u8(&mut self) -> Result { + if size_of::() <= self.remaining() { + Ok(self.get_u8()) + } else { + Err(ReadingError::EOF("u8".to_string())) } } - pub fn put_varint_arr(&mut self, v: &[i32]) { - self.put_list(v, |p, &v| p.put_var_int(&v.into())) + fn try_get_i8(&mut self) -> Result { + if size_of::() <= self.remaining() { + Ok(self.get_i8()) + } else { + Err(ReadingError::EOF("i8".to_string())) + } } - pub fn buf(&mut self) -> &mut BytesMut { - &mut self.buffer + fn try_get_u16(&mut self) -> Result { + if size_of::() <= self.remaining() { + Ok(self.get_u16()) + } else { + Err(ReadingError::EOF("u16".to_string())) + } } - // Trait equivalents - pub fn get_u8(&mut self) -> Result { - if self.buffer.has_remaining() { - Ok(self.buffer.get_u8()) + fn try_get_i16(&mut self) -> Result { + if size_of::() <= self.remaining() { + Ok(self.get_i16()) } else { - Err(DeserializerError::Message( - "No bytes left to consume".to_string(), - )) + Err(ReadingError::EOF("i16".to_string())) } } - pub fn get_i8(&mut self) -> Result { - if self.buffer.has_remaining() { - Ok(self.buffer.get_i8()) + fn try_get_u32(&mut self) -> Result { + if size_of::() <= self.remaining() { + Ok(self.get_u32()) } else { - Err(DeserializerError::Message( - "No bytes left to consume".to_string(), - )) + Err(ReadingError::EOF("u32".to_string())) } } - pub fn get_u16(&mut self) -> Result { - if self.buffer.remaining() >= 2 { - Ok(self.buffer.get_u16()) + fn try_get_i32(&mut self) -> Result { + if size_of::() <= self.remaining() { + Ok(self.get_i32()) } else { - Err(DeserializerError::Message( - "Less than 2 bytes left to consume".to_string(), - )) + Err(ReadingError::EOF("i32".to_string())) } } - pub fn get_i16(&mut self) -> Result { - if self.buffer.remaining() >= 2 { - Ok(self.buffer.get_i16()) + fn try_get_u64(&mut self) -> Result { + if size_of::() <= self.remaining() { + Ok(self.get_u64()) } else { - Err(DeserializerError::Message( - "Less than 2 bytes left to consume".to_string(), - )) + Err(ReadingError::EOF("u64".to_string())) } } - pub fn get_u32(&mut self) -> Result { - if self.buffer.remaining() >= 4 { - Ok(self.buffer.get_u32()) + fn try_get_i64(&mut self) -> Result { + if size_of::() <= self.remaining() { + Ok(self.get_i64()) } else { - Err(DeserializerError::Message( - "Less than 4 bytes left to consume".to_string(), - )) + Err(ReadingError::EOF("i64".to_string())) } } - pub fn get_i32(&mut self) -> Result { - if self.buffer.remaining() >= 4 { - Ok(self.buffer.get_i32()) + fn try_get_f32(&mut self) -> Result { + if size_of::() <= self.remaining() { + Ok(self.get_f32()) } else { - Err(DeserializerError::Message( - "Less than 4 bytes left to consume".to_string(), - )) + Err(ReadingError::EOF("f32".to_string())) } } - pub fn get_u64(&mut self) -> Result { - if self.buffer.remaining() >= 8 { - Ok(self.buffer.get_u64()) + fn try_get_f64(&mut self) -> Result { + if size_of::() <= self.remaining() { + Ok(self.get_f64()) } else { - Err(DeserializerError::Message( - "Less than 8 bytes left to consume".to_string(), - )) + Err(ReadingError::EOF("f64".to_string())) } } - pub fn get_i64(&mut self) -> Result { - if self.buffer.remaining() >= 8 { - Ok(self.buffer.get_i64()) + fn try_copy_to_bytes(&mut self, len: usize) -> Result { + if self.remaining() >= len { + Ok(self.copy_to_bytes(len)) } else { - Err(DeserializerError::Message( - "Less than 8 bytes left to consume".to_string(), - )) + Err(ReadingError::Message("Unable to copy bytes".to_string())) } } - pub fn get_f32(&mut self) -> Result { - if self.buffer.remaining() >= 4 { - Ok(self.buffer.get_f32()) + fn try_copy_to_slice(&mut self, dst: &mut [u8]) -> Result<(), ReadingError> { + if self.remaining() >= dst.len() { + self.copy_to_slice(dst); + Ok(()) } else { - Err(DeserializerError::Message( - "Less than 4 bytes left to consume".to_string(), - )) + Err(ReadingError::Message("Unable to copy slice".to_string())) } } - pub fn get_f64(&mut self) -> Result { - if self.buffer.remaining() >= 8 { - Ok(self.buffer.get_f64()) - } else { - Err(DeserializerError::Message( - "Less than 8 bytes left to consume".to_string(), - )) + fn try_get_var_int(&mut self) -> Result { + match VarInt::decode(self) { + Ok(var_int) => Ok(var_int), + Err(error) => match error { + DecodeError::Incomplete => Err(ReadingError::Incomplete("varint".to_string())), + DecodeError::TooLarge => Err(ReadingError::TooLarge("varint".to_string())), + }, + } + } + fn try_get_var_long(&mut self) -> Result { + match VarLong::decode(self) { + Ok(var_long) => Ok(var_long), + Err(error) => match error { + DecodeError::Incomplete => Err(ReadingError::Incomplete("varint".to_string())), + DecodeError::TooLarge => Err(ReadingError::TooLarge("varlong".to_string())), + }, } } - // TODO: SerializerError? - pub fn put_u8(&mut self, n: u8) { - self.buffer.put_u8(n) + fn try_get_string(&mut self) -> Result { + self.try_get_string_len(i16::MAX as usize) } - pub fn put_i8(&mut self, n: i8) { - self.buffer.put_i8(n) + fn try_get_string_len(&mut self, max_size: usize) -> Result { + let size = self.try_get_var_int()?.0; + if size as usize > max_size { + return Err(ReadingError::TooLarge("string".to_string())); + } + + let data = self.try_copy_to_bytes(size as usize)?; + if data.len() > max_size { + return Err(ReadingError::TooLarge("string".to_string())); + } + match str::from_utf8(&data) { + Ok(string_result) => Ok(string_result.to_string()), + Err(e) => Err(ReadingError::Message(e.to_string())), + } } - pub fn put_u16(&mut self, n: u16) { - self.buffer.put_u16(n) + fn try_get_option( + &mut self, + val: impl FnOnce(&mut Self) -> Result, + ) -> Result, ReadingError> { + if self.try_get_bool()? { + Ok(Some(val(self)?)) + } else { + Ok(None) + } } - pub fn put_i16(&mut self, n: i16) { - self.buffer.put_i16(n) + fn get_list( + &mut self, + val: impl Fn(&mut Self) -> Result, + ) -> Result, ReadingError> { + let len = self.try_get_var_int()?.0 as usize; + let mut list = Vec::with_capacity(len); + for _ in 0..len { + list.push(val(self)?); + } + Ok(list) } - pub fn put_u32(&mut self, n: u32) { - self.buffer.put_u32(n) + fn try_get_uuid(&mut self) -> Result { + let mut bytes = [0u8; 16]; + self.try_copy_to_slice(&mut bytes)?; + Ok(uuid::Uuid::from_slice(&bytes).expect("Failed to parse UUID")) } - pub fn put_i32(&mut self, n: i32) { - self.buffer.put_i32(n) + fn try_get_fixed_bitset(&mut self, bits: usize) -> Result { + self.try_copy_to_bytes(bits.div_ceil(8)) } - pub fn put_u64(&mut self, n: u64) { - self.buffer.put_u64(n) + fn try_get_identifer(&mut self) -> Result { + match Identifier::decode(self) { + Ok(identifer) => Ok(identifer), + Err(error) => match error { + DecodeError::Incomplete => Err(ReadingError::Incomplete("identifer".to_string())), + DecodeError::TooLarge => Err(ReadingError::TooLarge("identifer".to_string())), + }, + } } +} + +pub trait ByteBufMut { + fn put_bool(&mut self, v: bool); + + fn put_uuid(&mut self, v: &uuid::Uuid); + + fn put_string(&mut self, val: &str); + + fn put_string_len(&mut self, val: &str, max_size: usize); - pub fn put_i64(&mut self, n: i64) { - self.buffer.put_i64(n) + fn put_string_array(&mut self, array: &[String]); + + fn put_bit_set(&mut self, set: &BitSet); + + /// Writes `true` if the option is Some, or `false` if None. If the option is + /// some, then it also calls the `write` closure. + fn put_option(&mut self, val: &Option, write: impl FnOnce(&mut Self, &G)); + + fn put_list(&mut self, list: &[G], write: impl Fn(&mut Self, &G)); + + fn put_identifier(&mut self, val: &Identifier); + + fn put_var_int(&mut self, value: &VarInt); + + fn put_varint_arr(&mut self, v: &[i32]); +} + +impl ByteBufMut for T { + fn put_bool(&mut self, v: bool) { + if v { + self.put_u8(1); + } else { + self.put_u8(0); + } } - pub fn put_f32(&mut self, n: f32) { - self.buffer.put_f32(n) + fn put_uuid(&mut self, v: &uuid::Uuid) { + // thats the vanilla way + let pair = v.as_u64_pair(); + self.put_u64(pair.0); + self.put_u64(pair.1); } - pub fn put_f64(&mut self, n: f64) { - self.buffer.put_f64(n) + fn put_string(&mut self, val: &str) { + self.put_string_len(val, i16::MAX as usize); } - pub fn copy_to_bytes(&mut self, len: usize) -> Result { - if self.buffer.len() >= len { - Ok(self.buffer.copy_to_bytes(len)) - } else { - Err(DeserializerError::Message( - "Unable to copy bytes".to_string(), - )) + fn put_string_len(&mut self, val: &str, max_size: usize) { + if val.len() > max_size { + // Should be panic?, I mean its our fault + panic!("String is too big"); } + self.put_var_int(&val.len().into()); + self.put(val.as_bytes()); } - pub fn copy_to_slice(&mut self, dst: &mut [u8]) -> Result<(), DeserializerError> { - if self.buffer.remaining() >= dst.len() { - self.buffer.copy_to_slice(dst); - Ok(()) - } else { - Err(DeserializerError::Message( - "Unable to copy slice".to_string(), - )) + fn put_string_array(&mut self, array: &[String]) { + for string in array { + self.put_string(string) } } - pub fn put_slice(&mut self, src: &[u8]) { - self.buffer.put_slice(src) + fn put_var_int(&mut self, var_int: &VarInt) { + var_int.encode(self); } - pub fn put(&mut self, src: T) - where - Self: Sized, - { - self.buffer.put(src) + fn put_bit_set(&mut self, bit_set: &BitSet) { + bit_set.encode(self); } - pub fn reserve(&mut self, additional: usize) { - self.buffer.reserve(additional) + fn put_option(&mut self, val: &Option, write: impl FnOnce(&mut Self, &G)) { + self.put_bool(val.is_some()); + if let Some(v) = val { + write(self, v) + } + } + + fn put_list(&mut self, list: &[G], write: impl Fn(&mut Self, &G)) { + self.put_var_int(&list.len().into()); + for v in list { + write(self, v); + } + } + + fn put_varint_arr(&mut self, v: &[i32]) { + self.put_list(v, |p, &v| p.put_var_int(&v.into())) } - pub fn get_slice(&mut self) -> BytesMut { - self.buffer.split() + fn put_identifier(&mut self, val: &Identifier) { + val.encode(self); } } #[cfg(test)] mod test { + use bytes::{Bytes, BytesMut}; use serde::{Deserialize, Serialize}; use crate::{ - bytebuf::{deserializer, serializer, ByteBuffer}, + bytebuf::{deserializer, serializer}, VarInt, }; @@ -381,12 +377,12 @@ mod test { bar: i32, } let foo = Foo { bar: 69 }; - let mut serializer = serializer::Serializer::new(ByteBuffer::empty()); + let mut bytes = BytesMut::new(); + let mut serializer = serializer::Serializer::new(&mut bytes); foo.serialize(&mut serializer).unwrap(); - let mut serialized: ByteBuffer = serializer.into(); let deserialized: Foo = - Foo::deserialize(deserializer::Deserializer::new(&mut serialized)).unwrap(); + Foo::deserialize(deserializer::Deserializer::new(&mut Bytes::from(bytes))).unwrap(); assert_eq!(foo, deserialized); } @@ -398,12 +394,12 @@ mod test { bar: VarInt, } let foo = Foo { bar: 69.into() }; - let mut serializer = serializer::Serializer::new(ByteBuffer::empty()); + let mut bytes = BytesMut::new(); + let mut serializer = serializer::Serializer::new(&mut bytes); foo.serialize(&mut serializer).unwrap(); - let mut serialized: ByteBuffer = serializer.into(); let deserialized: Foo = - Foo::deserialize(deserializer::Deserializer::new(&mut serialized)).unwrap(); + Foo::deserialize(deserializer::Deserializer::new(&mut Bytes::from(bytes))).unwrap(); assert_eq!(foo, deserialized); } diff --git a/pumpkin-protocol/src/bytebuf/packet_id.rs b/pumpkin-protocol/src/bytebuf/packet_id.rs index 9f66e67d..6534033e 100644 --- a/pumpkin-protocol/src/bytebuf/packet_id.rs +++ b/pumpkin-protocol/src/bytebuf/packet_id.rs @@ -1,134 +1,9 @@ -use bytes::BufMut; -use serde::{ - de::{self, DeserializeOwned, SeqAccess, Visitor}, - Deserialize, Deserializer, Serialize, Serializer, -}; +use bytes::{Buf, BufMut}; +use serde::{de::DeserializeOwned, Serialize}; -use crate::{BitSet, ClientPacket, ServerPacket, VarInt, VarIntType, VarLong}; +use crate::{codec::var_int::VarIntType, ClientPacket, ServerPacket}; -use super::{deserializer, serializer, ByteBuffer, DeserializerError}; - -impl Serialize for BitSet<'_> { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - // TODO: make this right - (&self.0, self.1).serialize(serializer) - } -} - -impl Serialize for VarInt { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let mut value = self.0 as u32; - let mut buf = Vec::new(); - - while value > 0x7F { - buf.put_u8(value as u8 | 0x80); - value >>= 7; - } - - buf.put_u8(value as u8); - - serializer.serialize_bytes(&buf) - } -} - -impl<'de> Deserialize<'de> for VarInt { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - struct VarIntVisitor; - - impl<'de> Visitor<'de> for VarIntVisitor { - type Value = VarInt; - - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("a valid VarInt encoded in a byte sequence") - } - - fn visit_seq(self, mut seq: A) -> Result - where - A: SeqAccess<'de>, - { - let mut val = 0; - for i in 0..VarInt::MAX_SIZE { - if let Some(byte) = seq.next_element::()? { - val |= (i32::from(byte) & 0b01111111) << (i * 7); - if byte & 0b10000000 == 0 { - return Ok(VarInt(val)); - } - } else { - break; - } - } - Err(de::Error::custom("VarInt was too large")) - } - } - - deserializer.deserialize_seq(VarIntVisitor) - } -} - -impl Serialize for VarLong { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let mut value = self.0 as u64; - let mut buf = Vec::new(); - - while value > 0x7F { - buf.put_u8(value as u8 | 0x80); - value >>= 7; - } - - buf.put_u8(value as u8); - - serializer.serialize_bytes(&buf) - } -} - -impl<'de> Deserialize<'de> for VarLong { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - struct VarLongVisitor; - - impl<'de> Visitor<'de> for VarLongVisitor { - type Value = VarLong; - - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("a valid VarInt encoded in a byte sequence") - } - - fn visit_seq(self, mut seq: A) -> Result - where - A: SeqAccess<'de>, - { - let mut val = 0; - for i in 0..VarLong::MAX_SIZE { - if let Some(byte) = seq.next_element::()? { - val |= (i64::from(byte) & 0b01111111) << (i * 7); - if byte & 0b10000000 == 0 { - return Ok(VarLong(val)); - } - } else { - break; - } - } - Err(de::Error::custom("VarInt was too large")) - } - } - - deserializer.deserialize_seq(VarLongVisitor) - } -} +use super::{deserializer, serializer, ReadingError}; pub trait Packet { const PACKET_ID: VarIntType; @@ -138,14 +13,10 @@ impl

ClientPacket for P where P: Packet + Serialize, { - fn write(&self, bytebuf: &mut ByteBuffer) { - let mut serializer = serializer::Serializer::new(ByteBuffer::empty()); + fn write(&self, bytebuf: &mut impl BufMut) { + let mut serializer = serializer::Serializer::new(bytebuf); self.serialize(&mut serializer) .expect("Could not serialize packet"); - // We write the packet in an empty bytebuffer and then put it into our current one. - // In the future we may do packet batching thats the reason i don't let every packet create a new bytebuffer and use - // an existing instead - bytebuf.put(serializer.output.buf()); } } @@ -153,7 +24,7 @@ impl

ServerPacket for P where P: Packet + DeserializeOwned, { - fn read(bytebuf: &mut ByteBuffer) -> Result { + fn read(bytebuf: &mut impl Buf) -> Result { let deserializer = deserializer::Deserializer::new(bytebuf); P::deserialize(deserializer) } diff --git a/pumpkin-protocol/src/bytebuf/serializer.rs b/pumpkin-protocol/src/bytebuf/serializer.rs index d683bb03..5682aa7b 100644 --- a/pumpkin-protocol/src/bytebuf/serializer.rs +++ b/pumpkin-protocol/src/bytebuf/serializer.rs @@ -1,41 +1,24 @@ use std::fmt::Display; +use bytes::BufMut; use serde::{ ser::{self}, Serialize, }; use thiserror::Error; -use super::ByteBuffer; +use super::ByteBufMut; -pub struct Serializer { - pub output: ByteBuffer, +pub struct Serializer { + pub output: B, } -impl Serializer { - pub fn new(buf: ByteBuffer) -> Self { +impl Serializer { + pub fn new(buf: B) -> Self { Self { output: buf } } } -impl From for ByteBuffer { - fn from(val: Serializer) -> Self { - val.output - } -} - -impl AsRef for Serializer { - fn as_ref(&self) -> &ByteBuffer { - &self.output - } -} - -impl AsMut for Serializer { - fn as_mut(&mut self) -> &mut ByteBuffer { - &mut self.output - } -} - #[derive(Debug, Error)] pub enum SerializerError { #[error("serializer error {0}")] @@ -56,7 +39,7 @@ impl ser::Error for SerializerError { // Structs are ignored // Iterables' values are written in order, but NO information (e.g. size) about the // iterable itself is written (list sizes should be a separate field) -impl ser::Serializer for &mut Serializer { +impl ser::Serializer for &mut Serializer { type Ok = (); type Error = SerializerError; @@ -226,7 +209,7 @@ impl ser::Serializer for &mut Serializer { } } -impl ser::SerializeSeq for &mut Serializer { +impl ser::SerializeSeq for &mut Serializer { // Must match the `Ok` type of the serializer. type Ok = (); // Must match the `Error` type of the serializer. @@ -246,7 +229,7 @@ impl ser::SerializeSeq for &mut Serializer { } } -impl ser::SerializeTuple for &mut Serializer { +impl ser::SerializeTuple for &mut Serializer { type Ok = (); type Error = SerializerError; @@ -263,7 +246,7 @@ impl ser::SerializeTuple for &mut Serializer { } // Same thing but for tuple structs. -impl ser::SerializeTupleStruct for &mut Serializer { +impl ser::SerializeTupleStruct for &mut Serializer { type Ok = (); type Error = SerializerError; @@ -288,7 +271,7 @@ impl ser::SerializeTupleStruct for &mut Serializer { // // So the `end` method in this impl is responsible for closing both the `]` and // the `}`. -impl ser::SerializeTupleVariant for &mut Serializer { +impl ser::SerializeTupleVariant for &mut Serializer { type Ok = (); type Error = SerializerError; @@ -312,7 +295,7 @@ impl ser::SerializeTupleVariant for &mut Serializer { // `serialize_entry` method allows serializers to optimize for the case where // key and value are both available simultaneously. In JSON it doesn't make a // difference so the default behavior for `serialize_entry` is fine. -impl ser::SerializeMap for &mut Serializer { +impl ser::SerializeMap for &mut Serializer { type Ok = (); type Error = SerializerError; @@ -348,7 +331,7 @@ impl ser::SerializeMap for &mut Serializer { // Structs are like maps in which the keys are constrained to be compile-time // constant strings. -impl ser::SerializeStruct for &mut Serializer { +impl ser::SerializeStruct for &mut Serializer { type Ok = (); type Error = SerializerError; @@ -371,7 +354,7 @@ impl ser::SerializeStruct for &mut Serializer { // Similar to `SerializeTupleVariant`, here the `end` method is responsible for // closing both of the curly braces opened by `serialize_struct_variant`. -impl ser::SerializeStructVariant for &mut Serializer { +impl ser::SerializeStructVariant for &mut Serializer { type Ok = (); type Error = SerializerError; diff --git a/pumpkin-protocol/src/client/config/c_cookie_request.rs b/pumpkin-protocol/src/client/config/c_cookie_request.rs index 08bdcedb..71fb9aca 100644 --- a/pumpkin-protocol/src/client/config/c_cookie_request.rs +++ b/pumpkin-protocol/src/client/config/c_cookie_request.rs @@ -1,6 +1,6 @@ use pumpkin_macros::client_packet; -use crate::Identifier; +use crate::codec::identifier::Identifier; #[derive(serde::Serialize)] #[client_packet("config:cookie_request")] diff --git a/pumpkin-protocol/src/client/config/c_known_packs.rs b/pumpkin-protocol/src/client/config/c_known_packs.rs index 80b37580..7622b93e 100644 --- a/pumpkin-protocol/src/client/config/c_known_packs.rs +++ b/pumpkin-protocol/src/client/config/c_known_packs.rs @@ -1,6 +1,7 @@ +use bytes::BufMut; use pumpkin_macros::client_packet; -use crate::{bytebuf::ByteBuffer, ClientPacket, KnownPack}; +use crate::{bytebuf::ByteBufMut, ClientPacket, KnownPack}; #[client_packet("config:select_known_packs")] pub struct CKnownPacks<'a> { @@ -14,7 +15,7 @@ impl<'a> CKnownPacks<'a> { } impl ClientPacket for CKnownPacks<'_> { - fn write(&self, bytebuf: &mut ByteBuffer) { + fn write(&self, bytebuf: &mut impl BufMut) { bytebuf.put_list::(self.known_packs, |p, v| { p.put_string(v.namespace); p.put_string(v.id); diff --git a/pumpkin-protocol/src/client/config/c_registry_data.rs b/pumpkin-protocol/src/client/config/c_registry_data.rs index d346ca45..5a04e682 100644 --- a/pumpkin-protocol/src/client/config/c_registry_data.rs +++ b/pumpkin-protocol/src/client/config/c_registry_data.rs @@ -1,16 +1,16 @@ -use bytes::BytesMut; +use bytes::{BufMut, BytesMut}; use pumpkin_macros::client_packet; -use crate::{bytebuf::ByteBuffer, ClientPacket}; +use crate::{bytebuf::ByteBufMut, codec::identifier::Identifier, ClientPacket}; #[client_packet("config:registry_data")] pub struct CRegistryData<'a> { - registry_id: &'a str, - entries: &'a [RegistryEntry<'a>], + registry_id: &'a Identifier, + entries: &'a [RegistryEntry], } impl<'a> CRegistryData<'a> { - pub fn new(registry_id: &'a str, entries: &'a [RegistryEntry]) -> Self { + pub fn new(registry_id: &'a Identifier, entries: &'a [RegistryEntry]) -> Self { Self { registry_id, entries, @@ -18,18 +18,17 @@ impl<'a> CRegistryData<'a> { } } -pub struct RegistryEntry<'a> { - pub entry_id: &'a str, - pub data: BytesMut, +pub struct RegistryEntry { + pub entry_id: Identifier, + pub data: Option, } impl ClientPacket for CRegistryData<'_> { - fn write(&self, bytebuf: &mut ByteBuffer) { - bytebuf.put_string(self.registry_id); + fn write(&self, bytebuf: &mut impl BufMut) { + bytebuf.put_identifier(self.registry_id); bytebuf.put_list::(self.entries, |p, v| { - p.put_string(v.entry_id); - p.put_bool(!v.data.is_empty()); - p.put_slice(&v.data); + p.put_identifier(&v.entry_id); + p.put_option(&v.data, |p, v| p.put_slice(v)); }); } } diff --git a/pumpkin-protocol/src/client/config/c_server_links.rs b/pumpkin-protocol/src/client/config/c_server_links.rs index b4150d8e..b14547ca 100644 --- a/pumpkin-protocol/src/client/config/c_server_links.rs +++ b/pumpkin-protocol/src/client/config/c_server_links.rs @@ -1,87 +1,16 @@ -use crate::VarInt; -use pumpkin_core::text::TextComponent; +use crate::{Link, VarInt}; use pumpkin_macros::client_packet; -use serde::{Serialize, Serializer}; +use serde::Serialize; #[derive(Serialize)] #[client_packet("config:server_links")] -pub struct CServerLinks<'a> { +pub struct CConfigServerLinks<'a> { links_count: &'a VarInt, links: &'a [Link<'a>], } -impl<'a> CServerLinks<'a> { +impl<'a> CConfigServerLinks<'a> { pub fn new(links_count: &'a VarInt, links: &'a [Link<'a>]) -> Self { Self { links_count, links } } } - -pub enum Label<'a> { - BuiltIn(LinkType), - TextComponent(TextComponent<'a>), -} - -impl Serialize for Label<'_> { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - match self { - Label::BuiltIn(link_type) => link_type.serialize(serializer), - Label::TextComponent(component) => component.serialize(serializer), - } - } -} - -#[derive(Serialize)] -pub struct Link<'a> { - pub is_built_in: bool, - pub label: Label<'a>, - pub url: &'a String, -} - -impl<'a> Link<'a> { - pub fn new(label: Label<'a>, url: &'a String) -> Self { - Self { - is_built_in: match label { - Label::BuiltIn(_) => true, - Label::TextComponent(_) => false, - }, - label, - url, - } - } -} - -pub enum LinkType { - BugReport, - CommunityGuidelines, - Support, - Status, - Feedback, - Community, - Website, - Forums, - News, - Announcements, -} - -impl Serialize for LinkType { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - match self { - LinkType::BugReport => VarInt(0).serialize(serializer), - LinkType::CommunityGuidelines => VarInt(1).serialize(serializer), - LinkType::Support => VarInt(2).serialize(serializer), - LinkType::Status => VarInt(3).serialize(serializer), - LinkType::Feedback => VarInt(4).serialize(serializer), - LinkType::Community => VarInt(5).serialize(serializer), - LinkType::Website => VarInt(6).serialize(serializer), - LinkType::Forums => VarInt(7).serialize(serializer), - LinkType::News => VarInt(8).serialize(serializer), - LinkType::Announcements => VarInt(9).serialize(serializer), - } - } -} diff --git a/pumpkin-protocol/src/client/config/c_store_cookie.rs b/pumpkin-protocol/src/client/config/c_store_cookie.rs index 511bdd0d..0334df2d 100644 --- a/pumpkin-protocol/src/client/config/c_store_cookie.rs +++ b/pumpkin-protocol/src/client/config/c_store_cookie.rs @@ -1,4 +1,4 @@ -use crate::{Identifier, VarInt}; +use crate::{codec::identifier::Identifier, VarInt}; use pumpkin_macros::client_packet; #[derive(serde::Serialize)] diff --git a/pumpkin-protocol/src/client/login/c_cookie_request.rs b/pumpkin-protocol/src/client/login/c_cookie_request.rs index df592625..174e8569 100644 --- a/pumpkin-protocol/src/client/login/c_cookie_request.rs +++ b/pumpkin-protocol/src/client/login/c_cookie_request.rs @@ -1,15 +1,16 @@ -use crate::Identifier; use pumpkin_macros::client_packet; use serde::Serialize; +use crate::codec::identifier::Identifier; + #[derive(Serialize)] #[client_packet("login:cookie_request")] /// Requests a cookie that was previously stored. -pub struct CCookieRequest<'a> { +pub struct CLoginCookieRequest<'a> { key: &'a Identifier, } -impl<'a> CCookieRequest<'a> { +impl<'a> CLoginCookieRequest<'a> { pub fn new(key: &'a Identifier) -> Self { Self { key } } diff --git a/pumpkin-protocol/src/client/login/c_login_success.rs b/pumpkin-protocol/src/client/login/c_login_success.rs index 462fcce5..290ca4c2 100644 --- a/pumpkin-protocol/src/client/login/c_login_success.rs +++ b/pumpkin-protocol/src/client/login/c_login_success.rs @@ -1,6 +1,7 @@ +use bytes::BufMut; use pumpkin_macros::client_packet; -use crate::{bytebuf::ByteBuffer, ClientPacket, Property}; +use crate::{bytebuf::ByteBufMut, ClientPacket, Property}; #[client_packet("login:login_finished")] pub struct CLoginSuccess<'a> { @@ -20,7 +21,7 @@ impl<'a> CLoginSuccess<'a> { } impl ClientPacket for CLoginSuccess<'_> { - fn write(&self, bytebuf: &mut ByteBuffer) { + fn write(&self, bytebuf: &mut impl BufMut) { bytebuf.put_uuid(self.uuid); bytebuf.put_string(self.username); bytebuf.put_list::(self.properties, |p, v| { diff --git a/pumpkin-protocol/src/client/play/c_boss_event.rs b/pumpkin-protocol/src/client/play/c_boss_event.rs index 0100c154..e5b2292d 100644 --- a/pumpkin-protocol/src/client/play/c_boss_event.rs +++ b/pumpkin-protocol/src/client/play/c_boss_event.rs @@ -1,6 +1,7 @@ -use crate::bytebuf::ByteBuffer; +use crate::bytebuf::ByteBufMut; use crate::client::play::bossevent_action::BosseventAction; use crate::{ClientPacket, VarInt}; +use bytes::BufMut; use pumpkin_macros::client_packet; #[client_packet("play:boss_event")] @@ -16,7 +17,7 @@ impl<'a> CBossEvent<'a> { } impl ClientPacket for CBossEvent<'_> { - fn write(&self, bytebuf: &mut ByteBuffer) { + fn write(&self, bytebuf: &mut impl BufMut) { bytebuf.put_uuid(&self.uuid); let action = &self.action; match action { diff --git a/pumpkin-protocol/src/client/play/c_chunk_data.rs b/pumpkin-protocol/src/client/play/c_chunk_data.rs index cd98d1cf..1356508f 100644 --- a/pumpkin-protocol/src/client/play/c_chunk_data.rs +++ b/pumpkin-protocol/src/client/play/c_chunk_data.rs @@ -1,6 +1,6 @@ -use crate::{bytebuf::ByteBuffer, BitSet, ClientPacket, VarInt}; -use itertools::Itertools; +use crate::{bytebuf::ByteBufMut, codec::bit_set::BitSet, ClientPacket, VarInt}; +use bytes::{BufMut, BytesMut}; use pumpkin_macros::client_packet; use pumpkin_world::{chunk::ChunkData, DIRECT_PALETTE_BITS}; @@ -8,7 +8,7 @@ use pumpkin_world::{chunk::ChunkData, DIRECT_PALETTE_BITS}; pub struct CChunkData<'a>(pub &'a ChunkData); impl ClientPacket for CChunkData<'_> { - fn write(&self, buf: &mut crate::bytebuf::ByteBuffer) { + fn write(&self, buf: &mut impl BufMut) { // Chunk X buf.put_i32(self.0.position.x); // Chunk Z @@ -19,14 +19,14 @@ impl ClientPacket for CChunkData<'_> { // Heightmaps buf.put_slice(&heightmap_nbt); - let mut data_buf = ByteBuffer::empty(); + let mut data_buf = BytesMut::new(); self.0.blocks.iter_subchunks().for_each(|chunk| { let block_count = chunk.len() as i16; // Block count data_buf.put_i16(block_count); //// Block states - let palette = chunk.iter().dedup().collect_vec(); + let palette = chunk; // TODO: make dynamic block_size work // TODO: make direct block_size work enum PaletteType { @@ -55,7 +55,7 @@ impl ClientPacket for CChunkData<'_> { palette.iter().for_each(|id| { // Palette - data_buf.put_var_int(&VarInt(**id as i32)); + data_buf.put_var_int(&VarInt(*id as i32)); }); // Data array length let data_array_len = chunk.len().div_ceil(64 / block_size as usize); @@ -67,7 +67,7 @@ impl ClientPacket for CChunkData<'_> { for block in block_clump.iter().rev() { let index = palette .iter() - .position(|b| *b == block) + .position(|b| b == block) .expect("Its just got added, ofc it should be there"); out_long = out_long << block_size | (index as i64); } @@ -103,9 +103,9 @@ impl ClientPacket for CChunkData<'_> { }); // Size - buf.put_var_int(&VarInt(data_buf.buf().len() as i32)); + buf.put_var_int(&VarInt(data_buf.len() as i32)); // Data - buf.put_slice(data_buf.buf()); + buf.put_slice(&data_buf); // TODO: block entities buf.put_var_int(&VarInt(0)); @@ -113,13 +113,13 @@ impl ClientPacket for CChunkData<'_> { // Sky Light Mask // All of the chunks, this is not optimal and uses way more data than needed but will be // overhauled with full lighting system. - buf.put_bit_set(&BitSet(VarInt(1), &[0b01111111111111111111111110])); + buf.put_bit_set(&BitSet(VarInt(1), vec![0b01111111111111111111111110])); // Block Light Mask - buf.put_bit_set(&BitSet(VarInt(1), &[0])); + buf.put_bit_set(&BitSet(VarInt(1), vec![0])); // Empty Sky Light Mask - buf.put_bit_set(&BitSet(VarInt(1), &[0b0])); + buf.put_bit_set(&BitSet(VarInt(1), vec![0b0])); // Empty Block Light Mask - buf.put_bit_set(&BitSet(VarInt(1), &[0])); + buf.put_bit_set(&BitSet(VarInt(1), vec![0])); buf.put_var_int(&VarInt(self.0.blocks.subchunks_len() as i32)); self.0.blocks.iter_subchunks().for_each(|chunk| { diff --git a/pumpkin-protocol/src/client/play/c_command_suggestions.rs b/pumpkin-protocol/src/client/play/c_command_suggestions.rs index fae9b04f..25ae6683 100644 --- a/pumpkin-protocol/src/client/play/c_command_suggestions.rs +++ b/pumpkin-protocol/src/client/play/c_command_suggestions.rs @@ -1,7 +1,8 @@ +use bytes::BufMut; use pumpkin_core::text::TextComponent; use pumpkin_macros::client_packet; -use crate::{ClientPacket, VarInt}; +use crate::{bytebuf::ByteBufMut, ClientPacket, VarInt}; #[client_packet("play:command_suggestions")] pub struct CCommandSuggestions<'a> { @@ -28,7 +29,7 @@ impl<'a> CCommandSuggestions<'a> { } impl ClientPacket for CCommandSuggestions<'_> { - fn write(&self, bytebuf: &mut crate::bytebuf::ByteBuffer) { + fn write(&self, bytebuf: &mut impl BufMut) { bytebuf.put_var_int(&self.id); bytebuf.put_var_int(&self.start); bytebuf.put_var_int(&self.length); diff --git a/pumpkin-protocol/src/client/play/c_commands.rs b/pumpkin-protocol/src/client/play/c_commands.rs index bfd88556..264a5133 100644 --- a/pumpkin-protocol/src/client/play/c_commands.rs +++ b/pumpkin-protocol/src/client/play/c_commands.rs @@ -1,6 +1,7 @@ +use bytes::BufMut; use pumpkin_macros::client_packet; -use crate::{bytebuf::ByteBuffer, ClientPacket, VarInt}; +use crate::{bytebuf::ByteBufMut, ClientPacket, VarInt}; #[client_packet("play:commands")] pub struct CCommands<'a> { @@ -18,7 +19,7 @@ impl<'a> CCommands<'a> { } impl ClientPacket for CCommands<'_> { - fn write(&self, bytebuf: &mut ByteBuffer) { + fn write(&self, bytebuf: &mut impl BufMut) { bytebuf.put_list(&self.nodes, |bytebuf, node: &ProtoNode| { node.write_to(bytebuf) }); @@ -51,7 +52,7 @@ impl ProtoNode<'_> { const FLAG_HAS_REDIRECT: u8 = 8; const FLAG_HAS_SUGGESTION_TYPE: u8 = 16; - pub fn write_to(&self, bytebuf: &mut ByteBuffer) { + pub fn write_to(&self, bytebuf: &mut impl BufMut) { // flags let flags = match self.node_type { ProtoNodeType::Root => 0, @@ -187,7 +188,7 @@ impl ProtoCmdArgParser<'_> { pub const SCORE_HOLDER_FLAG_ALLOW_MULTIPLE: u8 = 1; - pub fn write_to_buffer(&self, bytebuf: &mut ByteBuffer) { + pub fn write_to_buffer(&self, bytebuf: &mut impl BufMut) { match self { Self::Bool => bytebuf.put_var_int(&0.into()), Self::Float { min, max } => Self::write_number_arg(&1.into(), *min, *max, bytebuf), @@ -269,7 +270,7 @@ impl ProtoCmdArgParser<'_> { id: &VarInt, min: Option, max: Option, - bytebuf: &mut ByteBuffer, + bytebuf: &mut impl BufMut, ) { let mut flags: u8 = 0; if min.is_some() { @@ -290,13 +291,13 @@ impl ProtoCmdArgParser<'_> { } } - fn write_with_flags(id: &VarInt, flags: u8, bytebuf: &mut ByteBuffer) { + fn write_with_flags(id: &VarInt, flags: u8, bytebuf: &mut impl BufMut) { bytebuf.put_var_int(id); bytebuf.put_u8(flags); } - fn write_with_identifier(id: &VarInt, extra_identifier: &str, bytebuf: &mut ByteBuffer) { + fn write_with_identifier(id: &VarInt, extra_identifier: &str, bytebuf: &mut impl BufMut) { bytebuf.put_var_int(id); bytebuf.put_string(extra_identifier); @@ -312,29 +313,29 @@ pub enum StringProtoArgBehavior { } trait NumberCmdArg { - fn write(self, bytebuf: &mut ByteBuffer); + fn write(self, bytebuf: &mut impl BufMut); } impl NumberCmdArg for f32 { - fn write(self, bytebuf: &mut ByteBuffer) { + fn write(self, bytebuf: &mut impl BufMut) { bytebuf.put_f32(self); } } impl NumberCmdArg for f64 { - fn write(self, bytebuf: &mut ByteBuffer) { + fn write(self, bytebuf: &mut impl BufMut) { bytebuf.put_f64(self); } } impl NumberCmdArg for i32 { - fn write(self, bytebuf: &mut ByteBuffer) { + fn write(self, bytebuf: &mut impl BufMut) { bytebuf.put_i32(self); } } impl NumberCmdArg for i64 { - fn write(self, bytebuf: &mut ByteBuffer) { + fn write(self, bytebuf: &mut impl BufMut) { bytebuf.put_i64(self); } } diff --git a/pumpkin-protocol/src/client/play/c_cookie_request.rs b/pumpkin-protocol/src/client/play/c_cookie_request.rs index 58d49494..fb9bc806 100644 --- a/pumpkin-protocol/src/client/play/c_cookie_request.rs +++ b/pumpkin-protocol/src/client/play/c_cookie_request.rs @@ -1,15 +1,16 @@ -use crate::Identifier; use pumpkin_macros::client_packet; use serde::Serialize; +use crate::codec::identifier::Identifier; + #[derive(Serialize)] #[client_packet("play:cookie_request")] /// Requests a cookie that was previously stored. -pub struct CCookieRequest<'a> { +pub struct CPlayCookieRequest<'a> { key: &'a Identifier, } -impl<'a> CCookieRequest<'a> { +impl<'a> CPlayCookieRequest<'a> { pub fn new(key: &'a Identifier) -> Self { Self { key } } diff --git a/pumpkin-protocol/src/client/play/c_entity_sound_effect.rs b/pumpkin-protocol/src/client/play/c_entity_sound_effect.rs index 62afd46d..27de764f 100644 --- a/pumpkin-protocol/src/client/play/c_entity_sound_effect.rs +++ b/pumpkin-protocol/src/client/play/c_entity_sound_effect.rs @@ -1,6 +1,7 @@ +use bytes::BufMut; use pumpkin_macros::client_packet; -use crate::{ClientPacket, IDOrSoundEvent, SoundCategory, SoundEvent, VarInt}; +use crate::{bytebuf::ByteBufMut, ClientPacket, IDOrSoundEvent, SoundCategory, SoundEvent, VarInt}; #[client_packet("play:sound_entity")] pub struct CEntitySoundEffect { @@ -38,11 +39,11 @@ impl CEntitySoundEffect { } impl ClientPacket for CEntitySoundEffect { - fn write(&self, bytebuf: &mut crate::bytebuf::ByteBuffer) { + fn write(&self, bytebuf: &mut impl BufMut) { bytebuf.put_var_int(&self.sound_event.id); if self.sound_event.id.0 == 0 { if let Some(test) = &self.sound_event.sound_event { - bytebuf.put_string(&test.sound_name); + bytebuf.put_identifier(&test.sound_name); bytebuf.put_option(&test.range, |p, v| { p.put_f32(*v); diff --git a/pumpkin-protocol/src/client/play/c_initialize_world_border.rs b/pumpkin-protocol/src/client/play/c_initialize_world_border.rs index f4d6663a..f2ab72f6 100644 --- a/pumpkin-protocol/src/client/play/c_initialize_world_border.rs +++ b/pumpkin-protocol/src/client/play/c_initialize_world_border.rs @@ -1,7 +1,7 @@ use pumpkin_macros::client_packet; use serde::Serialize; -use crate::{VarInt, VarLong}; +use crate::{codec::var_long::VarLong, VarInt}; #[derive(Serialize)] #[client_packet("play:initialize_border")] diff --git a/pumpkin-protocol/src/client/play/c_login.rs b/pumpkin-protocol/src/client/play/c_login.rs index 12a594f8..ec6e23bb 100644 --- a/pumpkin-protocol/src/client/play/c_login.rs +++ b/pumpkin-protocol/src/client/play/c_login.rs @@ -3,7 +3,7 @@ use pumpkin_core::math::position::WorldPosition; use pumpkin_macros::client_packet; use serde::Serialize; -use crate::VarInt; +use crate::{codec::identifier::Identifier, VarInt}; #[derive(Serialize)] #[client_packet("play:login")] @@ -11,7 +11,7 @@ pub struct CLogin<'a> { entity_id: i32, is_hardcore: bool, dimension_count: VarInt, - dimension_names: &'a [&'a str], + dimension_names: &'a [Identifier], max_players: VarInt, view_distance: VarInt, simulated_distance: VarInt, @@ -20,13 +20,13 @@ pub struct CLogin<'a> { limited_crafting: bool, // Spawn Info dimension_type: VarInt, - dimension_name: &'a str, + dimension_name: Identifier, hashed_seed: i64, game_mode: u8, previous_gamemode: i8, debug: bool, is_flat: bool, - death_dimension_name: Option<(&'a str, WorldPosition)>, + death_dimension_name: Option<(Identifier, WorldPosition)>, portal_cooldown: VarInt, sealevel: VarInt, enforce_secure_chat: bool, @@ -37,7 +37,7 @@ impl<'a> CLogin<'a> { pub fn new( entity_id: i32, is_hardcore: bool, - dimension_names: &'a [&'a str], + dimension_names: &'a [Identifier], max_players: VarInt, view_distance: VarInt, simulated_distance: VarInt, @@ -45,13 +45,13 @@ impl<'a> CLogin<'a> { enabled_respawn_screen: bool, limited_crafting: bool, dimension_type: VarInt, - dimension_name: &'a str, + dimension_name: Identifier, hashed_seed: i64, game_mode: u8, previous_gamemode: i8, debug: bool, is_flat: bool, - death_dimension_name: Option<(&'a str, WorldPosition)>, + death_dimension_name: Option<(Identifier, WorldPosition)>, portal_cooldown: VarInt, sealevel: VarInt, enforce_secure_chat: bool, diff --git a/pumpkin-protocol/src/client/play/c_player_chat_message.rs b/pumpkin-protocol/src/client/play/c_player_chat_message.rs index b774a2f2..0db6c3df 100644 --- a/pumpkin-protocol/src/client/play/c_player_chat_message.rs +++ b/pumpkin-protocol/src/client/play/c_player_chat_message.rs @@ -3,7 +3,7 @@ use pumpkin_core::text::TextComponent; use pumpkin_macros::client_packet; use serde::Serialize; -use crate::{BitSet, VarInt}; +use crate::{codec::bit_set::BitSet, VarInt}; #[derive(Serialize)] #[client_packet("play:player_chat")] @@ -18,7 +18,7 @@ pub struct CPlayerChatMessage<'a> { previous_messages_count: VarInt, previous_messages: &'a [PreviousMessage<'a>], // max 20 unsigned_content: Option>, - filter_type: FilterType<'a>, + filter_type: FilterType, chat_type: VarInt, sender_name: TextComponent<'a>, target_name: Option>, @@ -35,7 +35,7 @@ impl<'a> CPlayerChatMessage<'a> { salt: i64, previous_messages: &'a [PreviousMessage<'a>], unsigned_content: Option>, - filter_type: FilterType<'a>, + filter_type: FilterType, chat_type: VarInt, sender_name: TextComponent<'a>, target_name: Option>, @@ -66,11 +66,11 @@ pub struct PreviousMessage<'a> { #[derive(Serialize)] #[repr(i32)] -pub enum FilterType<'a> { +pub enum FilterType { /// Message is not filtered at all PassThrough = 0, /// Message is fully filtered FullyFiltered = 1, /// Only some characters in the message are filtered - PartiallyFiltered(BitSet<'a>) = 2, + PartiallyFiltered(BitSet) = 2, } diff --git a/pumpkin-protocol/src/client/play/c_player_info_update.rs b/pumpkin-protocol/src/client/play/c_player_info_update.rs index 09fbff94..72d912cb 100644 --- a/pumpkin-protocol/src/client/play/c_player_info_update.rs +++ b/pumpkin-protocol/src/client/play/c_player_info_update.rs @@ -1,6 +1,7 @@ +use bytes::BufMut; use pumpkin_macros::client_packet; -use crate::{bytebuf::ByteBuffer, ClientPacket, Property}; +use crate::{bytebuf::ByteBufMut, ClientPacket, Property}; use super::PlayerAction; @@ -22,7 +23,7 @@ impl<'a> CPlayerInfoUpdate<'a> { } impl ClientPacket for CPlayerInfoUpdate<'_> { - fn write(&self, bytebuf: &mut ByteBuffer) { + fn write(&self, bytebuf: &mut impl BufMut) { bytebuf.put_i8(self.actions); bytebuf.put_list::(self.players, |p, v| { p.put_uuid(&v.uuid); diff --git a/pumpkin-protocol/src/client/play/c_player_position.rs b/pumpkin-protocol/src/client/play/c_player_position.rs index 0a99a9e0..287c1b2f 100644 --- a/pumpkin-protocol/src/client/play/c_player_position.rs +++ b/pumpkin-protocol/src/client/play/c_player_position.rs @@ -1,7 +1,8 @@ +use bytes::BufMut; use pumpkin_core::math::vector3::Vector3; use pumpkin_macros::client_packet; -use crate::{ClientPacket, PositionFlag, VarInt}; +use crate::{bytebuf::ByteBufMut, ClientPacket, PositionFlag, VarInt}; #[client_packet("play:player_position")] pub struct CPlayerPosition<'a> { @@ -34,7 +35,7 @@ impl<'a> CPlayerPosition<'a> { } impl ClientPacket for CPlayerPosition<'_> { - fn write(&self, bytebuf: &mut crate::bytebuf::ByteBuffer) { + fn write(&self, bytebuf: &mut impl BufMut) { bytebuf.put_var_int(&self.teleport_id); bytebuf.put_f64(self.position.x); bytebuf.put_f64(self.position.y); diff --git a/pumpkin-protocol/src/client/play/c_respawn.rs b/pumpkin-protocol/src/client/play/c_respawn.rs index b892d53e..9d68b059 100644 --- a/pumpkin-protocol/src/client/play/c_respawn.rs +++ b/pumpkin-protocol/src/client/play/c_respawn.rs @@ -2,35 +2,35 @@ use pumpkin_core::math::position::WorldPosition; use pumpkin_macros::client_packet; use serde::Serialize; -use crate::VarInt; +use crate::{codec::identifier::Identifier, VarInt}; #[derive(Serialize)] #[client_packet("play:respawn")] -pub struct CRespawn<'a> { +pub struct CRespawn { dimension_type: VarInt, - dimension_name: &'a str, + dimension_name: Identifier, hashed_seed: i64, game_mode: u8, previous_gamemode: i8, debug: bool, is_flat: bool, - death_dimension_name: Option<(&'a str, WorldPosition)>, + death_dimension_name: Option<(Identifier, WorldPosition)>, portal_cooldown: VarInt, sealevel: VarInt, data_kept: u8, } -impl<'a> CRespawn<'a> { +impl CRespawn { #[expect(clippy::too_many_arguments)] pub fn new( dimension_type: VarInt, - dimension_name: &'a str, + dimension_name: Identifier, hashed_seed: i64, game_mode: u8, previous_gamemode: i8, debug: bool, is_flat: bool, - death_dimension_name: Option<(&'a str, WorldPosition)>, + death_dimension_name: Option<(Identifier, WorldPosition)>, portal_cooldown: VarInt, sealevel: VarInt, data_kept: u8, diff --git a/pumpkin-protocol/src/client/play/c_server_links.rs b/pumpkin-protocol/src/client/play/c_server_links.rs index 7619a0c6..a2cb76fa 100644 --- a/pumpkin-protocol/src/client/play/c_server_links.rs +++ b/pumpkin-protocol/src/client/play/c_server_links.rs @@ -1,87 +1,16 @@ -use crate::VarInt; -use pumpkin_core::text::TextComponent; +use crate::{Link, VarInt}; use pumpkin_macros::client_packet; -use serde::{Serialize, Serializer}; +use serde::Serialize; #[derive(Serialize)] #[client_packet("play:server_links")] -pub struct CServerLinks<'a> { +pub struct CPlayServerLinks<'a> { links_count: &'a VarInt, links: &'a [Link<'a>], } -impl<'a> CServerLinks<'a> { +impl<'a> CPlayServerLinks<'a> { pub fn new(links_count: &'a VarInt, links: &'a [Link<'a>]) -> Self { Self { links_count, links } } } - -pub enum Label<'a> { - BuiltIn(LinkType), - TextComponent(TextComponent<'a>), -} - -impl Serialize for Label<'_> { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - match self { - Label::BuiltIn(link_type) => link_type.serialize(serializer), - Label::TextComponent(component) => component.serialize(serializer), - } - } -} - -#[derive(Serialize)] -pub struct Link<'a> { - pub is_built_in: bool, - pub label: Label<'a>, - pub url: &'a String, -} - -impl<'a> Link<'a> { - pub fn new(label: Label<'a>, url: &'a String) -> Self { - Self { - is_built_in: match label { - Label::BuiltIn(_) => true, - Label::TextComponent(_) => false, - }, - label, - url, - } - } -} - -pub enum LinkType { - BugReport, - CommunityGuidelines, - Support, - Status, - Feedback, - Community, - Website, - Forums, - News, - Announcements, -} - -impl Serialize for LinkType { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - match self { - LinkType::BugReport => VarInt(0).serialize(serializer), - LinkType::CommunityGuidelines => VarInt(1).serialize(serializer), - LinkType::Support => VarInt(2).serialize(serializer), - LinkType::Status => VarInt(3).serialize(serializer), - LinkType::Feedback => VarInt(4).serialize(serializer), - LinkType::Community => VarInt(5).serialize(serializer), - LinkType::Website => VarInt(6).serialize(serializer), - LinkType::Forums => VarInt(7).serialize(serializer), - LinkType::News => VarInt(8).serialize(serializer), - LinkType::Announcements => VarInt(9).serialize(serializer), - } - } -} diff --git a/pumpkin-protocol/src/client/play/c_set_border_lerp_size.rs b/pumpkin-protocol/src/client/play/c_set_border_lerp_size.rs index 0d15ab9b..5f8861a7 100644 --- a/pumpkin-protocol/src/client/play/c_set_border_lerp_size.rs +++ b/pumpkin-protocol/src/client/play/c_set_border_lerp_size.rs @@ -1,7 +1,7 @@ use pumpkin_macros::client_packet; use serde::Serialize; -use crate::VarLong; +use crate::codec::var_long::VarLong; #[derive(Serialize)] #[client_packet("play:set_border_lerp_size")] diff --git a/pumpkin-protocol/src/client/play/c_set_container_content.rs b/pumpkin-protocol/src/client/play/c_set_container_content.rs index 38e6b39a..275739f2 100644 --- a/pumpkin-protocol/src/client/play/c_set_container_content.rs +++ b/pumpkin-protocol/src/client/play/c_set_container_content.rs @@ -1,4 +1,4 @@ -use crate::slot::Slot; +use crate::codec::slot::Slot; use crate::VarInt; use pumpkin_macros::client_packet; diff --git a/pumpkin-protocol/src/client/play/c_set_container_slot.rs b/pumpkin-protocol/src/client/play/c_set_container_slot.rs index 19e6b484..e632da4e 100644 --- a/pumpkin-protocol/src/client/play/c_set_container_slot.rs +++ b/pumpkin-protocol/src/client/play/c_set_container_slot.rs @@ -1,4 +1,4 @@ -use crate::slot::Slot; +use crate::codec::slot::Slot; use crate::VarInt; use pumpkin_macros::client_packet; diff --git a/pumpkin-protocol/src/client/play/c_sound_effect.rs b/pumpkin-protocol/src/client/play/c_sound_effect.rs index 14e92ec9..d34af971 100644 --- a/pumpkin-protocol/src/client/play/c_sound_effect.rs +++ b/pumpkin-protocol/src/client/play/c_sound_effect.rs @@ -1,6 +1,7 @@ +use bytes::BufMut; use pumpkin_macros::client_packet; -use crate::{ClientPacket, IDOrSoundEvent, SoundCategory, SoundEvent, VarInt}; +use crate::{bytebuf::ByteBufMut, ClientPacket, IDOrSoundEvent, SoundCategory, SoundEvent, VarInt}; #[client_packet("play:sound")] pub struct CSoundEffect { @@ -44,11 +45,11 @@ impl CSoundEffect { } impl ClientPacket for CSoundEffect { - fn write(&self, bytebuf: &mut crate::bytebuf::ByteBuffer) { + fn write(&self, bytebuf: &mut impl BufMut) { bytebuf.put_var_int(&self.sound_event.id); if self.sound_event.id.0 == 0 { if let Some(test) = &self.sound_event.sound_event { - bytebuf.put_string(&test.sound_name); + bytebuf.put_identifier(&test.sound_name); bytebuf.put_option(&test.range, |p, v| { p.put_f32(*v); diff --git a/pumpkin-protocol/src/client/play/c_store_cookie.rs b/pumpkin-protocol/src/client/play/c_store_cookie.rs index b9fc02ce..da715f22 100644 --- a/pumpkin-protocol/src/client/play/c_store_cookie.rs +++ b/pumpkin-protocol/src/client/play/c_store_cookie.rs @@ -1,4 +1,4 @@ -use crate::{Identifier, VarInt}; +use crate::{codec::identifier::Identifier, VarInt}; use pumpkin_macros::client_packet; use serde::Serialize; diff --git a/pumpkin-protocol/src/client/play/c_teleport_entity.rs b/pumpkin-protocol/src/client/play/c_teleport_entity.rs index b5bc2151..2ec00492 100644 --- a/pumpkin-protocol/src/client/play/c_teleport_entity.rs +++ b/pumpkin-protocol/src/client/play/c_teleport_entity.rs @@ -1,7 +1,8 @@ +use bytes::BufMut; use pumpkin_core::math::vector3::Vector3; use pumpkin_macros::client_packet; -use crate::{ClientPacket, PositionFlag, VarInt}; +use crate::{bytebuf::ByteBufMut, ClientPacket, PositionFlag, VarInt}; #[client_packet("play:teleport_entity")] pub struct CTeleportEntity<'a> { @@ -37,7 +38,7 @@ impl<'a> CTeleportEntity<'a> { } impl ClientPacket for CTeleportEntity<'_> { - fn write(&self, bytebuf: &mut crate::bytebuf::ByteBuffer) { + fn write(&self, bytebuf: &mut impl BufMut) { bytebuf.put_var_int(&self.entity_id); bytebuf.put_f64(self.position.x); bytebuf.put_f64(self.position.y); diff --git a/pumpkin-protocol/src/client/play/c_update_objectives.rs b/pumpkin-protocol/src/client/play/c_update_objectives.rs index e5ae2e2d..41c45ff5 100644 --- a/pumpkin-protocol/src/client/play/c_update_objectives.rs +++ b/pumpkin-protocol/src/client/play/c_update_objectives.rs @@ -1,7 +1,8 @@ +use bytes::BufMut; use pumpkin_core::text::TextComponent; use pumpkin_macros::client_packet; -use crate::{ClientPacket, NumberFormat, VarInt}; +use crate::{bytebuf::ByteBufMut, ClientPacket, NumberFormat, VarInt}; #[client_packet("play:set_objective")] pub struct CUpdateObjectives<'a> { @@ -31,7 +32,7 @@ impl<'a> CUpdateObjectives<'a> { } impl ClientPacket for CUpdateObjectives<'_> { - fn write(&self, bytebuf: &mut crate::bytebuf::ByteBuffer) { + fn write(&self, bytebuf: &mut impl BufMut) { bytebuf.put_string(self.objective_name); bytebuf.put_u8(self.mode); if self.mode == 0 || self.mode == 2 { diff --git a/pumpkin-protocol/src/codec/bit_set.rs b/pumpkin-protocol/src/codec/bit_set.rs new file mode 100644 index 00000000..27aa329d --- /dev/null +++ b/pumpkin-protocol/src/codec/bit_set.rs @@ -0,0 +1,53 @@ +use std::num::NonZeroUsize; + +use bytes::{Buf, BufMut}; +use serde::{Serialize, Serializer}; + +use crate::bytebuf::ByteBuf; +use crate::bytebuf::ByteBufMut; + +use super::{var_int::VarInt, Codec, DecodeError}; + +pub struct BitSet(pub VarInt, pub Vec); + +impl Codec for BitSet { + /// The maximum size of the BitSet is `remaining / 8`. + const MAX_SIZE: NonZeroUsize = unsafe { NonZeroUsize::new_unchecked(usize::MAX) }; + + fn written_size(&self) -> usize { + todo!() + } + + fn encode(&self, write: &mut impl BufMut) { + write.put_var_int(&self.0); + for b in &self.1 { + write.put_i64(*b); + } + } + + fn decode(read: &mut impl Buf) -> Result { + // read length + let length = read + .try_get_var_int() + .map_err(|_| DecodeError::Incomplete)?; + // vanilla uses remaining / 8 + if length.0 as usize >= read.remaining() / 8 { + return Err(DecodeError::TooLarge); + } + let mut array: Vec = Vec::with_capacity(size_of::() * length.0 as usize); + for _ in 0..length.0 { + let long = read.try_get_i64().map_err(|_| DecodeError::Incomplete)?; + array.push(long); + } + Ok(BitSet(length, array)) + } +} + +impl Serialize for BitSet { + fn serialize(&self, _serializer: S) -> Result + where + S: Serializer, + { + todo!() + } +} diff --git a/pumpkin-protocol/src/codec/identifier.rs b/pumpkin-protocol/src/codec/identifier.rs new file mode 100644 index 00000000..6be5675c --- /dev/null +++ b/pumpkin-protocol/src/codec/identifier.rs @@ -0,0 +1,101 @@ +use std::num::NonZeroUsize; + +use bytes::{Buf, BufMut}; +use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer}; + +use crate::bytebuf::{ByteBuf, ByteBufMut}; + +use super::{Codec, DecodeError}; + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct Identifier { + pub namespace: String, + pub path: String, +} + +impl Identifier { + pub fn vanilla(path: &str) -> Self { + Self { + namespace: "minecraft".to_string(), + path: path.to_string(), + } + } +} +impl Codec for Identifier { + /// The maximum number of bytes a `Identifer` is the same as for a normal String. + const MAX_SIZE: NonZeroUsize = unsafe { NonZeroUsize::new_unchecked(i16::MAX as usize) }; + + fn written_size(&self) -> usize { + todo!() + } + + fn encode(&self, write: &mut impl BufMut) { + write.put_string_len(&self.to_string(), Self::MAX_SIZE.get()); + } + + fn decode(read: &mut impl Buf) -> Result { + let identifer = read + .try_get_string_len(Self::MAX_SIZE.get()) + .map_err(|_| DecodeError::Incomplete)?; + match identifer.split_once(":") { + Some((namespace, path)) => Ok(Identifier { + namespace: namespace.to_string(), + path: path.to_string(), + }), + None => Err(DecodeError::Incomplete), + } + } +} + +impl Serialize for Identifier { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&self.to_string()) + } +} + +impl<'de> Deserialize<'de> for Identifier { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct IdentifierVisitor; + + impl Visitor<'_> for IdentifierVisitor { + type Value = Identifier; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a valid Identifier (namespace:path)") + } + + fn visit_string(self, v: String) -> Result + where + E: serde::de::Error, + { + self.visit_str(&v) + } + + fn visit_str(self, identifer: &str) -> Result + where + E: serde::de::Error, + { + match identifer.split_once(":") { + Some((namespace, path)) => Ok(Identifier { + namespace: namespace.to_string(), + path: path.to_string(), + }), + None => Err(serde::de::Error::custom("Identifier can't be split")), + } + } + } + deserializer.deserialize_str(IdentifierVisitor) + } +} + +impl std::fmt::Display for Identifier { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}:{}", self.namespace, self.path) + } +} diff --git a/pumpkin-protocol/src/codec/mod.rs b/pumpkin-protocol/src/codec/mod.rs new file mode 100644 index 00000000..57af2e19 --- /dev/null +++ b/pumpkin-protocol/src/codec/mod.rs @@ -0,0 +1,28 @@ +use std::num::NonZeroUsize; + +use bytes::{Buf, BufMut}; +use thiserror::Error; + +pub mod bit_set; +pub mod identifier; +pub mod slot; +pub mod var_int; +pub mod var_long; + +pub trait Codec { + const MAX_SIZE: NonZeroUsize; + + fn written_size(&self) -> usize; + + fn encode(&self, write: &mut impl BufMut); + + fn decode(read: &mut impl Buf) -> Result; +} + +#[derive(Copy, Clone, PartialEq, Eq, Debug, Error)] +pub enum DecodeError { + #[error("Incomplete VarInt decode")] + Incomplete, + #[error("VarInt is too large")] + TooLarge, +} diff --git a/pumpkin-protocol/src/slot.rs b/pumpkin-protocol/src/codec/slot.rs similarity index 100% rename from pumpkin-protocol/src/slot.rs rename to pumpkin-protocol/src/codec/slot.rs diff --git a/pumpkin-protocol/src/codec/var_int.rs b/pumpkin-protocol/src/codec/var_int.rs new file mode 100644 index 00000000..4f502687 --- /dev/null +++ b/pumpkin-protocol/src/codec/var_int.rs @@ -0,0 +1,157 @@ +use std::{num::NonZeroUsize, ops::Deref}; + +use super::{Codec, DecodeError}; +use bytes::{Buf, BufMut}; +use serde::{ + de::{SeqAccess, Visitor}, + Deserialize, Deserializer, Serialize, Serializer, +}; + +pub type VarIntType = i32; + +/** + * A variable-length integer type used by the Minecraft network protocol. + */ +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct VarInt(pub VarIntType); + +impl Codec for VarInt { + /// The maximum number of bytes a `VarInt` can occupy. + const MAX_SIZE: NonZeroUsize = unsafe { NonZeroUsize::new_unchecked(5) }; + + /// Returns the exact number of bytes this varint will write when + /// [`Encode::encode`] is called, assuming no error occurs. + fn written_size(&self) -> usize { + match self.0 { + 0 => 1, + n => (31 - n.leading_zeros() as usize) / 7 + 1, + } + } + + fn encode(&self, write: &mut impl BufMut) { + let mut val = self.0; + for _ in 0..Self::MAX_SIZE.get() { + let b: u8 = val as u8 & 0b01111111; + val >>= 7; + write.put_u8(if val == 0 { b } else { b | 0b10000000 }); + if val == 0 { + break; + } + } + } + + fn decode(read: &mut impl Buf) -> Result { + let mut val = 0; + for i in 0..Self::MAX_SIZE.get() { + if !read.has_remaining() { + return Err(DecodeError::Incomplete); + } + let byte = read.get_u8(); + val |= (i32::from(byte) & 0x7F) << (i * 7); + if byte & 0x80 == 0 { + return Ok(VarInt(val)); + } + } + Err(DecodeError::TooLarge) + } +} + +impl From for VarInt { + fn from(value: i32) -> Self { + VarInt(value) + } +} + +impl From for VarInt { + fn from(value: u32) -> Self { + VarInt(value as i32) + } +} + +impl From for VarInt { + fn from(value: u8) -> Self { + VarInt(value as i32) + } +} + +impl From for VarInt { + fn from(value: usize) -> Self { + VarInt(value as i32) + } +} + +impl From for i32 { + fn from(value: VarInt) -> Self { + value.0 + } +} + +impl AsRef for VarInt { + fn as_ref(&self) -> &i32 { + &self.0 + } +} + +impl Deref for VarInt { + type Target = i32; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl Serialize for VarInt { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut value = self.0 as u32; + let mut buf = Vec::new(); + + while value > 0x7F { + buf.put_u8(value as u8 | 0x80); + value >>= 7; + } + + buf.put_u8(value as u8); + + serializer.serialize_bytes(&buf) + } +} + +impl<'de> Deserialize<'de> for VarInt { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct VarIntVisitor; + + impl<'de> Visitor<'de> for VarIntVisitor { + type Value = VarInt; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a valid VarInt encoded in a byte sequence") + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: SeqAccess<'de>, + { + let mut val = 0; + for i in 0..VarInt::MAX_SIZE.get() { + if let Some(byte) = seq.next_element::()? { + val |= (i32::from(byte) & 0b01111111) << (i * 7); + if byte & 0b10000000 == 0 { + return Ok(VarInt(val)); + } + } else { + break; + } + } + Err(serde::de::Error::custom("VarInt was too large")) + } + } + + deserializer.deserialize_seq(VarIntVisitor) + } +} diff --git a/pumpkin-protocol/src/codec/var_long.rs b/pumpkin-protocol/src/codec/var_long.rs new file mode 100644 index 00000000..ceb2c1c9 --- /dev/null +++ b/pumpkin-protocol/src/codec/var_long.rs @@ -0,0 +1,158 @@ +use std::{num::NonZeroUsize, ops::Deref}; + +use super::{Codec, DecodeError}; +use bytes::{Buf, BufMut}; +use serde::{ + de::{self, SeqAccess, Visitor}, + Deserialize, Deserializer, Serialize, Serializer, +}; + +pub type VarLongType = i64; + +/** + * A variable-length long type used by the Minecraft network protocol. + */ +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct VarLong(pub VarLongType); + +impl Codec for VarLong { + /// The maximum number of bytes a `VarLong` can occupy. + const MAX_SIZE: NonZeroUsize = unsafe { NonZeroUsize::new_unchecked(10) }; + + /// Returns the exact number of bytes this varlong will write when + /// [`Encode::encode`] is called, assuming no error occurs. + fn written_size(&self) -> usize { + match self.0 { + 0 => 1, + n => (31 - n.leading_zeros() as usize) / 7 + 1, + } + } + + fn encode(&self, write: &mut impl BufMut) { + let mut x = self.0; + for _ in 0..Self::MAX_SIZE.get() { + let byte = (x & 0x7F) as u8; + x >>= 7; + if x == 0 { + write.put_slice(&[byte]); + break; + } + write.put_slice(&[byte | 0x80]); + } + } + + fn decode(read: &mut impl Buf) -> Result { + let mut val = 0; + for i in 0..Self::MAX_SIZE.get() { + if !read.has_remaining() { + return Err(DecodeError::Incomplete); + } + let byte = read.get_u8(); + val |= (i64::from(byte) & 0b01111111) << (i * 7); + if byte & 0b10000000 == 0 { + return Ok(VarLong(val)); + } + } + Err(DecodeError::TooLarge) + } +} + +impl From for VarLong { + fn from(value: i64) -> Self { + VarLong(value) + } +} + +impl From for VarLong { + fn from(value: u32) -> Self { + VarLong(value as i64) + } +} + +impl From for VarLong { + fn from(value: u8) -> Self { + VarLong(value as i64) + } +} + +impl From for VarLong { + fn from(value: usize) -> Self { + VarLong(value as i64) + } +} + +impl From for i64 { + fn from(value: VarLong) -> Self { + value.0 + } +} + +impl AsRef for VarLong { + fn as_ref(&self) -> &i64 { + &self.0 + } +} + +impl Deref for VarLong { + type Target = i64; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl Serialize for VarLong { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut value = self.0 as u64; + let mut buf = Vec::new(); + + while value > 0x7F { + buf.put_u8(value as u8 | 0x80); + value >>= 7; + } + + buf.put_u8(value as u8); + + serializer.serialize_bytes(&buf) + } +} + +impl<'de> Deserialize<'de> for VarLong { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct VarLongVisitor; + + impl<'de> Visitor<'de> for VarLongVisitor { + type Value = VarLong; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a valid VarInt encoded in a byte sequence") + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: SeqAccess<'de>, + { + let mut val = 0; + for i in 0..VarLong::MAX_SIZE.get() { + if let Some(byte) = seq.next_element::()? { + val |= (i64::from(byte) & 0b01111111) << (i * 7); + if byte & 0b10000000 == 0 { + return Ok(VarLong(val)); + } + } else { + break; + } + } + Err(de::Error::custom("VarInt was too large")) + } + } + + deserializer.deserialize_seq(VarLongVisitor) + } +} diff --git a/pumpkin-protocol/src/lib.rs b/pumpkin-protocol/src/lib.rs index f15928e9..8ef37f7a 100644 --- a/pumpkin-protocol/src/lib.rs +++ b/pumpkin-protocol/src/lib.rs @@ -1,35 +1,30 @@ -use bytebuf::{packet_id::Packet, ByteBuffer, DeserializerError}; +use std::num::NonZeroU16; + +use bytebuf::{packet_id::Packet, ReadingError}; +use bytes::{Buf, BufMut, Bytes}; +use codec::{identifier::Identifier, var_int::VarInt}; use pumpkin_core::text::{style::Style, TextComponent}; -use serde::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize, Serializer}; pub mod bytebuf; +#[cfg(feature = "clientbound")] pub mod client; +pub mod codec; pub mod packet_decoder; pub mod packet_encoder; +#[cfg(feature = "query")] pub mod query; +#[cfg(feature = "serverbound")] pub mod server; -pub mod slot; - -mod var_int; -pub use var_int::*; - -mod var_long; -pub use var_long::*; /// To current Minecraft protocol /// Don't forget to change this when porting -pub const CURRENT_MC_PROTOCOL: u32 = 769; +pub const CURRENT_MC_PROTOCOL: NonZeroU16 = unsafe { NonZeroU16::new_unchecked(769) }; pub const MAX_PACKET_SIZE: i32 = 2097152; -/// usually uses a namespace like "minecraft:thing" -pub type Identifier = String; -pub type VarIntType = i32; -pub type VarLongType = i64; pub type FixedBitSet = bytes::Bytes; -pub struct BitSet<'a>(pub VarInt, pub &'a [i64]); - #[derive(Debug, PartialEq, Clone, Copy)] pub enum ConnectionState { HandShake, @@ -76,21 +71,21 @@ pub struct IDOrSoundEvent { #[derive(Serialize)] pub struct SoundEvent { - pub sound_name: String, + pub sound_name: Identifier, pub range: Option, } pub struct RawPacket { pub id: VarInt, - pub bytebuf: ByteBuffer, + pub bytebuf: Bytes, } pub trait ClientPacket: Packet { - fn write(&self, bytebuf: &mut ByteBuffer); + fn write(&self, bytebuf: &mut impl BufMut); } pub trait ServerPacket: Packet + Sized { - fn read(bytebuf: &mut ByteBuffer) -> Result; + fn read(bytebuf: &mut impl Buf) -> Result; } #[derive(Serialize)] @@ -191,3 +186,73 @@ impl PositionFlag { flags.iter().fold(0, |acc, flag| acc | flag.get_mask()) } } + +pub enum Label<'a> { + BuiltIn(LinkType), + TextComponent(TextComponent<'a>), +} + +impl Serialize for Label<'_> { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match self { + Label::BuiltIn(link_type) => link_type.serialize(serializer), + Label::TextComponent(component) => component.serialize(serializer), + } + } +} + +#[derive(Serialize)] +pub struct Link<'a> { + pub is_built_in: bool, + pub label: Label<'a>, + pub url: &'a String, +} + +impl<'a> Link<'a> { + pub fn new(label: Label<'a>, url: &'a String) -> Self { + Self { + is_built_in: match label { + Label::BuiltIn(_) => true, + Label::TextComponent(_) => false, + }, + label, + url, + } + } +} + +pub enum LinkType { + BugReport, + CommunityGuidelines, + Support, + Status, + Feedback, + Community, + Website, + Forums, + News, + Announcements, +} + +impl Serialize for LinkType { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match self { + LinkType::BugReport => VarInt(0).serialize(serializer), + LinkType::CommunityGuidelines => VarInt(1).serialize(serializer), + LinkType::Support => VarInt(2).serialize(serializer), + LinkType::Status => VarInt(3).serialize(serializer), + LinkType::Feedback => VarInt(4).serialize(serializer), + LinkType::Community => VarInt(5).serialize(serializer), + LinkType::Website => VarInt(6).serialize(serializer), + LinkType::Forums => VarInt(7).serialize(serializer), + LinkType::News => VarInt(8).serialize(serializer), + LinkType::Announcements => VarInt(9).serialize(serializer), + } + } +} diff --git a/pumpkin-protocol/src/packet_decoder.rs b/pumpkin-protocol/src/packet_decoder.rs index 9472c28e..5059a2c5 100644 --- a/pumpkin-protocol/src/packet_decoder.rs +++ b/pumpkin-protocol/src/packet_decoder.rs @@ -1,9 +1,12 @@ use aes::cipher::{generic_array::GenericArray, BlockDecryptMut, BlockSizeUser, KeyIvInit}; -use bytes::{Buf, BytesMut}; +use bytes::{Buf, Bytes, BytesMut}; use libdeflater::{DecompressionError, Decompressor}; use thiserror::Error; -use crate::{bytebuf::ByteBuffer, RawPacket, VarInt, VarIntDecodeError, MAX_PACKET_SIZE}; +use crate::{ + codec::{Codec, DecodeError}, + RawPacket, VarInt, MAX_PACKET_SIZE, +}; type Cipher = cfb8::Decryptor; @@ -13,8 +16,8 @@ type Cipher = cfb8::Decryptor; pub struct PacketDecoder { buf: BytesMut, decompress_buf: BytesMut, - compression: bool, cipher: Option, + compression: bool, decompressor: Decompressor, } @@ -25,8 +28,8 @@ impl Default for PacketDecoder { Self { buf: BytesMut::new(), decompress_buf: BytesMut::new(), - compression: false, cipher: None, + compression: false, decompressor: Decompressor::new(), } } @@ -38,8 +41,8 @@ impl PacketDecoder { let packet_len = match VarInt::decode(&mut r) { Ok(len) => len, - Err(VarIntDecodeError::Incomplete) => return Ok(None), - Err(VarIntDecodeError::TooLarge) => Err(PacketDecodeError::MalformedLength)?, + Err(DecodeError::Incomplete) => return Ok(None), + Err(DecodeError::TooLarge) => Err(PacketDecodeError::MalformedLength)?, }; let packet_len = packet_len.0; @@ -113,24 +116,19 @@ impl PacketDecoder { data.advance(data.len() - r.len()); Ok(Some(RawPacket { id: packet_id, - bytebuf: ByteBuffer::new(data), + bytebuf: Bytes::from(data), })) } pub fn set_encryption(&mut self, key: Option<&[u8; 16]>) { if let Some(key) = key { assert!(self.cipher.is_none(), "encryption is already enabled"); - let mut cipher = Cipher::new_from_slices(key, key).expect("invalid key"); - // Don't forget to decrypt the data we already have. - Self::decrypt_bytes(&mut cipher, &mut self.buf); - self.cipher = Some(cipher); } else { assert!(self.cipher.is_some(), "encryption is already disabled"); - self.cipher = None; } } @@ -201,8 +199,11 @@ impl From for PacketDecodeError { #[cfg(test)] mod tests { + use crate::bytebuf::ByteBufMut; + use super::*; use aes::Aes128; + use bytes::BufMut; use cfb8::cipher::AsyncStreamCipher; use cfb8::Encryptor as Cfb8Encryptor; use libdeflater::{CompressionLvl, Compressor}; @@ -232,18 +233,18 @@ mod tests { key: Option<&[u8; 16]>, iv: Option<&[u8; 16]>, ) -> Vec { - let mut buffer = ByteBuffer::empty(); + let mut buffer = BytesMut::new(); if compress { // Create a buffer that includes packet_id_varint and payload - let mut data_to_compress = ByteBuffer::empty(); + let mut data_to_compress = BytesMut::new(); let packet_id_varint = VarInt(packet_id); data_to_compress.put_var_int(&packet_id_varint); data_to_compress.put_slice(payload); // Compress the combined data - let compressed_payload = compress_zlib(data_to_compress.buf()); - let data_len = data_to_compress.buf().len() as i32; // 1 + payload.len() + let compressed_payload = compress_zlib(&data_to_compress); + let data_len = data_to_compress.len() as i32; // 1 + payload.len() let data_len_varint = VarInt(data_len); buffer.put_var_int(&data_len_varint); buffer.put_slice(&compressed_payload); @@ -255,7 +256,7 @@ mod tests { } // Calculate packet length: length of buffer - let packet_len = buffer.buf().len() as i32; + let packet_len = buffer.len() as i32; let packet_len_varint = VarInt(packet_len); let mut packet_length_encoded = Vec::new(); { @@ -265,7 +266,7 @@ mod tests { // Create a new buffer for the entire packet let mut packet = Vec::new(); packet.extend_from_slice(&packet_length_encoded); - packet.extend_from_slice(buffer.buf()); + packet.extend_from_slice(&buffer); // Encrypt if key and iv are provided if let (Some(k), Some(v)) = (key, iv) { @@ -297,9 +298,9 @@ mod tests { let result = decoder.decode().expect("Decoding failed"); assert!(result.is_some()); - let mut raw_packet = result.unwrap(); + let raw_packet = result.unwrap(); assert_eq!(raw_packet.id.0, packet_id); - assert_eq!(raw_packet.bytebuf.buf().as_ref(), payload); + assert_eq!(raw_packet.bytebuf.as_ref(), payload); } /// Test decoding with compression @@ -323,9 +324,9 @@ mod tests { let result = decoder.decode().expect("Decoding failed"); assert!(result.is_some()); - let mut raw_packet = result.unwrap(); + let raw_packet = result.unwrap(); assert_eq!(raw_packet.id.0, packet_id); - assert_eq!(raw_packet.bytebuf.buf().as_ref(), payload); + assert_eq!(raw_packet.bytebuf.as_ref(), payload); } /// Test decoding with encryption @@ -354,9 +355,9 @@ mod tests { let result = decoder.decode().expect("Decoding failed"); assert!(result.is_some()); - let mut raw_packet = result.unwrap(); + let raw_packet = result.unwrap(); assert_eq!(raw_packet.id.0, packet_id); - assert_eq!(raw_packet.bytebuf.buf().as_ref(), payload); + assert_eq!(raw_packet.bytebuf.as_ref(), payload); } /// Test decoding with both compression and encryption @@ -385,9 +386,9 @@ mod tests { let result = decoder.decode().expect("Decoding failed"); assert!(result.is_some()); - let mut raw_packet = result.unwrap(); + let raw_packet = result.unwrap(); assert_eq!(raw_packet.id.0, packet_id); - assert_eq!(raw_packet.bytebuf.buf().as_ref(), payload); + assert_eq!(raw_packet.bytebuf.as_ref(), payload); } /// Test decoding with invalid compressed data @@ -398,28 +399,28 @@ mod tests { let invalid_compressed_data = vec![0xFF, 0xFF, 0xFF]; // Invalid Zlib data // Build the packet with compression enabled but invalid compressed data - let mut buffer = ByteBuffer::empty(); + let mut buffer = BytesMut::new(); let data_len_varint = VarInt(data_len); buffer.put_var_int(&data_len_varint); buffer.put_slice(&invalid_compressed_data); // Calculate packet length: VarInt(data_len) + invalid compressed data - let packet_len = buffer.buf().len() as i32; + let packet_len = buffer.len() as i32; let packet_len_varint = VarInt(packet_len); // Create a new buffer for the entire packet - let mut packet_buffer = ByteBuffer::empty(); + let mut packet_buffer = BytesMut::new(); packet_buffer.put_var_int(&packet_len_varint); - packet_buffer.put_slice(buffer.buf()); + packet_buffer.put_slice(&buffer); - let packet_bytes = packet_buffer.buf(); + let packet_bytes = packet_buffer; // Initialize the decoder with compression enabled let mut decoder = PacketDecoder::default(); decoder.set_compression(true); // Feed the invalid compressed packet to the decoder - decoder.queue_slice(packet_bytes); + decoder.queue_slice(&packet_bytes); // Attempt to decode and expect a decompression error let result = decoder.decode(); @@ -450,9 +451,9 @@ mod tests { let result = decoder.decode().expect("Decoding failed"); assert!(result.is_some()); - let mut raw_packet = result.unwrap(); + let raw_packet = result.unwrap(); assert_eq!(raw_packet.id.0, packet_id); - assert_eq!(raw_packet.bytebuf.buf().as_ref(), payload); + assert_eq!(raw_packet.bytebuf.as_ref(), payload); } /// Test decoding with maximum length packet @@ -484,13 +485,13 @@ mod tests { "Decoder returned None when it should have decoded a packet" ); - let mut raw_packet = result.unwrap(); + let raw_packet = result.unwrap(); assert_eq!( raw_packet.id.0, packet_id, "Decoded packet_id does not match" ); assert_eq!( - raw_packet.bytebuf.buf().as_ref(), + raw_packet.bytebuf.as_ref(), &payload[..], "Decoded payload does not match" ); diff --git a/pumpkin-protocol/src/packet_encoder.rs b/pumpkin-protocol/src/packet_encoder.rs index 0df2b157..1636d56e 100644 --- a/pumpkin-protocol/src/packet_encoder.rs +++ b/pumpkin-protocol/src/packet_encoder.rs @@ -5,18 +5,18 @@ use thiserror::Error; use libdeflater::{CompressionLvl, Compressor}; -use crate::{bytebuf::ByteBuffer, ClientPacket, VarInt, MAX_PACKET_SIZE}; +use crate::{codec::Codec, ClientPacket, VarInt, MAX_PACKET_SIZE}; type Cipher = cfb8::Encryptor; -// Encoder: Server -> Client -// Supports ZLib endecoding/compression -// Supports Aes128 Encryption +/// Encoder: Server -> Client +/// Supports ZLib endecoding/compression +/// Supports Aes128 Encryption pub struct PacketEncoder { buf: BytesMut, compress_buf: Vec, - compression: Option, cipher: Option, + compression_threshold: Option, compressor: Compressor, // Reuse the compressor for all packets } @@ -27,27 +27,54 @@ impl Default for PacketEncoder { Self { buf: BytesMut::with_capacity(1024), compress_buf: Vec::with_capacity(1024), - compression: None, cipher: None, - compressor: Compressor::new(CompressionLvl::fastest()), // init compressor with no compression level + compression_threshold: None, + compressor: Compressor::new(CompressionLvl::fastest()), // init compressor with fastest compression level } } } impl PacketEncoder { + /// Appends a Clientbound `ClientPacket` to the internal buffer and applies compression when needed. + /// + /// If compression is enabled and the packet size exceeds the threshold, the packet is compressed. + /// The packet is prefixed with its length and, if compressed, the uncompressed data length. + /// The packet format is as follows: + /// + /// **Uncompressed:** + /// |-----------------------| + /// | Packet Length (VarInt)| + /// |-----------------------| + /// | Packet ID (VarInt) | + /// |-----------------------| + /// | Data (Byte Array) | + /// |-----------------------| + /// + /// **Compressed:** + /// |------------------------| + /// | Packet Length (VarInt) | + /// |------------------------| + /// | Data Length (VarInt) | + /// |------------------------| + /// | Packet ID (VarInt) | + /// |------------------------| + /// | Data (Byte Array) | + /// |------------------------| + /// + /// - `Packet Length`: The total length of the packet *excluding* the `Packet Length` field itself. + /// - `Data Length`: (Only present in compressed packets) The length of the uncompressed `Packet ID` and `Data`. + /// - `Packet ID`: The ID of the packet. + /// - `Data`: The packet's data. pub fn append_packet(&mut self, packet: &P) -> Result<(), PacketEncodeError> { let start_len = self.buf.len(); // Write the Packet ID first VarInt(P::PACKET_ID).encode(&mut self.buf); - let mut packet_buf = ByteBuffer::empty(); // Now write the packet into an empty buffer - packet.write(&mut packet_buf); - self.buf.put(packet_buf.buf()); - + packet.write(&mut self.buf); let data_len = self.buf.len() - start_len; - if let Some(compression) = &self.compression { - if data_len > compression.threshold as usize { + if let Some(compression_threshold) = self.compression_threshold { + if data_len > compression_threshold as usize { // Get the data to compress let data_to_compress = &self.buf[start_len..]; @@ -75,7 +102,7 @@ impl PacketEncoder { let packet_len = data_len_size + compressed_size; if packet_len >= MAX_PACKET_SIZE as usize { - return Err(PacketEncodeError::TooLong); + return Err(PacketEncodeError::TooLong(packet_len)); } self.buf.truncate(start_len); @@ -88,7 +115,7 @@ impl PacketEncoder { let packet_len = data_len_size + data_len; if packet_len >= MAX_PACKET_SIZE as usize { - Err(PacketEncodeError::TooLong)? + Err(PacketEncodeError::TooLong(packet_len))? } let packet_len_size = VarInt(packet_len as i32).written_size(); @@ -112,7 +139,7 @@ impl PacketEncoder { let packet_len = data_len; if packet_len >= MAX_PACKET_SIZE as usize { - Err(PacketEncodeError::TooLong)? + Err(PacketEncodeError::TooLong(packet_len))? } let packet_len_size = VarInt(packet_len as i32).written_size(); @@ -126,6 +153,7 @@ impl PacketEncoder { Ok(()) } + /// Enable encryption for taking all packets buffer ` pub fn set_encryption(&mut self, key: Option<&[u8; 16]>) { if let Some(key) = key { assert!(self.cipher.is_none(), "encryption is already enabled"); @@ -138,23 +166,37 @@ impl PacketEncoder { } } - /// Enables ZLib Compression + /// Enables or disables Zlib compression with the given options. + /// + /// If `compression` is `Some`, compression is enabled with the provided + /// options. If `compression` is `None`, compression is disabled. pub fn set_compression(&mut self, compression: Option) { - self.compression = compression; - // Reset the compressor with the new compression level - if let Some(compression) = &self.compression { + if let Some(compression) = &compression { + self.compression_threshold = Some(compression.threshold); let compression_level = compression.level as i32; - let level = match CompressionLvl::new(compression_level) { Ok(level) => level, - Err(_) => return, + Err(error) => { + log::error!("Invalid compression level {:?}", error); + return; + } }; - self.compressor = Compressor::new(level); + } else { + self.compression_threshold = None; } } + /// Encrypts the data in the internal buffer and returns it as a `BytesMut`. + /// + /// If a cipher is set, the data is encrypted in-place using block cipher encryption. + /// The buffer is processed in chunks of the cipher's block size. If the buffer's + /// length is not a multiple of the block size, the last partial block is *not* encrypted. + /// It's important to ensure that the data being encrypted is padded appropriately + /// beforehand if necessary. + /// + /// If no cipher is set, the buffer is returned as is. pub fn take(&mut self) -> BytesMut { if let Some(cipher) = &mut self.cipher { for chunk in self.buf.chunks_mut(Cipher::block_size()) { @@ -167,20 +209,20 @@ impl PacketEncoder { } } +/// Errors that can occur during packet encoding. #[derive(Error, Debug)] pub enum PacketEncodeError { - #[error("packet exceeds maximum length")] - TooLong, - #[error("compression failed {0}")] + #[error("Packet exceeds maximum length: {0}")] + TooLong(usize), + #[error("Compression failed {0}")] CompressionFailed(String), } #[cfg(test)] mod tests { use super::*; - use crate::bytebuf::packet_id::Packet; use crate::client::status::CStatusResponse; - use crate::VarIntDecodeError; + use crate::{bytebuf::packet_id::Packet, codec::DecodeError}; use aes::Aes128; use cfb8::cipher::AsyncStreamCipher; use cfb8::Decryptor as Cfb8Decryptor; @@ -204,7 +246,7 @@ mod tests { } /// Helper function to decode a VarInt from bytes - fn decode_varint(buffer: &mut &[u8]) -> Result { + fn decode_varint(buffer: &mut &[u8]) -> Result { VarInt::decode(buffer).map(|varint| varint.0) } @@ -274,10 +316,10 @@ mod tests { // Remaining buffer is the payload // We need to obtain the expected payload - let mut expected_payload = ByteBuffer::empty(); + let mut expected_payload = BytesMut::new(); packet.write(&mut expected_payload); - assert_eq!(buffer, expected_payload.buf()); + assert_eq!(buffer, expected_payload); } /// Test encoding with compression @@ -308,10 +350,10 @@ mod tests { // Read data length VarInt (uncompressed data length) let data_length = decode_varint(&mut buffer).expect("Failed to decode data length"); - let mut expected_payload = ByteBuffer::empty(); + let mut expected_payload = BytesMut::new(); packet.write(&mut expected_payload); let uncompressed_data_length = - VarInt(CStatusResponse::PACKET_ID).written_size() + expected_payload.buf().len(); + VarInt(CStatusResponse::PACKET_ID).written_size() + expected_payload.len(); assert_eq!(data_length as usize, uncompressed_data_length); // Remaining buffer is the compressed data @@ -330,7 +372,7 @@ mod tests { assert_eq!(decoded_packet_id, CStatusResponse::PACKET_ID); // Remaining buffer is the payload - assert_eq!(decompressed_buffer, expected_payload.buf()); + assert_eq!(decompressed_buffer, expected_payload); } /// Test encoding with encryption @@ -364,10 +406,10 @@ mod tests { assert_eq!(decoded_packet_id, CStatusResponse::PACKET_ID); // Remaining buffer is the payload - let mut expected_payload = ByteBuffer::empty(); + let mut expected_payload = BytesMut::new(); packet.write(&mut expected_payload); - assert_eq!(buffer, expected_payload.buf()); + assert_eq!(buffer, expected_payload); } /// Test encoding with both compression and encryption @@ -405,10 +447,10 @@ mod tests { // Read data length VarInt (uncompressed data length) let data_length = decode_varint(&mut buffer).expect("Failed to decode data length"); - let mut expected_payload = ByteBuffer::empty(); + let mut expected_payload = BytesMut::new(); packet.write(&mut expected_payload); let uncompressed_data_length = - VarInt(CStatusResponse::PACKET_ID).written_size() + expected_payload.buf().len(); + VarInt(CStatusResponse::PACKET_ID).written_size() + expected_payload.len(); assert_eq!(data_length as usize, uncompressed_data_length); // Remaining buffer is the compressed data @@ -427,7 +469,7 @@ mod tests { assert_eq!(decoded_packet_id, CStatusResponse::PACKET_ID); // Remaining buffer is the payload - assert_eq!(decompressed_buffer, expected_payload.buf()); + assert_eq!(decompressed_buffer, expected_payload); } /// Test encoding with zero-length payload @@ -455,15 +497,15 @@ mod tests { assert_eq!(decoded_packet_id, CStatusResponse::PACKET_ID); // Remaining buffer is the payload (empty) - let mut expected_payload = ByteBuffer::empty(); + let mut expected_payload = BytesMut::new(); packet.write(&mut expected_payload); assert_eq!( buffer.len(), - expected_payload.buf().len(), + expected_payload.len(), "Payload length mismatch" ); - assert_eq!(buffer, expected_payload.buf()); + assert_eq!(buffer, expected_payload); } /// Test encoding with maximum length payload @@ -500,10 +542,10 @@ mod tests { assert_eq!(decoded_packet_id, CStatusResponse::PACKET_ID); // Remaining buffer is the payload - let mut expected_payload = ByteBuffer::empty(); + let mut expected_payload = BytesMut::new(); packet.write(&mut expected_payload); - assert_eq!(buffer, expected_payload.buf()); + assert_eq!(buffer, expected_payload); } /// Test encoding a packet that exceeds MAX_PACKET_SIZE @@ -557,9 +599,9 @@ mod tests { assert_eq!(decoded_packet_id, CStatusResponse::PACKET_ID); // Remaining buffer is the payload - let mut expected_payload = ByteBuffer::empty(); + let mut expected_payload = BytesMut::new(); packet.write(&mut expected_payload); - assert_eq!(buffer, expected_payload.buf()); + assert_eq!(buffer, expected_payload); } } diff --git a/pumpkin-protocol/src/server/config/s_cookie_response.rs b/pumpkin-protocol/src/server/config/s_cookie_response.rs index 69440254..b9e3a0a9 100644 --- a/pumpkin-protocol/src/server/config/s_cookie_response.rs +++ b/pumpkin-protocol/src/server/config/s_cookie_response.rs @@ -1,13 +1,17 @@ +use bytes::Buf; use pumpkin_macros::server_packet; use serde::de; -use crate::bytebuf::{ByteBuffer, DeserializerError}; -use crate::{Identifier, ServerPacket, VarInt}; +use crate::{ + bytebuf::{ByteBuf, ReadingError}, + codec::identifier::Identifier, + ServerPacket, VarInt, +}; #[server_packet("config:cookie_response")] /// Response to a Cookie Request (configuration) from the server. /// The Notchian (vanilla) server only accepts responses of up to 5 kiB in size. -pub struct SCookieResponse { +pub struct SConfigCookieResponse { pub key: Identifier, pub has_payload: bool, pub payload_length: Option, @@ -16,10 +20,10 @@ pub struct SCookieResponse { const MAX_PAYLOAD_SIZE: i32 = 5120; -impl ServerPacket for SCookieResponse { - fn read(bytebuf: &mut ByteBuffer) -> Result { - let key = bytebuf.get_string()?; - let has_payload = bytebuf.get_bool()?; +impl ServerPacket for SConfigCookieResponse { + fn read(bytebuf: &mut impl Buf) -> Result { + let key = bytebuf.try_get_identifer()?; + let has_payload = bytebuf.try_get_bool()?; if !has_payload { return Ok(Self { @@ -30,7 +34,7 @@ impl ServerPacket for SCookieResponse { }); } - let payload_length = bytebuf.get_var_int()?; + let payload_length = bytebuf.try_get_var_int()?; let length = payload_length.0; if length > MAX_PAYLOAD_SIZE { @@ -39,7 +43,7 @@ impl ServerPacket for SCookieResponse { )); } - let payload = bytebuf.copy_to_bytes(length as usize)?; + let payload = bytebuf.try_copy_to_bytes(length as usize)?; Ok(Self { key, diff --git a/pumpkin-protocol/src/server/config/s_plugin_message.rs b/pumpkin-protocol/src/server/config/s_plugin_message.rs index d5770139..d9e0abae 100644 --- a/pumpkin-protocol/src/server/config/s_plugin_message.rs +++ b/pumpkin-protocol/src/server/config/s_plugin_message.rs @@ -1,21 +1,23 @@ +use bytes::Buf; use pumpkin_macros::server_packet; use crate::{ - bytebuf::{ByteBuffer, DeserializerError}, - Identifier, ServerPacket, + bytebuf::{ByteBuf, ReadingError}, + codec::identifier::Identifier, + ServerPacket, }; #[server_packet("config:custom_payload")] pub struct SPluginMessage { pub channel: Identifier, - pub data: bytes::BytesMut, + pub data: bytes::Bytes, } impl ServerPacket for SPluginMessage { - fn read(bytebuf: &mut ByteBuffer) -> Result { + fn read(bytebuf: &mut impl Buf) -> Result { Ok(Self { - channel: bytebuf.get_string()?, - data: bytebuf.get_slice(), + channel: bytebuf.try_get_identifer()?, + data: bytebuf.try_copy_to_bytes(bytebuf.remaining())?, }) } } diff --git a/pumpkin-protocol/src/server/handshake/mod.rs b/pumpkin-protocol/src/server/handshake/mod.rs index a1d74352..9436e569 100644 --- a/pumpkin-protocol/src/server/handshake/mod.rs +++ b/pumpkin-protocol/src/server/handshake/mod.rs @@ -1,7 +1,8 @@ +use bytes::Buf; use pumpkin_macros::server_packet; use crate::{ - bytebuf::{ByteBuffer, DeserializerError}, + bytebuf::{ByteBuf, ReadingError}, ConnectionState, ServerPacket, VarInt, }; @@ -14,12 +15,12 @@ pub struct SHandShake { } impl ServerPacket for SHandShake { - fn read(bytebuf: &mut ByteBuffer) -> Result { + fn read(bytebuf: &mut impl Buf) -> Result { Ok(Self { - protocol_version: bytebuf.get_var_int()?, - server_address: bytebuf.get_string_len(255)?, - server_port: bytebuf.get_u16()?, - next_state: bytebuf.get_var_int()?.into(), + protocol_version: bytebuf.try_get_var_int()?, + server_address: bytebuf.try_get_string_len(255)?, + server_port: bytebuf.try_get_u16()?, + next_state: bytebuf.try_get_var_int()?.into(), }) } } diff --git a/pumpkin-protocol/src/server/login/s_cookie_response.rs b/pumpkin-protocol/src/server/login/s_cookie_response.rs index 8f70bfb5..5e498a1b 100644 --- a/pumpkin-protocol/src/server/login/s_cookie_response.rs +++ b/pumpkin-protocol/src/server/login/s_cookie_response.rs @@ -1,12 +1,16 @@ -use crate::bytebuf::{ByteBuffer, DeserializerError}; -use crate::{Identifier, ServerPacket, VarInt}; +use crate::{ + bytebuf::{ByteBuf, ReadingError}, + codec::identifier::Identifier, + ServerPacket, VarInt, +}; +use bytes::Buf; use pumpkin_macros::server_packet; use serde::de; #[server_packet("login:cookie_response")] /// Response to a Cookie Request (login) from the server. /// The Notchian server only accepts responses of up to 5 kiB in size. -pub struct SCookieResponse { +pub struct SLoginCookieResponse { pub key: Identifier, pub has_payload: bool, pub payload_length: Option, @@ -15,10 +19,10 @@ pub struct SCookieResponse { const MAX_PAYLOAD_SIZE: i32 = 5120; -impl ServerPacket for SCookieResponse { - fn read(bytebuf: &mut ByteBuffer) -> Result { - let key = bytebuf.get_string()?; - let has_payload = bytebuf.get_bool()?; +impl ServerPacket for SLoginCookieResponse { + fn read(bytebuf: &mut impl Buf) -> Result { + let key = bytebuf.try_get_identifer()?; + let has_payload = bytebuf.try_get_bool()?; if !has_payload { return Ok(Self { @@ -29,7 +33,7 @@ impl ServerPacket for SCookieResponse { }); } - let payload_length = bytebuf.get_var_int()?; + let payload_length = bytebuf.try_get_var_int()?; let length = payload_length.0; if length > MAX_PAYLOAD_SIZE { @@ -38,7 +42,7 @@ impl ServerPacket for SCookieResponse { )); } - let payload = bytebuf.copy_to_bytes(length as usize)?; + let payload = bytebuf.try_copy_to_bytes(length as usize)?; Ok(Self { key, diff --git a/pumpkin-protocol/src/server/login/s_encryption_response.rs b/pumpkin-protocol/src/server/login/s_encryption_response.rs index 64be02b4..f094231d 100644 --- a/pumpkin-protocol/src/server/login/s_encryption_response.rs +++ b/pumpkin-protocol/src/server/login/s_encryption_response.rs @@ -1,7 +1,8 @@ +use bytes::Buf; use pumpkin_macros::server_packet; use crate::{ - bytebuf::{ByteBuffer, DeserializerError}, + bytebuf::{ByteBuf, ReadingError}, ServerPacket, VarInt, }; @@ -14,11 +15,11 @@ pub struct SEncryptionResponse { } impl ServerPacket for SEncryptionResponse { - fn read(bytebuf: &mut ByteBuffer) -> Result { - let shared_secret_length = bytebuf.get_var_int()?; - let shared_secret = bytebuf.copy_to_bytes(shared_secret_length.0 as usize)?; - let verify_token_length = bytebuf.get_var_int()?; - let verify_token = bytebuf.copy_to_bytes(shared_secret_length.0 as usize)?; + fn read(bytebuf: &mut impl Buf) -> Result { + let shared_secret_length = bytebuf.try_get_var_int()?; + let shared_secret = bytebuf.try_copy_to_bytes(shared_secret_length.0 as usize)?; + let verify_token_length = bytebuf.try_get_var_int()?; + let verify_token = bytebuf.try_copy_to_bytes(shared_secret_length.0 as usize)?; Ok(Self { shared_secret_length, shared_secret, diff --git a/pumpkin-protocol/src/server/login/s_login_start.rs b/pumpkin-protocol/src/server/login/s_login_start.rs index 7a4a724d..88bda187 100644 --- a/pumpkin-protocol/src/server/login/s_login_start.rs +++ b/pumpkin-protocol/src/server/login/s_login_start.rs @@ -1,7 +1,8 @@ +use bytes::Buf; use pumpkin_macros::server_packet; use crate::{ - bytebuf::{ByteBuffer, DeserializerError}, + bytebuf::{ByteBuf, ReadingError}, ServerPacket, }; @@ -12,10 +13,10 @@ pub struct SLoginStart { } impl ServerPacket for SLoginStart { - fn read(bytebuf: &mut ByteBuffer) -> Result { + fn read(bytebuf: &mut impl Buf) -> Result { Ok(Self { - name: bytebuf.get_string_len(16)?, - uuid: bytebuf.get_uuid()?, + name: bytebuf.try_get_string_len(16)?, + uuid: bytebuf.try_get_uuid()?, }) } } diff --git a/pumpkin-protocol/src/server/login/s_plugin_response.rs b/pumpkin-protocol/src/server/login/s_plugin_response.rs index 24656471..dd92a1bb 100644 --- a/pumpkin-protocol/src/server/login/s_plugin_response.rs +++ b/pumpkin-protocol/src/server/login/s_plugin_response.rs @@ -1,21 +1,21 @@ use crate::{ - bytebuf::{ByteBuffer, DeserializerError}, + bytebuf::{ByteBuf, ReadingError}, ServerPacket, VarInt, }; -use bytes::BytesMut; +use bytes::{Buf, Bytes}; use pumpkin_macros::server_packet; #[server_packet("login:custom_query_answer")] pub struct SLoginPluginResponse { pub message_id: VarInt, - pub data: Option, + pub data: Option, } impl ServerPacket for SLoginPluginResponse { - fn read(bytebuf: &mut ByteBuffer) -> Result { + fn read(bytebuf: &mut impl Buf) -> Result { Ok(Self { - message_id: bytebuf.get_var_int()?, - data: bytebuf.get_option(|v| Ok(v.get_slice()))?, + message_id: bytebuf.try_get_var_int()?, + data: bytebuf.try_get_option(|v| v.try_copy_to_bytes(v.remaining()))?, }) } } diff --git a/pumpkin-protocol/src/server/play/s_chat_message.rs b/pumpkin-protocol/src/server/play/s_chat_message.rs index a8e61d74..c0049af2 100644 --- a/pumpkin-protocol/src/server/play/s_chat_message.rs +++ b/pumpkin-protocol/src/server/play/s_chat_message.rs @@ -1,8 +1,8 @@ -use bytes::Bytes; +use bytes::{Buf, Bytes}; use pumpkin_macros::server_packet; use crate::{ - bytebuf::{ByteBuffer, DeserializerError}, + bytebuf::{ByteBuf, ReadingError}, FixedBitSet, ServerPacket, VarInt, }; @@ -19,14 +19,14 @@ pub struct SChatMessage { // TODO impl ServerPacket for SChatMessage { - fn read(bytebuf: &mut ByteBuffer) -> Result { + fn read(bytebuf: &mut impl Buf) -> Result { Ok(Self { - message: bytebuf.get_string()?, - timestamp: bytebuf.get_i64()?, - salt: bytebuf.get_i64()?, - signature: bytebuf.get_option(|v| v.copy_to_bytes(256))?, - message_count: bytebuf.get_var_int()?, - acknowledged: bytebuf.get_fixed_bitset(20)?, + message: bytebuf.try_get_string()?, + timestamp: bytebuf.try_get_i64()?, + salt: bytebuf.try_get_i64()?, + signature: bytebuf.try_get_option(|v| v.try_copy_to_bytes(256))?, + message_count: bytebuf.try_get_var_int()?, + acknowledged: bytebuf.try_get_fixed_bitset(20)?, }) } } diff --git a/pumpkin-protocol/src/server/play/s_click_container.rs b/pumpkin-protocol/src/server/play/s_click_container.rs index 8de3f8a7..6dcf4abd 100644 --- a/pumpkin-protocol/src/server/play/s_click_container.rs +++ b/pumpkin-protocol/src/server/play/s_click_container.rs @@ -1,17 +1,18 @@ -use crate::slot::Slot; +use crate::codec::slot::Slot; use crate::VarInt; +use num_derive::FromPrimitive; +use num_traits::FromPrimitive; use pumpkin_macros::server_packet; use serde::de::SeqAccess; use serde::{de, Deserialize}; -#[derive(Debug)] #[server_packet("play:container_click")] pub struct SClickContainer { pub window_id: VarInt, pub state_id: VarInt, pub slot: i16, pub button: i8, - pub mode: VarInt, + pub mode: SlotActionType, pub length_of_array: VarInt, pub array_of_changed_slots: Vec<(i16, Slot)>, pub carried_item: Slot, @@ -73,7 +74,8 @@ impl<'de> Deserialize<'de> for SClickContainer { state_id, slot, button, - mode, + mode: SlotActionType::from_i32(mode.0) + .expect("Invalid Slot action, TODO better error handling ;D"), length_of_array, array_of_changed_slots, carried_item, @@ -84,3 +86,24 @@ impl<'de> Deserialize<'de> for SClickContainer { deserializer.deserialize_seq(Visitor) } } + +#[derive(Deserialize, FromPrimitive)] +pub enum SlotActionType { + /// Performs a normal slot click. This can pickup or place items in the slot, possibly merging the cursor stack into the slot, or swapping the slot stack with the cursor stack if they can't be merged. + Pickup, + /// Performs a shift-click. This usually quickly moves items between the player's inventory and the open screen handler. + QuickMove, + /// Exchanges items between a slot and a hotbar slot. This is usually triggered by the player pressing a 1-9 number key while hovering over a slot. + /// When the action type is swap, the click data is the hotbar slot to swap with (0-8). + Swap, + /// Clones the item in the slot. Usually triggered by middle clicking an item in creative mode. + Clone, + /// Throws the item out of the inventory. This is usually triggered by the player pressing Q while hovering over a slot, or clicking outside the window. + /// When the action type is throw, the click data determines whether to throw a whole stack (1) or a single item from that stack (0). + Throw, + /// Drags items between multiple slots. This is usually triggered by the player clicking and dragging between slots. + /// This action happens in 3 stages. Stage 0 signals that the drag has begun, and stage 2 signals that the drag has ended. In between multiple stage 1s signal which slots were dragged on. + QuickCraft, + /// Replenishes the cursor stack with items from the screen handler. This is usually triggered by the player double clicking + PickupAll, +} diff --git a/pumpkin-protocol/src/server/play/s_cookie_response.rs b/pumpkin-protocol/src/server/play/s_cookie_response.rs index b7ad9b70..7779d8ac 100644 --- a/pumpkin-protocol/src/server/play/s_cookie_response.rs +++ b/pumpkin-protocol/src/server/play/s_cookie_response.rs @@ -1,5 +1,9 @@ -use crate::bytebuf::{ByteBuffer, DeserializerError}; -use crate::{Identifier, ServerPacket, VarInt}; +use crate::{ + bytebuf::{ByteBuf, ReadingError}, + codec::identifier::Identifier, + ServerPacket, VarInt, +}; +use bytes::Buf; use pumpkin_macros::server_packet; use serde::de; @@ -16,9 +20,9 @@ pub struct SCookieResponse { const MAX_PAYLOAD_SIZE: i32 = 5120; impl ServerPacket for SCookieResponse { - fn read(bytebuf: &mut ByteBuffer) -> Result { - let key = bytebuf.get_string()?; - let has_payload = bytebuf.get_bool()?; + fn read(bytebuf: &mut impl Buf) -> Result { + let key = bytebuf.try_get_identifer()?; + let has_payload = bytebuf.try_get_bool()?; if !has_payload { return Ok(Self { @@ -29,7 +33,7 @@ impl ServerPacket for SCookieResponse { }); } - let payload_length = bytebuf.get_var_int()?; + let payload_length = bytebuf.try_get_var_int()?; let length = payload_length.0; if length > MAX_PAYLOAD_SIZE { @@ -38,7 +42,7 @@ impl ServerPacket for SCookieResponse { )); } - let payload = bytebuf.copy_to_bytes(length as usize)?; + let payload = bytebuf.try_copy_to_bytes(length as usize)?; Ok(Self { key, diff --git a/pumpkin-protocol/src/server/play/s_interact.rs b/pumpkin-protocol/src/server/play/s_interact.rs index f283f048..a6a3844d 100644 --- a/pumpkin-protocol/src/server/play/s_interact.rs +++ b/pumpkin-protocol/src/server/play/s_interact.rs @@ -1,9 +1,13 @@ +use bytes::Buf; use num_derive::FromPrimitive; use num_traits::FromPrimitive; use pumpkin_core::math::vector3::Vector3; use pumpkin_macros::server_packet; -use crate::{bytebuf::DeserializerError, ServerPacket, VarInt}; +use crate::{ + bytebuf::{ByteBuf, ReadingError}, + ServerPacket, VarInt, +}; #[server_packet("play:interact")] pub struct SInteract { @@ -16,27 +20,24 @@ pub struct SInteract { // Great job Mojang ;D impl ServerPacket for SInteract { - fn read( - bytebuf: &mut crate::bytebuf::ByteBuffer, - ) -> Result { - let entity_id = bytebuf.get_var_int()?; - let typ = bytebuf.get_var_int()?; - let action = ActionType::from_i32(typ.0).ok_or(DeserializerError::Message( - "invalid action type".to_string(), - ))?; + fn read(bytebuf: &mut impl Buf) -> Result { + let entity_id = bytebuf.try_get_var_int()?; + let typ = bytebuf.try_get_var_int()?; + let action = ActionType::from_i32(typ.0) + .ok_or(ReadingError::Message("invalid action type".to_string()))?; let target_position: Option> = match action { ActionType::Interact => None, ActionType::Attack => None, ActionType::InteractAt => Some(Vector3::new( - bytebuf.get_f32()?, - bytebuf.get_f32()?, - bytebuf.get_f32()?, + bytebuf.try_get_f32()?, + bytebuf.try_get_f32()?, + bytebuf.try_get_f32()?, )), }; let hand = match action { - ActionType::Interact => Some(bytebuf.get_var_int()?), + ActionType::Interact => Some(bytebuf.try_get_var_int()?), ActionType::Attack => None, - ActionType::InteractAt => Some(bytebuf.get_var_int()?), + ActionType::InteractAt => Some(bytebuf.try_get_var_int()?), }; Ok(Self { @@ -44,7 +45,7 @@ impl ServerPacket for SInteract { typ, target_position, hand, - sneaking: bytebuf.get_bool()?, + sneaking: bytebuf.try_get_bool()?, }) } } diff --git a/pumpkin-protocol/src/server/play/s_player_command.rs b/pumpkin-protocol/src/server/play/s_player_command.rs index aeafcf15..6a9b65a4 100644 --- a/pumpkin-protocol/src/server/play/s_player_command.rs +++ b/pumpkin-protocol/src/server/play/s_player_command.rs @@ -1,7 +1,11 @@ +use bytes::Buf; use num_derive::FromPrimitive; use pumpkin_macros::server_packet; -use crate::{bytebuf::DeserializerError, ServerPacket, VarInt}; +use crate::{ + bytebuf::{ByteBuf, ReadingError}, + ServerPacket, VarInt, +}; #[server_packet("play:player_command")] pub struct SPlayerCommand { @@ -23,11 +27,11 @@ pub enum Action { } impl ServerPacket for SPlayerCommand { - fn read(bytebuf: &mut crate::bytebuf::ByteBuffer) -> Result { + fn read(bytebuf: &mut impl Buf) -> Result { Ok(Self { - entity_id: bytebuf.get_var_int()?, - action: bytebuf.get_var_int()?, - jump_boost: bytebuf.get_var_int()?, + entity_id: bytebuf.try_get_var_int()?, + action: bytebuf.try_get_var_int()?, + jump_boost: bytebuf.try_get_var_int()?, }) } } diff --git a/pumpkin-protocol/src/server/play/s_set_creative_slot.rs b/pumpkin-protocol/src/server/play/s_set_creative_slot.rs index 0dff80b4..59835a43 100644 --- a/pumpkin-protocol/src/server/play/s_set_creative_slot.rs +++ b/pumpkin-protocol/src/server/play/s_set_creative_slot.rs @@ -1,6 +1,6 @@ use pumpkin_macros::server_packet; -use crate::slot::Slot; +use crate::codec::slot::Slot; #[derive(serde::Deserialize, Debug)] #[server_packet("play:set_creative_mode_slot")] diff --git a/pumpkin-protocol/src/var_int.rs b/pumpkin-protocol/src/var_int.rs deleted file mode 100644 index 7c461d1f..00000000 --- a/pumpkin-protocol/src/var_int.rs +++ /dev/null @@ -1,86 +0,0 @@ -use bytes::{Buf, BufMut}; -use thiserror::Error; - -use crate::VarIntType; - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct VarInt(pub VarIntType); - -impl VarInt { - /// The maximum number of bytes a `VarInt` - pub const MAX_SIZE: usize = 5; - - /// Returns the exact number of bytes this varint will write when - /// [`Encode::encode`] is called, assuming no error occurs. - pub const fn written_size(self) -> usize { - match self.0 { - 0 => 1, - n => (31 - n.leading_zeros() as usize) / 7 + 1, - } - } - - pub fn encode(&self, w: &mut impl BufMut) { - let mut val = self.0; - for _ in 0..Self::MAX_SIZE { - let b: u8 = val as u8 & 0b01111111; - val >>= 7; - w.put_u8(if val == 0 { b } else { b | 0b10000000 }); - if val == 0 { - break; - } - } - } - - pub fn decode(r: &mut impl Buf) -> Result { - let mut val = 0; - for i in 0..Self::MAX_SIZE { - if !r.has_remaining() { - return Err(VarIntDecodeError::Incomplete); - } - let byte = r.get_u8(); - val |= (i32::from(byte) & 0x7F) << (i * 7); - if byte & 0x80 == 0 { - return Ok(VarInt(val)); - } - } - Err(VarIntDecodeError::TooLarge) - } -} - -impl From for VarInt { - fn from(value: i32) -> Self { - VarInt(value) - } -} - -impl From for VarInt { - fn from(value: u32) -> Self { - VarInt(value as i32) - } -} - -impl From for VarInt { - fn from(value: u8) -> Self { - VarInt(value as i32) - } -} - -impl From for VarInt { - fn from(value: usize) -> Self { - VarInt(value as i32) - } -} - -impl From for i32 { - fn from(value: VarInt) -> Self { - value.0 - } -} - -#[derive(Copy, Clone, PartialEq, Eq, Debug, Error)] -pub enum VarIntDecodeError { - #[error("incomplete VarInt decode")] - Incomplete, - #[error("VarInt is too large")] - TooLarge, -} diff --git a/pumpkin-protocol/src/var_long.rs b/pumpkin-protocol/src/var_long.rs deleted file mode 100644 index 3f33ca77..00000000 --- a/pumpkin-protocol/src/var_long.rs +++ /dev/null @@ -1,87 +0,0 @@ -use bytes::{Buf, BufMut}; -use thiserror::Error; - -use crate::VarLongType; - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct VarLong(pub VarLongType); - -impl VarLong { - /// The maximum number of bytes a `VarLong` - pub const MAX_SIZE: usize = 10; - - /// Returns the exact number of bytes this varlong will write when - /// [`Encode::encode`] is called, assuming no error occurs. - pub const fn written_size(self) -> usize { - match self.0 { - 0 => 1, - n => (31 - n.leading_zeros() as usize) / 7 + 1, - } - } - - pub fn encode(&self, w: &mut impl BufMut) { - let mut x = self.0; - for _ in 0..Self::MAX_SIZE { - let byte = (x & 0x7F) as u8; - x >>= 7; - if x == 0 { - w.put_slice(&[byte]); - break; - } - w.put_slice(&[byte | 0x80]); - } - } - - pub fn decode(r: &mut impl Buf) -> Result { - let mut val = 0; - for i in 0..Self::MAX_SIZE { - if !r.has_remaining() { - return Err(VarLongDecodeError::Incomplete); - } - let byte = r.get_u8(); - val |= (i64::from(byte) & 0b01111111) << (i * 7); - if byte & 0b10000000 == 0 { - return Ok(VarLong(val)); - } - } - Err(VarLongDecodeError::TooLarge) - } -} - -impl From for VarLong { - fn from(value: i64) -> Self { - VarLong(value) - } -} - -impl From for VarLong { - fn from(value: u32) -> Self { - VarLong(value as i64) - } -} - -impl From for VarLong { - fn from(value: u8) -> Self { - VarLong(value as i64) - } -} - -impl From for VarLong { - fn from(value: usize) -> Self { - VarLong(value as i64) - } -} - -impl From for i64 { - fn from(value: VarLong) -> Self { - value.0 - } -} - -#[derive(Copy, Clone, PartialEq, Eq, Debug, Error)] -pub enum VarLongDecodeError { - #[error("incomplete VarLong decode")] - Incomplete, - #[error("VarLong is too large")] - TooLarge, -} diff --git a/pumpkin-registry/Cargo.toml b/pumpkin-registry/Cargo.toml index 3d55b6bb..a90646d7 100644 --- a/pumpkin-registry/Cargo.toml +++ b/pumpkin-registry/Cargo.toml @@ -13,9 +13,3 @@ indexmap = { version = "2.7.0", features = ["serde"] } serde.workspace = true serde_json.workspace = true -rayon.workspace = true - -num-traits.workspace = true -num-derive.workspace = true - -itertools.workspace = true diff --git a/pumpkin-registry/src/banner_pattern.rs b/pumpkin-registry/src/banner_pattern.rs index bac63fda..da631c33 100644 --- a/pumpkin-registry/src/banner_pattern.rs +++ b/pumpkin-registry/src/banner_pattern.rs @@ -1,7 +1,8 @@ +use pumpkin_protocol::codec::identifier::Identifier; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct BannerPattern { - asset_id: String, + asset_id: Identifier, translation_key: String, } diff --git a/pumpkin-registry/src/biome.rs b/pumpkin-registry/src/biome.rs index cbe03b41..e2d52253 100644 --- a/pumpkin-registry/src/biome.rs +++ b/pumpkin-registry/src/biome.rs @@ -1,4 +1,4 @@ -use pumpkin_protocol::VarInt; +use pumpkin_protocol::codec::var_int::VarInt; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Serialize, Deserialize)] diff --git a/pumpkin-registry/src/lib.rs b/pumpkin-registry/src/lib.rs index 6960a183..80786b17 100644 --- a/pumpkin-registry/src/lib.rs +++ b/pumpkin-registry/src/lib.rs @@ -10,7 +10,7 @@ use indexmap::IndexMap; use instrument::Instrument; use jukebox_song::JukeboxSong; use paint::Painting; -use pumpkin_protocol::client::config::RegistryEntry; +use pumpkin_protocol::{client::config::RegistryEntry, codec::identifier::Identifier}; pub use recipe::{ flatten_3x3, IngredientSlot, IngredientType, Recipe, RecipeResult, RecipeType, RECIPES, }; @@ -41,8 +41,8 @@ pub static SYNCED_REGISTRIES: LazyLock = LazyLock::new(|| { }); pub struct Registry { - pub registry_id: String, - pub registry_entries: Vec>, + pub registry_id: Identifier, + pub registry_entries: Vec, } #[derive(Serialize, Deserialize)] @@ -78,12 +78,12 @@ struct DataPool { } impl DimensionType { - pub fn name(&self) -> &str { + pub fn name(&self) -> Identifier { match self { - Self::Overworld => "minecraft:overworld", - Self::OverworldCaves => "minecraft:overworld_caves", - Self::TheEnd => "minecraft:the_end", - Self::TheNether => "minecraft:the_nether", + Self::Overworld => Identifier::vanilla("overworld"), + Self::OverworldCaves => Identifier::vanilla("overworld_caves"), + Self::TheEnd => Identifier::vanilla("the_end"), + Self::TheNether => Identifier::vanilla("the_nether"), } } } @@ -94,12 +94,12 @@ impl Registry { .biome .iter() .map(|s| RegistryEntry { - entry_id: s.0, - data: pumpkin_nbt::serializer::to_bytes_unnamed(&s.1).unwrap(), + entry_id: Identifier::vanilla(s.0), + data: Some(pumpkin_nbt::serializer::to_bytes_unnamed(&s.1).unwrap()), }) .collect(); let biome = Registry { - registry_id: "minecraft:worldgen/biome".to_string(), + registry_id: Identifier::vanilla("worldgen/biome"), registry_entries, }; @@ -107,12 +107,12 @@ impl Registry { .chat_type .iter() .map(|s| RegistryEntry { - entry_id: s.0, - data: pumpkin_nbt::serializer::to_bytes_unnamed(&s.1).unwrap(), + entry_id: Identifier::vanilla(s.0), + data: Some(pumpkin_nbt::serializer::to_bytes_unnamed(&s.1).unwrap()), }) .collect(); let chat_type = Registry { - registry_id: "minecraft:chat_type".to_string(), + registry_id: Identifier::vanilla("chat_type"), registry_entries, }; @@ -120,7 +120,7 @@ impl Registry { // .trim_pattern // .iter() // .map(|s| RegistryEntry { - // entry_id: s.0, + // entry_id: Identifier::vanilla(s.0), // data: pumpkin_nbt::serializer::to_bytes_unnamed(&s.1).unwrap(), // }) // .collect(); @@ -133,7 +133,7 @@ impl Registry { // .trim_material // .iter() // .map(|s| RegistryEntry { - // entry_id: s.0, + // entry_id: Identifier::vanilla(s.0), // data: pumpkin_nbt::serializer::to_bytes_unnamed(&s.1).unwrap(), // }) // .collect(); @@ -148,13 +148,13 @@ impl Registry { .map(|s| { let variant = s.1.clone(); RegistryEntry { - entry_id: s.0, - data: pumpkin_nbt::serializer::to_bytes_unnamed(&variant).unwrap(), + entry_id: Identifier::vanilla(s.0), + data: Some(pumpkin_nbt::serializer::to_bytes_unnamed(&variant).unwrap()), } }) .collect(); let wolf_variant = Registry { - registry_id: "minecraft:wolf_variant".to_string(), + registry_id: Identifier::vanilla("wolf_variant"), registry_entries, }; @@ -162,12 +162,12 @@ impl Registry { .painting_variant .iter() .map(|s| RegistryEntry { - entry_id: s.0, - data: pumpkin_nbt::serializer::to_bytes_unnamed(&s.1).unwrap(), + entry_id: Identifier::vanilla(s.0), + data: Some(pumpkin_nbt::serializer::to_bytes_unnamed(&s.1).unwrap()), }) .collect(); let painting_variant = Registry { - registry_id: "minecraft:painting_variant".to_string(), + registry_id: Identifier::vanilla("painting_variant"), registry_entries, }; @@ -175,12 +175,12 @@ impl Registry { .dimension_type .iter() .map(|s| RegistryEntry { - entry_id: s.0, - data: pumpkin_nbt::serializer::to_bytes_unnamed(&s.1).unwrap(), + entry_id: Identifier::vanilla(s.0), + data: Some(pumpkin_nbt::serializer::to_bytes_unnamed(&s.1).unwrap()), }) .collect(); let dimension_type = Registry { - registry_id: "minecraft:dimension_type".to_string(), + registry_id: Identifier::vanilla("dimension_type"), registry_entries, }; @@ -188,12 +188,12 @@ impl Registry { .damage_type .iter() .map(|s| RegistryEntry { - entry_id: s.0, - data: pumpkin_nbt::serializer::to_bytes_unnamed(&s.1).unwrap(), + entry_id: Identifier::vanilla(s.0), + data: Some(pumpkin_nbt::serializer::to_bytes_unnamed(&s.1).unwrap()), }) .collect(); let damage_type = Registry { - registry_id: "minecraft:damage_type".to_string(), + registry_id: Identifier::vanilla("damage_type"), registry_entries, }; @@ -201,12 +201,12 @@ impl Registry { .banner_pattern .iter() .map(|s| RegistryEntry { - entry_id: s.0, - data: pumpkin_nbt::serializer::to_bytes_unnamed(&s.1).unwrap(), + entry_id: Identifier::vanilla(s.0), + data: Some(pumpkin_nbt::serializer::to_bytes_unnamed(&s.1).unwrap()), }) .collect(); let banner_pattern = Registry { - registry_id: "minecraft:banner_pattern".to_string(), + registry_id: Identifier::vanilla("banner_pattern"), registry_entries, }; @@ -215,7 +215,7 @@ impl Registry { // .enchantment // .iter() // .map(|s| RegistryEntry { - // entry_id: s.0, + // entry_id: Identifier::vanilla(s.0), // data: pumpkin_nbt::serializer::to_bytes_unnamed(&s.1).unwrap(), // }) // .collect(); @@ -228,12 +228,12 @@ impl Registry { .jukebox_song .iter() .map(|s| RegistryEntry { - entry_id: s.0, - data: pumpkin_nbt::serializer::to_bytes_unnamed(&s.1).unwrap(), + entry_id: Identifier::vanilla(s.0), + data: Some(pumpkin_nbt::serializer::to_bytes_unnamed(&s.1).unwrap()), }) .collect(); let jukebox_song = Registry { - registry_id: "minecraft:jukebox_song".to_string(), + registry_id: Identifier::vanilla("jukebox_song"), registry_entries, }; @@ -241,7 +241,7 @@ impl Registry { // .instrument // .iter() // .map(|s| RegistryEntry { - // entry_id: s.0, + // entry_id: Identifier::vanilla(s.0), // data: pumpkin_nbt::serializer::to_bytes_unnamed(&s.1).unwrap(), // }) // .collect(); diff --git a/pumpkin-registry/src/paint.rs b/pumpkin-registry/src/paint.rs index 9c383f6f..1217798e 100644 --- a/pumpkin-registry/src/paint.rs +++ b/pumpkin-registry/src/paint.rs @@ -1,8 +1,9 @@ +use pumpkin_protocol::codec::identifier::Identifier; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Painting { - asset_id: String, + asset_id: Identifier, // #[serde(skip_serializing_if = "Option::is_none")] // title: Option>, // #[serde(skip_serializing_if = "Option::is_none")] diff --git a/pumpkin-registry/src/recipe/recipe_formats.rs b/pumpkin-registry/src/recipe/recipe_formats.rs index 7c26376a..5726c282 100644 --- a/pumpkin-registry/src/recipe/recipe_formats.rs +++ b/pumpkin-registry/src/recipe/recipe_formats.rs @@ -2,8 +2,6 @@ use super::super::recipe::RecipeType; use super::read::{ ingredients::IngredientSlot, CraftingType, RecipeKeys, RecipeResult, RecipeTrait, }; -use itertools::Itertools; - pub struct ShapedCrafting { keys: RecipeKeys, pattern: [[Option; 3]; 3], @@ -85,7 +83,7 @@ impl RecipeTrait for ShapelessCrafting { [v1, v2, v3] }) - .collect_vec() + .collect() } fn result(self) -> RecipeResult { diff --git a/pumpkin-registry/src/trim_pattern.rs b/pumpkin-registry/src/trim_pattern.rs index 48746df1..62f07daa 100644 --- a/pumpkin-registry/src/trim_pattern.rs +++ b/pumpkin-registry/src/trim_pattern.rs @@ -1,8 +1,9 @@ +use pumpkin_protocol::codec::identifier::Identifier; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct TrimPattern { - asset_id: String, + asset_id: Identifier, template_item: String, // description: TextComponent<'static>, decal: bool, diff --git a/pumpkin-world/Cargo.toml b/pumpkin-world/Cargo.toml index 18745f73..495ee3ff 100644 --- a/pumpkin-world/Cargo.toml +++ b/pumpkin-world/Cargo.toml @@ -4,6 +4,7 @@ version.workspace = true edition.workspace = true [dependencies] +pumpkin-nbt = { path = "../pumpkin-nbt" } pumpkin-core = { path = "../pumpkin-core" } pumpkin-config = { path = "../pumpkin-config" } pumpkin-macros = { path = "../pumpkin-macros" } @@ -11,24 +12,20 @@ pumpkin-macros = { path = "../pumpkin-macros" } tokio.workspace = true rayon.workspace = true derive_more.workspace = true -itertools.workspace = true thiserror.workspace = true serde.workspace = true serde_json.workspace = true log.workspace = true -parking_lot.workspace = true num-traits.workspace = true num-derive.workspace = true -futures = "0.3" dashmap = "6.1.0" -# Compression +# Compression flate2 = "1.0" lz4 = "1.28.0" enum_dispatch = "0.3.13" -derive-getters = "0.5.0" fastnbt = { git = "https://github.com/owengage/fastnbt.git" } @@ -39,7 +36,6 @@ rand = "0.8.5" [dev-dependencies] criterion = { version = "0.5.1", features = ["html_reports"] } - [[bench]] name = "chunk_noise" harness = false diff --git a/pumpkin-world/src/chunk/anvil.rs b/pumpkin-world/src/chunk/anvil.rs index c97142ac..2391537f 100644 --- a/pumpkin-world/src/chunk/anvil.rs +++ b/pumpkin-world/src/chunk/anvil.rs @@ -4,9 +4,8 @@ use std::{ }; use flate2::bufread::{GzDecoder, ZlibDecoder}; -use itertools::Itertools; -use crate::level::SaveFile; +use crate::level::LevelFolder; use super::{ChunkData, ChunkReader, ChunkReadingError, CompressionError}; @@ -88,7 +87,7 @@ impl Compression { impl ChunkReader for AnvilChunkReader { fn read_chunk( &self, - save_file: &SaveFile, + save_file: &LevelFolder, at: &pumpkin_core::math::vector2::Vector2, ) -> Result { let region = (at.x >> 5, at.z >> 5); @@ -143,7 +142,7 @@ impl ChunkReader for AnvilChunkReader { }; // TODO: check checksum to make sure chunk is not corrupted - let header = file_buf.drain(0..5).collect_vec(); + let header: Vec = file_buf.drain(0..5).collect(); let compression = Compression::from_byte(header[4]).ok_or( ChunkReadingError::Compression(CompressionError::UnknownCompression), @@ -152,7 +151,7 @@ impl ChunkReader for AnvilChunkReader { let size = u32::from_be_bytes(header[..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 chunk_data = file_buf.drain(0..size as usize - 1).collect(); let decompressed_chunk = compression .decompress_data(chunk_data) .map_err(ChunkReadingError::Compression)?; @@ -169,14 +168,14 @@ mod tests { use crate::{ chunk::{anvil::AnvilChunkReader, ChunkReader, ChunkReadingError}, - level::SaveFile, + level::LevelFolder, }; #[test] fn not_existing() { let region_path = PathBuf::from("not_existing"); let result = AnvilChunkReader::new().read_chunk( - &SaveFile { + &LevelFolder { root_folder: PathBuf::from(""), region_folder: region_path, }, diff --git a/pumpkin-world/src/chunk/mod.rs b/pumpkin-world/src/chunk/mod.rs index fcbb53f1..8473e030 100644 --- a/pumpkin-world/src/chunk/mod.rs +++ b/pumpkin-world/src/chunk/mod.rs @@ -9,7 +9,7 @@ use thiserror::Error; use crate::{ block::BlockState, coordinates::{ChunkRelativeBlockCoordinates, Height}, - level::SaveFile, + level::LevelFolder, WORLD_HEIGHT, }; @@ -22,7 +22,7 @@ const CHUNK_VOLUME: usize = CHUNK_AREA * WORLD_HEIGHT; pub trait ChunkReader: Sync + Send { fn read_chunk( &self, - save_file: &SaveFile, + save_file: &LevelFolder, at: &Vector2, ) -> Result; } diff --git a/pumpkin-world/src/cylindrical_chunk_iterator.rs b/pumpkin-world/src/cylindrical_chunk_iterator.rs index 67d1f498..d8b65fe3 100644 --- a/pumpkin-world/src/cylindrical_chunk_iterator.rs +++ b/pumpkin-world/src/cylindrical_chunk_iterator.rs @@ -1,14 +1,15 @@ -use itertools::Itertools; +use std::num::NonZeroU8; + use pumpkin_core::math::vector2::Vector2; #[derive(Debug, Clone, Copy, PartialEq)] pub struct Cylindrical { pub center: Vector2, - pub view_distance: u8, + pub view_distance: NonZeroU8, } impl Cylindrical { - pub fn new(center: Vector2, view_distance: u8) -> Self { + pub fn new(center: Vector2, view_distance: NonZeroU8) -> Self { Self { center, view_distance, @@ -37,19 +38,19 @@ impl Cylindrical { } fn left(&self) -> i32 { - self.center.x - self.view_distance as i32 - 1 + self.center.x - self.view_distance.get() as i32 - 1 } fn bottom(&self) -> i32 { - self.center.z - self.view_distance as i32 - 1 + self.center.z - self.view_distance.get() as i32 - 1 } fn right(&self) -> i32 { - self.center.x + self.view_distance as i32 + 1 + self.center.x + self.view_distance.get() as i32 + 1 } fn top(&self) -> i32 { - self.center.z + self.view_distance as i32 + 1 + self.center.z + self.view_distance.get() as i32 + 1 } fn is_within_distance(&self, x: i32, z: i32) -> bool { @@ -60,7 +61,7 @@ impl Cylindrical { let min_leg = rel_x.min(rel_z) as i64; let hyp_sqr = max_leg * max_leg + min_leg * min_leg; - hyp_sqr < (self.view_distance as i64 * self.view_distance as i64) + hyp_sqr < (self.view_distance.get() as i64 * self.view_distance.get() as i64) } /// Returns an iterator of all chunks within this cylinder @@ -76,19 +77,21 @@ impl Cylindrical { all_chunks .into_iter() .filter(|chunk| self.is_within_distance(chunk.x, chunk.z)) - .collect_vec() + .collect() } } #[cfg(test)] mod test { + use std::num::NonZeroU8; + use super::Cylindrical; use pumpkin_core::math::vector2::Vector2; #[test] fn test_bounds() { - let cylinder = Cylindrical::new(Vector2::new(0, 0), 10); + let cylinder = Cylindrical::new(Vector2::new(0, 0), unsafe { NonZeroU8::new_unchecked(1) }); for chunk in cylinder.all_chunks_within() { assert!(chunk.x >= cylinder.left() && chunk.x <= cylinder.right()); assert!(chunk.z >= cylinder.bottom() && chunk.z <= cylinder.top()); diff --git a/pumpkin-world/src/world_gen/aquifer_sampler.rs b/pumpkin-world/src/generation/aquifer_sampler.rs similarity index 99% rename from pumpkin-world/src/world_gen/aquifer_sampler.rs rename to pumpkin-world/src/generation/aquifer_sampler.rs index ebe4d6ac..8dfb4646 100644 --- a/pumpkin-world/src/world_gen/aquifer_sampler.rs +++ b/pumpkin-world/src/generation/aquifer_sampler.rs @@ -661,7 +661,7 @@ mod test { use crate::{ block::BlockState, - world_gen::{ + generation::{ chunk_noise::{ BlockStateSampler, ChunkNoiseDensityFunctions, ChunkNoiseGenerator, ChunkNoiseState, LAVA_BLOCK, WATER_BLOCK, diff --git a/pumpkin-world/src/world_gen/blender/mod.rs b/pumpkin-world/src/generation/blender/mod.rs similarity index 100% rename from pumpkin-world/src/world_gen/blender/mod.rs rename to pumpkin-world/src/generation/blender/mod.rs diff --git a/pumpkin-world/src/world_gen/chunk_noise.rs b/pumpkin-world/src/generation/chunk_noise.rs similarity index 99% rename from pumpkin-world/src/world_gen/chunk_noise.rs rename to pumpkin-world/src/generation/chunk_noise.rs index 5ff2bb27..cab1abf0 100644 --- a/pumpkin-world/src/world_gen/chunk_noise.rs +++ b/pumpkin-world/src/generation/chunk_noise.rs @@ -6,11 +6,11 @@ use pumpkin_macros::block_state; use crate::{ block::BlockState, - match_ref_implementations, - world_gen::{ + generation::{ noise::{density::basic::WrapperType, lerp3}, section_coords, }, + match_ref_implementations, }; use super::{ @@ -1379,7 +1379,7 @@ impl ChunkNoiseGenerator { mod test { use pumpkin_core::math::vector2::Vector2; - use crate::world_gen::{ + use crate::generation::{ aquifer_sampler::{FluidLevel, FluidLevelSampler}, generation_shapes::GenerationShape, noise::{config::NoiseConfig, router::OVERWORLD_NOISE_ROUTER}, diff --git a/pumpkin-world/src/world_gen/generation_shapes.rs b/pumpkin-world/src/generation/generation_shapes.rs similarity index 100% rename from pumpkin-world/src/world_gen/generation_shapes.rs rename to pumpkin-world/src/generation/generation_shapes.rs diff --git a/pumpkin-world/src/world_gen/generator.rs b/pumpkin-world/src/generation/generator.rs similarity index 97% rename from pumpkin-world/src/world_gen/generator.rs rename to pumpkin-world/src/generation/generator.rs index 8a0120c7..a570c8c3 100644 --- a/pumpkin-world/src/world_gen/generator.rs +++ b/pumpkin-world/src/generation/generator.rs @@ -6,7 +6,7 @@ use crate::biome::Biome; use crate::block::block_state::BlockState; use crate::chunk::{ChunkBlocks, ChunkData}; use crate::coordinates::{BlockCoordinates, ChunkRelativeBlockCoordinates, XZBlockCoordinates}; -use crate::world_gen::Seed; +use crate::generation::Seed; pub trait GeneratorInit { fn new(seed: Seed) -> Self; diff --git a/pumpkin-world/src/world_gen/generic_generator.rs b/pumpkin-world/src/generation/generic_generator.rs similarity index 100% rename from pumpkin-world/src/world_gen/generic_generator.rs rename to pumpkin-world/src/generation/generic_generator.rs diff --git a/pumpkin-world/src/world_gen/height_limit.rs b/pumpkin-world/src/generation/height_limit.rs similarity index 100% rename from pumpkin-world/src/world_gen/height_limit.rs rename to pumpkin-world/src/generation/height_limit.rs diff --git a/pumpkin-world/src/world_gen/implementation/mod.rs b/pumpkin-world/src/generation/implementation/mod.rs similarity index 100% rename from pumpkin-world/src/world_gen/implementation/mod.rs rename to pumpkin-world/src/generation/implementation/mod.rs diff --git a/pumpkin-world/src/world_gen/implementation/overworld/biome/mod.rs b/pumpkin-world/src/generation/implementation/overworld/biome/mod.rs similarity index 100% rename from pumpkin-world/src/world_gen/implementation/overworld/biome/mod.rs rename to pumpkin-world/src/generation/implementation/overworld/biome/mod.rs diff --git a/pumpkin-world/src/world_gen/implementation/overworld/biome/plains.rs b/pumpkin-world/src/generation/implementation/overworld/biome/plains.rs similarity index 99% rename from pumpkin-world/src/world_gen/implementation/overworld/biome/plains.rs rename to pumpkin-world/src/generation/implementation/overworld/biome/plains.rs index dde27c9c..775d3e5f 100644 --- a/pumpkin-world/src/world_gen/implementation/overworld/biome/plains.rs +++ b/pumpkin-world/src/generation/implementation/overworld/biome/plains.rs @@ -7,7 +7,7 @@ use crate::{ biome::Biome, chunk::ChunkBlocks, coordinates::{BlockCoordinates, ChunkRelativeBlockCoordinates, XZBlockCoordinates}, - world_gen::{ + generation::{ generator::{BiomeGenerator, GeneratorInit, PerlinTerrainGenerator}, generic_generator::GenericGenerator, Seed, diff --git a/pumpkin-world/src/world_gen/implementation/overworld/mod.rs b/pumpkin-world/src/generation/implementation/overworld/mod.rs similarity index 100% rename from pumpkin-world/src/world_gen/implementation/overworld/mod.rs rename to pumpkin-world/src/generation/implementation/overworld/mod.rs diff --git a/pumpkin-world/src/world_gen/implementation/superflat.rs b/pumpkin-world/src/generation/implementation/superflat.rs similarity index 98% rename from pumpkin-world/src/world_gen/implementation/superflat.rs rename to pumpkin-world/src/generation/implementation/superflat.rs index 2ec9fdad..4e12eba7 100644 --- a/pumpkin-world/src/world_gen/implementation/superflat.rs +++ b/pumpkin-world/src/generation/implementation/superflat.rs @@ -4,7 +4,7 @@ use crate::{ biome::Biome, block::block_state::BlockState, coordinates::XZBlockCoordinates, - world_gen::{ + generation::{ generator::{BiomeGenerator, GeneratorInit, TerrainGenerator}, generic_generator::GenericGenerator, Seed, diff --git a/pumpkin-world/src/world_gen/implementation/test.rs b/pumpkin-world/src/generation/implementation/test.rs similarity index 99% rename from pumpkin-world/src/world_gen/implementation/test.rs rename to pumpkin-world/src/generation/implementation/test.rs index 6b360c45..3a1f08f1 100644 --- a/pumpkin-world/src/world_gen/implementation/test.rs +++ b/pumpkin-world/src/generation/implementation/test.rs @@ -14,7 +14,7 @@ use crate::{ coordinates::{ ChunkRelativeBlockCoordinates, ChunkRelativeXZBlockCoordinates, XZBlockCoordinates, }, - world_gen::{ + generation::{ generator::{BiomeGenerator, GeneratorInit, TerrainGenerator}, proto_chunk::ProtoChunk, Seed, WorldGenerator, @@ -115,7 +115,7 @@ impl TerrainGenerator for TestTerrainGenerator { let entry = self.chunks.entry(*at); match entry { Entry::Vacant(entry) => { - let mut proto_chunk = ProtoChunk::new(*at, self.seed.0 as u64); + let mut proto_chunk = ProtoChunk::new(*at, self.seed.0); //let inst = std::time::Instant::now(); //println!("Populating chunk: {:?}", at); proto_chunk.populate_noise(); diff --git a/pumpkin-world/src/world_gen/mod.rs b/pumpkin-world/src/generation/mod.rs similarity index 100% rename from pumpkin-world/src/world_gen/mod.rs rename to pumpkin-world/src/generation/mod.rs diff --git a/pumpkin-world/src/world_gen/noise/config.rs b/pumpkin-world/src/generation/noise/config.rs similarity index 99% rename from pumpkin-world/src/world_gen/noise/config.rs rename to pumpkin-world/src/generation/noise/config.rs index 4ce194ee..a1e16a0f 100644 --- a/pumpkin-world/src/world_gen/noise/config.rs +++ b/pumpkin-world/src/generation/noise/config.rs @@ -151,8 +151,7 @@ mod test { }; use crate::{ - read_data_from_file, - world_gen::noise::{ + generation::noise::{ config::NoiseConfig, density::{ built_in_density_function::{ @@ -165,6 +164,7 @@ mod test { }, router::OVERWORLD_NOISE_ROUTER, }, + read_data_from_file, }; use super::LegacyChunkNoiseVisitor; diff --git a/pumpkin-world/src/world_gen/noise/density/basic.rs b/pumpkin-world/src/generation/noise/density/basic.rs similarity index 99% rename from pumpkin-world/src/world_gen/noise/density/basic.rs rename to pumpkin-world/src/generation/noise/density/basic.rs index cb118a4a..a96be636 100644 --- a/pumpkin-world/src/world_gen/noise/density/basic.rs +++ b/pumpkin-world/src/generation/noise/density/basic.rs @@ -1,6 +1,6 @@ use std::{hash::Hash, marker::PhantomData}; -use crate::{match_ref_implementations, world_gen::noise::clamped_map}; +use crate::{generation::noise::clamped_map, match_ref_implementations}; use super::{ component_functions::{ @@ -419,10 +419,10 @@ mod test { use std::{fs, path::Path}; use crate::{ - read_data_from_file, - world_gen::noise::density::{ + generation::noise::density::{ component_functions::ImmutableComponentFunctionImpl, NoisePos, UnblendedNoisePos, }, + read_data_from_file, }; use super::YClampedFunction; diff --git a/pumpkin-world/src/world_gen/noise/density/blend.rs b/pumpkin-world/src/generation/noise/density/blend.rs similarity index 99% rename from pumpkin-world/src/world_gen/noise/density/blend.rs rename to pumpkin-world/src/generation/noise/density/blend.rs index 309fa345..bf6bbb70 100644 --- a/pumpkin-world/src/world_gen/noise/density/blend.rs +++ b/pumpkin-world/src/generation/noise/density/blend.rs @@ -1,6 +1,6 @@ use std::marker::PhantomData; -use crate::world_gen::blender::BlenderImpl; +use crate::generation::blender::BlenderImpl; use super::{ component_functions::{ diff --git a/pumpkin-world/src/world_gen/noise/density/component_functions.rs b/pumpkin-world/src/generation/noise/density/component_functions.rs similarity index 99% rename from pumpkin-world/src/world_gen/noise/density/component_functions.rs rename to pumpkin-world/src/generation/noise/density/component_functions.rs index 6c47665e..036c4eba 100644 --- a/pumpkin-world/src/world_gen/noise/density/component_functions.rs +++ b/pumpkin-world/src/generation/noise/density/component_functions.rs @@ -953,7 +953,7 @@ mod test { use pumpkin_core::random::{legacy_rand::LegacyRand, RandomDeriver, RandomImpl}; - use crate::world_gen::noise::{ + use crate::generation::noise::{ built_in_noise_params, density::{ noise::{InternalNoise, NoiseFunction}, diff --git a/pumpkin-world/src/world_gen/noise/density/end.rs b/pumpkin-world/src/generation/noise/density/end.rs similarity index 97% rename from pumpkin-world/src/world_gen/noise/density/end.rs rename to pumpkin-world/src/generation/noise/density/end.rs index 39ee37bc..c1c3db33 100644 --- a/pumpkin-world/src/world_gen/noise/density/end.rs +++ b/pumpkin-world/src/generation/noise/density/end.rs @@ -1,6 +1,6 @@ use pumpkin_core::random::{legacy_rand::LegacyRand, RandomImpl}; -use crate::world_gen::noise::simplex::SimplexNoiseSampler; +use crate::generation::noise::simplex::SimplexNoiseSampler; use super::{ component_functions::{ diff --git a/pumpkin-world/src/world_gen/noise/density/math.rs b/pumpkin-world/src/generation/noise/density/math.rs similarity index 100% rename from pumpkin-world/src/world_gen/noise/density/math.rs rename to pumpkin-world/src/generation/noise/density/math.rs diff --git a/pumpkin-world/src/world_gen/noise/density/mod.rs b/pumpkin-world/src/generation/noise/density/mod.rs similarity index 99% rename from pumpkin-world/src/world_gen/noise/density/mod.rs rename to pumpkin-world/src/generation/noise/density/mod.rs index ecec8350..e789b3bd 100644 --- a/pumpkin-world/src/world_gen/noise/density/mod.rs +++ b/pumpkin-world/src/generation/noise/density/mod.rs @@ -10,7 +10,7 @@ use component_functions::{ use enum_dispatch::enum_dispatch; use noise::{InternalNoise, NoiseFunction}; -use crate::world_gen::{blender::Blender, chunk_noise::ChunkNoisePos}; +use crate::generation::{blender::Blender, chunk_noise::ChunkNoisePos}; use super::perlin::DoublePerlinNoiseParameters; @@ -76,8 +76,8 @@ pub trait NoisePosImpl { pub mod built_in_density_function { use std::sync::{Arc, LazyLock}; - use crate::world_gen::noise::built_in_noise_params::{self}; - use crate::world_gen::positions::{MAX_COLUMN_HEIGHT, MIN_HEIGHT}; + use crate::generation::noise::built_in_noise_params::{self}; + use crate::generation::positions::{MAX_COLUMN_HEIGHT, MIN_HEIGHT}; use pumpkin_core::math::floor_div; @@ -879,7 +879,7 @@ mod test { legacy_rand::LegacyRand, RandomDeriver, RandomDeriverImpl, RandomImpl, }; - use crate::world_gen::noise::{ + use crate::generation::noise::{ built_in_noise_params, density::{built_in_density_function::*, NoisePos, UnblendedNoisePos}, perlin::DoublePerlinNoiseSampler, diff --git a/pumpkin-world/src/world_gen/noise/density/noise.rs b/pumpkin-world/src/generation/noise/density/noise.rs similarity index 99% rename from pumpkin-world/src/world_gen/noise/density/noise.rs rename to pumpkin-world/src/generation/noise/density/noise.rs index 353273b4..1fe13937 100644 --- a/pumpkin-world/src/world_gen/noise/density/noise.rs +++ b/pumpkin-world/src/generation/noise/density/noise.rs @@ -3,11 +3,11 @@ use std::{marker::PhantomData, sync::Arc}; use pumpkin_core::random::{xoroshiro128::Xoroshiro, RandomGenerator, RandomImpl}; use crate::{ - match_ref_implementations, - world_gen::noise::{ + generation::noise::{ clamped_lerp, perlin::{DoublePerlinNoiseParameters, DoublePerlinNoiseSampler, OctavePerlinNoiseSampler}, }, + match_ref_implementations, }; use super::{ diff --git a/pumpkin-world/src/world_gen/noise/density/offset.rs b/pumpkin-world/src/generation/noise/density/offset.rs similarity index 100% rename from pumpkin-world/src/world_gen/noise/density/offset.rs rename to pumpkin-world/src/generation/noise/density/offset.rs diff --git a/pumpkin-world/src/world_gen/noise/density/spline.rs b/pumpkin-world/src/generation/noise/density/spline.rs similarity index 99% rename from pumpkin-world/src/world_gen/noise/density/spline.rs rename to pumpkin-world/src/generation/noise/density/spline.rs index 9f469818..142079a9 100644 --- a/pumpkin-world/src/world_gen/noise/density/spline.rs +++ b/pumpkin-world/src/generation/noise/density/spline.rs @@ -1,9 +1,8 @@ use std::{marker::PhantomData, sync::Arc}; use enum_dispatch::enum_dispatch; -use itertools::Itertools; -use crate::world_gen::noise::lerp; +use crate::generation::noise::lerp; use super::{ component_functions::{ @@ -302,13 +301,13 @@ impl> MutableSpline = converted_points .into_iter() .map(|point| match point { SplinePoint::Immutable(point) => point, _ => unreachable!(), }) - .collect_vec(); + .collect(); SplineRef::Immutable( ImmutableSpline { function: shared, @@ -387,7 +386,7 @@ impl> MutableSplineImpl< let converted_points = points .into_iter() .map(|point| point.convert(converter)) - .collect_vec(); + .collect(); Self::create_new_spline(converted_base, converted_points) } @@ -398,7 +397,7 @@ impl> MutableSplineImpl< .points .iter() .map(|point| point.clone_to_new_point()) - .collect_vec(); + .collect(); Self::create_new_spline(cloned_function, cloned_points) } @@ -462,18 +461,18 @@ impl ImmutableSplineRef { converter: &mut dyn ConverterImpl, ) -> Option> { let converted_base = self.0.function.maybe_convert(converter); - let maybe_converted_points = self + let maybe_converted_points: Vec>> = self .0 .points .iter() .map(|point| point.maybe_convert(converter)) - .collect_vec(); + .collect(); if converted_base.is_none() && maybe_converted_points.iter().all(|point| point.is_none()) { None } else { let converted_base = converted_base.unwrap_or_else(|| self.0.function.clone().into()); - let converted_points = maybe_converted_points + let converted_points: Vec> = maybe_converted_points .into_iter() .enumerate() .map(|(index, point)| { @@ -483,7 +482,7 @@ impl ImmutableSplineRef { self.0.points[index].clone().into() } }) - .collect_vec(); + .collect(); Some(match converted_base { ComponentReferenceImplementation::Shared(shared) => { @@ -491,13 +490,13 @@ impl ImmutableSplineRef { .iter() .all(|point| matches!(point, SplinePoint::Immutable(_))) { - let immutable_points = converted_points + let immutable_points: Vec = converted_points .into_iter() .map(|point| match point { SplinePoint::Immutable(point) => point, _ => unreachable!(), }) - .collect_vec(); + .collect(); SplineRef::Immutable( ImmutableSpline { function: shared, @@ -745,7 +744,7 @@ mod test { use pumpkin_core::random::{legacy_rand::LegacyRand, RandomDeriver, RandomImpl}; - use crate::world_gen::noise::density::{ + use crate::generation::noise::density::{ built_in_density_function::CONTINENTS_OVERWORLD, component_functions::{ComponentReference, NoEnvironment, SharedComponentReference}, test::{FakeEnvironment, OwnedConverter, TestConverter}, diff --git a/pumpkin-world/src/world_gen/noise/density/terrain_helpers.rs b/pumpkin-world/src/generation/noise/density/terrain_helpers.rs similarity index 99% rename from pumpkin-world/src/world_gen/noise/density/terrain_helpers.rs rename to pumpkin-world/src/generation/noise/density/terrain_helpers.rs index b19df8c6..cc8ad704 100644 --- a/pumpkin-world/src/world_gen/noise/density/terrain_helpers.rs +++ b/pumpkin-world/src/generation/noise/density/terrain_helpers.rs @@ -1,7 +1,7 @@ // From da java -use crate::world_gen::noise::density::peaks_valleys_noise; -use crate::world_gen::noise::lerp; +use crate::generation::noise::density::peaks_valleys_noise; +use crate::generation::noise::lerp; use super::component_functions::SharedComponentReference; use super::spline::{FloatAmplifier, ImmutableSpline, ImmutableSplineRef, SplineBuilder}; @@ -523,7 +523,7 @@ pub fn create_jaggedness_spline( mod test { use pumpkin_core::random::{legacy_rand::LegacyRand, RandomDeriver, RandomImpl}; - use crate::world_gen::noise::density::{ + use crate::generation::noise::density::{ built_in_density_function::{ CONTINENTS_OVERWORLD, EROSION_OVERWORLD, RIDGES_FOLDED_OVERWORLD, RIDGES_OVERWORLD, }, diff --git a/pumpkin-world/src/world_gen/noise/density/unary.rs b/pumpkin-world/src/generation/noise/density/unary.rs similarity index 100% rename from pumpkin-world/src/world_gen/noise/density/unary.rs rename to pumpkin-world/src/generation/noise/density/unary.rs diff --git a/pumpkin-world/src/world_gen/noise/density/weird.rs b/pumpkin-world/src/generation/noise/density/weird.rs similarity index 100% rename from pumpkin-world/src/world_gen/noise/density/weird.rs rename to pumpkin-world/src/generation/noise/density/weird.rs diff --git a/pumpkin-world/src/world_gen/noise/mod.rs b/pumpkin-world/src/generation/noise/mod.rs similarity index 100% rename from pumpkin-world/src/world_gen/noise/mod.rs rename to pumpkin-world/src/generation/noise/mod.rs diff --git a/pumpkin-world/src/world_gen/noise/perlin.rs b/pumpkin-world/src/generation/noise/perlin.rs similarity index 97% rename from pumpkin-world/src/world_gen/noise/perlin.rs rename to pumpkin-world/src/generation/noise/perlin.rs index 4faf760f..4e8de285 100644 --- a/pumpkin-world/src/world_gen/noise/perlin.rs +++ b/pumpkin-world/src/generation/noise/perlin.rs @@ -1,6 +1,5 @@ use std::sync::Arc; -use itertools::{izip, Itertools}; use num_traits::Pow; use pumpkin_core::random::RandomGenerator; @@ -226,19 +225,6 @@ impl OctavePerlinNoiseSampler { random.skip(262); } } - - #[cfg(debug_assertions)] - { - use itertools::Itertools; - use num_traits::Zero; - - if let Ok(length1) = samplers.iter().filter(|x| x.is_some()).try_len() { - if let Ok(length2) = amplitudes.iter().filter(|x| !x.is_zero()).try_len() { - assert_eq!(length1, length2); - } - } - assert!(j >= i as i32 - 1); - } } else { let splitter = random.next_splitter(); for k in 0..i { @@ -256,20 +242,20 @@ impl OctavePerlinNoiseSampler { let mut lacunarity = 2f64.pow((-j) as f64); let max_value = Self::get_total_amplitude(2f64, persistence, amplitudes); - let persistences = (0..amplitudes.len()) + let persistences: Vec = (0..amplitudes.len()) .map(|_| { let result = persistence; persistence /= 2f64; result }) - .collect_vec(); - let lacunarities = (0..amplitudes.len()) + .collect(); + let lacunarities: Vec = (0..amplitudes.len()) .map(|_| { let result = lacunarity; lacunarity *= 2f64; result }) - .collect_vec(); + .collect(); Self { octave_samplers: samplers.into(), @@ -284,13 +270,13 @@ impl OctavePerlinNoiseSampler { pub fn sample(&self, x: f64, y: f64, z: f64) -> f64 { let mut d = 0f64; - for (sampler, amplitude, persistence, lacunarity) in izip!( - &self.octave_samplers, - &self.amplitudes, - &self.persistences, - &self.lacunarities - ) { - if let Some(sampler) = sampler { + let num_octaves = self.octave_samplers.len(); + for i in 0..num_octaves { + if let Some(sampler) = &self.octave_samplers[i] { + let lacunarity = self.lacunarities[i]; + let amplitude = self.amplitudes[i]; + let persistence = self.persistences[i]; + let g = sampler.sample_no_fade( Self::maintain_precision(x * lacunarity), Self::maintain_precision(y * lacunarity), @@ -395,7 +381,7 @@ mod double_perlin_noise_sampler_test { legacy_rand::LegacyRand, xoroshiro128::Xoroshiro, RandomGenerator, RandomImpl, }; - use crate::world_gen::noise::perlin::{DoublePerlinNoiseParameters, DoublePerlinNoiseSampler}; + use crate::generation::noise::perlin::{DoublePerlinNoiseParameters, DoublePerlinNoiseSampler}; #[test] fn sample_legacy() { @@ -774,7 +760,7 @@ mod perlin_noise_sampler_test { random::{xoroshiro128::Xoroshiro, RandomDeriverImpl, RandomGenerator, RandomImpl}, }; - use crate::{read_data_from_file, world_gen::noise::perlin::PerlinNoiseSampler}; + use crate::{generation::noise::perlin::PerlinNoiseSampler, read_data_from_file}; use super::OctavePerlinNoiseSampler; diff --git a/pumpkin-world/src/world_gen/noise/router.rs b/pumpkin-world/src/generation/noise/router.rs similarity index 99% rename from pumpkin-world/src/world_gen/noise/router.rs rename to pumpkin-world/src/generation/noise/router.rs index ddf605c3..cb8093c1 100644 --- a/pumpkin-world/src/world_gen/noise/router.rs +++ b/pumpkin-world/src/generation/noise/router.rs @@ -1,6 +1,6 @@ use std::sync::{Arc, LazyLock}; -use crate::world_gen::{ +use crate::generation::{ noise::density::{ apply_blend_density, basic::RangeFunction, @@ -476,8 +476,7 @@ mod test { }; use crate::{ - read_data_from_file, - world_gen::noise::{ + generation::noise::{ config::LegacyChunkNoiseVisitor, density::{ built_in_density_function::{EROSION_OVERWORLD, SLOPED_CHEESE_OVERWORLD}, @@ -491,6 +490,7 @@ mod test { perlin::DoublePerlinNoiseSampler, router::OVERWORLD_NOISE_ROUTER, }, + read_data_from_file, }; use super::{apply_surface_slides, create_caves}; diff --git a/pumpkin-world/src/world_gen/noise/simplex.rs b/pumpkin-world/src/generation/noise/simplex.rs similarity index 99% rename from pumpkin-world/src/world_gen/noise/simplex.rs rename to pumpkin-world/src/generation/noise/simplex.rs index ff9afd6e..c69128c3 100644 --- a/pumpkin-world/src/world_gen/noise/simplex.rs +++ b/pumpkin-world/src/generation/noise/simplex.rs @@ -276,7 +276,7 @@ impl OctaveSimplexNoiseSampler { mod octave_simplex_noise_sampler_test { use pumpkin_core::random::{xoroshiro128::Xoroshiro, RandomImpl}; - use crate::world_gen::noise::simplex::OctaveSimplexNoiseSampler; + use crate::generation::noise::simplex::OctaveSimplexNoiseSampler; #[test] fn test_new() { @@ -413,7 +413,7 @@ mod simplex_noise_sampler_test { use pumpkin_core::random::{xoroshiro128::Xoroshiro, RandomImpl}; - use crate::world_gen::noise::simplex::SimplexNoiseSampler; + use crate::generation::noise::simplex::SimplexNoiseSampler; #[test] fn test_create() { diff --git a/pumpkin-world/src/world_gen/ore_sampler.rs b/pumpkin-world/src/generation/ore_sampler.rs similarity index 100% rename from pumpkin-world/src/world_gen/ore_sampler.rs rename to pumpkin-world/src/generation/ore_sampler.rs diff --git a/pumpkin-world/src/world_gen/positions.rs b/pumpkin-world/src/generation/positions.rs similarity index 100% rename from pumpkin-world/src/world_gen/positions.rs rename to pumpkin-world/src/generation/positions.rs diff --git a/pumpkin-world/src/world_gen/proto_chunk.rs b/pumpkin-world/src/generation/proto_chunk.rs similarity index 98% rename from pumpkin-world/src/world_gen/proto_chunk.rs rename to pumpkin-world/src/generation/proto_chunk.rs index aceefc18..056a702f 100644 --- a/pumpkin-world/src/world_gen/proto_chunk.rs +++ b/pumpkin-world/src/generation/proto_chunk.rs @@ -2,7 +2,7 @@ use pumpkin_core::math::{vector2::Vector2, vector3::Vector3}; use crate::{ block::BlockState, - world_gen::{ + generation::{ chunk_noise::CHUNK_DIM, generation_shapes::GenerationShape, noise::{config::NoiseConfig, router::OVERWORLD_NOISE_ROUTER}, @@ -216,7 +216,6 @@ impl ProtoChunk { mod test { use std::{fs, path::Path}; - use itertools::Itertools; use pumpkin_core::math::vector2::Vector2; use crate::read_data_from_file; @@ -235,7 +234,7 @@ mod test { .flat_block_map .into_iter() .map(|state| state.state_id) - .collect_vec() + .collect::>() ); } @@ -252,7 +251,7 @@ mod test { .flat_block_map .into_iter() .map(|state| state.state_id) - .collect_vec() + .collect::>() ); } } diff --git a/pumpkin-world/src/generation/seed.rs b/pumpkin-world/src/generation/seed.rs new file mode 100644 index 00000000..1d9aa9cb --- /dev/null +++ b/pumpkin-world/src/generation/seed.rs @@ -0,0 +1,20 @@ +use pumpkin_core::random::{get_seed, java_string_hash, legacy_rand::LegacyRand, RandomImpl}; + +#[derive(Clone, Copy)] +pub struct Seed(pub u64); + +impl From<&str> for Seed { + fn from(value: &str) -> Self { + let trimmed = value.trim(); + let value = if !trimmed.is_empty() { + let i64_value = trimmed + .parse::() + .unwrap_or_else(|_| java_string_hash(trimmed) as i64); + Some(i64_value as u64) + } else { + None + }; + + Seed(value.unwrap_or_else(|| LegacyRand::from_seed(get_seed()).next_i64() as u64)) + } +} diff --git a/pumpkin-world/src/item/item_registry.rs b/pumpkin-world/src/item/item_registry.rs index e446e362..e393acc1 100644 --- a/pumpkin-world/src/item/item_registry.rs +++ b/pumpkin-world/src/item/item_registry.rs @@ -57,6 +57,15 @@ pub struct Modifier { pub type_val: String, pub id: String, pub amount: f64, - pub operation: String, + pub operation: Operation, + // TODO: Make this an enum pub slot: String, } + +#[derive(Deserialize, Clone, Debug, PartialEq)] +#[serde(rename_all = "snake_case")] +pub enum Operation { + AddValue, + AddMultipliedBase, + AddMultipliedTotal, +} diff --git a/pumpkin-world/src/level.rs b/pumpkin-world/src/level.rs index b8628082..930363ec 100644 --- a/pumpkin-world/src/level.rs +++ b/pumpkin-world/src/level.rs @@ -2,7 +2,6 @@ use std::{path::PathBuf, sync::Arc}; use dashmap::{DashMap, Entry}; use num_traits::Zero; -use pumpkin_config::BASIC_CONFIG; use pumpkin_core::math::vector2::Vector2; use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; use tokio::{ @@ -14,7 +13,8 @@ use crate::{ chunk::{ anvil::AnvilChunkReader, ChunkData, ChunkParsingError, ChunkReader, ChunkReadingError, }, - world_gen::{get_world_gen, Seed, WorldGenerator}, + generation::{get_world_gen, Seed, WorldGenerator}, + world_info::{anvil::AnvilLevelInfo, LevelData, WorldInfoReader, WorldInfoWriter}, }; /// The `Level` module provides functionality for working with chunks within or outside a Minecraft world. @@ -28,7 +28,9 @@ use crate::{ /// For more details on world generation, refer to the `WorldGenerator` module. pub struct Level { pub seed: Seed, - save_file: Option, + pub level_info: LevelData, + world_info_writer: Arc, + level_folder: LevelFolder, loaded_chunks: Arc, Arc>>>, chunk_watchers: Arc, usize>>, chunk_reader: Arc, @@ -36,52 +38,53 @@ pub struct Level { } #[derive(Clone)] -pub struct SaveFile { +pub struct LevelFolder { pub root_folder: PathBuf, pub region_folder: PathBuf, } -fn get_or_create_seed() -> Seed { - // TODO: if there is a seed in the config (!= 0) use it. Otherwise make a random one - Seed::from(BASIC_CONFIG.seed.as_str()) -} - impl Level { pub fn from_root_folder(root_folder: PathBuf) -> Self { // If we are using an already existing world we want to read the seed from the level.dat, If not we want to check if there is a seed in the config, if not lets create a random one - if root_folder.exists() { - let region_folder = root_folder.join("region"); - assert!( - region_folder.exists(), - "World region folder does not exist, despite there being a root folder." - ); - // TODO: read seed from level.dat - let seed = Seed(0); - let world_gen = get_world_gen(seed).into(); // TODO Read Seed from config. + let region_folder = root_folder.join("region"); + if !region_folder.exists() { + std::fs::create_dir_all(®ion_folder).expect("Failed to create Region folder"); + } + let level_folder = LevelFolder { + root_folder, + region_folder, + }; - Self { - seed, - world_gen, - save_file: Some(SaveFile { - root_folder, - region_folder, - }), - chunk_reader: Arc::new(AnvilChunkReader::new()), - loaded_chunks: Arc::new(DashMap::new()), - chunk_watchers: Arc::new(DashMap::new()), - } - } else { - let seed = get_or_create_seed(); - let world_gen = get_world_gen(seed).into(); // TODO Read Seed from config. - Self { - seed, - world_gen, - save_file: None, - chunk_reader: Arc::new(AnvilChunkReader::new()), - loaded_chunks: Arc::new(DashMap::new()), - chunk_watchers: Arc::new(DashMap::new()), - } + // TODO: Load info correctly based on world format type + let level_info = AnvilLevelInfo + .read_world_info(&level_folder) + .unwrap_or_default(); // TODO: Improve error handling + let seed = Seed(level_info.world_gen_settings.seed as u64); + let world_gen = get_world_gen(seed).into(); + + Self { + seed, + world_gen, + world_info_writer: Arc::new(AnvilLevelInfo), + level_folder, + chunk_reader: Arc::new(AnvilChunkReader::new()), + loaded_chunks: Arc::new(DashMap::new()), + chunk_watchers: Arc::new(DashMap::new()), + level_info, + } + } + + pub async fn save(&self) { + log::info!("Saving level..."); + // lets first save all chunks + for chunk in self.loaded_chunks.iter() { + let chunk = chunk.read().await; + self.clean_chunk(&chunk.position); } + // then lets save the world info + self.world_info_writer + .write_world_info(self.level_info.clone(), &self.level_folder) + .expect("Failed to save world info"); } pub fn get_block() {} @@ -195,10 +198,10 @@ impl Level { fn load_chunk_from_save( chunk_reader: Arc, - save_file: SaveFile, + save_file: &LevelFolder, chunk_pos: Vector2, ) -> Result>>, ChunkReadingError> { - match chunk_reader.read_chunk(&save_file, &chunk_pos) { + match chunk_reader.read_chunk(save_file, &chunk_pos) { Ok(data) => Ok(Some(Arc::new(RwLock::new(data)))), Err( ChunkReadingError::ChunkNotExist @@ -223,7 +226,7 @@ impl Level { let channel = channel.clone(); let loaded_chunks = self.loaded_chunks.clone(); let chunk_reader = self.chunk_reader.clone(); - let save_file = self.save_file.clone(); + let level_info = self.level_folder.clone(); let world_gen = self.world_gen.clone(); let chunk_pos = *at; @@ -231,20 +234,18 @@ impl Level { .get(&chunk_pos) .map(|entry| entry.value().clone()) .unwrap_or_else(|| { - let loaded_chunk = save_file - .and_then(|save_file| { - match Self::load_chunk_from_save(chunk_reader, save_file, chunk_pos) { - Ok(chunk) => chunk, - Err(err) => { - log::error!( - "Failed to read chunk (regenerating) {:?}: {:?}", - chunk_pos, - err - ); - None - } + let loaded_chunk = + match Self::load_chunk_from_save(chunk_reader, &level_info, chunk_pos) { + Ok(chunk) => chunk, + Err(err) => { + log::error!( + "Failed to read chunk (regenerating) {:?}: {:?}", + chunk_pos, + err + ); + None } - }) + } .unwrap_or_else(|| { Arc::new(RwLock::new(world_gen.generate_chunk(chunk_pos))) }); diff --git a/pumpkin-world/src/lib.rs b/pumpkin-world/src/lib.rs index d1d6284c..8e26b106 100644 --- a/pumpkin-world/src/lib.rs +++ b/pumpkin-world/src/lib.rs @@ -1,11 +1,11 @@ -use pumpkin_core::math::vector2::Vector2; -use world_gen::{ +use generation::{ aquifer_sampler::{FluidLevel, FluidLevelSampler}, chunk_noise::{ChunkNoiseGenerator, LAVA_BLOCK, WATER_BLOCK}, generation_shapes::GenerationShape, noise::{config::NoiseConfig, router::OVERWORLD_NOISE_ROUTER}, proto_chunk::{ProtoChunk, StandardChunkFluidLevelSampler}, }; +use pumpkin_core::math::vector2::Vector2; pub mod biome; pub mod block; @@ -13,10 +13,10 @@ pub mod chunk; pub mod coordinates; pub mod cylindrical_chunk_iterator; pub mod dimension; +mod generation; pub mod item; pub mod level; -mod world_gen; - +pub mod world_info; pub const WORLD_HEIGHT: usize = 384; pub const WORLD_LOWEST_Y: i16 = -64; pub const WORLD_MAX_Y: i16 = WORLD_HEIGHT as i16 - WORLD_LOWEST_Y.abs(); diff --git a/pumpkin-world/src/world_gen/seed.rs b/pumpkin-world/src/world_gen/seed.rs deleted file mode 100644 index 137a6ecd..00000000 --- a/pumpkin-world/src/world_gen/seed.rs +++ /dev/null @@ -1,15 +0,0 @@ -use std::hash::{DefaultHasher, Hash, Hasher}; - -#[derive(Clone, Copy)] -pub struct Seed(pub i64); - -impl From<&str> for Seed { - fn from(value: &str) -> Self { - // TODO replace with a deterministic hasher (the same as vanilla?) - let mut hasher = DefaultHasher::new(); - value.hash(&mut hasher); - - // TODO use cast_signed once the feature is stabilized. - Self(hasher.finish() as i64) - } -} diff --git a/pumpkin-world/src/world_info/anvil.rs b/pumpkin-world/src/world_info/anvil.rs new file mode 100644 index 00000000..080d61e1 --- /dev/null +++ b/pumpkin-world/src/world_info/anvil.rs @@ -0,0 +1,88 @@ +use std::{ + fs::OpenOptions, + io::{Read, Write}, + time::{SystemTime, UNIX_EPOCH}, +}; + +use flate2::{read::GzDecoder, write::GzEncoder, Compression}; +use serde::{Deserialize, Serialize}; + +use crate::level::LevelFolder; + +use super::{LevelData, WorldInfoError, WorldInfoReader, WorldInfoWriter}; + +const LEVEL_DAT_FILE_NAME: &str = "level.dat"; + +pub struct AnvilLevelInfo; + +impl WorldInfoReader for AnvilLevelInfo { + fn read_world_info(&self, level_folder: &LevelFolder) -> Result { + let path = level_folder.root_folder.join(LEVEL_DAT_FILE_NAME); + + let mut world_info_file = OpenOptions::new().read(true).open(path)?; + + let mut buffer = Vec::new(); + world_info_file.read_to_end(&mut buffer)?; + + // try to decompress using GZip + let mut decoder = GzDecoder::new(&buffer[..]); + let mut decompressed_data = Vec::new(); + decoder.read_to_end(&mut decompressed_data)?; + + let info = fastnbt::from_bytes::(&decompressed_data) + .map_err(|e| WorldInfoError::DeserializationError(e.to_string()))?; + + // todo check version + + Ok(info.data) + } +} + +impl WorldInfoWriter for AnvilLevelInfo { + fn write_world_info( + &self, + info: LevelData, + level_folder: &LevelFolder, + ) -> Result<(), WorldInfoError> { + let start = SystemTime::now(); + let since_the_epoch = start + .duration_since(UNIX_EPOCH) + .expect("Time went backwards"); + let level = LevelDat { + data: LevelData { + allow_commands: info.allow_commands, + data_version: info.data_version, + difficulty: info.difficulty, + world_gen_settings: info.world_gen_settings, + last_played: since_the_epoch.as_millis() as i64, + level_name: info.level_name, + spawn_x: info.spawn_x, + spawn_y: info.spawn_y, + spawn_z: info.spawn_z, + nbt_version: info.nbt_version, + version: info.version, + }, + }; + // convert it into nbt + let nbt = pumpkin_nbt::serializer::to_bytes_unnamed(&level).unwrap(); + // now compress using GZip, TODO: im not sure about the to_vec, but writer is not implemented for BytesMut, see https://github.com/tokio-rs/bytes/pull/478 + let mut encoder = GzEncoder::new(nbt.to_vec(), Compression::best()); + let compressed_data = Vec::new(); + encoder.write_all(&compressed_data)?; + + // open file + let path = level_folder.root_folder.join(LEVEL_DAT_FILE_NAME); + let mut world_info_file = OpenOptions::new().write(true).open(path)?; + // write compressed data into file + world_info_file.write_all(&compressed_data).unwrap(); + + Ok(()) + } +} + +#[derive(Serialize, Deserialize)] +pub struct LevelDat { + // This tag contains all the level data. + #[serde(rename = "Data")] + pub data: LevelData, +} diff --git a/pumpkin-world/src/world_info/mod.rs b/pumpkin-world/src/world_info/mod.rs new file mode 100644 index 00000000..d06be497 --- /dev/null +++ b/pumpkin-world/src/world_info/mod.rs @@ -0,0 +1,130 @@ +use pumpkin_config::BASIC_CONFIG; +use pumpkin_core::Difficulty; +use serde::{Deserialize, Serialize}; +use thiserror::Error; + +use crate::{generation::Seed, level::LevelFolder}; + +pub mod anvil; + +pub(crate) trait WorldInfoReader { + fn read_world_info(&self, level_folder: &LevelFolder) -> Result; +} + +pub(crate) trait WorldInfoWriter: Sync + Send { + fn write_world_info( + &self, + info: LevelData, + level_folder: &LevelFolder, + ) -> Result<(), WorldInfoError>; +} + +#[derive(Serialize, Deserialize, Clone)] +#[serde(rename_all = "PascalCase")] +pub struct LevelData { + // true if cheats are enabled. + pub allow_commands: bool, + // An integer displaying the data version. + pub data_version: i32, + // The current difficulty setting. + pub difficulty: Difficulty, + // the generation settings for each dimension. + pub world_gen_settings: WorldGenSettings, + // The Unix time in milliseconds when the level was last loaded. + pub last_played: i64, + // The name of the level. + pub level_name: String, + // The X coordinate of the world spawn. + pub spawn_x: i32, + // The Y coordinate of the world spawn. + pub spawn_y: i32, + // The Z coordinate of the world spawn. + pub spawn_z: i32, + #[serde(rename = "version")] + // The NBT version of the level + pub nbt_version: i32, + #[serde(rename = "Version")] + pub version: WorldVersion, + // TODO: Implement the rest of the fields +} + +#[derive(Serialize, Deserialize, Clone)] +pub struct WorldGenSettings { + // the numerical seed of the world + pub seed: i64, +} + +fn get_or_create_seed() -> Seed { + // TODO: if there is a seed in the config (!= 0) use it. Otherwise make a random one + Seed::from(BASIC_CONFIG.seed.as_str()) +} + +impl Default for WorldGenSettings { + fn default() -> Self { + Self { + seed: get_or_create_seed().0 as i64, + } + } +} + +#[derive(Serialize, Deserialize, Clone)] +#[serde(rename_all = "PascalCase")] +pub struct WorldVersion { + // The version name as a string, e.g. "15w32b". + pub name: String, + // An integer displaying the data version. + pub id: i32, + // Whether the version is a snapshot or not. + pub snapshot: bool, + // Developing series. In 1.18 experimental snapshots, it was set to "ccpreview". In others, set to "main". + pub series: String, +} + +impl Default for WorldVersion { + fn default() -> Self { + Self { + name: "1.24.4".to_string(), + id: -1, + snapshot: false, + series: "main".to_string(), + } + } +} + +impl Default for LevelData { + fn default() -> Self { + Self { + allow_commands: true, + // TODO + data_version: -1, + difficulty: Difficulty::Normal, + world_gen_settings: Default::default(), + last_played: -1, + level_name: "world".to_string(), + spawn_x: 0, + spawn_y: 200, + spawn_z: 0, + nbt_version: -1, + version: Default::default(), + } + } +} + +#[derive(Error, Debug)] +pub enum WorldInfoError { + #[error("Io error: {0}")] + IoError(std::io::ErrorKind), + #[error("Info not found!")] + InfoNotFound, + #[error("Deserialization error: {0}")] + DeserializationError(String), +} + +impl From for WorldInfoError { + fn from(value: std::io::Error) -> Self { + match value.kind() { + std::io::ErrorKind::NotFound => Self::InfoNotFound, + value => Self::IoError(value), + } + } +} diff --git a/pumpkin/Cargo.toml b/pumpkin/Cargo.toml index 38bd7190..a3f4a14e 100644 --- a/pumpkin/Cargo.toml +++ b/pumpkin/Cargo.toml @@ -21,7 +21,6 @@ pumpkin-protocol = { path = "../pumpkin-protocol" } pumpkin-registry = { path = "../pumpkin-registry" } pumpkin-macros = { path = "../pumpkin-macros" } -itertools.workspace = true log.workspace = true crossbeam.workspace = true uuid.workspace = true @@ -31,7 +30,6 @@ thiserror.workspace = true num-traits.workspace = true num-derive.workspace = true -parking_lot.workspace = true # config serde.workspace = true @@ -56,14 +54,14 @@ reqwest = { version = "0.12.9", default-features = false, features = [ ] } sha1 = "0.10.6" -digest = "=0.11.0-pre.9" # velocity en hmac = "0.12.1" sha2 = "0.10.8" -# icon loading base64 = "0.22.1" + +# icon loading png = "0.17.15" # logging diff --git a/pumpkin/src/block/block_manager.rs b/pumpkin/src/block/block_manager.rs index 5a48b255..0fb26ede 100644 --- a/pumpkin/src/block/block_manager.rs +++ b/pumpkin/src/block/block_manager.rs @@ -22,8 +22,7 @@ pub struct BlockManager { impl BlockManager { pub fn register(&mut self, block: T) { - self.blocks - .insert(block.name().to_string(), Arc::new(block)); + self.blocks.insert(block.name(), Arc::new(block)); } pub async fn on_use( diff --git a/pumpkin/src/command/args/arg_block.rs b/pumpkin/src/command/args/arg_block.rs index 41e3ebd3..fe11228f 100644 --- a/pumpkin/src/command/args/arg_block.rs +++ b/pumpkin/src/command/args/arg_block.rs @@ -65,12 +65,14 @@ impl<'a> FindArg<'a> for BlockArgumentConsumer { fn find_arg(args: &'a super::ConsumedArgs, name: &'a str) -> Result { match args.get(name) { - Some(Arg::Block(name)) => match block_registry::get_block(name) { - Some(block) => Ok(block), - None => Err(CommandError::GeneralCommandIssue(format!( - "Block {name} does not exist." - ))), - }, + Some(Arg::Block(name)) => block_registry::get_block(name).map_or_else( + || { + Err(CommandError::GeneralCommandIssue(format!( + "Block {name} does not exist." + ))) + }, + Result::Ok, + ), _ => Err(CommandError::InvalidConsumption(Some(name.to_string()))), } } diff --git a/pumpkin/src/command/args/arg_bounded_num.rs b/pumpkin/src/command/args/arg_bounded_num.rs index 4a9f718b..41410367 100644 --- a/pumpkin/src/command/args/arg_bounded_num.rs +++ b/pumpkin/src/command/args/arg_bounded_num.rs @@ -22,7 +22,7 @@ pub(crate) struct BoundedNumArgumentConsumer { #[async_trait] impl ArgumentConsumer for BoundedNumArgumentConsumer where - BoundedNumArgumentConsumer: GetClientSideArgParser, + Self: GetClientSideArgParser, { async fn consume<'a>( &self, @@ -236,7 +236,7 @@ impl GetClientSideArgParser for BoundedNumArgumentConsumer { impl DefaultNameArgConsumer for BoundedNumArgumentConsumer where - BoundedNumArgumentConsumer: ArgumentConsumer, + Self: ArgumentConsumer, { fn default_name(&self) -> &'static str { // setting a single default name for all BoundedNumArgumentConsumer variants is probably a bad idea since it would lead to confusion diff --git a/pumpkin/src/command/args/arg_command.rs b/pumpkin/src/command/args/arg_command.rs index ef933977..93955987 100644 --- a/pumpkin/src/command/args/arg_command.rs +++ b/pumpkin/src/command/args/arg_command.rs @@ -38,10 +38,9 @@ impl ArgumentConsumer for CommandTreeArgumentConsumer { let s = args.pop()?; let dispatcher = &server.command_dispatcher; - return match dispatcher.get_tree(s) { - Ok(tree) => Some(Arg::CommandTree(tree)), - Err(_) => None, - }; + return dispatcher + .get_tree(s) + .map_or_else(|_| None, |tree| Some(Arg::CommandTree(tree))); } async fn suggest<'a>( @@ -71,7 +70,7 @@ impl DefaultNameArgConsumer for CommandTreeArgumentConsumer { } fn get_argument_consumer(&self) -> &dyn ArgumentConsumer { - &CommandTreeArgumentConsumer + &Self } } diff --git a/pumpkin/src/command/args/arg_entities.rs b/pumpkin/src/command/args/arg_entities.rs index 9f077597..102a177b 100644 --- a/pumpkin/src/command/args/arg_entities.rs +++ b/pumpkin/src/command/args/arg_entities.rs @@ -62,7 +62,7 @@ impl DefaultNameArgConsumer for EntitiesArgumentConsumer { } fn get_argument_consumer(&self) -> &dyn ArgumentConsumer { - &EntitiesArgumentConsumer + &Self } } diff --git a/pumpkin/src/command/args/arg_entity.rs b/pumpkin/src/command/args/arg_entity.rs index dce52240..37e4dac0 100644 --- a/pumpkin/src/command/args/arg_entity.rs +++ b/pumpkin/src/command/args/arg_entity.rs @@ -84,7 +84,7 @@ impl DefaultNameArgConsumer for EntityArgumentConsumer { } fn get_argument_consumer(&self) -> &dyn ArgumentConsumer { - &EntityArgumentConsumer + &Self } } diff --git a/pumpkin/src/command/args/arg_gamemode.rs b/pumpkin/src/command/args/arg_gamemode.rs index 469a92f9..d520b25c 100644 --- a/pumpkin/src/command/args/arg_gamemode.rs +++ b/pumpkin/src/command/args/arg_gamemode.rs @@ -65,7 +65,7 @@ impl DefaultNameArgConsumer for GamemodeArgumentConsumer { } fn get_argument_consumer(&self) -> &dyn ArgumentConsumer { - &GamemodeArgumentConsumer + &Self } } diff --git a/pumpkin/src/command/args/arg_item.rs b/pumpkin/src/command/args/arg_item.rs index 3deeb2fe..481feb7d 100644 --- a/pumpkin/src/command/args/arg_item.rs +++ b/pumpkin/src/command/args/arg_item.rs @@ -63,12 +63,14 @@ impl<'a> FindArg<'a> for ItemArgumentConsumer { fn find_arg(args: &'a super::ConsumedArgs, name: &'a str) -> Result { match args.get(name) { - Some(Arg::Item(name)) => match item_registry::get_item(name) { - Some(item) => Ok((name, item)), - None => Err(CommandError::GeneralCommandIssue(format!( - "Item {name} does not exist." - ))), - }, + Some(Arg::Item(name)) => item_registry::get_item(name).map_or_else( + || { + Err(CommandError::GeneralCommandIssue(format!( + "Item {name} does not exist." + ))) + }, + |item| Ok((*name, item)), + ), _ => Err(CommandError::InvalidConsumption(Some(name.to_string()))), } } diff --git a/pumpkin/src/command/args/arg_message.rs b/pumpkin/src/command/args/arg_message.rs index e2815de2..00af2d87 100644 --- a/pumpkin/src/command/args/arg_message.rs +++ b/pumpkin/src/command/args/arg_message.rs @@ -60,7 +60,7 @@ impl DefaultNameArgConsumer for MsgArgConsumer { } fn get_argument_consumer(&self) -> &dyn ArgumentConsumer { - &MsgArgConsumer + &Self } } diff --git a/pumpkin/src/command/args/arg_players.rs b/pumpkin/src/command/args/arg_players.rs index 7c06f8e4..0e8876a7 100644 --- a/pumpkin/src/command/args/arg_players.rs +++ b/pumpkin/src/command/args/arg_players.rs @@ -53,11 +53,7 @@ impl ArgumentConsumer for PlayersArgumentConsumer { _ => None, }, "@r" => { - if let Some(p) = server.get_random_player().await { - Some(vec![p.clone()]) - } else { - Some(vec![]) - } + (server.get_random_player().await).map_or_else(|| Some(vec![]), |p| Some(vec![p])) } "@a" | "@e" => Some(server.get_all_players().await), name => server.get_player_by_name(name).await.map(|p| vec![p]), @@ -82,7 +78,7 @@ impl DefaultNameArgConsumer for PlayersArgumentConsumer { } fn get_argument_consumer(&self) -> &dyn ArgumentConsumer { - &PlayersArgumentConsumer + &Self } } diff --git a/pumpkin/src/command/args/arg_position_2d.rs b/pumpkin/src/command/args/arg_position_2d.rs index 9dc45073..107ea432 100644 --- a/pumpkin/src/command/args/arg_position_2d.rs +++ b/pumpkin/src/command/args/arg_position_2d.rs @@ -78,7 +78,7 @@ impl DefaultNameArgConsumer for Position2DArgumentConsumer { } fn get_argument_consumer(&self) -> &dyn ArgumentConsumer { - &Position2DArgumentConsumer + &Self } } diff --git a/pumpkin/src/command/args/arg_position_3d.rs b/pumpkin/src/command/args/arg_position_3d.rs index 4940b4db..1e58ad81 100644 --- a/pumpkin/src/command/args/arg_position_3d.rs +++ b/pumpkin/src/command/args/arg_position_3d.rs @@ -81,7 +81,7 @@ impl DefaultNameArgConsumer for Position3DArgumentConsumer { } fn get_argument_consumer(&self) -> &dyn ArgumentConsumer { - &Position3DArgumentConsumer + &Self } } diff --git a/pumpkin/src/command/args/arg_position_block.rs b/pumpkin/src/command/args/arg_position_block.rs index b8ae25db..861eec12 100644 --- a/pumpkin/src/command/args/arg_position_block.rs +++ b/pumpkin/src/command/args/arg_position_block.rs @@ -82,7 +82,7 @@ impl DefaultNameArgConsumer for BlockPosArgumentConsumer { } fn get_argument_consumer(&self) -> &dyn ArgumentConsumer { - &BlockPosArgumentConsumer + &Self } } diff --git a/pumpkin/src/command/args/arg_rotation.rs b/pumpkin/src/command/args/arg_rotation.rs index 4efdbde0..6e53f2f9 100644 --- a/pumpkin/src/command/args/arg_rotation.rs +++ b/pumpkin/src/command/args/arg_rotation.rs @@ -66,7 +66,7 @@ impl DefaultNameArgConsumer for RotationArgumentConsumer { } fn get_argument_consumer(&self) -> &dyn ArgumentConsumer { - &RotationArgumentConsumer + &Self } } diff --git a/pumpkin/src/command/commands/cmd_list.rs b/pumpkin/src/command/commands/cmd_list.rs index fe1b4e0c..5a81f8f9 100644 --- a/pumpkin/src/command/commands/cmd_list.rs +++ b/pumpkin/src/command/commands/cmd_list.rs @@ -1,7 +1,6 @@ use std::sync::Arc; use async_trait::async_trait; -use itertools::Itertools; use pumpkin_config::BASIC_CONFIG; use pumpkin_core::text::TextComponent; @@ -35,10 +34,7 @@ impl CommandExecutor for ListExecutor { "There are {} of a max of {} players online: {}", players.len(), BASIC_CONFIG.max_players, - players - .iter() - .map(|player| &player.gameprofile.name) - .join(", ") + get_player_names(players) ) }; @@ -48,6 +44,17 @@ impl CommandExecutor for ListExecutor { } } +fn get_player_names(players: Vec>) -> String { + let mut names = String::new(); + for player in players { + if !names.is_empty() { + names.push_str(", "); + } + names.push_str(&player.gameprofile.name); + } + names +} + pub fn init_command_tree<'a>() -> CommandTree<'a> { CommandTree::new(NAMES, DESCRIPTION).execute(&ListExecutor) } diff --git a/pumpkin/src/command/commands/cmd_pumpkin.rs b/pumpkin/src/command/commands/cmd_pumpkin.rs index 2b2f41a2..3dad98ec 100644 --- a/pumpkin/src/command/commands/cmd_pumpkin.rs +++ b/pumpkin/src/command/commands/cmd_pumpkin.rs @@ -59,6 +59,32 @@ impl CommandExecutor for PumpkinExecutor { "Click to Copy Minecraft Version", ))) .color_named(NamedColor::Gold), + ) + .add_child(TextComponent::text(" ")) + // https://snowiiii.github.io/Pumpkin/ + .add_child( + TextComponent::text("Github Repository") + .click_event(ClickEvent::OpenUrl(Cow::from( + "https://github.com/Snowiiii/Pumpkin", + ))) + .hover_event(HoverEvent::ShowText(Cow::from( + "Click to open repository.", + ))) + .color_named(NamedColor::Blue) + .bold() + .underlined(), + ) + // Added docs. and a space for spacing + .add_child(TextComponent::text(" ")) + .add_child( + TextComponent::text("Docs") + .click_event(ClickEvent::OpenUrl(Cow::from( + "https://snowiiii.github.io/Pumpkin/", + ))) + .hover_event(HoverEvent::ShowText(Cow::from("Click to open docs."))) + .color_named(NamedColor::Blue) + .bold() + .underlined(), ), ) .await; diff --git a/pumpkin/src/command/commands/cmd_seed.rs b/pumpkin/src/command/commands/cmd_seed.rs index 18b4c360..fb93acb0 100644 --- a/pumpkin/src/command/commands/cmd_seed.rs +++ b/pumpkin/src/command/commands/cmd_seed.rs @@ -24,11 +24,9 @@ impl CommandExecutor for PumpkinExecutor { _args: &ConsumedArgs<'a>, ) -> Result<(), CommandError> { let seed = match sender { - CommandSender::Player(player) => { - player.living_entity.entity.world.level.seed.0.to_string() - } + CommandSender::Player(player) => player.living_entity.entity.world.level.seed.0, _ => match server.worlds.first() { - Some(world) => world.level.seed.0.to_string(), + Some(world) => world.level.seed.0, None => { return Err(CommandError::GeneralCommandIssue( "Unable to get Seed".to_string(), @@ -36,6 +34,7 @@ impl CommandExecutor for PumpkinExecutor { } }, }; + let seed = (seed as i64).to_string(); sender .send_message( diff --git a/pumpkin/src/command/commands/cmd_stop.rs b/pumpkin/src/command/commands/cmd_stop.rs index 0f1bc7af..de7803b1 100644 --- a/pumpkin/src/command/commands/cmd_stop.rs +++ b/pumpkin/src/command/commands/cmd_stop.rs @@ -32,7 +32,7 @@ impl CommandExecutor for StopExecutor { for player in server.get_all_players().await { player.kick(kick_message.clone()).await; } - + server.save().await; std::process::exit(0) } } diff --git a/pumpkin/src/command/commands/cmd_transfer.rs b/pumpkin/src/command/commands/cmd_transfer.rs index 1af9ae3c..7660b808 100644 --- a/pumpkin/src/command/commands/cmd_transfer.rs +++ b/pumpkin/src/command/commands/cmd_transfer.rs @@ -2,7 +2,7 @@ use async_trait::async_trait; use pumpkin_core::text::color::{Color, NamedColor}; use pumpkin_core::text::TextComponent; use pumpkin_protocol::client::play::CTransfer; -use pumpkin_protocol::VarInt; +use pumpkin_protocol::codec::var_int::VarInt; use crate::command::args::arg_bounded_num::BoundedNumArgumentConsumer; use crate::command::args::arg_players::PlayersArgumentConsumer; diff --git a/pumpkin/src/command/dispatcher.rs b/pumpkin/src/command/dispatcher.rs index 9448c505..e63a67ac 100644 --- a/pumpkin/src/command/dispatcher.rs +++ b/pumpkin/src/command/dispatcher.rs @@ -32,11 +32,11 @@ impl CommandError { pub fn into_string_or_pumpkin_error(self, cmd: &str) -> Result> { match self { InvalidConsumption(s) => { - println!("Error while parsing command \"{cmd}\": {s:?} was consumed, but couldn't be parsed"); + log::error!("Error while parsing command \"{cmd}\": {s:?} was consumed, but couldn't be parsed"); Ok("Internal Error (See logs for details)".into()) } InvalidRequirement => { - println!("Error while parsing command \"{cmd}\": a requirement that was expected was not met."); + log::error!("Error while parsing command \"{cmd}\": a requirement that was expected was not met."); Ok("Internal Error (See logs for details)".into()) } GeneralCommandIssue(s) => Ok(s), diff --git a/pumpkin/src/entity/mod.rs b/pumpkin/src/entity/mod.rs index 92544ccb..649144a7 100644 --- a/pumpkin/src/entity/mod.rs +++ b/pumpkin/src/entity/mod.rs @@ -12,7 +12,7 @@ use pumpkin_core::math::{ use pumpkin_entity::{entity_type::EntityType, pose::EntityPose, EntityId}; use pumpkin_protocol::{ client::play::{CSetEntityMetadata, CTeleportEntity, Metadata}, - VarInt, + codec::var_int::VarInt, }; use crate::world::World; diff --git a/pumpkin/src/entity/player.rs b/pumpkin/src/entity/player.rs index 2fbfa002..dbc84e74 100644 --- a/pumpkin/src/entity/player.rs +++ b/pumpkin/src/entity/player.rs @@ -1,4 +1,5 @@ use std::{ + num::NonZeroU8, sync::{ atomic::{AtomicBool, AtomicI32, AtomicI64, AtomicU32, AtomicU8}, Arc, @@ -39,23 +40,26 @@ use pumpkin_protocol::{ SPlayerCommand, SPlayerInput, SPlayerPosition, SPlayerPositionRotation, SPlayerRotation, SSetCreativeSlot, SSetHeldItem, SSetPlayerGround, SSwingArm, SUseItem, SUseItemOn, }, - RawPacket, ServerPacket, SoundCategory, VarInt, + RawPacket, ServerPacket, SoundCategory, }; +use pumpkin_protocol::{client::play::CUpdateTime, codec::var_int::VarInt}; use pumpkin_protocol::{ client::play::{CSetEntityMetadata, Metadata}, server::play::{SClickContainer, SKeepAlive}, }; use pumpkin_world::{ cylindrical_chunk_iterator::Cylindrical, - item::{item_registry::get_item_by_id, ItemStack}, + item::{ + item_registry::{get_item_by_id, Operation}, + ItemStack, + }, }; use tokio::sync::{Mutex, Notify}; use super::Entity; -use crate::error::PumpkinError; +use crate::{error::PumpkinError, net::GameProfile}; use crate::{ - client::{ - authentication::GameProfile, + net::{ combat::{self, player_attack_sound, AttackType}, Client, PlayerConfig, }, @@ -129,7 +133,7 @@ impl Player { ) -> Self { let gameprofile = client.gameprofile.lock().await.clone().map_or_else( || { - log::error!("No gameprofile?. Impossible"); + log::error!("Client {} has no game profile!", client.id); GameProfile { id: uuid::Uuid::new_v4(), name: String::new(), @@ -140,7 +144,6 @@ impl Player { |profile| profile, ); let config = client.config.lock().await.clone().unwrap_or_default(); - let view_distance = config.view_distance; let bounding_box_size = BoundingBoxSize { width: 0.6, height: 1.8, @@ -171,7 +174,13 @@ impl Player { teleport_id_count: AtomicI32::new(0), abilities: Mutex::new(Abilities::default()), gamemode: AtomicCell::new(gamemode), - watched_section: AtomicCell::new(Cylindrical::new(Vector2::new(0, 0), view_distance)), + // We want this to be an impossible watched section so that `player_chunker::update_position` + // will mark chunks as watched for a new join rather than a respawn + // (We left shift by one so we can search around that chunk) + watched_section: AtomicCell::new(Cylindrical::new( + Vector2::new(i32::MAX >> 1, i32::MAX >> 1), + unsafe { NonZeroU8::new_unchecked(1) }, + )), wait_for_keep_alive: AtomicBool::new(false), keep_alive_id: AtomicI64::new(0), last_keep_alive_time: AtomicCell::new(std::time::Instant::now()), @@ -211,18 +220,18 @@ impl Player { ); // Decrement value of watched chunks - let chunks_to_clean = world.mark_chunks_as_not_watched(&radial_chunks); + let chunks_to_clean = world.level.mark_chunks_as_not_watched(&radial_chunks); // Remove chunks with no watchers from the cache - world.clean_chunks(&chunks_to_clean); + world.level.clean_chunks(&chunks_to_clean); // Remove left over entries from all possiblily loaded chunks - world.clean_memory(&radial_chunks); + world.level.clean_memory(&radial_chunks); log::debug!( "Removed player id {} ({}) ({} chunks remain cached)", self.gameprofile.name, self.client.id, - self.world().get_cached_chunk_len() + self.world().level.loaded_chunk_count() ); //self.world().level.list_cached(); @@ -250,7 +259,7 @@ impl Player { // TODO: this should be cached in memory if let Some(modifiers) = &item.components.attribute_modifiers { for item_mod in &modifiers.modifiers { - if item_mod.operation == "add_value" { + if item_mod.operation == Operation::AddValue { if item_mod.id == "minecraft:base_attack_damage" { add_damage = item_mod.amount; } @@ -438,6 +447,18 @@ impl Player { self.permission_lvl } + /// Sends the world time to just the player. + pub async fn send_time(&self, world: &World) { + let l_world = world.level_time.lock().await; + self.client + .send_packet(&CUpdateTime::new( + l_world.world_age, + l_world.time_of_day, + true, + )) + .await; + } + /// Yaw and Pitch in degrees /// Rarly used, For example when waking up player from bed or first time spawn. Otherwise entity teleport is used /// Player should respond with the `SConfirmTeleport` packet @@ -541,8 +562,6 @@ impl Player { "Setting the same gamemode as already is" ); self.gamemode.store(gamemode); - // The client is using the same method for setting abilities when receiving the CGameEvent ChangeGameMode packet. - // So we can just update the abilities without sending them. { // use another scope so we instantly unlock abilities let mut abilities = self.abilities.lock().await; @@ -566,6 +585,7 @@ impl Player { } } } + self.send_abilities_update().await; self.living_entity .entity .world @@ -775,7 +795,7 @@ impl Default for Abilities { flying: false, allow_flying: false, creative: false, - fly_speed: 0.4, + fly_speed: 0.05, walk_speed_fov: 0.1, } } diff --git a/pumpkin/src/error.rs b/pumpkin/src/error.rs index d83e9a3e..10cd79ca 100644 --- a/pumpkin/src/error.rs +++ b/pumpkin/src/error.rs @@ -1,6 +1,6 @@ use log::log; use pumpkin_inventory::InventoryError; -use pumpkin_protocol::bytebuf::DeserializerError; +use pumpkin_protocol::bytebuf::ReadingError; use std::fmt::Display; pub trait PumpkinError: Send + std::error::Error + Display { @@ -52,7 +52,7 @@ impl PumpkinError for InventoryError { } } -impl PumpkinError for DeserializerError { +impl PumpkinError for ReadingError { fn is_kick(&self) -> bool { true } diff --git a/pumpkin/src/main.rs b/pumpkin/src/main.rs index d33cd9e1..232f30a1 100644 --- a/pumpkin/src/main.rs +++ b/pumpkin/src/main.rs @@ -2,7 +2,22 @@ #![deny(clippy::pedantic)] // #![warn(clippy::restriction)] #![deny(clippy::cargo)] +// to keep consistency #![deny(clippy::if_then_some_else_none)] +#![deny(clippy::empty_enum_variants_with_brackets)] +#![deny(clippy::empty_structs_with_brackets)] +#![deny(clippy::separated_literal_suffix)] +#![deny(clippy::semicolon_outside_block)] +#![deny(clippy::non_zero_suggestions)] +#![deny(clippy::string_lit_chars_any)] +#![deny(clippy::use_self)] +#![deny(clippy::useless_let_if_seq)] +#![deny(clippy::branches_sharing_code)] +#![deny(clippy::equatable_if_let)] +#![deny(clippy::option_if_let_else)] +// use log crate +#![deny(clippy::print_stdout)] +#![deny(clippy::print_stderr)] // REMOVE SOME WHEN RELEASE #![expect(clippy::cargo_common_metadata)] #![expect(clippy::multiple_crate_versions)] @@ -20,7 +35,7 @@ compile_error!("Compiling for WASI targets is not supported!"); use log::LevelFilter; -use client::Client; +use net::{lan_broadcast, query, rcon::RCONServer, Client}; use server::{ticker::Ticker, Server}; use std::io::{self}; use tokio::io::{AsyncBufReadExt, BufReader}; @@ -35,19 +50,14 @@ use crate::server::CURRENT_MC_VERSION; use pumpkin_config::{ADVANCED_CONFIG, BASIC_CONFIG}; use pumpkin_core::text::{color::NamedColor, TextComponent}; use pumpkin_protocol::CURRENT_MC_PROTOCOL; -use rcon::RCONServer; use std::time::Instant; // Setup some tokens to allow us to identify which event is for which socket. pub mod block; -pub mod client; pub mod command; pub mod entity; pub mod error; -pub mod lan_broadcast; -pub mod proxy; -pub mod query; -pub mod rcon; +pub mod net; pub mod server; pub mod world; @@ -182,8 +192,8 @@ async fn main() { let server = server.clone(); tokio::spawn(async move { ticker.run(&server).await; - }); - } + }) + }; let mut master_client_id: u16 = 0; loop { diff --git a/pumpkin/src/client/authentication.rs b/pumpkin/src/net/authentication.rs similarity index 85% rename from pumpkin/src/client/authentication.rs rename to pumpkin/src/net/authentication.rs index f99a62cb..4136a74a 100644 --- a/pumpkin/src/client/authentication.rs +++ b/pumpkin/src/net/authentication.rs @@ -2,15 +2,14 @@ use std::{collections::HashMap, net::IpAddr}; use base64::{engine::general_purpose, Engine}; use pumpkin_config::{auth::TextureConfig, ADVANCED_CONFIG}; -use pumpkin_core::ProfileAction; use pumpkin_protocol::Property; use reqwest::{StatusCode, Url}; use serde::Deserialize; -use sha1::Digest; -use sha2::Sha256; use thiserror::Error; use uuid::Uuid; +use super::GameProfile; + #[derive(Deserialize, Clone, Debug)] #[expect(dead_code)] #[serde(rename_all = "camelCase")] @@ -29,14 +28,8 @@ pub struct Texture { metadata: Option>, } -#[derive(Deserialize, Clone, Debug)] -pub struct GameProfile { - pub id: Uuid, - pub name: String, - pub properties: Vec, - #[serde(rename = "profileActions")] - pub profile_actions: Option>, -} +const MOJANG_AUTHENTICATION_URL: &str = "https://sessionserver.mojang.com/session/minecraft/hasJoined?username={username}&serverId={server_hash}"; +const MOJANG_PREVENT_PROXY_AUTHENTICATION_URL: &str = "https://sessionserver.mojang.com/session/minecraft/hasJoined?username={username}&serverId={server_hash}"; /// Sends a GET request to Mojang's authentication servers to verify a client's Minecraft account. /// @@ -50,20 +43,19 @@ pub struct GameProfile { /// 2. Mojang's servers verify the client's credentials and add the player to the their Servers /// 3. Now our server will send a Request to the Session servers and check if the Player has joined the Session Server . /// -/// See +/// See pub async fn authenticate( username: &str, server_hash: &str, ip: &IpAddr, auth_client: &reqwest::Client, ) -> Result { - assert!(ADVANCED_CONFIG.authentication.enabled); let address = if ADVANCED_CONFIG.authentication.prevent_proxy_connections { let auth_url = ADVANCED_CONFIG .authentication .prevent_proxy_connection_auth_url .as_deref() - .unwrap_or("https://sessionserver.mojang.com/session/minecraft/hasJoined?username={username}&serverId={server_hash}&ip={ip}"); + .unwrap_or(MOJANG_PREVENT_PROXY_AUTHENTICATION_URL); auth_url .replace("{username}", username) @@ -74,7 +66,7 @@ pub async fn authenticate( .authentication .auth_url .as_deref() - .unwrap_or("https://sessionserver.mojang.com/session/minecraft/hasJoined?username={username}&serverId={server_hash}"); + .unwrap_or(MOJANG_AUTHENTICATION_URL); auth_url .replace("{username}", username) @@ -129,10 +121,6 @@ pub fn is_texture_url_valid(url: &Url, config: &TextureConfig) -> Result<(), Tex Ok(()) } -pub fn offline_uuid(username: &str) -> Result { - Uuid::from_slice(&Sha256::digest(username)[..16]) -} - #[derive(Error, Debug)] pub enum AuthError { #[error("Missing auth client")] diff --git a/pumpkin/src/client/combat.rs b/pumpkin/src/net/combat.rs similarity index 98% rename from pumpkin/src/client/combat.rs rename to pumpkin/src/net/combat.rs index 8b5b0ed1..12cb0a90 100644 --- a/pumpkin/src/client/combat.rs +++ b/pumpkin/src/net/combat.rs @@ -4,7 +4,8 @@ use pumpkin_core::math::vector3::Vector3; use pumpkin_macros::{particle, sound}; use pumpkin_protocol::{ client::play::{CEntityVelocity, CParticle}, - SoundCategory, VarInt, + codec::var_int::VarInt, + SoundCategory, }; use pumpkin_world::item::ItemStack; diff --git a/pumpkin/src/client/container.rs b/pumpkin/src/net/container.rs similarity index 97% rename from pumpkin/src/client/container.rs rename to pumpkin/src/net/container.rs index 10b631fa..c157b5e2 100644 --- a/pumpkin/src/client/container.rs +++ b/pumpkin/src/net/container.rs @@ -1,6 +1,5 @@ use crate::entity::player::Player; use crate::server::Server; -use itertools::Itertools; use pumpkin_core::text::TextComponent; use pumpkin_core::GameMode; use pumpkin_inventory::container_click::{ @@ -13,9 +12,9 @@ use pumpkin_inventory::{Container, WindowType}; use pumpkin_protocol::client::play::{ CCloseContainer, COpenScreen, CSetContainerContent, CSetContainerProperty, CSetContainerSlot, }; +use pumpkin_protocol::codec::slot::Slot; +use pumpkin_protocol::codec::var_int::VarInt; use pumpkin_protocol::server::play::SClickContainer; -use pumpkin_protocol::slot::Slot; -use pumpkin_protocol::VarInt; use pumpkin_world::item::item_registry::Item; use pumpkin_world::item::ItemStack; use std::sync::Arc; @@ -59,11 +58,11 @@ impl Player { let container = OptionallyCombinedContainer::new(&mut inventory, container); - let slots = container + let slots: Vec = container .all_slots_ref() .into_iter() .map(Slot::from) - .collect_vec(); + .collect(); let carried_item = self .carried_item @@ -136,15 +135,7 @@ impl Player { return Err(InventoryError::ClosedContainerInteract(self.entity_id())); } - let click = Click::new( - packet - .mode - .0 - .try_into() - .expect("Mode can only be between 0-6"), - packet.button, - packet.slot, - )?; + let click = Click::new(packet.mode, packet.button, packet.slot)?; let (crafted_item, crafted_item_slot) = { let mut inventory = self.inventory().lock().await; let combined = @@ -463,7 +454,7 @@ impl Player { } async fn get_current_players_in_container(&self, server: &Server) -> Vec> { - let player_ids = { + let player_ids: Vec = { let open_containers = server.open_containers.read().await; open_containers .get(&self.open_container.load().unwrap()) @@ -471,7 +462,7 @@ impl Player { .all_player_ids() .into_iter() .filter(|player_id| *player_id != self.entity_id()) - .collect_vec() + .collect() }; let player_token = self.gameprofile.id; @@ -494,7 +485,7 @@ impl Player { player_ids.contains(&entity_id).then(|| player.clone()) } }) - .collect_vec(); + .collect(); players } diff --git a/pumpkin/src/lan_broadcast.rs b/pumpkin/src/net/lan_broadcast.rs similarity index 100% rename from pumpkin/src/lan_broadcast.rs rename to pumpkin/src/net/lan_broadcast.rs diff --git a/pumpkin/src/client/mod.rs b/pumpkin/src/net/mod.rs similarity index 91% rename from pumpkin/src/client/mod.rs rename to pumpkin/src/net/mod.rs index 6c260447..0e5f9e9c 100644 --- a/pumpkin/src/client/mod.rs +++ b/pumpkin/src/net/mod.rs @@ -1,6 +1,7 @@ use std::{ collections::VecDeque, net::SocketAddr, + num::NonZeroU8, sync::{ atomic::{AtomicBool, AtomicI32}, Arc, @@ -12,34 +13,57 @@ use crate::{ server::Server, }; -use authentication::GameProfile; use crossbeam::atomic::AtomicCell; use pumpkin_config::compression::CompressionInfo; -use pumpkin_core::text::TextComponent; +use pumpkin_core::{text::TextComponent, ProfileAction}; use pumpkin_protocol::{ - bytebuf::{packet_id::Packet, DeserializerError}, + bytebuf::{packet_id::Packet, ReadingError}, client::{config::CConfigDisconnect, login::CLoginDisconnect, play::CPlayDisconnect}, packet_decoder::PacketDecoder, packet_encoder::{PacketEncodeError, PacketEncoder}, server::{ - config::{SAcknowledgeFinishConfig, SClientInformationConfig, SKnownPacks, SPluginMessage}, + config::{ + SAcknowledgeFinishConfig, SClientInformationConfig, SConfigCookieResponse, SKnownPacks, + SPluginMessage, + }, handshake::SHandShake, - login::{SEncryptionResponse, SLoginAcknowledged, SLoginPluginResponse, SLoginStart}, + login::{ + SEncryptionResponse, SLoginAcknowledged, SLoginCookieResponse, SLoginPluginResponse, + SLoginStart, + }, status::{SStatusPingRequest, SStatusRequest}, }, - ClientPacket, ConnectionState, RawPacket, ServerPacket, + ClientPacket, ConnectionState, Property, RawPacket, ServerPacket, }; +use serde::Deserialize; +use sha1::Digest; +use sha2::Sha256; use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::sync::Mutex; -use pumpkin_protocol::server::config::SCookieResponse as SCCookieResponse; -use pumpkin_protocol::server::login::SCookieResponse as SLCookieResponse; use thiserror::Error; -pub mod authentication; -mod client_packet; +use uuid::Uuid; +mod authentication; pub mod combat; mod container; -pub mod player_packet; +pub mod lan_broadcast; +mod packet; +mod proxy; +pub mod query; +pub mod rcon; + +#[derive(Deserialize, Clone, Debug)] +pub struct GameProfile { + pub id: Uuid, + pub name: String, + pub properties: Vec, + #[serde(rename = "profileActions")] + pub profile_actions: Option>, +} + +pub fn offline_uuid(username: &str) -> Result { + Uuid::from_slice(&Sha256::digest(username)[..16]) +} /// Represents a player's configuration settings. /// @@ -53,7 +77,7 @@ pub struct PlayerConfig { /// The player's preferred language. pub locale: String, // 16 /// The maximum distance at which chunks are rendered. - pub view_distance: u8, + pub view_distance: NonZeroU8, /// The player's chat mode settings pub chat_mode: ChatMode, /// Whether chat colors are enabled. @@ -72,7 +96,7 @@ impl Default for PlayerConfig { fn default() -> Self { Self { locale: "en_us".to_string(), - view_distance: 2, + view_distance: unsafe { NonZeroU8::new_unchecked(10) }, chat_mode: ChatMode::Enabled, chat_colors: true, skin_parts: 0, @@ -101,8 +125,6 @@ pub struct Client { pub server_address: Mutex, /// The current connection state of the client (e.g., Handshaking, Status, Play). pub connection_state: AtomicCell, - /// Whether encryption is enabled for the connection. - pub encryption: AtomicBool, /// Indicates if the client connection is closed. pub closed: AtomicBool, /// The underlying TCP connection to the client. @@ -137,7 +159,6 @@ impl Client { connection_writer: Arc::new(Mutex::new(connection_writer)), enc: Arc::new(Mutex::new(PacketEncoder::default())), dec: Arc::new(Mutex::new(PacketDecoder::default())), - encryption: AtomicBool::new(false), closed: AtomicBool::new(false), client_packets_queue: Arc::new(Mutex::new(VecDeque::new())), make_player: AtomicBool::new(false), @@ -181,8 +202,6 @@ impl Client { shared_secret: Option<&[u8]>, // decrypted ) -> Result<(), EncryptionError> { if let Some(shared_secret) = shared_secret { - self.encryption - .store(true, std::sync::atomic::Ordering::Relaxed); let crypt_key: [u8; 16] = shared_secret .try_into() .map_err(|_| EncryptionError::SharedWrongLength)?; @@ -295,8 +314,8 @@ impl Client { /// /// # Arguments /// - /// * `server`: A reference to the `Arc` instance. - pub async fn process_packets(&self, server: &Arc) { + /// * `server`: A reference to the `Server` instance. + pub async fn process_packets(&self, server: &Server) { let mut packet_queue = self.client_packets_queue.lock().await; while let Some(mut packet) = packet_queue.pop_front() { if self.closed.load(std::sync::atomic::Ordering::Relaxed) { @@ -329,7 +348,7 @@ impl Client { /// /// # Arguments /// - /// * `server`: A reference to the `Arc` instance. + /// * `server`: A reference to the `Server` instance. /// * `packet`: A mutable reference to the `RawPacket` to be processed. /// /// # Returns @@ -341,9 +360,9 @@ impl Client { /// Returns a `DeserializerError` if an error occurs during packet deserialization. pub async fn handle_packet( &self, - server: &Arc, + server: &Server, packet: &mut RawPacket, - ) -> Result<(), DeserializerError> { + ) -> Result<(), ReadingError> { match self.connection_state.load() { pumpkin_protocol::ConnectionState::HandShake => { self.handle_handshake_packet(packet).await @@ -366,10 +385,7 @@ impl Client { } } - async fn handle_handshake_packet( - &self, - packet: &mut RawPacket, - ) -> Result<(), DeserializerError> { + async fn handle_handshake_packet(&self, packet: &mut RawPacket) -> Result<(), ReadingError> { log::debug!("Handling handshake group"); let bytebuf = &mut packet.bytebuf; match packet.id.0 { @@ -388,9 +404,9 @@ impl Client { async fn handle_status_packet( &self, - server: &Arc, + server: &Server, packet: &mut RawPacket, - ) -> Result<(), DeserializerError> { + ) -> Result<(), ReadingError> { log::debug!("Handling status group"); let bytebuf = &mut packet.bytebuf; match packet.id.0 { @@ -414,9 +430,9 @@ impl Client { async fn handle_login_packet( &self, - server: &Arc, + server: &Server, packet: &mut RawPacket, - ) -> Result<(), DeserializerError> { + ) -> Result<(), ReadingError> { log::debug!("Handling login group for id"); let bytebuf = &mut packet.bytebuf; match packet.id.0 { @@ -435,8 +451,8 @@ impl Client { SLoginAcknowledged::PACKET_ID => { self.handle_login_acknowledged(server).await; } - SLCookieResponse::PACKET_ID => { - self.handle_login_cookie_response(SLCookieResponse::read(bytebuf)?); + SLoginCookieResponse::PACKET_ID => { + self.handle_login_cookie_response(SLoginCookieResponse::read(bytebuf)?); } _ => { log::error!( @@ -450,9 +466,9 @@ impl Client { async fn handle_config_packet( &self, - server: &Arc, + server: &Server, packet: &mut RawPacket, - ) -> Result<(), DeserializerError> { + ) -> Result<(), ReadingError> { log::debug!("Handling config group"); let bytebuf = &mut packet.bytebuf; match packet.id.0 { @@ -471,8 +487,8 @@ impl Client { self.handle_known_packs(server, SKnownPacks::read(bytebuf)?) .await; } - SCCookieResponse::PACKET_ID => { - self.handle_config_cookie_response(SCCookieResponse::read(bytebuf)?); + SConfigCookieResponse::PACKET_ID => { + self.handle_config_cookie_response(SConfigCookieResponse::read(bytebuf)?); } _ => { log::error!( diff --git a/pumpkin/src/net/packet/config.rs b/pumpkin/src/net/packet/config.rs new file mode 100644 index 00000000..ed1d8769 --- /dev/null +++ b/pumpkin/src/net/packet/config.rs @@ -0,0 +1,98 @@ +use std::num::NonZeroU8; + +use crate::{ + entity::player::{ChatMode, Hand}, + net::{Client, PlayerConfig}, + server::Server, +}; +use core::str; +use num_traits::FromPrimitive; +use pumpkin_protocol::{ + client::config::{CFinishConfig, CRegistryData}, + codec::var_int::VarInt, + server::config::{ + SClientInformationConfig, SConfigCookieResponse, SKnownPacks, SPluginMessage, + }, + ConnectionState, +}; + +impl Client { + pub async fn handle_client_information_config( + &self, + client_information: SClientInformationConfig, + ) { + log::debug!("Handling client settings"); + if client_information.view_distance <= 0 { + self.kick("Cannot have zero or negative view distance!") + .await; + return; + } + + if let (Some(main_hand), Some(chat_mode)) = ( + Hand::from_i32(client_information.main_hand.into()), + ChatMode::from_i32(client_information.chat_mode.into()), + ) { + *self.config.lock().await = Some(PlayerConfig { + locale: client_information.locale, + view_distance: unsafe { + NonZeroU8::new_unchecked(client_information.view_distance as u8) + }, + chat_mode, + chat_colors: client_information.chat_colors, + skin_parts: client_information.skin_parts, + main_hand, + text_filtering: client_information.text_filtering, + server_listing: client_information.server_listing, + }); + } else { + self.kick("Invalid hand or chat type").await; + } + } + + pub async fn handle_plugin_message(&self, plugin_message: SPluginMessage) { + log::debug!("Handling plugin message"); + if plugin_message + .channel + .to_string() + .starts_with("minecraft:brand") + { + log::debug!("got a client brand"); + match str::from_utf8(&plugin_message.data) { + Ok(brand) => *self.brand.lock().await = Some(brand.to_string()), + Err(e) => self.kick(&e.to_string()).await, + } + } + } + + pub fn handle_config_cookie_response(&self, packet: SConfigCookieResponse) { + // TODO: allow plugins to access this + log::debug!( + "Received cookie_response[config]: key: \"{}\", has_payload: \"{}\", payload_length: \"{}\"", + packet.key.to_string(), + packet.has_payload, + packet.payload_length.unwrap_or(VarInt::from(0)).0 + ); + } + + pub async fn handle_known_packs(&self, server: &Server, _config_acknowledged: SKnownPacks) { + log::debug!("Handling known packs"); + for registry in &server.cached_registry { + self.send_packet(&CRegistryData::new( + ®istry.registry_id, + ®istry.registry_entries, + )) + .await; + } + + // We are done with configuring + log::debug!("finished config"); + self.send_packet(&CFinishConfig::new()).await; + } + + pub fn handle_config_acknowledged(&self) { + log::debug!("Handling config acknowledge"); + self.connection_state.store(ConnectionState::Play); + self.make_player + .store(true, std::sync::atomic::Ordering::Relaxed); + } +} diff --git a/pumpkin/src/net/packet/handshake.rs b/pumpkin/src/net/packet/handshake.rs new file mode 100644 index 00000000..58705210 --- /dev/null +++ b/pumpkin/src/net/packet/handshake.rs @@ -0,0 +1,29 @@ +use std::num::NonZeroI32; + +use pumpkin_protocol::{server::handshake::SHandShake, ConnectionState, CURRENT_MC_PROTOCOL}; + +use crate::{net::Client, server::CURRENT_MC_VERSION}; + +impl Client { + pub async fn handle_handshake(&self, handshake: SHandShake) { + let version = handshake.protocol_version.0; + self.protocol_version + .store(version, std::sync::atomic::Ordering::Relaxed); + *self.server_address.lock().await = handshake.server_address; + + log::debug!("Handshake: next state {:?}", &handshake.next_state); + self.connection_state.store(handshake.next_state); + if self.connection_state.load() != ConnectionState::Status { + let protocol = version; + match protocol.cmp(&NonZeroI32::from(CURRENT_MC_PROTOCOL).get()) { + std::cmp::Ordering::Less => { + self.kick(&format!("Client outdated ({protocol}), Server uses Minecraft {CURRENT_MC_VERSION}, Protocol {CURRENT_MC_PROTOCOL}")).await; + } + std::cmp::Ordering::Equal => {} + std::cmp::Ordering::Greater => { + self.kick(&format!("Server outdated, Server uses Minecraft {CURRENT_MC_VERSION}, Protocol {CURRENT_MC_PROTOCOL}")).await; + } + } + } + } +} diff --git a/pumpkin/src/client/client_packet.rs b/pumpkin/src/net/packet/login.rs similarity index 63% rename from pumpkin/src/client/client_packet.rs rename to pumpkin/src/net/packet/login.rs index ac21d99f..585413d6 100644 --- a/pumpkin/src/client/client_packet.rs +++ b/pumpkin/src/net/packet/login.rs @@ -1,37 +1,29 @@ -use super::{authentication::AuthError, Client, PlayerConfig}; -use crate::{ - client::authentication::{self, offline_uuid, validate_textures, GameProfile}, - entity::player::{ChatMode, Hand}, - proxy::{ - bungeecord, - velocity::{self, velocity_login}, - }, - server::{Server, CURRENT_MC_VERSION}, -}; -use core::str; -use num_traits::FromPrimitive; +use std::sync::LazyLock; + use pumpkin_config::{ADVANCED_CONFIG, BASIC_CONFIG}; use pumpkin_core::text::TextComponent; -use pumpkin_protocol::client::config::{CServerLinks, Label, Link, LinkType}; -use pumpkin_protocol::server::config::SCookieResponse as SCCookieResponse; -use pumpkin_protocol::server::login::SCookieResponse as SLCookieResponse; use pumpkin_protocol::{ client::{ - config::{CConfigAddResourcePack, CFinishConfig, CKnownPacks, CRegistryData}, + config::{CConfigAddResourcePack, CConfigServerLinks, CKnownPacks}, login::{CLoginSuccess, CSetCompression}, - status::CPingResponse, - }, - server::{ - config::{SClientInformationConfig, SKnownPacks, SPluginMessage}, - handshake::SHandShake, - login::{SEncryptionResponse, SLoginPluginResponse, SLoginStart}, - status::SStatusPingRequest, }, - ConnectionState, KnownPack, VarInt, CURRENT_MC_PROTOCOL, + codec::var_int::VarInt, + server::login::{SEncryptionResponse, SLoginCookieResponse, SLoginPluginResponse, SLoginStart}, + ConnectionState, KnownPack, Label, Link, LinkType, }; -use std::sync::LazyLock; use uuid::Uuid; +use crate::{ + net::{ + authentication::{self, AuthError}, + offline_uuid, + packet::is_valid_player_name, + proxy::{bungeecord, velocity}, + Client, GameProfile, + }, + server::Server, +}; + static LINKS: LazyLock> = LazyLock::new(|| { let mut links: Vec = Vec::new(); @@ -92,52 +84,7 @@ static LINKS: LazyLock> = LazyLock::new(|| { links }); -/// Processes incoming Packets from the Client to the Server -/// Implements the `Client` Packets -/// NEVER TRUST THE CLIENT. HANDLE EVERY ERROR, UNWRAP/EXPECT impl Client { - pub async fn handle_handshake(&self, handshake: SHandShake) { - let version = handshake.protocol_version.0; - self.protocol_version - .store(version, std::sync::atomic::Ordering::Relaxed); - *self.server_address.lock().await = handshake.server_address; - - log::debug!("Handshake: next state {:?}", &handshake.next_state); - self.connection_state.store(handshake.next_state); - if self.connection_state.load() != ConnectionState::Status { - let protocol = version; - match protocol.cmp(&(CURRENT_MC_PROTOCOL as i32)) { - std::cmp::Ordering::Less => { - self.kick(&format!("Client outdated ({protocol}), Server uses Minecraft {CURRENT_MC_VERSION}, Protocol {CURRENT_MC_PROTOCOL}")).await; - } - std::cmp::Ordering::Equal => {} - std::cmp::Ordering::Greater => { - self.kick(&format!("Server outdated, Server uses Minecraft {CURRENT_MC_VERSION}, Protocol {CURRENT_MC_PROTOCOL}")).await; - } - } - } - } - - pub async fn handle_status_request(&self, server: &Server) { - log::debug!("Handling status request"); - let status = server.get_status(); - self.send_packet(&status.lock().await.get_status()).await; - } - - pub async fn handle_ping_request(&self, ping_request: SStatusPingRequest) { - log::debug!("Handling ping request"); - self.send_packet(&CPingResponse::new(ping_request.payload)) - .await; - self.close(); - } - - fn is_valid_player_name(name: &str) -> bool { - name.len() <= 16 - && name - .chars() - .all(|c| c > 32_u8 as char && c < 127_u8 as char) - } - pub async fn handle_login_start(&self, server: &Server, login_start: SLoginStart) { log::debug!("login start"); @@ -151,7 +98,7 @@ impl Client { return; } - if !Self::is_valid_player_name(&login_start.name) { + if !is_valid_player_name(&login_start.name) { self.kick("Invalid characters in username").await; return; } @@ -161,9 +108,15 @@ impl Client { let proxy = &ADVANCED_CONFIG.proxy; if proxy.enabled { if proxy.velocity.enabled { - velocity_login(self).await; + velocity::velocity_login(self).await; } else if proxy.bungeecord.enabled { - match bungeecord::bungeecord_login(self, login_start.name).await { + match bungeecord::bungeecord_login( + &self.address, + &self.server_address.lock().await, + login_start.name, + ) + .await + { Ok((_ip, profile)) => { // self.address.lock() = ip; self.finish_login(&profile).await; @@ -306,24 +259,26 @@ impl Client { } // validate textures for property in &profile.properties { - validate_textures(property, &ADVANCED_CONFIG.authentication.textures) - .map_err(AuthError::TextureError)?; + authentication::validate_textures( + property, + &ADVANCED_CONFIG.authentication.textures, + ) + .map_err(AuthError::TextureError)?; } return Ok(profile); } Err(AuthError::MissingAuthClient) } - pub fn handle_login_cookie_response(&self, packet: SLCookieResponse) { + pub fn handle_login_cookie_response(&self, packet: SLoginCookieResponse) { // TODO: allow plugins to access this log::debug!( - "Received cookie_response[login]: key: \"{}\", has_payload: \"{}\", payload_length: \"{}\"", - packet.key, - packet.has_payload, - packet.payload_length.unwrap_or(VarInt::from(0)).0 - ); + "Received cookie_response[login]: key: \"{}\", has_payload: \"{}\", payload_length: \"{}\"", + packet.key.to_string(), + packet.has_payload, + packet.payload_length.unwrap_or(VarInt::from(0)).0 + ); } - pub async fn handle_plugin_response(&self, plugin_response: SLoginPluginResponse) { log::debug!("Handling plugin"); let velocity_config = &ADVANCED_CONFIG.proxy.velocity; @@ -370,8 +325,11 @@ impl Client { } if ADVANCED_CONFIG.server_links.enabled { - self.send_packet(&CServerLinks::new(&VarInt(LINKS.len() as i32), &LINKS)) - .await; + self.send_packet(&CConfigServerLinks::new( + &VarInt(LINKS.len() as i32), + &LINKS, + )) + .await; } // known data packs @@ -383,72 +341,4 @@ impl Client { .await; log::debug!("login acknowledged"); } - pub async fn handle_client_information_config( - &self, - client_information: SClientInformationConfig, - ) { - log::debug!("Handling client settings"); - if let (Some(main_hand), Some(chat_mode)) = ( - Hand::from_i32(client_information.main_hand.into()), - ChatMode::from_i32(client_information.chat_mode.into()), - ) { - *self.config.lock().await = Some(PlayerConfig { - locale: client_information.locale, - view_distance: client_information.view_distance as u8, - chat_mode, - chat_colors: client_information.chat_colors, - skin_parts: client_information.skin_parts, - main_hand, - text_filtering: client_information.text_filtering, - server_listing: client_information.server_listing, - }); - } else { - self.kick("Invalid hand or chat type").await; - } - } - - pub async fn handle_plugin_message(&self, plugin_message: SPluginMessage) { - log::debug!("Handling plugin message"); - if plugin_message.channel.starts_with("minecraft:brand") - || plugin_message.channel.starts_with("MC|Brand") - { - log::debug!("got a client brand"); - match str::from_utf8(&plugin_message.data) { - Ok(brand) => *self.brand.lock().await = Some(brand.to_string()), - Err(e) => self.kick(&e.to_string()).await, - } - } - } - - pub fn handle_config_cookie_response(&self, packet: SCCookieResponse) { - // TODO: allow plugins to access this - log::debug!( - "Received cookie_response[config]: key: \"{}\", has_payload: \"{}\", payload_length: \"{}\"", - packet.key, - packet.has_payload, - packet.payload_length.unwrap_or(VarInt::from(0)).0 - ); - } - - pub async fn handle_known_packs(&self, server: &Server, _config_acknowledged: SKnownPacks) { - log::debug!("Handling known packs"); - for registry in &server.cached_registry { - self.send_packet(&CRegistryData::new( - ®istry.registry_id, - ®istry.registry_entries, - )) - .await; - } - - // We are done with configuring - log::debug!("finished config"); - self.send_packet(&CFinishConfig::new()).await; - } - - pub fn handle_config_acknowledged(&self) { - log::debug!("Handling config acknowledge"); - self.connection_state.store(ConnectionState::Play); - self.make_player - .store(true, std::sync::atomic::Ordering::Relaxed); - } } diff --git a/pumpkin/src/net/packet/mod.rs b/pumpkin/src/net/packet/mod.rs new file mode 100644 index 00000000..0dbc697d --- /dev/null +++ b/pumpkin/src/net/packet/mod.rs @@ -0,0 +1,9 @@ +mod config; +mod handshake; +mod login; +mod play; +mod status; + +fn is_valid_player_name(name: &str) -> bool { + name.len() <= 16 && name.chars().all(|c| c > 32u8 as char && c < 127u8 as char) +} diff --git a/pumpkin/src/client/player_packet.rs b/pumpkin/src/net/packet/play.rs similarity index 92% rename from pumpkin/src/client/player_packet.rs rename to pumpkin/src/net/packet/play.rs index 6af75c86..6fdaffc7 100644 --- a/pumpkin/src/client/player_packet.rs +++ b/pumpkin/src/net/packet/play.rs @@ -1,7 +1,8 @@ +use std::num::NonZeroU8; use std::sync::Arc; -use super::PlayerConfig; use crate::block::block_manager::BlockActionResult; +use crate::net::PlayerConfig; use crate::{ command::CommandSender, entity::player::{ChatMode, Hand, Player}, @@ -17,12 +18,12 @@ use pumpkin_core::{ text::TextComponent, GameMode, }; -use pumpkin_inventory::InventoryError; +use pumpkin_inventory::{InventoryError, WindowType}; +use pumpkin_protocol::codec::var_int::VarInt; use pumpkin_protocol::server::play::SCookieResponse as SPCookieResponse; use pumpkin_protocol::{ client::play::CCommandSuggestions, server::play::{SCloseContainer, SCommandSuggestion, SKeepAlive, SSetPlayerGround, SUseItem}, - VarInt, }; use pumpkin_protocol::{ client::play::{ @@ -435,28 +436,70 @@ impl Player { ) */ } - pub async fn handle_client_information(&self, client_information: SClientInformationPlay) { + pub async fn handle_client_information( + self: &Arc, + client_information: SClientInformationPlay, + ) { if let (Some(main_hand), Some(chat_mode)) = ( Hand::from_i32(client_information.main_hand.into()), ChatMode::from_i32(client_information.chat_mode.into()), ) { - let mut config = self.config.lock().await; - let update = - config.main_hand != main_hand || config.skin_parts != client_information.skin_parts; - - *config = PlayerConfig { - locale: client_information.locale, - // A Negative view distance would be impossible and make no sense right ?, Mojang: Lets make is signed :D - view_distance: client_information.view_distance as u8, - chat_mode, - chat_colors: client_information.chat_colors, - skin_parts: client_information.skin_parts, - main_hand, - text_filtering: client_information.text_filtering, - server_listing: client_information.server_listing, + if client_information.view_distance <= 0 { + self.kick(TextComponent::text( + "Cannot have zero or negative view distance!", + )) + .await; + return; + } + + let (update_skin, update_watched) = { + let mut config = self.config.lock().await; + let update_skin = config.main_hand != main_hand + || config.skin_parts != client_information.skin_parts; + + let old_view_distance = config.view_distance; + + let update_watched = + if old_view_distance.get() == client_information.view_distance as u8 { + false + } else { + log::debug!( + "Player {} ({}) updated render distance: {} -> {}.", + self.gameprofile.name, + self.client.id, + old_view_distance, + client_information.view_distance + ); + + true + }; + + *config = PlayerConfig { + locale: client_information.locale, + // A Negative view distance would be impossible and make no sense right ?, Mojang: Lets make is signed :D + view_distance: unsafe { + NonZeroU8::new_unchecked(client_information.view_distance as u8) + }, + chat_mode, + chat_colors: client_information.chat_colors, + skin_parts: client_information.skin_parts, + main_hand, + text_filtering: client_information.text_filtering, + server_listing: client_information.server_listing, + }; + (update_skin, update_watched) }; - drop(config); - if update { + + if update_watched { + player_chunker::update_position(self).await; + } + + if update_skin { + log::debug!( + "Player {} ({}) updated their skin.", + self.gameprofile.name, + self.client.id, + ); self.update_client_information().await; } } else { @@ -855,7 +898,7 @@ impl Player { // TODO: allow plugins to access this log::debug!( "Received cookie_response[play]: key: \"{}\", has_payload: \"{}\", payload_length: \"{}\"", - packet.key, + packet.key.to_string(), packet.has_payload, packet.payload_length.unwrap_or(VarInt::from(0)).0 ); diff --git a/pumpkin/src/net/packet/status.rs b/pumpkin/src/net/packet/status.rs new file mode 100644 index 00000000..0e9790c2 --- /dev/null +++ b/pumpkin/src/net/packet/status.rs @@ -0,0 +1,18 @@ +use pumpkin_protocol::{client::status::CPingResponse, server::status::SStatusPingRequest}; + +use crate::{net::Client, server::Server}; + +impl Client { + pub async fn handle_status_request(&self, server: &Server) { + log::debug!("Handling status request"); + let status = server.get_status(); + self.send_packet(&status.lock().await.get_status()).await; + } + + pub async fn handle_ping_request(&self, ping_request: SStatusPingRequest) { + log::debug!("Handling ping request"); + self.send_packet(&CPingResponse::new(ping_request.payload)) + .await; + self.close(); + } +} diff --git a/pumpkin/src/proxy/bungeecord.rs b/pumpkin/src/net/proxy/bungeecord.rs similarity index 59% rename from pumpkin/src/proxy/bungeecord.rs rename to pumpkin/src/net/proxy/bungeecord.rs index c8c9d1ce..dc197671 100644 --- a/pumpkin/src/proxy/bungeecord.rs +++ b/pumpkin/src/net/proxy/bungeecord.rs @@ -1,12 +1,10 @@ -use std::net::IpAddr; +use std::{net::IpAddr, net::SocketAddr}; use pumpkin_protocol::Property; use thiserror::Error; +use tokio::sync::Mutex; -use crate::{ - client::authentication::{offline_uuid, GameProfile}, - Client, -}; +use crate::net::{offline_uuid, GameProfile}; #[derive(Error, Debug)] pub enum BungeeCordError { @@ -20,11 +18,23 @@ pub enum BungeeCordError { FailedMakeOfflineUUID, } +/// Attempts to login a player via `BungeeCord`. +/// +/// This function should be called when receiving the `SLoginStart` packet. +/// It utilizes the `server_address` received in the `SHandShake` packet, +/// which may contain optional data about the client: +/// +/// 1. IP address (if `ip_forward` is enabled on the `BungeeCord` server) +/// 2. UUID (if `ip_forward` is enabled on the `BungeeCord` server) +/// 3. Game profile properties (if `ip_forward` and `online_mode` are enabled on the `BungeeCord` server) +/// +/// If any of the optional data is missing, the function will attempt to +/// determine the player's information locally. pub async fn bungeecord_login( - client: &Client, - username: String, + client_address: &Mutex, + server_address: &str, + name: String, ) -> Result<(IpAddr, GameProfile), BungeeCordError> { - let server_address = client.server_address.lock().await; let data = server_address.split('\0').take(4).collect::>(); // Ip of player, only given if ip_forward on bungee is true @@ -32,15 +42,13 @@ pub async fn bungeecord_login( Some(ip) => ip .parse() .map_err(|_| BungeeCordError::FailedParseAddress)?, - None => client.address.lock().await.ip(), + None => client_address.lock().await.ip(), }; // Uuid of player, only given if ip_forward on bungee is true let id = match data.get(2) { Some(uuid) => uuid.parse().map_err(|_| BungeeCordError::FailedParseUUID)?, - None => { - offline_uuid(username.as_str()).map_err(|_| BungeeCordError::FailedMakeOfflineUUID)? - } + None => offline_uuid(name.as_str()).map_err(|_| BungeeCordError::FailedMakeOfflineUUID)?, }; // Read properties and get textures @@ -57,7 +65,7 @@ pub async fn bungeecord_login( ip, GameProfile { id, - name: username, + name, properties, profile_actions: None, }, diff --git a/pumpkin/src/proxy/mod.rs b/pumpkin/src/net/proxy/mod.rs similarity index 100% rename from pumpkin/src/proxy/mod.rs rename to pumpkin/src/net/proxy/mod.rs diff --git a/pumpkin/src/proxy/velocity.rs b/pumpkin/src/net/proxy/velocity.rs similarity index 87% rename from pumpkin/src/proxy/velocity.rs rename to pumpkin/src/net/proxy/velocity.rs index 95f9aa07..461c9e0f 100644 --- a/pumpkin/src/proxy/velocity.rs +++ b/pumpkin/src/net/proxy/velocity.rs @@ -7,14 +7,14 @@ use bytes::{BufMut, BytesMut}; use hmac::{Hmac, Mac}; use pumpkin_config::proxy::VelocityConfig; use pumpkin_protocol::{ - bytebuf::ByteBuffer, client::login::CLoginPluginRequest, server::login::SLoginPluginResponse, + bytebuf::ByteBuf, client::login::CLoginPluginRequest, server::login::SLoginPluginResponse, Property, }; use rand::Rng; use sha2::Sha256; use thiserror::Error; -use crate::client::{authentication::GameProfile, Client}; +use crate::net::{Client, GameProfile}; type HmacSha256 = Hmac; @@ -68,19 +68,19 @@ pub fn check_integrity(data: (&[u8], &[u8]), secret: &str) -> bool { mac.verify_slice(signature).is_ok() } -fn read_game_profile(buf: &mut ByteBuffer) -> Result { +fn read_game_profile(buf: &mut BytesMut) -> Result { let id = buf - .get_uuid() + .try_get_uuid() .map_err(|_| VelocityError::FailedReadProfileUUID)?; let name = buf - .get_string() + .try_get_string() .map_err(|_| VelocityError::FailedReadProfileName)?; let properties = buf .get_list(|data| { - let name = data.get_string()?; - let value = data.get_string()?; - let signature = data.get_option(pumpkin_protocol::bytebuf::ByteBuffer::get_string)?; + let name = data.try_get_string()?; + let value = data.try_get_string()?; + let signature = data.try_get_option(ByteBuf::try_get_string)?; Ok(Property { name, @@ -109,12 +109,12 @@ pub fn receive_velocity_plugin_response( if !check_integrity((signature, data_without_signature), &config.secret) { return Err(VelocityError::FailedVerifyIntegrity); } - let mut buf = ByteBuffer::new(BytesMut::new()); + let mut buf = BytesMut::new(); buf.put_slice(data_without_signature); // check velocity version let version = buf - .get_var_int() + .try_get_var_int() .map_err(|_| VelocityError::FailedReadForwardVersion)?; let version = version.0 as u8; if version > MAX_SUPPORTED_FORWARDING_VERSION { @@ -124,7 +124,7 @@ pub fn receive_velocity_plugin_response( )); } let addr = buf - .get_string() + .try_get_string() .map_err(|_| VelocityError::FailedReadAddress)?; let socket_addr: SocketAddr = SocketAddr::new( diff --git a/pumpkin/src/query.rs b/pumpkin/src/net/query.rs similarity index 100% rename from pumpkin/src/query.rs rename to pumpkin/src/net/query.rs diff --git a/pumpkin/src/rcon/mod.rs b/pumpkin/src/net/rcon/mod.rs similarity index 98% rename from pumpkin/src/rcon/mod.rs rename to pumpkin/src/net/rcon/mod.rs index 20bc12b0..96adbc96 100644 --- a/pumpkin/src/rcon/mod.rs +++ b/pumpkin/src/net/rcon/mod.rs @@ -13,7 +13,6 @@ pub struct RCONServer; impl RCONServer { pub async fn new(config: &RCONConfig, server: Arc) -> Result { - assert!(config.enabled, "RCON is not enabled"); let listener = tokio::net::TcpListener::bind(config.address).await.unwrap(); let password = Arc::new(config.password.clone()); diff --git a/pumpkin/src/rcon/packet.rs b/pumpkin/src/net/rcon/packet.rs similarity index 100% rename from pumpkin/src/rcon/packet.rs rename to pumpkin/src/net/rcon/packet.rs diff --git a/pumpkin/src/server/connection_cache.rs b/pumpkin/src/server/connection_cache.rs index 8e6fce8f..ccc8e9dd 100644 --- a/pumpkin/src/server/connection_cache.rs +++ b/pumpkin/src/server/connection_cache.rs @@ -2,6 +2,7 @@ use core::error; use std::{ fs::File, io::{Cursor, Read}, + num::NonZeroU32, path::Path, }; @@ -9,7 +10,8 @@ use base64::{engine::general_purpose, Engine as _}; use pumpkin_config::{BasicConfiguration, BASIC_CONFIG}; use pumpkin_protocol::{ client::{config::CPluginMessage, status::CStatusResponse}, - Players, StatusResponse, VarInt, Version, CURRENT_MC_PROTOCOL, + codec::{var_int::VarInt, Codec}, + Players, StatusResponse, Version, CURRENT_MC_PROTOCOL, }; use super::CURRENT_MC_VERSION; @@ -24,6 +26,7 @@ fn load_icon_from_file>(path: P) -> Result Result> { + assert!(!png_data.is_empty(), "PNG data is empty"); let icon = png::Decoder::new(Cursor::new(&png_data)); let reader = icon.read_info()?; let info = reader.info(); @@ -68,6 +71,7 @@ impl CachedBranding { } impl CachedStatus { + #[must_use] pub fn new() -> Self { let status_response = Self::build_response(&BASIC_CONFIG); let status_response_json = serde_json::to_string(&status_response) @@ -105,7 +109,7 @@ impl CachedStatus { } pub fn build_response(config: &BasicConfiguration) -> StatusResponse { - let icon = if config.use_favicon { + let favicon = if config.use_favicon { let icon_path = &config.favicon_path; log::debug!("Loading server favicon from '{}'", icon_path); match load_icon_from_file(icon_path).or_else(|err| { @@ -142,7 +146,7 @@ impl CachedStatus { StatusResponse { version: Some(Version { name: CURRENT_MC_VERSION.into(), - protocol: CURRENT_MC_PROTOCOL, + protocol: NonZeroU32::from(CURRENT_MC_PROTOCOL).get(), }), players: Some(Players { max: config.max_players, @@ -150,7 +154,7 @@ impl CachedStatus { sample: vec![], }), description: config.motd.clone(), - favicon: icon, + favicon, enforce_secure_chat: false, } } diff --git a/pumpkin/src/server/key_store.rs b/pumpkin/src/server/key_store.rs index daf7f734..84d2c0d8 100644 --- a/pumpkin/src/server/key_store.rs +++ b/pumpkin/src/server/key_store.rs @@ -5,7 +5,7 @@ use rsa::{traits::PublicKeyParts as _, Pkcs1v15Encrypt, RsaPrivateKey}; use sha1::Sha1; use sha2::Digest; -use crate::client::EncryptionError; +use crate::net::EncryptionError; pub struct KeyStore { pub private_key: RsaPrivateKey, @@ -13,6 +13,7 @@ pub struct KeyStore { } impl KeyStore { + #[must_use] pub fn new() -> Self { log::debug!("Creating encryption keys..."); let private_key = Self::generate_private_key(); diff --git a/pumpkin/src/server/mod.rs b/pumpkin/src/server/mod.rs index 5bf35b9d..8f87204e 100644 --- a/pumpkin/src/server/mod.rs +++ b/pumpkin/src/server/mod.rs @@ -26,12 +26,12 @@ use tokio::sync::{Mutex, RwLock}; use crate::block::block_manager::BlockManager; use crate::block::default_block_manager; -use crate::client::EncryptionError; +use crate::net::EncryptionError; use crate::world::custom_bossbar::CustomBossbars; use crate::{ - client::Client, command::{default_dispatcher, dispatcher::CommandDispatcher}, entity::player::Player, + net::Client, world::World, }; @@ -77,8 +77,6 @@ impl Server { #[allow(clippy::new_without_default)] #[must_use] pub fn new() -> Self { - // TODO: only create when needed - let auth_client = BASIC_CONFIG.online_mode.then(|| { reqwest::Client::builder() .timeout(Duration::from_millis(5000)) @@ -183,6 +181,12 @@ impl Server { self.server_listing.lock().await.remove_player(); } + pub async fn save(&self) { + for world in &self.worlds { + world.save().await; + } + } + pub async fn try_get_container( &self, player_id: EntityId, diff --git a/pumpkin/src/world/bossbar.rs b/pumpkin/src/world/bossbar.rs index fdc06243..dc9bb74d 100644 --- a/pumpkin/src/world/bossbar.rs +++ b/pumpkin/src/world/bossbar.rs @@ -43,7 +43,7 @@ pub struct Bossbar { impl Bossbar { #[must_use] - pub fn new(title: String) -> Bossbar { + pub fn new(title: String) -> Self { let uuid = Uuid::new_v4(); Self { diff --git a/pumpkin/src/world/custom_bossbar.rs b/pumpkin/src/world/custom_bossbar.rs index f6d02679..99a7a0b8 100644 --- a/pumpkin/src/world/custom_bossbar.rs +++ b/pumpkin/src/world/custom_bossbar.rs @@ -53,7 +53,7 @@ impl Default for CustomBossbars { impl CustomBossbars { #[must_use] - pub fn new() -> CustomBossbars { + pub fn new() -> Self { Self { custom_bossbars: HashMap::new(), } diff --git a/pumpkin/src/world/mod.rs b/pumpkin/src/world/mod.rs index 867303bb..02c13b10 100644 --- a/pumpkin/src/world/mod.rs +++ b/pumpkin/src/world/mod.rs @@ -9,14 +9,16 @@ use crate::{ error::PumpkinError, server::Server, }; -use itertools::Itertools; use level_time::LevelTime; use pumpkin_config::BasicConfiguration; use pumpkin_core::math::vector2::Vector2; use pumpkin_core::math::{position::WorldPosition, vector3::Vector3}; use pumpkin_core::text::{color::NamedColor, TextComponent}; use pumpkin_entity::{entity_type::EntityType, EntityId}; -use pumpkin_protocol::client::play::CLevelEvent; +use pumpkin_protocol::{ + client::play::CLevelEvent, + codec::{identifier::Identifier, var_int::VarInt}, +}; use pumpkin_protocol::{ client::play::{CBlockUpdate, CRespawn, CSoundEffect, CWorldEvent}, SoundCategory, @@ -26,7 +28,7 @@ use pumpkin_protocol::{ CChunkData, CGameEvent, CLogin, CPlayerInfoUpdate, CRemoveEntities, CRemovePlayerInfo, CSetEntityMetadata, CSpawnEntity, GameEvent, Metadata, PlayerAction, }, - ClientPacket, VarInt, + ClientPacket, }; use pumpkin_registry::DimensionType; use pumpkin_world::chunk::ChunkData; @@ -116,6 +118,10 @@ impl World { } } + pub async fn save(&self) { + self.level.save().await; + } + /// Broadcasts a packet to all connected players within the world. /// /// Sends the specified packet to every player currently logged in to the world. @@ -224,11 +230,8 @@ impl World { server: &Server, ) { let command_dispatcher = &server.command_dispatcher; - let dimensions = &server - .dimensions - .iter() - .map(DimensionType::name) - .collect_vec(); + let dimensions: Vec = + server.dimensions.iter().map(DimensionType::name).collect(); // This code follows the vanilla packet order let entity_id = player.entity_id(); @@ -245,10 +248,10 @@ impl World { .send_packet(&CLogin::new( entity_id, base_config.hardcore, - dimensions, + &dimensions, base_config.max_players.into(), - base_config.view_distance.into(), // TODO: view distance - base_config.simulation_distance.into(), // TODO: sim view dinstance + base_config.view_distance.get().into(), // TODO: view distance + base_config.simulation_distance.get().into(), // TODO: sim view dinstance false, true, false, @@ -329,7 +332,7 @@ impl World { .client .send_packet(&CPlayerInfoUpdate::new(0x01 | 0x08, &entries)) .await; - } + }; let gameprofile = &player.gameprofile; @@ -411,8 +414,11 @@ impl World { .init_client(&player.client) .await; + // Sends initial time + player.send_time(self).await; + // Spawn in initial chunks - player_chunker::player_join(self, player.clone()).await; + player_chunker::player_join(&player).await; // if let Some(bossbars) = self..lock().await.get_player_bars(&player.gameprofile.id) { // for bossbar in bossbars { @@ -514,33 +520,13 @@ impl World { ) .await; - player_chunker::player_join(self, player.clone()).await; + player_chunker::player_join(player).await; self.broadcast_packet_all(&entity_metadata_packet).await; // update commands player.set_health(20.0, 20, 20.0).await; } - pub fn mark_chunks_as_not_watched(&self, chunks: &[Vector2]) -> Vec> { - self.level.mark_chunks_as_not_watched(chunks) - } - - pub fn mark_chunks_as_watched(&self, chunks: &[Vector2]) { - self.level.mark_chunks_as_newly_watched(chunks); - } - - pub fn clean_chunks(&self, chunks: &[Vector2]) { - self.level.clean_chunks(chunks); - } - - pub fn clean_memory(&self, chunks_to_check: &[Vector2]) { - self.level.clean_memory(chunks_to_check); - } - - pub fn get_cached_chunk_len(&self) -> usize { - self.level.loaded_chunk_count() - } - /// IMPORTANT: Chunks have to be non-empty fn spawn_world_chunks( &self, @@ -567,7 +553,6 @@ impl World { rel_x * rel_x + rel_z * rel_z }); - player.world().mark_chunks_as_watched(&chunks); let mut receiver = self.receive_chunks(chunks); let level = self.level.clone(); @@ -577,10 +562,9 @@ impl World { let packet = CChunkData(&chunk_data); #[cfg(debug_assertions)] if chunk_data.position == (0, 0).into() { - use pumpkin_protocol::bytebuf::ByteBuffer; - let mut test = ByteBuffer::empty(); + let mut test = bytes::BytesMut::new(); packet.write(&mut test); - let len = test.buf().len(); + let len = test.len(); log::debug!( "Chunk packet size: {}B {}KB {}MB", len, diff --git a/pumpkin/src/world/player_chunker.rs b/pumpkin/src/world/player_chunker.rs index 73f5c324..98e1157d 100644 --- a/pumpkin/src/world/player_chunker.rs +++ b/pumpkin/src/world/player_chunker.rs @@ -1,4 +1,4 @@ -use std::sync::Arc; +use std::{num::NonZeroU8, sync::Arc}; use pumpkin_config::BASIC_CONFIG; use pumpkin_core::{ @@ -10,29 +10,16 @@ use pumpkin_world::cylindrical_chunk_iterator::Cylindrical; use crate::entity::player::Player; -use super::World; - -pub async fn get_view_distance(player: &Player) -> u8 { - player - .config - .lock() - .await - .view_distance - .clamp(2, BASIC_CONFIG.view_distance) +pub async fn get_view_distance(player: &Player) -> NonZeroU8 { + player.config.lock().await.view_distance.clamp( + unsafe { NonZeroU8::new_unchecked(2) }, + BASIC_CONFIG.view_distance, + ) } -pub async fn player_join(world: &World, player: Arc) { - let new_watched = chunk_section_from_pos(&player.living_entity.entity.block_pos.load()); - - let mut cylindrical = player.watched_section.load(); - cylindrical.center = new_watched.into(); - player.watched_section.store(cylindrical); - +pub async fn player_join(player: &Arc) { let chunk_pos = player.living_entity.entity.chunk_pos.load(); - assert_eq!(new_watched.x, chunk_pos.x); - assert_eq!(new_watched.z, chunk_pos.z); - log::debug!("Sending center chunk to {}", player.gameprofile.name); player .client @@ -41,20 +28,15 @@ pub async fn player_join(world: &World, player: Arc) { chunk_z: chunk_pos.z.into(), }) .await; - let view_distance = get_view_distance(&player).await; + let view_distance = get_view_distance(player).await; log::debug!( "Player {} ({}) joined with view distance: {}", player.gameprofile.name, - player.gameprofile.name, + player.client.id, view_distance ); - let new_cylindrical = Cylindrical::new(chunk_pos, view_distance); - let loading_chunks = new_cylindrical.all_chunks_within(); - - if !loading_chunks.is_empty() { - world.spawn_world_chunks(player, loading_chunks, chunk_pos); - } + update_position(player).await; } pub async fn update_position(player: &Arc) { @@ -74,8 +56,6 @@ pub async fn update_position(player: &Arc) { let new_cylindrical = Cylindrical::new(new_chunk_center, view_distance); if old_cylindrical != new_cylindrical { - player.watched_section.store(new_cylindrical); - player .client .send_packet(&CCenterChunk { @@ -97,14 +77,21 @@ pub async fn update_position(player: &Arc) { }, ); - if !unloading_chunks.is_empty() { - //let inst = std::time::Instant::now(); + // Make sure the watched section and the chunk watcher updates are async atomic. We want to + // ensure what we unload when the player disconnects is correct + entity + .world + .level + .mark_chunks_as_newly_watched(&loading_chunks); + let chunks_to_clean = entity + .world + .level + .mark_chunks_as_not_watched(&unloading_chunks); + player.watched_section.store(new_cylindrical); - //log::debug!("Unloading chunks took {:?} (1)", inst.elapsed()); - let chunks_to_clean = entity.world.mark_chunks_as_not_watched(&unloading_chunks); - entity.world.clean_chunks(&chunks_to_clean); + if !chunks_to_clean.is_empty() { + entity.world.level.clean_chunks(&chunks_to_clean); - //log::debug!("Unloading chunks took {:?} (2)", inst.elapsed()); // This can take a little if we are sending a bunch of packets, queue it up :p let client = player.client.clone(); tokio::spawn(async move { @@ -118,22 +105,12 @@ pub async fn update_position(player: &Arc) { .await; } }); - //log::debug!("Unloading chunks took {:?} (3)", inst.elapsed()); } if !loading_chunks.is_empty() { - //let inst = std::time::Instant::now(); - - // loading_chunks.sort_by(|a, b| { - // let distance_a_squared = a.sub(a).length_squared(); - // let distance_b_squared = b.sub(a).length_squared(); - // distance_a_squared.cmp(&distance_b_squared) - // }); - entity .world .spawn_world_chunks(player.clone(), loading_chunks, new_chunk_center); - //log::debug!("Loading chunks took {:?}", inst.elapsed()); } } } diff --git a/pumpkin/src/world/scoreboard.rs b/pumpkin/src/world/scoreboard.rs index 61279eed..16a3e329 100644 --- a/pumpkin/src/world/scoreboard.rs +++ b/pumpkin/src/world/scoreboard.rs @@ -3,7 +3,8 @@ use std::collections::HashMap; use pumpkin_core::text::TextComponent; use pumpkin_protocol::{ client::play::{CDisplayObjective, CUpdateObjectives, CUpdateScore, RenderType}, - NumberFormat, VarInt, + codec::var_int::VarInt, + NumberFormat, }; use super::World; diff --git a/pumpkin/src/world/worldborder.rs b/pumpkin/src/world/worldborder.rs index 6402a3f9..3426d6c7 100644 --- a/pumpkin/src/world/worldborder.rs +++ b/pumpkin/src/world/worldborder.rs @@ -3,7 +3,7 @@ use pumpkin_protocol::client::play::{ CSetBorderWarningDelay, CSetBorderWarningDistance, }; -use crate::client::Client; +use crate::net::Client; use super::World;