From c3b4bb33357c65514972170210e0e1051fb19f8e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Sep 2024 00:52:06 +0000 Subject: [PATCH 01/65] Bump actions/configure-pages from 4 to 5 Bumps [actions/configure-pages](https://github.com/actions/configure-pages) from 4 to 5. - [Release notes](https://github.com/actions/configure-pages/releases) - [Commits](https://github.com/actions/configure-pages/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/configure-pages dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 1ef83c66f..df45c9d43 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -32,7 +32,7 @@ jobs: node-version: 20 cache: npm - name: Setup Pages - uses: actions/configure-pages@v4 + uses: actions/configure-pages@v5 - name: Install dependencies run: npm ci - name: Build with VitePress From c8fee9b23c76d1d69cadd632c012593431a01207 Mon Sep 17 00:00:00 2001 From: Snowiiii Date: Mon, 9 Sep 2024 21:12:49 +0200 Subject: [PATCH 02/65] Try to solve Pumpkin's locking problem --- pumpkin-inventory/src/player.rs | 6 +- pumpkin-protocol/src/packet_encoder.rs | 1 - pumpkin/src/client/client_packet.rs | 60 +++--- pumpkin/src/client/container.rs | 63 +++--- pumpkin/src/client/mod.rs | 138 +++++++------ pumpkin/src/client/player_packet.rs | 274 ++++++++++++------------- pumpkin/src/commands/arg_player.rs | 2 +- pumpkin/src/commands/cmd_echest.rs | 5 +- pumpkin/src/commands/cmd_gamemode.rs | 10 +- pumpkin/src/commands/mod.rs | 4 +- pumpkin/src/entity/mod.rs | 41 ++-- pumpkin/src/entity/player.rs | 169 +++++++-------- pumpkin/src/main.rs | 36 ++-- pumpkin/src/proxy/velocity.rs | 6 +- pumpkin/src/server/mod.rs | 25 +-- pumpkin/src/world/mod.rs | 128 ++++++------ pumpkin/src/world/player_chunker.rs | 37 ++-- 17 files changed, 508 insertions(+), 497 deletions(-) diff --git a/pumpkin-inventory/src/player.rs b/pumpkin-inventory/src/player.rs index d8e127b78..999c9a9e1 100644 --- a/pumpkin-inventory/src/player.rs +++ b/pumpkin-inventory/src/player.rs @@ -1,3 +1,5 @@ +use std::sync::atomic::AtomicU32; + use crate::container_click::MouseClick; use crate::{handle_item_change, Container, InventoryError, WindowType}; use pumpkin_world::item::ItemStack; @@ -11,7 +13,7 @@ pub struct PlayerInventory { offhand: Option, // current selected slot in hotbar selected: usize, - pub state_id: u32, + pub state_id: AtomicU32, // Notchian server wraps this value at 100, we can just keep it as a u8 that automatically wraps pub total_opened_containers: u8, } @@ -32,7 +34,7 @@ impl PlayerInventory { offhand: None, // TODO: What when player spawns in with an different index ? selected: 0, - state_id: 0, + state_id: AtomicU32::new(0), total_opened_containers: 2, } } diff --git a/pumpkin-protocol/src/packet_encoder.rs b/pumpkin-protocol/src/packet_encoder.rs index 36b9c3697..1c545804b 100644 --- a/pumpkin-protocol/src/packet_encoder.rs +++ b/pumpkin-protocol/src/packet_encoder.rs @@ -85,7 +85,6 @@ impl PacketEncoder { let mut front = &mut self.buf[start_len..]; - #[allow(clippy::needless_borrows_for_generic_args)] VarInt(packet_len as i32) .encode(&mut front) .map_err(|_| PacketError::EncodeLength)?; diff --git a/pumpkin/src/client/client_packet.rs b/pumpkin/src/client/client_packet.rs index 38c734b40..b81b40790 100644 --- a/pumpkin/src/client/client_packet.rs +++ b/pumpkin/src/client/client_packet.rs @@ -37,12 +37,16 @@ use super::{ /// NEVER TRUST THE CLIENT. HANDLE EVERY ERROR, UNWRAP/EXPECT /// TODO: REMOVE ALL UNWRAPS impl Client { - pub fn handle_handshake(&mut self, _server: &Arc, handshake: SHandShake) { + pub fn handle_handshake(&self, _server: &Arc, handshake: SHandShake) { dbg!("handshake"); - self.protocol_version = handshake.protocol_version.0; - self.connection_state = handshake.next_state; - if self.connection_state != ConnectionState::Status { - let protocol = self.protocol_version; + let version = handshake.protocol_version.0; + self.protocol_version + .store(version, std::sync::atomic::Ordering::Relaxed); + let mut connection_state = self.connection_state.lock().unwrap(); + + *connection_state = handshake.next_state; + if *connection_state != 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}")); @@ -55,11 +59,11 @@ impl Client { } } - pub fn handle_status_request(&mut self, server: &Arc, _status_request: SStatusRequest) { + pub fn handle_status_request(&self, server: &Arc, _status_request: SStatusRequest) { self.send_packet(&CStatusResponse::new(&server.status_response_json)); } - pub fn handle_ping_request(&mut self, _server: &Arc, ping_request: SStatusPingRequest) { + pub fn handle_ping_request(&self, _server: &Arc, ping_request: SStatusPingRequest) { dbg!("ping"); self.send_packet(&CPingResponse::new(ping_request.payload)); self.close(); @@ -72,7 +76,7 @@ impl Client { .all(|c| c > 32_u8 as char && c < 127_u8 as char) } - pub fn handle_login_start(&mut self, server: &Arc, login_start: SLoginStart) { + pub fn handle_login_start(&self, server: &Arc, login_start: SLoginStart) { log::debug!("login start, State {:?}", self.connection_state); if !Self::is_valid_player_name(&login_start.name) { @@ -81,7 +85,8 @@ impl Client { } // default game profile, when no online mode // TODO: make offline uuid - self.gameprofile = Some(GameProfile { + let mut gameprofile = self.gameprofile.lock().unwrap(); + *gameprofile = Some(GameProfile { id: login_start.uuid, name: login_start.name, properties: vec![], @@ -108,7 +113,7 @@ impl Client { } pub async fn handle_encryption_response( - &mut self, + &self, server: &Arc, encryption_response: SEncryptionResponse, ) { @@ -120,15 +125,17 @@ impl Client { self.enable_encryption(&shared_secret) .unwrap_or_else(|e| self.kick(&e.to_string())); + let mut gameprofile = self.gameprofile.lock().unwrap(); + if BASIC_CONFIG.online_mode { let hash = Sha1::new() .chain_update(&shared_secret) .chain_update(&server.public_key_der) .finalize(); let hash = auth_digest(&hash); - let ip = self.address.ip(); + let ip = self.address.lock().unwrap().ip(); match authentication::authenticate( - &self.gameprofile.as_ref().unwrap().name, + &gameprofile.as_ref().unwrap().name, &hash, &ip, server, @@ -159,12 +166,12 @@ impl Client { } } } - self.gameprofile = Some(p); + *gameprofile = Some(p); } Err(e) => self.kick(&e.to_string()), } } - for ele in self.gameprofile.as_ref().unwrap().properties.clone() { + for ele in gameprofile.as_ref().unwrap().properties.clone() { // todo, use this unpack_textures(ele, &ADVANCED_CONFIG.authentication.textures); } @@ -177,7 +184,7 @@ impl Client { self.set_compression(Some((threshold, level))); } - if let Some(profile) = self.gameprofile.as_ref().cloned() { + if let Some(profile) = gameprofile.as_ref().cloned() { let packet = CLoginSuccess::new(&profile.id, &profile.name, &profile.properties, false); self.send_packet(&packet); } else { @@ -186,18 +193,18 @@ impl Client { } pub fn handle_plugin_response( - &mut self, + &self, _server: &Arc, _plugin_response: SLoginPluginResponse, ) { } pub fn handle_login_acknowledged( - &mut self, + &self, server: &Arc, _login_acknowledged: SLoginAcknowledged, ) { - self.connection_state = ConnectionState::Config; + *self.connection_state.lock().unwrap() = ConnectionState::Config; server.send_brand(self); let resource_config = &ADVANCED_CONFIG.resource_pack; @@ -228,12 +235,12 @@ impl Client { dbg!("login achnowlaged"); } pub fn handle_client_information_config( - &mut self, + &self, _server: &Arc, client_information: SClientInformationConfig, ) { dbg!("got client settings"); - self.config = Some(PlayerConfig { + *self.config.lock().unwrap() = Some(PlayerConfig { locale: client_information.locale, view_distance: client_information.view_distance, chat_mode: ChatMode::from_i32(client_information.chat_mode.into()).unwrap(), @@ -245,19 +252,19 @@ impl Client { }); } - pub fn handle_plugin_message(&mut self, _server: &Arc, plugin_message: SPluginMessage) { + pub fn handle_plugin_message(&self, _server: &Arc, plugin_message: SPluginMessage) { if plugin_message.channel.starts_with("minecraft:brand") || plugin_message.channel.starts_with("MC|Brand") { dbg!("got a client brand"); match String::from_utf8(plugin_message.data) { - Ok(brand) => self.brand = Some(brand), + Ok(brand) => *self.brand.lock().unwrap() = Some(brand), Err(e) => self.kick(&e.to_string()), } } } - pub fn handle_known_packs(&mut self, server: &Arc, _config_acknowledged: SKnownPacks) { + pub fn handle_known_packs(&self, server: &Arc, _config_acknowledged: SKnownPacks) { for registry in &server.cached_registry { self.send_packet(&CRegistryData::new( ®istry.registry_id, @@ -271,12 +278,13 @@ impl Client { } pub async fn handle_config_acknowledged( - &mut self, + &self, _server: &Arc, _config_acknowledged: SAcknowledgeFinishConfig, ) { dbg!("config acknowledged"); - self.connection_state = ConnectionState::Play; - self.make_player = true; + *self.connection_state.lock().unwrap() = ConnectionState::Play; + self.make_player + .store(true, std::sync::atomic::Ordering::Relaxed); } } diff --git a/pumpkin/src/client/container.rs b/pumpkin/src/client/container.rs index 68b4391b9..c11ccb381 100644 --- a/pumpkin/src/client/container.rs +++ b/pumpkin/src/client/container.rs @@ -19,7 +19,7 @@ use pumpkin_world::item::ItemStack; use std::sync::{Arc, Mutex}; impl Player { - pub fn open_container(&mut self, server: &Arc, minecraft_menu_id: &str) { + pub fn open_container(&self, server: &Arc, minecraft_menu_id: &str) { self.inventory.state_id = 0; let total_opened_containers = self.inventory.total_opened_containers; let container = self.get_open_container(server); @@ -49,9 +49,9 @@ impl Player { self.set_container_content(container.as_deref_mut()); } - pub fn set_container_content(&mut self, container: Option<&mut Box>) { + pub fn set_container_content(&self, container: Option<&mut Box>) { let total_opened_containers = self.inventory.total_opened_containers; - let container = OptionallyCombinedContainer::new(&mut self.inventory, container); + let container = OptionallyCombinedContainer::new(&mut self.inventory.lock().unwrap(), container); let slots = container .all_slots_ref() @@ -77,7 +77,7 @@ impl Player { } /// The official Minecraft client is weird, and will always just close *any* window that is opened when this gets sent - pub fn close_container(&mut self) { + pub fn close_container(&self) { self.inventory.total_opened_containers += 1; self.client.send_packet(&CCloseContainer::new( self.inventory.total_opened_containers, @@ -97,7 +97,7 @@ impl Player { } pub async fn handle_click_container( - &mut self, + &self, server: &Arc, packet: SClickContainer, ) -> Result<(), InventoryError> { @@ -178,7 +178,7 @@ impl Player { self.send_whole_container_change(server).await?; } else if let container_click::Slot::Normal(slot_index) = click.slot { let combined_container = OptionallyCombinedContainer::new( - &mut self.inventory, + &mut self.inventory.lock().unwrap().lock().unwrap().lock().unwrap(), Some(&mut opened_container), ); if let Some(slot) = combined_container.get_slot_excluding_inventory(slot_index) { @@ -193,27 +193,30 @@ impl Player { } fn mouse_click( - &mut self, + &self, opened_container: Option<&mut Box>, mouse_click: MouseClick, slot: container_click::Slot, ) -> Result<(), InventoryError> { - let mut container = OptionallyCombinedContainer::new(&mut self.inventory, opened_container); - + let mut inventory = self.inventory.lock().unwrap(); + let mut container = + OptionallyCombinedContainer::new(&mut inventory, opened_container); + match slot { container_click::Slot::Normal(slot) => { - container.handle_item_change(&mut self.carried_item, slot, mouse_click) + container.handle_item_change(&mut self.carried_item.lock().unwrap(), slot, mouse_click) } container_click::Slot::OutsideInventory => Ok(()), } } fn shift_mouse_click( - &mut self, + &self, opened_container: Option<&mut Box>, slot: container_click::Slot, ) -> Result<(), InventoryError> { - let mut container = OptionallyCombinedContainer::new(&mut self.inventory, opened_container); + let mut inventory = self.inventory.lock().unwrap(); + let mut container = OptionallyCombinedContainer::new(&mut inventory, opened_container); match slot { container_click::Slot::Normal(slot) => { @@ -265,7 +268,7 @@ impl Player { KeyClick::Offhand => 45, }; let mut changing_item_slot = self.inventory.get_slot(changing_slot as usize)?.to_owned(); - let mut container = OptionallyCombinedContainer::new(&mut self.inventory, opened_container); + let mut container = OptionallyCombinedContainer::new(&mut self.inventory.lock().unwrap(), opened_container); container.handle_item_change(&mut changing_item_slot, slot, MouseClick::Left)?; *self.inventory.get_slot(changing_slot as usize)? = changing_item_slot; @@ -280,7 +283,7 @@ impl Player { if self.gamemode != GameMode::Creative { return Err(InventoryError::PermissionError); } - let mut container = OptionallyCombinedContainer::new(&mut self.inventory, opened_container); + let mut container = OptionallyCombinedContainer::new(&mut self.inventory.lock().unwrap(), opened_container); if let Some(Some(item)) = container.all_slots().get_mut(slot) { self.carried_item = Some(item.to_owned()) } @@ -292,7 +295,7 @@ impl Player { opened_container: Option<&mut Box>, slot: usize, ) -> Result<(), InventoryError> { - let mut container = OptionallyCombinedContainer::new(&mut self.inventory, opened_container); + let mut container = OptionallyCombinedContainer::new(&mut self.inventory.lock().unwrap(), opened_container); let mut slots = container.all_slots(); let Some(item) = slots.get_mut(slot) else { @@ -345,7 +348,7 @@ impl Player { MouseDragState::AddSlot(slot) => drag_handler.add_slot(container_id, player_id, slot), MouseDragState::End => { let mut container = - OptionallyCombinedContainer::new(&mut self.inventory, opened_container); + OptionallyCombinedContainer::new(&mut self.inventory.lock().unwrap(), opened_container); drag_handler.apply_drag( &mut self.carried_item, &mut container, @@ -356,10 +359,7 @@ impl Player { } } - async fn get_current_players_in_container( - &mut self, - server: &Server, - ) -> Vec>> { + async fn get_current_players_in_container(&self, server: &Server) -> Vec> { let player_ids = { let open_containers = server .open_containers @@ -378,13 +378,18 @@ impl Player { // TODO: Figure out better way to get only the players from player_ids // Also refactor out a better method to get individual advanced state ids - let world = self.entity.world.lock().await; - let players = world + let players = self + .entity + .lock() + .unwrap() + .world .current_players + .lock() + .unwrap() .iter() .filter_map(|(token, player)| { if *token != player_token { - let entity_id = player.lock().unwrap().entity_id(); + let entity_id = player.entity_id(); if player_ids.contains(&entity_id) { Some(player.clone()) } else { @@ -405,13 +410,16 @@ impl Player { slot: Slot, ) -> Result<(), InventoryError> { for player in self.get_current_players_in_container(server).await { - let mut player = player.lock().unwrap(); let total_opened_containers = player.inventory.total_opened_containers; - player.inventory.state_id += 1; + // Returns previous value + let i = player + .inventory + .state_id + .fetch_add(1, std::sync::atomic::Ordering::Relaxed); let packet = CSetContainerSlot::new( total_opened_containers as i8, - player.inventory.state_id as i32, + (i + 1) as i32, slot_index, &slot, ); @@ -420,11 +428,10 @@ impl Player { Ok(()) } - async fn send_whole_container_change(&mut self, server: &Server) -> Result<(), InventoryError> { + async fn send_whole_container_change(&self, server: &Server) -> Result<(), InventoryError> { let players = self.get_current_players_in_container(server).await; for player in players { - let mut player = player.lock().unwrap(); let container = player.get_open_container(server); let mut container = container.as_ref().map(|v| v.lock().unwrap()); player.set_container_content(container.as_deref_mut()); diff --git a/pumpkin/src/client/mod.rs b/pumpkin/src/client/mod.rs index 7a7d9ba05..53ca10189 100644 --- a/pumpkin/src/client/mod.rs +++ b/pumpkin/src/client/mod.rs @@ -1,7 +1,10 @@ use std::{ io::{self, Write}, net::SocketAddr, - sync::Arc, + sync::{ + atomic::{AtomicBool, AtomicI32}, + Arc, Mutex, + }, }; use crate::{ @@ -31,7 +34,7 @@ use thiserror::Error; pub mod authentication; mod client_packet; -mod container; +// mod container; pub mod player_packet; #[derive(Clone)] @@ -62,95 +65,104 @@ impl Default for PlayerConfig { } pub struct Client { - pub gameprofile: Option, + pub gameprofile: Mutex>, - pub config: Option, - pub brand: Option, + pub config: Mutex>, + pub brand: Mutex>, - pub protocol_version: i32, - pub connection_state: ConnectionState, - pub encryption: bool, - pub closed: bool, + pub protocol_version: AtomicI32, + pub connection_state: Mutex, + pub encryption: AtomicBool, + pub closed: AtomicBool, pub token: Token, - pub connection: TcpStream, - pub address: SocketAddr, - enc: PacketEncoder, - dec: PacketDecoder, - pub client_packets_queue: Vec, + pub connection: Arc>, + pub address: Mutex, + enc: Arc>, + dec: Arc>, + pub client_packets_queue: Arc>>, - pub make_player: bool, + pub make_player: AtomicBool, } impl Client { pub fn new(token: Token, connection: TcpStream, address: SocketAddr) -> Self { Self { - protocol_version: 0, - gameprofile: None, - config: None, - brand: None, + protocol_version: AtomicI32::new(0), + gameprofile: Mutex::new(None), + config: Mutex::new(None), + brand: Mutex::new(None), token, - address, - connection_state: ConnectionState::HandShake, - connection, - enc: PacketEncoder::default(), - dec: PacketDecoder::default(), - encryption: true, - closed: false, - client_packets_queue: Vec::new(), - make_player: false, + address: Mutex::new(address), + connection_state: Mutex::new(ConnectionState::HandShake), + connection: Arc::new(Mutex::new(connection)), + 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(Vec::new())), + make_player: AtomicBool::new(false), } } /// adds a Incoming packet to the queue - pub fn add_packet(&mut self, packet: RawPacket) { - self.client_packets_queue.push(packet); + pub fn add_packet(&self, packet: RawPacket) { + let mut client_packets_queue = self.client_packets_queue.lock().unwrap(); + client_packets_queue.push(packet); } /// enables encryption pub fn enable_encryption( - &mut self, + &self, shared_secret: &[u8], // decrypted ) -> Result<(), EncryptionError> { - self.encryption = true; + self.encryption + .store(true, std::sync::atomic::Ordering::Relaxed); let crypt_key: [u8; 16] = shared_secret .try_into() .map_err(|_| EncryptionError::SharedWrongLength)?; - self.dec.enable_encryption(&crypt_key); - self.enc.enable_encryption(&crypt_key); + self.dec.lock().unwrap().enable_encryption(&crypt_key); + self.enc.lock().unwrap().enable_encryption(&crypt_key); Ok(()) } // Compression threshold, Compression level - pub fn set_compression(&mut self, compression: Option<(u32, u32)>) { - self.dec.set_compression(compression.map(|v| v.0)); - self.enc.set_compression(compression); + pub fn set_compression(&self, compression: Option<(u32, u32)>) { + self.dec + .lock() + .unwrap() + .set_compression(compression.map(|v| v.0)); + self.enc.lock().unwrap().set_compression(compression); } /// Send a Clientbound Packet to the Client - pub fn send_packet(&mut self, packet: &P) { + pub fn send_packet(&self, packet: &P) { // assert!(!self.closed); - - self.enc - .append_packet(packet) + let mut enc = self.enc.lock().unwrap(); + enc.append_packet(packet) .unwrap_or_else(|e| self.kick(&e.to_string())); self.connection - .write_all(&self.enc.take()) + .lock() + .unwrap() + .write_all(&enc.take()) .map_err(|_| PacketError::ConnectionWrite) .unwrap_or_else(|e| self.kick(&e.to_string())); } - pub fn try_send_packet(&mut self, packet: &P) -> Result<(), PacketError> { + pub fn try_send_packet(&self, packet: &P) -> Result<(), PacketError> { // assert!(!self.closed); - self.enc.append_packet(packet)?; + let mut enc = self.enc.lock().unwrap(); + enc.append_packet(packet)?; self.connection - .write_all(&self.enc.take()) + .lock() + .unwrap() + .write_all(&enc.take()) .map_err(|_| PacketError::ConnectionWrite)?; Ok(()) } - pub async fn process_packets(&mut self, server: &Arc) { - while let Some(mut packet) = self.client_packets_queue.pop() { + pub async fn process_packets(&self, server: &Arc) { + while let Some(mut packet) = self.client_packets_queue.lock().unwrap().pop() { match self.handle_packet(server, &mut packet).await { Ok(_) => {} Err(e) => { @@ -164,13 +176,13 @@ impl Client { /// Handles an incoming decoded not Play state Packet pub async fn handle_packet( - &mut self, + &self, server: &Arc, packet: &mut RawPacket, ) -> Result<(), DeserializerError> { // TODO: handle each packet's Error instead of calling .unwrap() let bytebuf = &mut packet.bytebuf; - match self.connection_state { + match *self.connection_state.lock().unwrap() { pumpkin_protocol::ConnectionState::HandShake => match packet.id.0 { SHandShake::PACKET_ID => { self.handle_handshake(server, SHandShake::read(bytebuf)?); @@ -268,15 +280,16 @@ impl Client { } } - // Reads the connection until our buffer of len 4096 is full, then decode - /// Close connection when an error occurs - pub async fn poll(&mut self, event: &Event) { + /// Reads the connection until our buffer of len 4096 is full, then decode + /// Close connection when an error occurs or when the Client closed the connection + pub async fn poll(&self, event: &Event) { if event.is_readable() { let mut received_data = vec![0; 4096]; let mut bytes_read = 0; - // We can (maybe) read from the connection. loop { - match self.connection.read(&mut received_data[bytes_read..]) { + let connection = self.connection.clone(); + let mut connection = connection.lock().unwrap(); + match connection.read(&mut received_data[bytes_read..]) { Ok(0) => { // Reading 0 bytes means the other side has closed the // connection or is done writing, then so are we. @@ -297,9 +310,9 @@ impl Client { } if bytes_read != 0 { - self.dec.reserve(4096); - self.dec.queue_slice(&received_data[..bytes_read]); - match self.dec.decode() { + let mut dec = self.dec.lock().unwrap(); + dec.queue_slice(&received_data[..bytes_read]); + match dec.decode() { Ok(packet) => { if let Some(packet) = packet { self.add_packet(packet); @@ -307,15 +320,15 @@ impl Client { } Err(err) => self.kick(&err.to_string()), } - self.dec.clear(); + dec.clear(); } } } /// Kicks the Client with a reason depending on the connection state - pub fn kick(&mut self, reason: &str) { + pub fn kick(&self, reason: &str) { dbg!(reason); - match self.connection_state { + match *self.connection_state.lock().unwrap() { ConnectionState::Login => { self.try_send_packet(&CLoginDisconnect::new( &serde_json::to_string_pretty(&reason).unwrap_or("".into()), @@ -339,8 +352,9 @@ impl Client { } /// You should prefer to use `kick` when you can - pub fn close(&mut self) { - self.closed = true; + pub fn close(&self) { + self.closed + .store(true, std::sync::atomic::Ordering::Relaxed); } } diff --git a/pumpkin/src/client/player_packet.rs b/pumpkin/src/client/player_packet.rs index ac40d57b1..04c7788f6 100644 --- a/pumpkin/src/client/player_packet.rs +++ b/pumpkin/src/client/player_packet.rs @@ -42,16 +42,20 @@ fn modulus(a: f32, b: f32) -> f32 { /// NEVER TRUST THE CLIENT. HANDLE EVERY ERROR, UNWRAP/EXPECT ARE FORBIDDEN impl Player { pub fn handle_confirm_teleport( - &mut self, + &self, _server: &Arc, confirm_teleport: SConfirmTeleport, ) { - if let Some((id, position)) = self.awaiting_teleport.as_ref() { + let mut awaiting_teleport = self.awaiting_teleport.lock().unwrap(); + if let Some((id, position)) = awaiting_teleport.as_ref() { if id == &confirm_teleport.teleport_id { // we should set the pos now to that we requested in the teleport packet, Is may fixed issues when the client sended position packets while being teleported - self.entity.set_pos(position.x, position.y, position.z); + self.entity + .lock() + .unwrap() + .set_pos(position.x, position.y, position.z); - self.awaiting_teleport = None; + *awaiting_teleport = None; } else { self.kick(TextComponent::text("Wrong teleport id")) } @@ -70,13 +74,14 @@ impl Player { pos.clamp(-2.0E7, 2.0E7) } - pub async fn handle_position(&mut self, _server: &Arc, position: SPlayerPosition) { + pub async fn handle_position(&self, _server: &Arc, position: SPlayerPosition) { if position.x.is_nan() || position.feet_y.is_nan() || position.z.is_nan() { self.kick(TextComponent::text("Invalid movement")); return; } - let entity = &mut self.entity; - self.last_position = entity.pos; + let mut entity = self.entity.lock().unwrap(); + let mut last_position = self.last_position.lock().unwrap(); + *last_position = entity.pos; entity.set_pos( Self::clamp_horizontal(position.x), Self::clamp_vertical(position.feet_y), @@ -86,9 +91,8 @@ impl Player { let on_ground = entity.on_ground; let entity_id = entity.entity_id; let (x, y, z) = entity.pos.into(); - let (lastx, lasty, lastz) = self.last_position.into(); - let world = self.entity.world.clone(); - let world = world.lock().await; + let (lastx, lasty, lastz) = (last_position.x, last_position.y, last_position.z); + let world = entity.world.clone(); // let delta = Vector3::new(x - lastx, y - lasty, z - lastz); // let velocity = self.velocity; @@ -103,7 +107,7 @@ impl Player { // return; // } // send new position to all other players - world.broadcast_packet( + world.broadcast_packet_expect( &[self.client.token], &CUpdateEntityPos::new( entity_id.into(), @@ -117,7 +121,7 @@ impl Player { } pub async fn handle_position_rotation( - &mut self, + &self, _server: &Arc, position_rotation: SPlayerPositionRotation, ) { @@ -132,9 +136,10 @@ impl Player { self.kick(TextComponent::text("Invalid rotation")); return; } - let entity = &mut self.entity; + let mut entity = self.entity.lock().unwrap(); - self.last_position = entity.pos; + let mut last_position = self.last_position.lock().unwrap(); + *last_position = entity.pos; entity.set_pos( Self::clamp_horizontal(position_rotation.x), Self::clamp_vertical(position_rotation.feet_y), @@ -147,12 +152,12 @@ impl Player { let on_ground = entity.on_ground; let entity_id = entity.entity_id; let (x, y, z) = entity.pos.into(); - let (lastx, lasty, lastz) = self.last_position.into(); + let (lastx, lasty, lastz) = (last_position.x, last_position.y, last_position.z); let yaw = modulus(entity.yaw * 256.0 / 360.0, 256.0); let pitch = modulus(entity.pitch * 256.0 / 360.0, 256.0); // let head_yaw = (entity.head_yaw * 256.0 / 360.0).floor(); - let world = self.entity.world.clone(); - let world = world.lock().await; + let entity = self.entity.lock().unwrap(); + let world = &entity.world; // let delta = Vector3::new(x - lastx, y - lasty, z - lastz); // let velocity = self.velocity; @@ -168,7 +173,7 @@ impl Player { // } // send new position to all other players - world.broadcast_packet( + world.broadcast_packet_expect( &[self.client.token], &CUpdateEntityPosRot::new( entity_id.into(), @@ -180,20 +185,20 @@ impl Player { on_ground, ), ); - world.broadcast_packet( + world.broadcast_packet_expect( &[self.client.token], &CHeadRot::new(entity_id.into(), yaw as u8), ); - player_chunker::update_position(&world, self).await; + player_chunker::update_position(world, self).await; } - pub async fn handle_rotation(&mut self, _server: &Arc, rotation: SPlayerRotation) { + pub async fn handle_rotation(&self, _server: &Arc, rotation: SPlayerRotation) { if !rotation.yaw.is_finite() || !rotation.pitch.is_finite() { self.kick(TextComponent::text("Invalid rotation")); return; } - let entity = &mut self.entity; + let mut entity = self.entity.lock().unwrap(); entity.on_ground = rotation.ground; entity.yaw = wrap_degrees(rotation.yaw) % 360.0; entity.pitch = wrap_degrees(rotation.pitch).clamp(-90.0, 90.0) % 360.0; @@ -204,61 +209,58 @@ impl Player { let pitch = modulus(entity.pitch * 256.0 / 360.0, 256.0); // let head_yaw = modulus(entity.head_yaw * 256.0 / 360.0, 256.0); - let world = self.entity.world.lock().await; + let world = &entity.world; let packet = CUpdateEntityRot::new(entity_id.into(), yaw as u8, pitch as u8, on_ground); - // self.client.send_packet(&packet); - world.broadcast_packet(&[self.client.token], &packet); + world.broadcast_packet_expect(&[self.client.token], &packet); let packet = CHeadRot::new(entity_id.into(), yaw as u8); - // self.client.send_packet(&packet); - world.broadcast_packet(&[self.client.token], &packet); + world.broadcast_packet_expect(&[self.client.token], &packet); } - pub fn handle_chat_command(&mut self, server: &Arc, command: SChatCommand) { + pub fn handle_chat_command(&self, server: &Arc, command: SChatCommand) { let dispatcher = server.command_dispatcher.clone(); dispatcher.handle_command(&mut CommandSender::Player(self), server, &command.command); } - pub fn handle_player_ground(&mut self, _server: &Arc, ground: SSetPlayerGround) { - self.entity.on_ground = ground.on_ground; + pub fn handle_player_ground(&self, _server: &Arc, ground: SSetPlayerGround) { + self.entity.lock().unwrap().on_ground = ground.on_ground; } - pub async fn handle_player_command(&mut self, _server: &Arc, command: SPlayerCommand) { - if command.entity_id != self.entity.entity_id.into() { + pub async fn handle_player_command(&self, _server: &Arc, command: SPlayerCommand) { + if command.entity_id != self.entity_id().into() { return; } if let Some(action) = Action::from_i32(command.action.0) { + let mut entity = self.entity.lock().unwrap(); match action { pumpkin_protocol::server::play::Action::StartSneaking => { - if !self.entity.sneaking { - self.entity.set_sneaking(&mut self.client, true).await + if !entity.sneaking { + entity.set_sneaking(true).await } } pumpkin_protocol::server::play::Action::StopSneaking => { - if self.entity.sneaking { - self.entity.set_sneaking(&mut self.client, false).await + if entity.sneaking { + entity.set_sneaking(false).await } } pumpkin_protocol::server::play::Action::LeaveBed => todo!(), pumpkin_protocol::server::play::Action::StartSprinting => { - if !self.entity.sprinting { - self.entity.set_sprinting(&mut self.client, true).await + if !entity.sprinting { + entity.set_sprinting(true).await } } pumpkin_protocol::server::play::Action::StopSprinting => { - if self.entity.sprinting { - self.entity.set_sprinting(&mut self.client, false).await + if entity.sprinting { + entity.set_sprinting(false).await } } pumpkin_protocol::server::play::Action::StartHorseJump => todo!(), pumpkin_protocol::server::play::Action::StopHorseJump => todo!(), pumpkin_protocol::server::play::Action::OpenVehicleInventory => todo!(), pumpkin_protocol::server::play::Action::StartFlyingElytra => { - let fall_flying = self.entity.check_fall_flying(); - if self.entity.fall_flying != fall_flying { - self.entity - .set_fall_flying(&mut self.client, fall_flying) - .await; + let fall_flying = entity.check_fall_flying(); + if entity.fall_flying != fall_flying { + entity.set_fall_flying(fall_flying).await; } } // TODO } @@ -267,7 +269,7 @@ impl Player { } } - pub async fn handle_swing_arm(&mut self, _server: &Arc, swing_arm: SSwingArm) { + pub async fn handle_swing_arm(&self, _server: &Arc, swing_arm: SSwingArm) { match Hand::from_i32(swing_arm.hand.0) { Some(hand) => { let animation = match hand { @@ -275,8 +277,9 @@ impl Player { Hand::Off => Animation::SwingOffhand, }; let id = self.entity_id(); - let world = self.entity.world.lock().await; - world.broadcast_packet( + let entity = self.entity.lock().unwrap(); + let world = &entity.world; + world.broadcast_packet_expect( &[self.client.token], &CEntityAnimation::new(id.into(), animation as u8), ) @@ -287,7 +290,7 @@ impl Player { }; } - pub async fn handle_chat_message(&mut self, _server: &Arc, chat_message: SChatMessage) { + pub async fn handle_chat_message(&self, _server: &Arc, chat_message: SChatMessage) { dbg!("got message"); let message = chat_message.message; @@ -299,24 +302,22 @@ impl Player { // TODO: filter message & validation let gameprofile = &self.gameprofile; - let world = self.entity.world.lock().await; - world.broadcast_packet( - &[self.client.token], - &CPlayerChatMessage::new( - pumpkin_protocol::uuid::UUID(gameprofile.id), - 1.into(), - chat_message.signature.as_deref(), - &message, - chat_message.timestamp, - chat_message.salt, - &[], - Some(TextComponent::text(&message)), - FilterType::PassThrough, - 1.into(), - TextComponent::text(&gameprofile.name.clone()), - None, - ), - ) + let entity = self.entity.lock().unwrap(); + let world = &entity.world; + world.broadcast_packet_all(&CPlayerChatMessage::new( + pumpkin_protocol::uuid::UUID(gameprofile.id), + 1.into(), + chat_message.signature.as_deref(), + &message, + chat_message.timestamp, + chat_message.salt, + &[], + Some(TextComponent::text(&message)), + FilterType::PassThrough, + 1.into(), + TextComponent::text(&gameprofile.name.clone()), + None, + )) /* server.broadcast_packet( self, @@ -330,7 +331,7 @@ impl Player { } pub fn handle_client_information_play( - &mut self, + &self, _server: &Arc, client_information: SClientInformationPlay, ) { @@ -338,7 +339,7 @@ impl Player { Hand::from_i32(client_information.main_hand.into()), ChatMode::from_i32(client_information.chat_mode.into()), ) { - self.config = PlayerConfig { + *self.config.lock().unwrap() = PlayerConfig { locale: client_information.locale, view_distance: client_information.view_distance, chat_mode, @@ -353,10 +354,11 @@ impl Player { } } - pub async fn handle_interact(&mut self, _: &Arc, interact: SInteract) { + pub async fn handle_interact(&self, _: &Arc, interact: SInteract) { let sneaking = interact.sneaking; - if self.entity.sneaking != sneaking { - self.entity.set_sneaking(&mut self.client, sneaking).await; + let mut entity = self.entity.lock().unwrap(); + if entity.sneaking != sneaking { + entity.set_sneaking(sneaking).await; } match ActionType::from_i32(interact.typ.0) { Some(action) => match action { @@ -365,19 +367,20 @@ impl Player { // TODO: do validation and stuff let config = &ADVANCED_CONFIG.pvp; if config.enabled { - let world = self.entity.world.clone(); - let world = world.lock().await; + let world = entity.world.clone(); let attacked_player = world.get_by_entityid(self, entity_id.0 as EntityId); - if let Some(mut player) = attacked_player { - let token = player.client.token; - let velo = player.entity.velocity; - if config.protect_creative && player.gamemode == GameMode::Creative { + if let Some(player) = attacked_player { + let mut victem_entity = player.entity.lock().unwrap(); + let velo = victem_entity.velocity; + if config.protect_creative + && *player.gamemode.lock().unwrap() == GameMode::Creative + { return; } if config.knockback { - let yaw = self.entity.yaw; + let yaw = entity.yaw; let strength = 1.0; - player.entity.knockback( + victem_entity.knockback( strength * 0.5, (yaw * (PI / 180.0)).sin() as f64, -(yaw * (PI / 180.0)).cos() as f64, @@ -388,21 +391,15 @@ impl Player { velo.y as f32, velo.z as f32, ); - self.entity.velocity = self.entity.velocity.multiply(0.6, 1.0, 0.6); + entity.velocity = entity.velocity.multiply(0.6, 1.0, 0.6); - player.entity.velocity = velo; + victem_entity.velocity = velo; player.client.send_packet(packet); } if config.hurt_animation { - // TODO - // thats how we prevent borrow errors :c - let packet = &CHurtAnimation::new(&entity_id, self.entity.yaw); - self.client.send_packet(packet); - player.client.send_packet(packet); - world.broadcast_packet( - &[self.client.token, token], - &CHurtAnimation::new(&entity_id, 10.0), - ) + world.broadcast_packet_all(&CHurtAnimation::new( + &entity_id, entity.yaw, + )) } if config.swing {} } else { @@ -420,11 +417,7 @@ impl Player { None => self.kick(TextComponent::text("Invalid action type")), } } - pub async fn handle_player_action( - &mut self, - _server: &Arc, - player_action: SPlayerAction, - ) { + pub async fn handle_player_action(&self, _server: &Arc, player_action: SPlayerAction) { match Status::from_i32(player_action.status.0) { Some(status) => match status { Status::StartedDigging => { @@ -434,20 +427,15 @@ impl Player { } // TODO: do validation // TODO: Config - if self.gamemode == GameMode::Creative { + if *self.gamemode.lock().unwrap() == GameMode::Creative { let location = player_action.location; // Block break & block break sound // TODO: currently this is always dirt replace it - let world = self.entity.world.lock().await; - world.broadcast_packet( - &[self.client.token], - &CWorldEvent::new(2001, &location, 11, false), - ); + let entity = self.entity.lock().unwrap(); + let world = &entity.world; + world.broadcast_packet_all(&CWorldEvent::new(2001, &location, 11, false)); // AIR - world.broadcast_packet( - &[self.client.token], - &CBlockUpdate::new(&location, 0.into()), - ); + world.broadcast_packet_all(&CBlockUpdate::new(&location, 0.into())); } } Status::CancelledDigging => { @@ -455,7 +443,8 @@ impl Player { // TODO: maybe log? return; } - self.current_block_destroy_stage = 0; + self.current_block_destroy_stage + .store(0, std::sync::atomic::Ordering::Relaxed); } Status::FinishedDigging => { // TODO: do validation @@ -466,16 +455,11 @@ impl Player { } // Block break & block break sound // TODO: currently this is always dirt replace it - let world = self.entity.world.lock().await; - world.broadcast_packet( - &[self.client.token], - &CWorldEvent::new(2001, &location, 11, false), - ); + let entity = self.entity.lock().unwrap(); + let world = &entity.world; + world.broadcast_packet_all(&CWorldEvent::new(2001, &location, 11, false)); // AIR - world.broadcast_packet( - &[self.client.token], - &CBlockUpdate::new(&location, 0.into()), - ); + world.broadcast_packet_all(&CBlockUpdate::new(&location, 0.into())); // TODO: Send this every tick self.client .send_packet(&CAcknowledgeBlockChange::new(player_action.sequence)); @@ -497,12 +481,12 @@ impl Player { } } - pub fn handle_play_ping_request(&mut self, _server: &Arc, request: SPlayPingRequest) { + pub fn handle_play_ping_request(&self, _server: &Arc, request: SPlayPingRequest) { self.client .send_packet(&CPingResponse::new(request.payload)); } - pub async fn handle_use_item_on(&mut self, _server: &Arc, use_item_on: SUseItemOn) { + pub async fn handle_use_item_on(&self, _server: &Arc, use_item_on: SUseItemOn) { let location = use_item_on.location; if !self.can_interact_with_block_at(&location, 1.0) { @@ -511,25 +495,23 @@ impl Player { } if let Some(face) = BlockFace::from_i32(use_item_on.face.0) { - if let Some(item) = self.inventory.held_item() { + if let Some(item) = self.inventory.lock().unwrap().held_item() { let minecraft_id = global_registry::find_minecraft_id( global_registry::ITEM_REGISTRY, item.item_id, ) .expect("All item ids are in the global registry"); if let Ok(block_state_id) = BlockId::new(minecraft_id, None) { - let world = self.entity.world.lock().await; - world.broadcast_packet( - &[self.client.token], - &CBlockUpdate::new(&location, block_state_id.get_id_mojang_repr().into()), - ); - world.broadcast_packet( - &[self.client.token], - &CBlockUpdate::new( - &WorldPosition(location.0 + face.to_offset()), - block_state_id.get_id_mojang_repr().into(), - ), - ); + let entity = self.entity.lock().unwrap(); + let world = &entity.world; + world.broadcast_packet_all(&CBlockUpdate::new( + &location, + block_state_id.get_id_mojang_repr().into(), + )); + world.broadcast_packet_all(&CBlockUpdate::new( + &WorldPosition(location.0 + face.to_offset()), + block_state_id.get_id_mojang_repr().into(), + )); } } self.client @@ -539,38 +521,46 @@ impl Player { } } - pub fn handle_use_item(&mut self, _server: &Arc, _use_item: SUseItem) { + pub fn handle_use_item(&self, _server: &Arc, _use_item: SUseItem) { // TODO: handle packet correctly log::error!("An item was used(SUseItem), but the packet is not implemented yet"); } - pub fn handle_set_held_item(&mut self, _server: &Arc, held: SSetHeldItem) { + pub fn handle_set_held_item(&self, _server: &Arc, held: SSetHeldItem) { let slot = held.slot; if !(0..=8).contains(&slot) { self.kick(TextComponent::text("Invalid held slot")) } - self.inventory.set_selected(slot as usize); + self.inventory.lock().unwrap().set_selected(slot as usize); } pub fn handle_set_creative_slot( - &mut self, + &self, _server: &Arc, packet: SSetCreativeSlot, ) -> Result<(), InventoryError> { - if self.gamemode != GameMode::Creative { + if *self.gamemode.lock().unwrap() != GameMode::Creative { return Err(InventoryError::PermissionError); } - self.inventory - .set_slot(packet.slot as usize, packet.clicked_item.to_item(), false) + self.inventory.lock().unwrap().set_slot( + packet.slot as usize, + packet.clicked_item.to_item(), + false, + ) } // TODO: // This function will in the future be used to keep track of if the client is in a valid state. // But this is not possible yet - pub fn handle_close_container(&mut self, server: &Arc, packet: SCloseContainer) { + pub fn handle_close_container(&self, server: &Arc, packet: SCloseContainer) { // window_id 0 represents both 9x1 Generic AND inventory here - self.inventory.state_id = 0; - if let Some(id) = self.open_container { + self.inventory + .lock() + .unwrap() + .state_id + .store(0, std::sync::atomic::Ordering::Relaxed); + let mut open_container = self.open_container.lock().unwrap(); + if let Some(id) = *open_container { let mut open_containers = server .open_containers .write() @@ -578,7 +568,7 @@ impl Player { if let Some(container) = open_containers.get_mut(&id) { container.remove_player(self.entity_id()) } - self.open_container = None; + *open_container = None; } let Some(_window_type) = WindowType::from_u8(packet.window_id) else { self.kick(TextComponent::text("Invalid window ID")); diff --git a/pumpkin/src/commands/arg_player.rs b/pumpkin/src/commands/arg_player.rs index e3506b4cb..98a71fde7 100644 --- a/pumpkin/src/commands/arg_player.rs +++ b/pumpkin/src/commands/arg_player.rs @@ -31,7 +31,7 @@ pub fn parse_arg_player<'a>( src: &'a mut CommandSender, arg_name: &str, consumed_args: &ConsumedArgs, -) -> Result<&'a mut crate::entity::player::Player, InvalidTreeError> { +) -> Result<&'a crate::entity::player::Player, InvalidTreeError> { let s = consumed_args .get(arg_name) .ok_or(InvalidConsumptionError(None))? diff --git a/pumpkin/src/commands/cmd_echest.rs b/pumpkin/src/commands/cmd_echest.rs index c942d4c31..fbf7bdd69 100644 --- a/pumpkin/src/commands/cmd_echest.rs +++ b/pumpkin/src/commands/cmd_echest.rs @@ -11,7 +11,7 @@ pub(crate) fn init_command_tree<'a>() -> CommandTree<'a> { CommandTree::new(NAMES, DESCRIPTION).execute(&|sender, server, _| { if let Some(player) = sender.as_mut_player() { let entity_id = player.entity_id(); - player.open_container = Some(0); + *player.open_container.lock().unwrap() = Some(0); { let mut open_containers = server .open_containers @@ -27,7 +27,8 @@ pub(crate) fn init_command_tree<'a>() -> CommandTree<'a> { } } } - player.open_container(server, "minecraft:generic_9x3"); + // TODO + // player.open_container(server, "minecraft:generic_9x3"); } Ok(()) diff --git a/pumpkin/src/commands/cmd_gamemode.rs b/pumpkin/src/commands/cmd_gamemode.rs index d7b25ecf5..92600d395 100644 --- a/pumpkin/src/commands/cmd_gamemode.rs +++ b/pumpkin/src/commands/cmd_gamemode.rs @@ -65,15 +65,14 @@ pub(crate) fn init_command_tree<'a>() -> CommandTree<'a> { let gamemode = parse_arg_gamemode(args)?; return if let Player(target) = sender { - if target.gamemode == gamemode { + if *target.gamemode.lock().unwrap() == gamemode { target.send_system_message(TextComponent::text(&format!( "You already in {:?} gamemode", gamemode ))); } else { // TODO - #[expect(clippy::let_underscore_future)] - let _ = target.set_gamemode(gamemode); + target.set_gamemode(gamemode); target.send_system_message(TextComponent::text(&format!( "Game mode was set to {:?}", gamemode @@ -90,15 +89,14 @@ pub(crate) fn init_command_tree<'a>() -> CommandTree<'a> { let gamemode = parse_arg_gamemode(args)?; let target = parse_arg_player(sender, ARG_TARGET, args)?; - if target.gamemode == gamemode { + if *target.gamemode.lock().unwrap() == gamemode { target.send_system_message(TextComponent::text(&format!( "You already in {:?} gamemode", gamemode ))); } else { // TODO - #[expect(clippy::let_underscore_future)] - let _ = target.set_gamemode(gamemode); + target.set_gamemode(gamemode); target.send_system_message(TextComponent::text(&format!( "Game mode was set to {:?}", gamemode diff --git a/pumpkin/src/commands/mod.rs b/pumpkin/src/commands/mod.rs index 3b3350b0d..17393346e 100644 --- a/pumpkin/src/commands/mod.rs +++ b/pumpkin/src/commands/mod.rs @@ -21,7 +21,7 @@ mod tree_format; pub enum CommandSender<'a> { Rcon(&'a mut Vec), Console, - Player(&'a mut Player), + Player(&'a Player), } impl<'a> CommandSender<'a> { @@ -49,7 +49,7 @@ impl<'a> CommandSender<'a> { CommandSender::Rcon(_) => true, } } - pub fn as_mut_player(&mut self) -> Option<&mut Player> { + pub fn as_mut_player(&mut self) -> Option<&Player> { match self { CommandSender::Player(player) => Some(player), CommandSender::Console => None, diff --git a/pumpkin/src/entity/mod.rs b/pumpkin/src/entity/mod.rs index 986df49c0..a8f03821d 100644 --- a/pumpkin/src/entity/mod.rs +++ b/pumpkin/src/entity/mod.rs @@ -9,14 +9,14 @@ use pumpkin_protocol::{ VarInt, }; -use crate::{client::Client, world::World}; +use crate::world::World; pub mod player; pub struct Entity { pub entity_id: EntityId, pub entity_type: EntityType, - pub world: Arc>, + pub world: Arc, pub pos: Vector3, pub block_pos: WorldPosition, @@ -38,11 +38,10 @@ pub struct Entity { pub pose: EntityPose, } -// TODO: Remove client: &mut Client, world: Arc> bs impl Entity { pub fn new( entity_id: EntityId, - world: Arc>, + world: Arc, entity_type: EntityType, standing_eye_height: f32, ) -> Self { @@ -87,6 +86,10 @@ impl Entity { } } + pub async fn remove(&mut self) { + self.world.remove_entity(self); + } + pub fn knockback(&mut self, strength: f64, x: f64, z: f64) { // This has some vanilla magic let mut x = x; @@ -109,11 +112,10 @@ impl Entity { ); } - pub async fn set_sneaking(&mut self, client: &mut Client, sneaking: bool) { + pub async fn set_sneaking(&mut self, sneaking: bool) { assert!(self.sneaking != sneaking); self.sneaking = sneaking; - self.set_flag(client, Self::SNEAKING_FLAG_INDEX, sneaking) - .await; + self.set_flag(Self::SNEAKING_FLAG_INDEX, sneaking).await; // if sneaking { // self.set_pose(EntityPose::Crouching).await; // } else { @@ -121,21 +123,20 @@ impl Entity { // } } - pub async fn set_sprinting(&mut self, client: &mut Client, sprinting: bool) { + pub async fn set_sprinting(&mut self, sprinting: bool) { assert!(self.sprinting != sprinting); self.sprinting = sprinting; - self.set_flag(client, Self::SPRINTING_FLAG_INDEX, sprinting) - .await; + self.set_flag(Self::SPRINTING_FLAG_INDEX, sprinting).await; } pub fn check_fall_flying(&self) -> bool { !self.on_ground } - pub async fn set_fall_flying(&mut self, client: &mut Client, fall_flying: bool) { + pub async fn set_fall_flying(&mut self, fall_flying: bool) { assert!(self.fall_flying != fall_flying); self.fall_flying = fall_flying; - self.set_flag(client, Self::FALL_FLYING_FLAG_INDEX, fall_flying) + self.set_flag(Self::FALL_FLYING_FLAG_INDEX, fall_flying) .await; } @@ -146,7 +147,7 @@ impl Entity { pub const INVISIBLE_FLAG_INDEX: u32 = 5; pub const GLOWING_FLAG_INDEX: u32 = 6; pub const FALL_FLYING_FLAG_INDEX: u32 = 7; - async fn set_flag(&mut self, client: &mut Client, index: u32, value: bool) { + async fn set_flag(&mut self, index: u32, value: bool) { let mut b = 0i8; if value { b |= 1 << index; @@ -154,24 +155,16 @@ impl Entity { b &= !(1 << index); } let packet = CSetEntityMetadata::new(self.entity_id.into(), Metadata::new(0, 0.into(), b)); - client.send_packet(&packet); - self.world - .lock() - .await - .broadcast_packet(&[client.token], &packet); + self.world.broadcast_packet_all(&packet); } - pub async fn set_pose(&mut self, client: &mut Client, pose: EntityPose) { + pub async fn set_pose(&mut self, pose: EntityPose) { self.pose = pose; let pose = self.pose as i32; let packet = CSetEntityMetadata::::new( self.entity_id.into(), Metadata::new(6, 20.into(), (pose).into()), ); - client.send_packet(&packet); - self.world - .lock() - .await - .broadcast_packet(&[client.token], &packet) + self.world.broadcast_packet_all(&packet) } } diff --git a/pumpkin/src/entity/player.rs b/pumpkin/src/entity/player.rs index 774d308ec..a46116d15 100644 --- a/pumpkin/src/entity/player.rs +++ b/pumpkin/src/entity/player.rs @@ -1,4 +1,7 @@ -use std::sync::Arc; +use std::sync::{ + atomic::{AtomicI32, AtomicU8}, + Arc, Mutex, + }; use num_derive::FromPrimitive; use num_traits::ToPrimitive; @@ -58,43 +61,38 @@ impl Default for PlayerAbilities { } pub struct Player { - pub entity: Entity, + pub entity: Mutex, pub gameprofile: GameProfile, pub client: Client, - pub config: PlayerConfig, + pub config: Mutex, /// Current gamemode - pub gamemode: GameMode, + pub gamemode: Mutex, // TODO: prbly should put this into an Living Entitiy or something - pub health: f32, - pub food: i32, - pub food_saturation: f32, - pub inventory: PlayerInventory, - pub open_container: Option, - pub carried_item: Option, + pub health: Mutex, + pub food: AtomicI32, + pub food_saturation: Mutex, + pub inventory: Mutex, + pub open_container: Mutex>, + pub carried_item: Mutex>, /// send `send_abilties_update` when changed pub abilities: PlayerAbilities, - pub last_position: Vector3, + pub last_position: Mutex>, // TODO: This is currently unused, We have to calculate the block breaking speed our own and then break the block our own if its done - pub current_block_destroy_stage: u8, + pub current_block_destroy_stage: AtomicU8, - pub teleport_id_count: i32, + pub teleport_id_count: AtomicI32, // Current awaiting teleport id and location, None if did not teleport - pub awaiting_teleport: Option<(VarInt, Vector3)>, + pub awaiting_teleport: Mutex)>>, - pub watched_section: Vector3, + pub watched_section: Mutex>, } impl Player { - pub fn new( - client: Client, - world: Arc>, - entity_id: EntityId, - gamemode: GameMode, - ) -> Self { - let gameprofile = match client.gameprofile.clone() { + pub fn new(client: Client, world: Arc, entity_id: EntityId, gamemode: GameMode) -> Self { + let gameprofile = match client.gameprofile.lock().unwrap().clone() { Some(profile) => profile, None => { log::error!("No gameprofile?. Impossible"); @@ -106,37 +104,36 @@ impl Player { } } }; - let config = client.config.clone().unwrap_or_default(); + let config = client.config.lock().unwrap().clone().unwrap_or_default(); Self { - entity: Entity::new(entity_id, world, EntityType::Player, 1.62), - config, + entity: Mutex::new(Entity::new(entity_id, world, EntityType::Player, 1.62)), + config: Mutex::new(config), gameprofile, client, - awaiting_teleport: None, + awaiting_teleport: Mutex::new(None), // TODO: Load this from previous instance - health: 20.0, - food: 20, - food_saturation: 20.0, - current_block_destroy_stage: 0, - inventory: PlayerInventory::new(), - open_container: None, - carried_item: None, - teleport_id_count: 0, + health: Mutex::new(20.0), + food: AtomicI32::new(20), + food_saturation: Mutex::new(20.0), + current_block_destroy_stage: AtomicU8::new(0), + inventory: Mutex::new(PlayerInventory::new()), + open_container: Mutex::new(None), + carried_item: Mutex::new(None), + teleport_id_count: AtomicI32::new(0), abilities: PlayerAbilities::default(), - gamemode, - watched_section: Vector3::new(0, 0, 0), - last_position: Vector3::new(0.0, 0.0, 0.0), + gamemode: Mutex::new(gamemode), + watched_section: Mutex::new(Vector3::new(0, 0, 0)), + last_position: Mutex::new(Vector3::new(0.0, 0.0, 0.0)), } } - // TODO: Put this into entity /// Removes the Player out of the current World - pub async fn remove(&mut self) { - self.entity.world.lock().await.remove_player(self); + pub async fn remove(&self) { + self.entity.lock().unwrap().world.remove_player(self); } pub fn entity_id(&self) -> EntityId { - self.entity.entity_id + self.entity.lock().unwrap().entity_id } pub fn send_abilties_update(&mut self) { @@ -162,17 +159,22 @@ impl Player { )); } - pub fn teleport(&mut self, x: f64, y: f64, z: f64, yaw: f32, pitch: f32) { + pub fn teleport(&self, x: f64, y: f64, z: f64, yaw: f32, pitch: f32) { // this is the ultra special magic code used to create the teleport id - self.teleport_id_count += 1; - if self.teleport_id_count == i32::MAX { - self.teleport_id_count = 0; + // This returns the old value + let i = self + .teleport_id_count + .fetch_add(1, std::sync::atomic::Ordering::Relaxed); + if i + 2 == i32::MAX { + self.teleport_id_count + .store(0, std::sync::atomic::Ordering::Relaxed); } - let entity = &mut self.entity; + let teleport_id = i + 1; + let mut entity = self.entity.lock().unwrap(); entity.set_pos(x, y, z); entity.yaw = yaw; entity.pitch = pitch; - self.awaiting_teleport = Some((self.teleport_id_count.into(), Vector3::new(x, y, z))); + *self.awaiting_teleport.lock().unwrap() = Some((teleport_id.into(), Vector3::new(x, y, z))); self.client.send_packet(&CSyncPlayerPosition::new( x, y, @@ -180,12 +182,12 @@ impl Player { yaw, pitch, 0, - self.teleport_id_count.into(), + teleport_id.into(), )); } pub fn block_interaction_range(&self) -> f64 { - if self.gamemode == GameMode::Creative { + if *self.gamemode.lock().unwrap() == GameMode::Creative { 5.0 } else { 4.5 @@ -195,17 +197,21 @@ impl Player { pub fn can_interact_with_block_at(&self, pos: &WorldPosition, additional_range: f64) -> bool { let d = self.block_interaction_range() + additional_range; let box_pos = BoundingBox::from_block(pos); + let entity = self.entity.lock().unwrap(); box_pos.squared_magnitude(Vector3 { - x: self.entity.pos.x, - y: self.entity.pos.y + self.entity.standing_eye_height as f64, - z: self.entity.pos.z, + x: entity.pos.x, + y: entity.pos.y + entity.standing_eye_height as f64, + z: entity.pos.z, }) < d * d } /// Kicks the Client with a reason depending on the connection state - pub fn kick(&mut self, reason: TextComponent) { - assert!(self.client.connection_state == ConnectionState::Play); - assert!(!self.client.closed); + pub fn kick(&self, reason: TextComponent) { + assert!(*self.client.connection_state.lock().unwrap() == ConnectionState::Play); + assert!(!self + .client + .closed + .load(std::sync::atomic::Ordering::Relaxed)); self.client .try_send_packet(&CPlayDisconnect::new(&reason)) @@ -218,53 +224,47 @@ impl Player { self.client.close() } - pub fn update_health(&mut self, health: f32, food: i32, food_saturation: f32) { - self.health = health; - self.food = food; - self.food_saturation = food_saturation; + pub fn update_health(&self, health: f32, food: i32, food_saturation: f32) { + *self.health.lock().unwrap() = health; + self.food.store(food, std::sync::atomic::Ordering::Relaxed); + *self.food_saturation.lock().unwrap() = food_saturation; } - pub async fn set_gamemode(&mut self, gamemode: GameMode) { + pub fn set_gamemode(&self, gamemode: GameMode) { // We could send the same gamemode without problems. But why waste bandwidth ? + let mut current_gamemode = self.gamemode.lock().unwrap(); assert!( - self.gamemode != gamemode, + *current_gamemode != gamemode, "Setting the same gamemode as already is" ); - self.gamemode = gamemode; + *current_gamemode = gamemode; // So a little story time. I actually made an abitlties_from_gamemode function. I looked at vanilla and they always send the abilties from the gamemode. But the funny thing actually is. That the client // does actually use the same method and set the abilties when receiving the CGameEvent gamemode packet. Just Mojang nonsense - - // TODO: fix this ugly mess :c, It gives me a liftime error when saving packet as a var - self.client.send_packet(&CPlayerInfoUpdate::new( - 0x04, - &[pumpkin_protocol::client::play::Player { - uuid: self.gameprofile.id, - actions: vec![PlayerAction::UpdateGameMode((self.gamemode as i32).into())], - }], - )); - self.entity.world.lock().await.broadcast_packet( - &[self.client.token], - &CPlayerInfoUpdate::new( + self.entity + .lock() + .unwrap() + .world + .broadcast_packet_all(&CPlayerInfoUpdate::new( 0x04, &[pumpkin_protocol::client::play::Player { uuid: self.gameprofile.id, - actions: vec![PlayerAction::UpdateGameMode((self.gamemode as i32).into())], + actions: vec![PlayerAction::UpdateGameMode((gamemode as i32).into())], }], - ), - ); + )); self.client .send_packet(&CGameEvent::new(3, gamemode.to_f32().unwrap())); } - pub fn send_system_message(&mut self, text: TextComponent) { + pub fn send_system_message(&self, text: TextComponent) { self.client .send_packet(&CSystemChatMessage::new(text, false)); } } impl Player { - pub async fn process_packets(&mut self, server: &Arc) { - while let Some(mut packet) = self.client.client_packets_queue.pop() { + pub async fn process_packets(&self, server: &Arc) { + let mut packets = self.client.client_packets_queue.lock().unwrap(); + while let Some(mut packet) = packets.pop() { match self.handle_play_packet(server, &mut packet).await { Ok(_) => {} Err(e) => { @@ -277,7 +277,7 @@ impl Player { } pub async fn handle_play_packet( - &mut self, + &self, server: &Arc, packet: &mut RawPacket, ) -> Result<(), DeserializerError> { @@ -362,9 +362,10 @@ impl Player { Ok(()) } SClickContainer::PACKET_ID => { - self.handle_click_container(server, SClickContainer::read(bytebuf)?) - .await - .unwrap(); + // TODO + // self.handle_click_container(server, SClickContainer::read(bytebuf)?) + // .await + // .unwrap(); Ok(()) } SCloseContainer::PACKET_ID => { diff --git a/pumpkin/src/main.rs b/pumpkin/src/main.rs index 4be8e54c6..f90f5b041 100644 --- a/pumpkin/src/main.rs +++ b/pumpkin/src/main.rs @@ -7,7 +7,7 @@ use mio::net::TcpListener; use mio::{Events, Interest, Poll, Token}; use std::collections::HashMap; -use std::io::{self}; +use std::io::{self, Read}; use client::{interrupted, Client}; use server::Server; @@ -24,7 +24,7 @@ pub mod util; pub mod world; fn main() -> io::Result<()> { - use std::sync::{Arc, Mutex}; + use std::sync::Arc; use entity::player::Player; use pumpkin_config::{ADVANCED_CONFIG, BASIC_CONFIG}; @@ -79,7 +79,7 @@ fn main() -> io::Result<()> { let rcon = ADVANCED_CONFIG.rcon.clone(); let mut clients: HashMap = HashMap::new(); - let mut players: HashMap>> = HashMap::new(); + let mut players: HashMap> = HashMap::new(); let server = Arc::new(Server::new()); log::info!("Started Server took {}ms", time.elapsed().as_millis()); @@ -159,17 +159,19 @@ fn main() -> io::Result<()> { token => { // Poll Players if let Some(player) = players.get_mut(&token) { - let mut player = player.lock().unwrap(); player.client.poll(event).await; - if !player.client.closed { + let closed = player + .client + .closed + .load(std::sync::atomic::Ordering::Relaxed); + if !closed { player.process_packets(&server).await; } - if player.client.closed { - drop(player); + if closed { if let Some(player) = players.remove(&token) { - let mut player = player.lock().unwrap(); player.remove().await; - poll.registry().deregister(&mut player.client.connection)?; + let connection = &mut player.client.connection.lock().unwrap(); + poll.registry().deregister(connection.by_ref())?; } } }; @@ -178,23 +180,29 @@ fn main() -> io::Result<()> { // Maybe received an event for a TCP connection. let (done, make_player) = if let Some(client) = clients.get_mut(&token) { client.poll(event).await; - if !client.closed { + let closed = client.closed.load(std::sync::atomic::Ordering::Relaxed); + if !closed { client.process_packets(&server).await; } - (client.closed, client.make_player) + ( + closed, + client + .make_player + .load(std::sync::atomic::Ordering::Relaxed), + ) } else { // Sporadic events happen, we can safely ignore them. (false, false) }; if done || make_player { - if let Some(mut client) = clients.remove(&token) { + if let Some(client) = clients.remove(&token) { if done { - poll.registry().deregister(&mut client.connection)?; + let connection = &mut client.connection.lock().unwrap(); + poll.registry().deregister(connection.by_ref())?; } else if make_player { let token = client.token; let (player, world) = server.add_player(token, client).await; players.insert(token, player.clone()); - let mut world = world.lock().await; world.spawn_player(&BASIC_CONFIG, player).await; } } diff --git a/pumpkin/src/proxy/velocity.rs b/pumpkin/src/proxy/velocity.rs index b23e17082..869f470f2 100644 --- a/pumpkin/src/proxy/velocity.rs +++ b/pumpkin/src/proxy/velocity.rs @@ -15,7 +15,7 @@ type HmacSha256 = Hmac; const MAX_SUPPORTED_FORWARDING_VERSION: i32 = 4; const PLAYER_INFO_CHANNEL: &str = "velocity:player_info"; -pub fn velocity_login(client: &mut Client) { +pub fn velocity_login(client: &Client) { let velocity_message_id: i32 = 0; let mut buf = BytesMut::new(); @@ -36,7 +36,7 @@ pub fn check_integrity(data: (&[u8], &[u8]), secret: String) -> bool { } pub fn receive_plugin_response( - client: &mut Client, + client: &Client, config: VelocityConfig, response: SLoginPluginResponse, ) { @@ -62,7 +62,7 @@ pub fn receive_plugin_response( } // TODO: no unwrap let addr: SocketAddr = buf.get_string().unwrap().parse().unwrap(); - client.address = addr; + *client.address.lock().unwrap() = addr; todo!() } else { client.kick("This server requires you to connect with Velocity.") diff --git a/pumpkin/src/server/mod.rs b/pumpkin/src/server/mod.rs index d9aaf61a3..986842d60 100644 --- a/pumpkin/src/server/mod.rs +++ b/pumpkin/src/server/mod.rs @@ -45,7 +45,7 @@ pub struct Server { pub command_dispatcher: Arc>, - pub worlds: Vec>>, + pub worlds: Vec>, pub status_response: StatusResponse, // We cache the json response here so we don't parse it every time someone makes a Status request. // Keep in mind that we must parse this again, when the StatusResponse changes which usally happen when a player joins or leaves @@ -109,7 +109,7 @@ impl Server { drag_handler: DragHandler::new(), // 0 is invalid entity_id: 2.into(), - worlds: vec![Arc::new(tokio::sync::Mutex::new(world))], + worlds: vec![Arc::new(world)], public_key, cached_server_brand, private_key, @@ -121,11 +121,7 @@ impl Server { } } - pub async fn add_player( - &self, - token: Token, - client: Client, - ) -> (Arc>, Arc>) { + pub async fn add_player(&self, token: Token, client: Client) -> (Arc, Arc) { let entity_id = self.new_entity_id(); let gamemode = match BASIC_CONFIG.default_gamemode { GameMode::Undefined => GameMode::Survival, @@ -135,13 +131,8 @@ impl Server { // TODO: select default from config let world = self.worlds[0].clone(); - let player = Arc::new(Mutex::new(Player::new( - client, - world.clone(), - entity_id, - gamemode, - ))); - world.lock().await.add_player(token, player.clone()); + let player = Arc::new(Player::new(client, world.clone(), entity_id, gamemode)); + world.add_player(token, player.clone()); (player, world) } @@ -161,12 +152,12 @@ impl Server { } /// Sends a Packet to all Players in all worlds - pub fn broadcast_packet_all

(&self, except: &[Token], packet: &P) + pub fn broadcast_packet_all

(&self, packet: &P) where P: ClientPacket, { for world in &self.worlds { - world.blocking_lock().broadcast_packet(except, packet) + world.broadcast_packet_all(packet) } } @@ -183,7 +174,7 @@ impl Server { buf } - pub fn send_brand(&self, client: &mut Client) { + pub fn send_brand(&self, client: &Client) { // send server brand client.send_packet(&CPluginMessage::new( "minecraft:brand", diff --git a/pumpkin/src/world/mod.rs b/pumpkin/src/world/mod.rs index 4c4f514c2..672e37136 100644 --- a/pumpkin/src/world/mod.rs +++ b/pumpkin/src/world/mod.rs @@ -1,7 +1,6 @@ use std::{ collections::HashMap, - ops::DerefMut, - sync::{Arc, Mutex, MutexGuard}, + sync::{Arc, Mutex}, }; pub mod player_chunker; @@ -22,11 +21,14 @@ use pumpkin_protocol::{ use pumpkin_world::level::Level; use tokio::sync::mpsc; -use crate::{client::Client, entity::player::Player}; +use crate::{ + client::Client, + entity::{player::Player, Entity}, +}; pub struct World { pub level: Arc>, - pub current_players: HashMap>>, + pub current_players: Arc>>>, // entities, players... } @@ -34,35 +36,36 @@ impl World { pub fn load(level: Level) -> Self { Self { level: Arc::new(Mutex::new(level)), - current_players: HashMap::new(), + current_players: Arc::new(Mutex::new(HashMap::new())), } } - /// Sends a Packet to all Players, Expect some players. Because we can't lock them twice - pub fn broadcast_packet

(&self, except: &[Token], packet: &P) + /// Sends a Packet to all Players in the World + pub fn broadcast_packet_all

(&self, packet: &P) where P: ClientPacket, { - for (_, player) in self - .current_players - .iter() - .filter(|c| !except.contains(c.0)) - { - let mut player = player.lock().unwrap(); + let current_players = self.current_players.lock().unwrap(); + for (_, player) in current_players.iter() { player.client.send_packet(packet); } } - pub async fn spawn_player( - &mut self, - base_config: &BasicConfiguration, - player: Arc>, - ) { - let mut player = player.lock().unwrap(); - let player = player.deref_mut(); + /// Sends a Packet to all Players in the World, Expect the Players given the the expect parameter + pub fn broadcast_packet_expect

(&self, except: &[Token], packet: &P) + where + P: ClientPacket, + { + let current_players = self.current_players.lock().unwrap(); + for (_, player) in current_players.iter().filter(|c| !except.contains(c.0)) { + player.client.send_packet(packet); + } + } + + pub async fn spawn_player(&self, base_config: &BasicConfiguration, player: Arc) { // This code follows the vanilla packet order let entity_id = player.entity_id(); - let gamemode = player.gamemode; + let gamemode = player.gamemode.lock().unwrap(); log::debug!("spawning player, entity id {}", entity_id); // login packet for our new player @@ -104,7 +107,7 @@ impl World { let gameprofile = &player.gameprofile; // first send info update to our new player, So he can see his Skin // also send his info to everyone else - player.client.send_packet(&CPlayerInfoUpdate::new( + self.broadcast_packet_all(&CPlayerInfoUpdate::new( 0x01 | 0x08, &[pumpkin_protocol::client::play::Player { uuid: gameprofile.id, @@ -117,31 +120,16 @@ impl World { ], }], )); - self.broadcast_packet( - &[player.client.token], - &CPlayerInfoUpdate::new( - 0x01 | 0x08, - &[pumpkin_protocol::client::play::Player { - uuid: gameprofile.id, - actions: vec![ - PlayerAction::AddPlayer { - name: gameprofile.name.clone(), - properties: gameprofile.properties.clone(), - }, - PlayerAction::UpdateListed(true), - ], - }], - ), - ); // here we send all the infos of already joined players let mut entries = Vec::new(); for (_, playerr) in self .current_players + .lock() + .unwrap() .iter() .filter(|(c, _)| **c != player.client.token) { - let playerr = playerr.as_ref().lock().unwrap(); let gameprofile = &playerr.gameprofile; entries.push(pumpkin_protocol::client::play::Player { uuid: gameprofile.id, @@ -161,7 +149,7 @@ impl World { let gameprofile = &player.gameprofile; // spawn player for every client - self.broadcast_packet( + self.broadcast_packet_expect( &[player.client.token], // TODO: add velo &CSpawnEntity::new( @@ -182,9 +170,14 @@ impl World { ); // spawn players for our client let token = player.client.token; - for (_, existing_player) in self.current_players.iter().filter(|c| c.0 != &token) { - let existing_player = existing_player.as_ref().lock().unwrap(); - let entity = &existing_player.entity; + for (_, existing_player) in self + .current_players + .lock() + .unwrap() + .iter() + .filter(|c| c.0 != &token) + { + let entity = existing_player.entity.lock().unwrap(); let gameprofile = &existing_player.gameprofile; player.client.send_packet(&CSpawnEntity::new( existing_player.entity_id().into(), @@ -204,33 +197,27 @@ impl World { } // entity meta data // set skin parts - if let Some(config) = player.client.config.as_ref() { + if let Some(config) = player.client.config.lock().unwrap().as_ref() { let packet = CSetEntityMetadata::new( entity_id.into(), Metadata::new(17, VarInt(0), config.skin_parts), ); - player.client.send_packet(&packet); - self.broadcast_packet(&[player.client.token], &packet) + self.broadcast_packet_all(&packet) } // Start waiting for level chunks player.client.send_packet(&CGameEvent::new(13, 0.0)); // Spawn in inital chunks - player_chunker::player_join(self, player).await; + player_chunker::player_join(self, player.clone()).await; } - async fn spawn_world_chunks( - &self, - client: &mut Client, - chunks: Vec>, - distance: i32, - ) { + async fn spawn_world_chunks(&self, client: &Client, chunks: Vec>, distance: i32) { let inst = std::time::Instant::now(); let (sender, mut chunk_receiver) = mpsc::channel(distance as usize); let level = self.level.clone(); - let closed = client.closed; + let closed = client.closed.load(std::sync::atomic::Ordering::Relaxed); let chunks = Arc::new(chunks); tokio::task::spawn_blocking(move || { level.lock().unwrap().fetch_chunks(&chunks, sender, closed) @@ -255,42 +242,47 @@ impl World { len / (1024 * 1024) ); } - if !client.closed { + if !client.closed.load(std::sync::atomic::Ordering::Relaxed) { client.send_packet(&CChunkData(&chunk_data)); } } dbg!("DONE CHUNKS", inst.elapsed()); } - /// TODO: This definitly should be in world - pub fn get_by_entityid(&self, from: &Player, id: EntityId) -> Option> { + pub fn get_by_entityid(&self, from: &Player, id: EntityId) -> Option> { for (_, player) in self .current_players + .lock() + .unwrap() .iter() .filter(|c| c.0 != &from.client.token) { - let player = player.lock().unwrap(); if player.entity_id() == id { - return Some(player); + return Some(player.clone()); } } None } - pub fn add_player(&mut self, token: Token, player: Arc>) { - self.current_players.insert(token, player); + pub fn add_player(&self, token: Token, player: Arc) { + self.current_players.lock().unwrap().insert(token, player); } - pub fn remove_player(&mut self, player: &Player) { - self.current_players.remove(&player.client.token).unwrap(); - // despawn the player - // todo: put this into the entitiy struct - let id = player.entity_id(); + pub fn remove_player(&self, player: &Player) { + self.current_players + .lock() + .unwrap() + .remove(&player.client.token) + .unwrap(); let uuid = player.gameprofile.id; - self.broadcast_packet( + self.broadcast_packet_expect( &[player.client.token], &CRemovePlayerInfo::new(1.into(), &[UUID(uuid)]), ); - self.broadcast_packet(&[player.client.token], &CRemoveEntities::new(&[id.into()])) + self.remove_entity(&player.entity.lock().unwrap()); + } + + pub fn remove_entity(&self, entity: &Entity) { + self.broadcast_packet_all(&CRemoveEntities::new(&[entity.entity_id.into()])) } } diff --git a/pumpkin/src/world/player_chunker.rs b/pumpkin/src/world/player_chunker.rs index a060dfbd0..6c91e3508 100644 --- a/pumpkin/src/world/player_chunker.rs +++ b/pumpkin/src/world/player_chunker.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use pumpkin_config::BASIC_CONFIG; use pumpkin_core::math::{ get_section_cord, position::WorldPosition, vector2::Vector2, vector3::Vector3, @@ -12,22 +14,26 @@ use super::World; fn get_view_distance(player: &Player) -> i8 { player .config + .lock() + .unwrap() .view_distance .clamp(2, BASIC_CONFIG.view_distance as i8) } -pub async fn player_join(world: &World, player: &mut Player) { - let new_watched = chunk_section_from_pos(&player.entity.block_pos); - player.watched_section = new_watched; - let chunk_pos = player.entity.chunk_pos; +pub async fn player_join(world: &World, player: Arc) { + let entity = player.entity.lock().unwrap(); + let new_watched = chunk_section_from_pos(&entity.block_pos); + let mut watched_section = player.watched_section.lock().unwrap(); + *watched_section = new_watched; + let chunk_pos = entity.chunk_pos; player.client.send_packet(&CCenterChunk { chunk_x: chunk_pos.x.into(), chunk_z: chunk_pos.z.into(), }); - let view_distance = get_view_distance(player) as i32; + let view_distance = get_view_distance(&player) as i32; dbg!(view_distance); let old_cylindrical = Cylindrical::new( - Vector2::new(player.watched_section.x, player.watched_section.z), + Vector2::new(watched_section.x, watched_section.z), view_distance, ); let new_cylindrical = Cylindrical::new(Vector2::new(chunk_pos.x, chunk_pos.z), view_distance); @@ -47,16 +53,17 @@ pub async fn player_join(world: &World, player: &mut Player) { ); if !loading_chunks.is_empty() { world - .spawn_world_chunks(&mut player.client, loading_chunks, view_distance) + .spawn_world_chunks(&player.client, loading_chunks, view_distance) .await; } } -pub async fn update_position(world: &World, player: &mut Player) { - let current_watched = player.watched_section; - let new_watched = chunk_section_from_pos(&player.entity.block_pos); - if current_watched != new_watched { - let chunk_pos = player.entity.chunk_pos; +pub async fn update_position(world: &World, player: &Player) { + let mut current_watched = player.watched_section.lock().unwrap(); + let entity = player.entity.lock().unwrap(); + let new_watched = chunk_section_from_pos(&entity.block_pos); + if *current_watched != new_watched { + let chunk_pos = entity.chunk_pos; player.client.send_packet(&CCenterChunk { chunk_x: chunk_pos.x.into(), chunk_z: chunk_pos.z.into(), @@ -64,12 +71,12 @@ pub async fn update_position(world: &World, player: &mut Player) { let view_distance = get_view_distance(player) as i32; let old_cylindrical = Cylindrical::new( - Vector2::new(player.watched_section.x, player.watched_section.z), + Vector2::new(current_watched.x, current_watched.z), view_distance, ); let new_cylindrical = Cylindrical::new(Vector2::new(chunk_pos.x, chunk_pos.z), view_distance); - player.watched_section = new_watched; + *current_watched = new_watched; let mut loading_chunks = Vec::new(); Cylindrical::for_each_changed_chunk( old_cylindrical, @@ -86,7 +93,7 @@ pub async fn update_position(world: &World, player: &mut Player) { ); if !loading_chunks.is_empty() { world - .spawn_world_chunks(&mut player.client, loading_chunks, view_distance) + .spawn_world_chunks(&player.client, loading_chunks, view_distance) .await; } } From 58831cd0c939f7a5a57ca8d415f5506db6fd685c Mon Sep 17 00:00:00 2001 From: Snowiiii Date: Mon, 9 Sep 2024 21:14:22 +0200 Subject: [PATCH 03/65] Fix: Format --- pumpkin/src/client/client_packet.rs | 2 +- pumpkin/src/entity/player.rs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pumpkin/src/client/client_packet.rs b/pumpkin/src/client/client_packet.rs index b81b40790..7fc6d11d1 100644 --- a/pumpkin/src/client/client_packet.rs +++ b/pumpkin/src/client/client_packet.rs @@ -43,7 +43,7 @@ impl Client { self.protocol_version .store(version, std::sync::atomic::Ordering::Relaxed); let mut connection_state = self.connection_state.lock().unwrap(); - + *connection_state = handshake.next_state; if *connection_state != ConnectionState::Status { let protocol = version; diff --git a/pumpkin/src/entity/player.rs b/pumpkin/src/entity/player.rs index a46116d15..ed2e78b59 100644 --- a/pumpkin/src/entity/player.rs +++ b/pumpkin/src/entity/player.rs @@ -1,7 +1,7 @@ use std::sync::{ - atomic::{AtomicI32, AtomicU8}, - Arc, Mutex, - }; + atomic::{AtomicI32, AtomicU8}, + Arc, Mutex, +}; use num_derive::FromPrimitive; use num_traits::ToPrimitive; From e5bfbce2ce484e0914e7b072bf3d219d9fa3c252 Mon Sep 17 00:00:00 2001 From: Snowiiii Date: Mon, 9 Sep 2024 23:22:55 +0200 Subject: [PATCH 04/65] Fix dead locks --- pumpkin-protocol/src/lib.rs | 2 +- pumpkin/src/client/mod.rs | 5 +- pumpkin/src/client/player_packet.rs | 134 +++++++++++++++------------- pumpkin/src/entity/mod.rs | 128 ++++++++++++++------------ pumpkin/src/entity/player.rs | 24 +++-- pumpkin/src/main.rs | 4 + pumpkin/src/world/mod.rs | 17 ++-- pumpkin/src/world/player_chunker.rs | 17 ++-- 8 files changed, 181 insertions(+), 150 deletions(-) diff --git a/pumpkin-protocol/src/lib.rs b/pumpkin-protocol/src/lib.rs index 66caee7e9..dfabd2e99 100644 --- a/pumpkin-protocol/src/lib.rs +++ b/pumpkin-protocol/src/lib.rs @@ -151,7 +151,7 @@ pub enum PacketError { MalformedLength, } -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone)] pub enum ConnectionState { HandShake, Status, diff --git a/pumpkin/src/client/mod.rs b/pumpkin/src/client/mod.rs index 53ca10189..19a89fc1f 100644 --- a/pumpkin/src/client/mod.rs +++ b/pumpkin/src/client/mod.rs @@ -182,7 +182,10 @@ impl Client { ) -> Result<(), DeserializerError> { // TODO: handle each packet's Error instead of calling .unwrap() let bytebuf = &mut packet.bytebuf; - match *self.connection_state.lock().unwrap() { + let locked_state = self.connection_state.lock().unwrap(); + let state = locked_state.clone(); + drop(locked_state); + match state { pumpkin_protocol::ConnectionState::HandShake => match packet.id.0 { SHandShake::PACKET_ID => { self.handle_handshake(server, SHandShake::read(bytebuf)?); diff --git a/pumpkin/src/client/player_packet.rs b/pumpkin/src/client/player_packet.rs index 04c7788f6..d2c2a2716 100644 --- a/pumpkin/src/client/player_packet.rs +++ b/pumpkin/src/client/player_packet.rs @@ -50,10 +50,7 @@ impl Player { if let Some((id, position)) = awaiting_teleport.as_ref() { if id == &confirm_teleport.teleport_id { // we should set the pos now to that we requested in the teleport packet, Is may fixed issues when the client sended position packets while being teleported - self.entity - .lock() - .unwrap() - .set_pos(position.x, position.y, position.z); + self.entity.set_pos(position.x, position.y, position.z); *awaiting_teleport = None; } else { @@ -79,18 +76,20 @@ impl Player { self.kick(TextComponent::text("Invalid movement")); return; } - let mut entity = self.entity.lock().unwrap(); - let mut last_position = self.last_position.lock().unwrap(); - *last_position = entity.pos; + let entity = &self.entity; entity.set_pos( Self::clamp_horizontal(position.x), Self::clamp_vertical(position.feet_y), Self::clamp_horizontal(position.z), ); - entity.on_ground = position.ground; - let on_ground = entity.on_ground; + let mut last_position = self.last_position.lock().unwrap(); + let pos = entity.pos.lock().unwrap(); + *last_position = *pos; + entity + .on_ground + .store(position.ground, std::sync::atomic::Ordering::Relaxed); let entity_id = entity.entity_id; - let (x, y, z) = entity.pos.into(); + let (x, y, z) = (*pos).into(); let (lastx, lasty, lastz) = (last_position.x, last_position.y, last_position.z); let world = entity.world.clone(); @@ -114,10 +113,10 @@ impl Player { (x * 4096.0 - lastx * 4096.0) as i16, (y * 4096.0 - lasty * 4096.0) as i16, (z * 4096.0 - lastz * 4096.0) as i16, - on_ground, + position.ground, ), ); - player_chunker::update_position(&world, self).await; + player_chunker::update_position(entity, self).await; } pub async fn handle_position_rotation( @@ -136,27 +135,31 @@ impl Player { self.kick(TextComponent::text("Invalid rotation")); return; } - let mut entity = self.entity.lock().unwrap(); + let entity = &self.entity; - let mut last_position = self.last_position.lock().unwrap(); - *last_position = entity.pos; entity.set_pos( Self::clamp_horizontal(position_rotation.x), Self::clamp_vertical(position_rotation.feet_y), Self::clamp_horizontal(position_rotation.z), ); - entity.on_ground = position_rotation.ground; - entity.yaw = wrap_degrees(position_rotation.yaw) % 360.0; - entity.pitch = wrap_degrees(position_rotation.pitch).clamp(-90.0, 90.0) % 360.0; + let mut last_position = self.last_position.lock().unwrap(); + let pos = entity.pos.lock().unwrap(); + *last_position = *pos; + entity.on_ground.store( + position_rotation.ground, + std::sync::atomic::Ordering::Relaxed, + ); + entity.set_rotation( + wrap_degrees(position_rotation.yaw) % 360.0, + wrap_degrees(position_rotation.pitch).clamp(-90.0, 90.0) % 360.0, + ); - let on_ground = entity.on_ground; let entity_id = entity.entity_id; - let (x, y, z) = entity.pos.into(); + let (x, y, z) = (*pos).into(); let (lastx, lasty, lastz) = (last_position.x, last_position.y, last_position.z); - let yaw = modulus(entity.yaw * 256.0 / 360.0, 256.0); - let pitch = modulus(entity.pitch * 256.0 / 360.0, 256.0); + let yaw = modulus(*entity.yaw.lock().unwrap() * 256.0 / 360.0, 256.0); + let pitch = modulus(*entity.pitch.lock().unwrap() * 256.0 / 360.0, 256.0); // let head_yaw = (entity.head_yaw * 256.0 / 360.0).floor(); - let entity = self.entity.lock().unwrap(); let world = &entity.world; // let delta = Vector3::new(x - lastx, y - lasty, z - lastz); @@ -182,15 +185,14 @@ impl Player { (z * 4096.0 - lastz * 4096.0) as i16, yaw as u8, pitch as u8, - on_ground, + position_rotation.ground, ), ); world.broadcast_packet_expect( &[self.client.token], &CHeadRot::new(entity_id.into(), yaw as u8), ); - - player_chunker::update_position(world, self).await; + player_chunker::update_position(entity, self).await; } pub async fn handle_rotation(&self, _server: &Arc, rotation: SPlayerRotation) { @@ -198,19 +200,23 @@ impl Player { self.kick(TextComponent::text("Invalid rotation")); return; } - let mut entity = self.entity.lock().unwrap(); - entity.on_ground = rotation.ground; - entity.yaw = wrap_degrees(rotation.yaw) % 360.0; - entity.pitch = wrap_degrees(rotation.pitch).clamp(-90.0, 90.0) % 360.0; + let entity = &self.entity; + entity + .on_ground + .store(rotation.ground, std::sync::atomic::Ordering::Relaxed); + entity.set_rotation( + wrap_degrees(rotation.yaw) % 360.0, + wrap_degrees(rotation.pitch).clamp(-90.0, 90.0) % 360.0, + ); // send new position to all other players - let on_ground = entity.on_ground; let entity_id = entity.entity_id; - let yaw = modulus(entity.yaw * 256.0 / 360.0, 256.0); - let pitch = modulus(entity.pitch * 256.0 / 360.0, 256.0); + let yaw = modulus(*entity.yaw.lock().unwrap() * 256.0 / 360.0, 256.0); + let pitch = modulus(*entity.pitch.lock().unwrap() * 256.0 / 360.0, 256.0); // let head_yaw = modulus(entity.head_yaw * 256.0 / 360.0, 256.0); let world = &entity.world; - let packet = CUpdateEntityRot::new(entity_id.into(), yaw as u8, pitch as u8, on_ground); + let packet = + CUpdateEntityRot::new(entity_id.into(), yaw as u8, pitch as u8, rotation.ground); world.broadcast_packet_expect(&[self.client.token], &packet); let packet = CHeadRot::new(entity_id.into(), yaw as u8); world.broadcast_packet_expect(&[self.client.token], &packet); @@ -222,7 +228,9 @@ impl Player { } pub fn handle_player_ground(&self, _server: &Arc, ground: SSetPlayerGround) { - self.entity.lock().unwrap().on_ground = ground.on_ground; + self.entity + .on_ground + .store(ground.on_ground, std::sync::atomic::Ordering::Relaxed); } pub async fn handle_player_command(&self, _server: &Arc, command: SPlayerCommand) { @@ -231,26 +239,26 @@ impl Player { } if let Some(action) = Action::from_i32(command.action.0) { - let mut entity = self.entity.lock().unwrap(); + let entity = &self.entity; match action { pumpkin_protocol::server::play::Action::StartSneaking => { - if !entity.sneaking { + if !entity.sneaking.load(std::sync::atomic::Ordering::Relaxed) { entity.set_sneaking(true).await } } pumpkin_protocol::server::play::Action::StopSneaking => { - if entity.sneaking { + if entity.sneaking.load(std::sync::atomic::Ordering::Relaxed) { entity.set_sneaking(false).await } } pumpkin_protocol::server::play::Action::LeaveBed => todo!(), pumpkin_protocol::server::play::Action::StartSprinting => { - if !entity.sprinting { + if !entity.sprinting.load(std::sync::atomic::Ordering::Relaxed) { entity.set_sprinting(true).await } } pumpkin_protocol::server::play::Action::StopSprinting => { - if entity.sprinting { + if entity.sprinting.load(std::sync::atomic::Ordering::Relaxed) { entity.set_sprinting(false).await } } @@ -259,7 +267,11 @@ impl Player { pumpkin_protocol::server::play::Action::OpenVehicleInventory => todo!(), pumpkin_protocol::server::play::Action::StartFlyingElytra => { let fall_flying = entity.check_fall_flying(); - if entity.fall_flying != fall_flying { + if entity + .fall_flying + .load(std::sync::atomic::Ordering::Relaxed) + != fall_flying + { entity.set_fall_flying(fall_flying).await; } } // TODO @@ -277,8 +289,7 @@ impl Player { Hand::Off => Animation::SwingOffhand, }; let id = self.entity_id(); - let entity = self.entity.lock().unwrap(); - let world = &entity.world; + let world = &self.entity.world; world.broadcast_packet_expect( &[self.client.token], &CEntityAnimation::new(id.into(), animation as u8), @@ -302,7 +313,7 @@ impl Player { // TODO: filter message & validation let gameprofile = &self.gameprofile; - let entity = self.entity.lock().unwrap(); + let entity = &self.entity; let world = &entity.world; world.broadcast_packet_all(&CPlayerChatMessage::new( pumpkin_protocol::uuid::UUID(gameprofile.id), @@ -356,8 +367,8 @@ impl Player { pub async fn handle_interact(&self, _: &Arc, interact: SInteract) { let sneaking = interact.sneaking; - let mut entity = self.entity.lock().unwrap(); - if entity.sneaking != sneaking { + let entity = &self.entity; + if entity.sneaking.load(std::sync::atomic::Ordering::Relaxed) != sneaking { entity.set_sneaking(sneaking).await; } match ActionType::from_i32(interact.typ.0) { @@ -370,35 +381,38 @@ impl Player { let world = entity.world.clone(); let attacked_player = world.get_by_entityid(self, entity_id.0 as EntityId); if let Some(player) = attacked_player { - let mut victem_entity = player.entity.lock().unwrap(); - let velo = victem_entity.velocity; + let victem_entity = &player.entity; if config.protect_creative && *player.gamemode.lock().unwrap() == GameMode::Creative { return; } if config.knockback { - let yaw = entity.yaw; + let yaw = entity.yaw.lock().unwrap(); let strength = 1.0; + let mut victem_velocity = victem_entity.velocity.lock().unwrap(); + let saved_velo = *victem_velocity; victem_entity.knockback( strength * 0.5, - (yaw * (PI / 180.0)).sin() as f64, - -(yaw * (PI / 180.0)).cos() as f64, + (*yaw * (PI / 180.0)).sin() as f64, + -(*yaw * (PI / 180.0)).cos() as f64, ); let packet = &CEntityVelocity::new( &entity_id, - velo.x as f32, - velo.y as f32, - velo.z as f32, + victem_velocity.x as f32, + victem_velocity.y as f32, + victem_velocity.z as f32, ); - entity.velocity = entity.velocity.multiply(0.6, 1.0, 0.6); + let mut velocity = entity.velocity.lock().unwrap(); + *velocity = velocity.multiply(0.6, 1.0, 0.6); - victem_entity.velocity = velo; + *victem_velocity = saved_velo; player.client.send_packet(packet); } if config.hurt_animation { world.broadcast_packet_all(&CHurtAnimation::new( - &entity_id, entity.yaw, + &entity_id, + *entity.yaw.lock().unwrap(), )) } if config.swing {} @@ -431,7 +445,7 @@ impl Player { let location = player_action.location; // Block break & block break sound // TODO: currently this is always dirt replace it - let entity = self.entity.lock().unwrap(); + let entity = &self.entity; let world = &entity.world; world.broadcast_packet_all(&CWorldEvent::new(2001, &location, 11, false)); // AIR @@ -455,7 +469,7 @@ impl Player { } // Block break & block break sound // TODO: currently this is always dirt replace it - let entity = self.entity.lock().unwrap(); + let entity = &self.entity; let world = &entity.world; world.broadcast_packet_all(&CWorldEvent::new(2001, &location, 11, false)); // AIR @@ -502,7 +516,7 @@ impl Player { ) .expect("All item ids are in the global registry"); if let Ok(block_state_id) = BlockId::new(minecraft_id, None) { - let entity = self.entity.lock().unwrap(); + let entity = &self.entity; let world = &entity.world; world.broadcast_packet_all(&CBlockUpdate::new( &location, diff --git a/pumpkin/src/entity/mod.rs b/pumpkin/src/entity/mod.rs index a8f03821d..132dfedf0 100644 --- a/pumpkin/src/entity/mod.rs +++ b/pumpkin/src/entity/mod.rs @@ -1,4 +1,4 @@ -use std::sync::Arc; +use std::sync::{atomic::AtomicBool, Arc, Mutex}; use pumpkin_core::math::{ get_section_cord, position::WorldPosition, vector2::Vector2, vector3::Vector3, @@ -18,24 +18,24 @@ pub struct Entity { pub entity_type: EntityType, pub world: Arc, - pub pos: Vector3, - pub block_pos: WorldPosition, - pub chunk_pos: Vector2, + pub pos: Mutex>, + pub block_pos: Mutex, + pub chunk_pos: Mutex>, - pub sneaking: bool, - pub sprinting: bool, - pub fall_flying: bool, - pub velocity: Vector3, + pub sneaking: AtomicBool, + pub sprinting: AtomicBool, + pub fall_flying: AtomicBool, + pub velocity: Mutex>, // Should be not trusted - pub on_ground: bool, + pub on_ground: AtomicBool, - pub yaw: f32, - pub head_yaw: f32, - pub pitch: f32, + pub yaw: Mutex, + pub head_yaw: Mutex, + pub pitch: Mutex, // TODO: Change this in diffrent poses pub standing_eye_height: f32, - pub pose: EntityPose, + pub pose: Mutex, } impl Entity { @@ -48,49 +48,58 @@ impl Entity { Self { entity_id, entity_type, - on_ground: false, - pos: Vector3::new(0.0, 0.0, 0.0), - block_pos: WorldPosition(Vector3::new(0, 0, 0)), - chunk_pos: Vector2::new(0, 0), - sneaking: false, + on_ground: AtomicBool::new(false), + pos: Mutex::new(Vector3::new(0.0, 0.0, 0.0)), + block_pos: Mutex::new(WorldPosition(Vector3::new(0, 0, 0))), + chunk_pos: Mutex::new(Vector2::new(0, 0)), + sneaking: AtomicBool::new(false), world, - sprinting: false, - fall_flying: false, - yaw: 0.0, - head_yaw: 0.0, - pitch: 0.0, - velocity: Vector3::new(0.0, 0.0, 0.0), + sprinting: AtomicBool::new(false), + fall_flying: AtomicBool::new(false), + yaw: Mutex::new(0.0), + head_yaw: Mutex::new(0.0), + pitch: Mutex::new(0.0), + velocity: Mutex::new(Vector3::new(0.0, 0.0, 0.0)), standing_eye_height, - pose: EntityPose::Standing, + pose: Mutex::new(EntityPose::Standing), } } - pub fn set_pos(&mut self, x: f64, y: f64, z: f64) { - if self.pos.x != x || self.pos.y != y || self.pos.z != z { - self.pos = Vector3::new(x, y, z); + pub fn set_pos(&self, x: f64, y: f64, z: f64) { + let mut pos = self.pos.lock().unwrap(); + if pos.x != x || pos.y != y || pos.z != z { + *pos = Vector3::new(x, y, z); let i = x.floor() as i32; let j = y.floor() as i32; let k = z.floor() as i32; - let block_pos = self.block_pos.0; - if i != block_pos.x || j != block_pos.y || k != block_pos.z { - self.block_pos = WorldPosition(Vector3::new(i, j, k)); + let mut block_pos = self.block_pos.lock().unwrap(); + let block_pos_vec = block_pos.0; + if i != block_pos_vec.x || j != block_pos_vec.y || k != block_pos_vec.z { + *block_pos = WorldPosition(Vector3::new(i, j, k)); - if get_section_cord(i) != self.chunk_pos.x - || get_section_cord(k) != self.chunk_pos.z - { - self.chunk_pos = - Vector2::new(get_section_cord(block_pos.x), get_section_cord(block_pos.z)); + let mut chunk_pos = self.chunk_pos.lock().unwrap(); + if get_section_cord(i) != chunk_pos.x || get_section_cord(k) != chunk_pos.z { + *chunk_pos = Vector2::new( + get_section_cord(block_pos_vec.x), + get_section_cord(block_pos_vec.z), + ); } } } } + pub fn set_rotation(&self, yaw: f32, pitch: f32) { + // TODO + *self.yaw.lock().unwrap() = yaw; + *self.pitch.lock().unwrap() = pitch + } + pub async fn remove(&mut self) { self.world.remove_entity(self); } - pub fn knockback(&mut self, strength: f64, x: f64, z: f64) { + pub fn knockback(&self, strength: f64, x: f64, z: f64) { // This has some vanilla magic let mut x = x; let mut z = z; @@ -100,21 +109,22 @@ impl Entity { } let var8 = Vector3::new(x, 0.0, z).normalize() * strength; - let var7 = self.velocity; - self.velocity = Vector3::new( - var7.x / 2.0 - var8.x, - if self.on_ground { - (var7.y / 2.0 + strength).min(0.4) + let mut velocity = self.velocity.lock().unwrap(); + *velocity = Vector3::new( + velocity.x / 2.0 - var8.x, + if self.on_ground.load(std::sync::atomic::Ordering::Relaxed) { + (velocity.y / 2.0 + strength).min(0.4) } else { - var7.y + velocity.y }, - var7.z / 2.0 - var8.z, + velocity.z / 2.0 - var8.z, ); } - pub async fn set_sneaking(&mut self, sneaking: bool) { - assert!(self.sneaking != sneaking); - self.sneaking = sneaking; + pub async fn set_sneaking(&self, sneaking: bool) { + assert!(self.sneaking.load(std::sync::atomic::Ordering::Relaxed) != sneaking); + self.sneaking + .store(sneaking, std::sync::atomic::Ordering::Relaxed); self.set_flag(Self::SNEAKING_FLAG_INDEX, sneaking).await; // if sneaking { // self.set_pose(EntityPose::Crouching).await; @@ -123,19 +133,21 @@ impl Entity { // } } - pub async fn set_sprinting(&mut self, sprinting: bool) { - assert!(self.sprinting != sprinting); - self.sprinting = sprinting; + pub async fn set_sprinting(&self, sprinting: bool) { + assert!(self.sprinting.load(std::sync::atomic::Ordering::Relaxed) != sprinting); + self.sprinting + .store(sprinting, std::sync::atomic::Ordering::Relaxed); self.set_flag(Self::SPRINTING_FLAG_INDEX, sprinting).await; } pub fn check_fall_flying(&self) -> bool { - !self.on_ground + !self.on_ground.load(std::sync::atomic::Ordering::Relaxed) } - pub async fn set_fall_flying(&mut self, fall_flying: bool) { - assert!(self.fall_flying != fall_flying); - self.fall_flying = fall_flying; + pub async fn set_fall_flying(&self, fall_flying: bool) { + assert!(self.fall_flying.load(std::sync::atomic::Ordering::Relaxed) != fall_flying); + self.fall_flying + .store(fall_flying, std::sync::atomic::Ordering::Relaxed); self.set_flag(Self::FALL_FLYING_FLAG_INDEX, fall_flying) .await; } @@ -147,7 +159,7 @@ impl Entity { pub const INVISIBLE_FLAG_INDEX: u32 = 5; pub const GLOWING_FLAG_INDEX: u32 = 6; pub const FALL_FLYING_FLAG_INDEX: u32 = 7; - async fn set_flag(&mut self, index: u32, value: bool) { + async fn set_flag(&self, index: u32, value: bool) { let mut b = 0i8; if value { b |= 1 << index; @@ -158,9 +170,9 @@ impl Entity { self.world.broadcast_packet_all(&packet); } - pub async fn set_pose(&mut self, pose: EntityPose) { - self.pose = pose; - let pose = self.pose as i32; + pub async fn set_pose(&self, pose: EntityPose) { + *self.pose.lock().unwrap() = pose; + let pose = pose as i32; let packet = CSetEntityMetadata::::new( self.entity_id.into(), Metadata::new(6, 20.into(), (pose).into()), diff --git a/pumpkin/src/entity/player.rs b/pumpkin/src/entity/player.rs index ed2e78b59..67ed05e8f 100644 --- a/pumpkin/src/entity/player.rs +++ b/pumpkin/src/entity/player.rs @@ -61,7 +61,7 @@ impl Default for PlayerAbilities { } pub struct Player { - pub entity: Mutex, + pub entity: Entity, pub gameprofile: GameProfile, pub client: Client, @@ -106,7 +106,7 @@ impl Player { }; let config = client.config.lock().unwrap().clone().unwrap_or_default(); Self { - entity: Mutex::new(Entity::new(entity_id, world, EntityType::Player, 1.62)), + entity: Entity::new(entity_id, world, EntityType::Player, 1.62), config: Mutex::new(config), gameprofile, client, @@ -129,11 +129,11 @@ impl Player { /// Removes the Player out of the current World pub async fn remove(&self) { - self.entity.lock().unwrap().world.remove_player(self); + self.entity.world.remove_player(self); } pub fn entity_id(&self) -> EntityId { - self.entity.lock().unwrap().entity_id + self.entity.entity_id } pub fn send_abilties_update(&mut self) { @@ -170,10 +170,9 @@ impl Player { .store(0, std::sync::atomic::Ordering::Relaxed); } let teleport_id = i + 1; - let mut entity = self.entity.lock().unwrap(); + let entity = &self.entity; entity.set_pos(x, y, z); - entity.yaw = yaw; - entity.pitch = pitch; + entity.set_rotation(yaw, pitch); *self.awaiting_teleport.lock().unwrap() = Some((teleport_id.into(), Vector3::new(x, y, z))); self.client.send_packet(&CSyncPlayerPosition::new( x, @@ -197,11 +196,12 @@ impl Player { pub fn can_interact_with_block_at(&self, pos: &WorldPosition, additional_range: f64) -> bool { let d = self.block_interaction_range() + additional_range; let box_pos = BoundingBox::from_block(pos); - let entity = self.entity.lock().unwrap(); + let entity_pos = self.entity.pos.lock().unwrap(); + let standing_eye_height = self.entity.standing_eye_height; box_pos.squared_magnitude(Vector3 { - x: entity.pos.x, - y: entity.pos.y + entity.standing_eye_height as f64, - z: entity.pos.z, + x: entity_pos.x, + y: entity_pos.y + standing_eye_height as f64, + z: entity_pos.z, }) < d * d } @@ -241,8 +241,6 @@ impl Player { // So a little story time. I actually made an abitlties_from_gamemode function. I looked at vanilla and they always send the abilties from the gamemode. But the funny thing actually is. That the client // does actually use the same method and set the abilties when receiving the CGameEvent gamemode packet. Just Mojang nonsense self.entity - .lock() - .unwrap() .world .broadcast_packet_all(&CPlayerInfoUpdate::new( 0x04, diff --git a/pumpkin/src/main.rs b/pumpkin/src/main.rs index f90f5b041..8a00ebbb4 100644 --- a/pumpkin/src/main.rs +++ b/pumpkin/src/main.rs @@ -169,8 +169,12 @@ fn main() -> io::Result<()> { } if closed { if let Some(player) = players.remove(&token) { + dbg!("a"); player.remove().await; + dbg!("b"); let connection = &mut player.client.connection.lock().unwrap(); + dbg!("c"); + poll.registry().deregister(connection.by_ref())?; } } diff --git a/pumpkin/src/world/mod.rs b/pumpkin/src/world/mod.rs index 672e37136..253fa27b4 100644 --- a/pumpkin/src/world/mod.rs +++ b/pumpkin/src/world/mod.rs @@ -177,18 +177,19 @@ impl World { .iter() .filter(|c| c.0 != &token) { - let entity = existing_player.entity.lock().unwrap(); + let entity = &existing_player.entity; + let pos = entity.pos.lock().unwrap(); let gameprofile = &existing_player.gameprofile; player.client.send_packet(&CSpawnEntity::new( existing_player.entity_id().into(), UUID(gameprofile.id), (EntityType::Player as i32).into(), - entity.pos.x, - entity.pos.y, - entity.pos.z, - entity.yaw, - entity.pitch, - entity.head_yaw, + pos.x, + pos.y, + pos.z, + *entity.yaw.lock().unwrap(), + *entity.pitch.lock().unwrap(), + *entity.head_yaw.lock().unwrap(), 0.into(), 0.0, 0.0, @@ -279,7 +280,7 @@ impl World { &[player.client.token], &CRemovePlayerInfo::new(1.into(), &[UUID(uuid)]), ); - self.remove_entity(&player.entity.lock().unwrap()); + self.remove_entity(&player.entity); } pub fn remove_entity(&self, entity: &Entity) { diff --git a/pumpkin/src/world/player_chunker.rs b/pumpkin/src/world/player_chunker.rs index 6c91e3508..0a8464883 100644 --- a/pumpkin/src/world/player_chunker.rs +++ b/pumpkin/src/world/player_chunker.rs @@ -7,7 +7,7 @@ use pumpkin_core::math::{ use pumpkin_protocol::client::play::{CCenterChunk, CUnloadChunk}; use pumpkin_world::cylindrical_chunk_iterator::Cylindrical; -use crate::entity::player::Player; +use crate::entity::{player::Player, Entity}; use super::World; @@ -21,11 +21,10 @@ fn get_view_distance(player: &Player) -> i8 { } pub async fn player_join(world: &World, player: Arc) { - let entity = player.entity.lock().unwrap(); - let new_watched = chunk_section_from_pos(&entity.block_pos); + let new_watched = chunk_section_from_pos(&player.entity.block_pos.lock().unwrap()); let mut watched_section = player.watched_section.lock().unwrap(); *watched_section = new_watched; - let chunk_pos = entity.chunk_pos; + let chunk_pos = player.entity.chunk_pos.lock().unwrap(); player.client.send_packet(&CCenterChunk { chunk_x: chunk_pos.x.into(), chunk_z: chunk_pos.z.into(), @@ -58,12 +57,11 @@ pub async fn player_join(world: &World, player: Arc) { } } -pub async fn update_position(world: &World, player: &Player) { +pub async fn update_position(entity: &Entity, player: &Player) { let mut current_watched = player.watched_section.lock().unwrap(); - let entity = player.entity.lock().unwrap(); - let new_watched = chunk_section_from_pos(&entity.block_pos); + let new_watched = chunk_section_from_pos(&entity.block_pos.lock().unwrap()); if *current_watched != new_watched { - let chunk_pos = entity.chunk_pos; + let chunk_pos = entity.chunk_pos.lock().unwrap(); player.client.send_packet(&CCenterChunk { chunk_x: chunk_pos.x.into(), chunk_z: chunk_pos.z.into(), @@ -92,7 +90,8 @@ pub async fn update_position(world: &World, player: &Player) { false, ); if !loading_chunks.is_empty() { - world + entity + .world .spawn_world_chunks(&player.client, loading_chunks, view_distance) .await; } From ada0d82a72f7eafe38bf896c2fab2ffb8c95faa7 Mon Sep 17 00:00:00 2001 From: Snowiiii Date: Tue, 10 Sep 2024 09:57:54 +0200 Subject: [PATCH 05/65] Added Container back --- pumpkin/src/client/container.rs | 116 ++++++++++++++++------------ pumpkin/src/client/mod.rs | 2 +- pumpkin/src/client/player_packet.rs | 4 +- pumpkin/src/commands/cmd_echest.rs | 3 +- 4 files changed, 71 insertions(+), 54 deletions(-) diff --git a/pumpkin/src/client/container.rs b/pumpkin/src/client/container.rs index c11ccb381..86769642e 100644 --- a/pumpkin/src/client/container.rs +++ b/pumpkin/src/client/container.rs @@ -20,8 +20,11 @@ use std::sync::{Arc, Mutex}; impl Player { pub fn open_container(&self, server: &Arc, minecraft_menu_id: &str) { - self.inventory.state_id = 0; - let total_opened_containers = self.inventory.total_opened_containers; + let inventory = self.inventory.lock().unwrap(); + inventory + .state_id + .store(0, std::sync::atomic::Ordering::Relaxed); + let total_opened_containers = inventory.total_opened_containers; let container = self.get_open_container(server); let mut container = container .as_ref() @@ -38,7 +41,7 @@ impl Player { let window_title = container .as_ref() .map(|container| container.window_name()) - .unwrap_or(self.inventory.window_name()); + .unwrap_or(inventory.window_name()); let title = TextComponent::text(window_title); self.client.send_packet(&COpenScreen::new( @@ -46,12 +49,15 @@ impl Player { menu_protocol_id, title, )); + drop(inventory); self.set_container_content(container.as_deref_mut()); } pub fn set_container_content(&self, container: Option<&mut Box>) { - let total_opened_containers = self.inventory.total_opened_containers; - let container = OptionallyCombinedContainer::new(&mut self.inventory.lock().unwrap(), container); + let mut inventory = self.inventory.lock().unwrap(); + + let total_opened_containers = inventory.total_opened_containers; + let container = OptionallyCombinedContainer::new(&mut inventory, container); let slots = container .all_slots_ref() @@ -60,16 +66,19 @@ impl Player { .collect_vec(); let carried_item = { - if let Some(item) = self.carried_item.as_ref() { + if let Some(item) = self.carried_item.lock().unwrap().as_ref() { item.into() } else { Slot::empty() } }; - self.inventory.state_id += 1; + // Gets the previous value + let i = inventory + .state_id + .fetch_add(1, std::sync::atomic::Ordering::Relaxed); let packet = CSetContainerContent::new( total_opened_containers, - (self.inventory.state_id as i32).into(), + ((i + 1) as i32).into(), &slots, &carried_item, ); @@ -78,10 +87,10 @@ impl Player { /// The official Minecraft client is weird, and will always just close *any* window that is opened when this gets sent pub fn close_container(&self) { - self.inventory.total_opened_containers += 1; - self.client.send_packet(&CCloseContainer::new( - self.inventory.total_opened_containers, - )) + let mut inventory = self.inventory.lock().unwrap(); + inventory.total_opened_containers += 1; + self.client + .send_packet(&CCloseContainer::new(inventory.total_opened_containers)) } pub fn set_container_property( @@ -90,7 +99,7 @@ impl Player { ) { let (id, value) = window_property.into_tuple(); self.client.send_packet(&CSetContainerProperty::new( - self.inventory.total_opened_containers, + self.inventory.lock().unwrap().total_opened_containers, id, value, )); @@ -107,7 +116,12 @@ impl Player { .map(|container| container.lock().unwrap()); let drag_handler = &server.drag_handler; - let state_id = self.inventory.state_id; + let state_id = self + .inventory + .lock() + .unwrap() + .state_id + .load(std::sync::atomic::Ordering::Relaxed); // This is just checking for regular desync, client hasn't done anything malicious if state_id != packet.state_id.0 as u32 { self.set_container_content(opened_container.as_deref_mut()); @@ -115,7 +129,7 @@ impl Player { } if opened_container.is_some() { - if packet.window_id != self.inventory.total_opened_containers { + if packet.window_id != self.inventory.lock().unwrap().total_opened_containers { return Err(InventoryError::ClosedContainerInteract(self.entity_id())); } } else if packet.window_id != 0 { @@ -177,10 +191,9 @@ impl Player { drop(opened_container); self.send_whole_container_change(server).await?; } else if let container_click::Slot::Normal(slot_index) = click.slot { - let combined_container = OptionallyCombinedContainer::new( - &mut self.inventory.lock().unwrap().lock().unwrap().lock().unwrap(), - Some(&mut opened_container), - ); + let mut inventory = self.inventory.lock().unwrap(); + let combined_container = + OptionallyCombinedContainer::new(&mut inventory, Some(&mut opened_container)); if let Some(slot) = combined_container.get_slot_excluding_inventory(slot_index) { let slot = Slot::from(slot); drop(opened_container); @@ -199,13 +212,14 @@ impl Player { slot: container_click::Slot, ) -> Result<(), InventoryError> { let mut inventory = self.inventory.lock().unwrap(); - let mut container = - OptionallyCombinedContainer::new(&mut inventory, opened_container); - + let mut container = OptionallyCombinedContainer::new(&mut inventory, opened_container); + match slot { - container_click::Slot::Normal(slot) => { - container.handle_item_change(&mut self.carried_item.lock().unwrap(), slot, mouse_click) - } + container_click::Slot::Normal(slot) => container.handle_item_change( + &mut self.carried_item.lock().unwrap(), + slot, + mouse_click, + ), container_click::Slot::OutsideInventory => Ok(()), } } @@ -215,7 +229,7 @@ impl Player { opened_container: Option<&mut Box>, slot: container_click::Slot, ) -> Result<(), InventoryError> { - let mut inventory = self.inventory.lock().unwrap(); + let mut inventory = self.inventory.lock().unwrap(); let mut container = OptionallyCombinedContainer::new(&mut inventory, opened_container); match slot { @@ -258,7 +272,7 @@ impl Player { } fn number_button_pressed( - &mut self, + &self, opened_container: Option<&mut Box>, key_click: KeyClick, slot: usize, @@ -267,35 +281,38 @@ impl Player { KeyClick::Slot(slot) => slot, KeyClick::Offhand => 45, }; - let mut changing_item_slot = self.inventory.get_slot(changing_slot as usize)?.to_owned(); - let mut container = OptionallyCombinedContainer::new(&mut self.inventory.lock().unwrap(), opened_container); + let mut inventory = self.inventory.lock().unwrap(); + let mut changing_item_slot = inventory.get_slot(changing_slot as usize)?.to_owned(); + let mut container = OptionallyCombinedContainer::new(&mut inventory, opened_container); container.handle_item_change(&mut changing_item_slot, slot, MouseClick::Left)?; - *self.inventory.get_slot(changing_slot as usize)? = changing_item_slot; + *inventory.get_slot(changing_slot as usize)? = changing_item_slot; Ok(()) } fn creative_pick_item( - &mut self, + &self, opened_container: Option<&mut Box>, slot: usize, ) -> Result<(), InventoryError> { - if self.gamemode != GameMode::Creative { + if *self.gamemode.lock().unwrap() != GameMode::Creative { return Err(InventoryError::PermissionError); } - let mut container = OptionallyCombinedContainer::new(&mut self.inventory.lock().unwrap(), opened_container); + let mut inventory = self.inventory.lock().unwrap(); + let mut container = OptionallyCombinedContainer::new(&mut inventory, opened_container); if let Some(Some(item)) = container.all_slots().get_mut(slot) { - self.carried_item = Some(item.to_owned()) + *self.carried_item.lock().unwrap() = Some(item.to_owned()) } Ok(()) } fn double_click( - &mut self, + &self, opened_container: Option<&mut Box>, slot: usize, ) -> Result<(), InventoryError> { - let mut container = OptionallyCombinedContainer::new(&mut self.inventory.lock().unwrap(), opened_container); + let mut inventory = self.inventory.lock().unwrap(); + let mut container = OptionallyCombinedContainer::new(&mut inventory, opened_container); let mut slots = container.all_slots(); let Some(item) = slots.get_mut(slot) else { @@ -323,12 +340,12 @@ impl Player { } } } - self.carried_item = Some(carried_item); + *self.carried_item.lock().unwrap() = Some(carried_item); Ok(()) } fn mouse_drag( - &mut self, + &self, drag_handler: &DragHandler, opened_container: Option<&mut Box>, mouse_drag_state: MouseDragState, @@ -340,17 +357,20 @@ impl Player { .unwrap_or(player_id as u64); match mouse_drag_state { MouseDragState::Start(drag_type) => { - if drag_type == MouseDragType::Middle && self.gamemode != GameMode::Creative { + if drag_type == MouseDragType::Middle + && *self.gamemode.lock().unwrap() != GameMode::Creative + { Err(InventoryError::PermissionError)? } drag_handler.new_drag(container_id, player_id, drag_type) } MouseDragState::AddSlot(slot) => drag_handler.add_slot(container_id, player_id, slot), MouseDragState::End => { + let mut inventory = self.inventory.lock().unwrap(); let mut container = - OptionallyCombinedContainer::new(&mut self.inventory.lock().unwrap(), opened_container); + OptionallyCombinedContainer::new(&mut inventory, opened_container); drag_handler.apply_drag( - &mut self.carried_item, + &mut self.carried_item.lock().unwrap(), &mut container, &container_id, player_id, @@ -366,7 +386,7 @@ impl Player { .read() .expect("open_containers is poisoned"); open_containers - .get(&self.open_container.unwrap()) + .get(&self.open_container.lock().unwrap().unwrap()) .unwrap() .all_player_ids() .into_iter() @@ -380,8 +400,6 @@ impl Player { let players = self .entity - .lock() - .unwrap() .world .current_players .lock() @@ -404,17 +422,17 @@ impl Player { } async fn send_container_changes( - &mut self, + &self, server: &Server, slot_index: usize, slot: Slot, ) -> Result<(), InventoryError> { for player in self.get_current_players_in_container(server).await { - let total_opened_containers = player.inventory.total_opened_containers; + let inventory = player.inventory.lock().unwrap(); + let total_opened_containers = inventory.total_opened_containers; // Returns previous value - let i = player - .inventory + let i = inventory .state_id .fetch_add(1, std::sync::atomic::Ordering::Relaxed); let packet = CSetContainerSlot::new( @@ -440,7 +458,7 @@ impl Player { } pub fn get_open_container(&self, server: &Server) -> Option>>> { - if let Some(id) = self.open_container { + if let Some(id) = *self.open_container.lock().unwrap() { server.try_get_container(self.entity_id(), id) } else { None diff --git a/pumpkin/src/client/mod.rs b/pumpkin/src/client/mod.rs index 19a89fc1f..f4e89a450 100644 --- a/pumpkin/src/client/mod.rs +++ b/pumpkin/src/client/mod.rs @@ -34,7 +34,7 @@ use thiserror::Error; pub mod authentication; mod client_packet; -// mod container; +mod container; pub mod player_packet; #[derive(Clone)] diff --git a/pumpkin/src/client/player_packet.rs b/pumpkin/src/client/player_packet.rs index d2c2a2716..7f342d872 100644 --- a/pumpkin/src/client/player_packet.rs +++ b/pumpkin/src/client/player_packet.rs @@ -91,7 +91,7 @@ impl Player { let entity_id = entity.entity_id; let (x, y, z) = (*pos).into(); let (lastx, lasty, lastz) = (last_position.x, last_position.y, last_position.z); - let world = entity.world.clone(); + let world = &entity.world; // let delta = Vector3::new(x - lastx, y - lasty, z - lastz); // let velocity = self.velocity; @@ -378,7 +378,7 @@ impl Player { // TODO: do validation and stuff let config = &ADVANCED_CONFIG.pvp; if config.enabled { - let world = entity.world.clone(); + let world = &entity.world; let attacked_player = world.get_by_entityid(self, entity_id.0 as EntityId); if let Some(player) = attacked_player { let victem_entity = &player.entity; diff --git a/pumpkin/src/commands/cmd_echest.rs b/pumpkin/src/commands/cmd_echest.rs index fbf7bdd69..fe55c9608 100644 --- a/pumpkin/src/commands/cmd_echest.rs +++ b/pumpkin/src/commands/cmd_echest.rs @@ -27,8 +27,7 @@ pub(crate) fn init_command_tree<'a>() -> CommandTree<'a> { } } } - // TODO - // player.open_container(server, "minecraft:generic_9x3"); + player.open_container(server, "minecraft:generic_9x3"); } Ok(()) From 4c8fa33b23e3dc0fad51bee79083c1a41b128b9e Mon Sep 17 00:00:00 2001 From: kralverde Date: Tue, 10 Sep 2024 18:55:53 -0400 Subject: [PATCH 06/65] start work on noise for chunk generation --- pumpkin-world/src/world_gen/mod.rs | 6 + pumpkin-world/src/world_gen/noise.rs | 1158 ++++++++++++++++++++++++++ 2 files changed, 1164 insertions(+) create mode 100644 pumpkin-world/src/world_gen/noise.rs diff --git a/pumpkin-world/src/world_gen/mod.rs b/pumpkin-world/src/world_gen/mod.rs index ddb1bbb9e..db5470680 100644 --- a/pumpkin-world/src/world_gen/mod.rs +++ b/pumpkin-world/src/world_gen/mod.rs @@ -1,10 +1,12 @@ mod generator; mod generic_generator; mod implementation; +mod noise; mod seed; pub use generator::WorldGenerator; use implementation::overworld::biome::plains::PlainsGenerator; +use pumpkin_core::random::Random; pub use seed::Seed; use generator::GeneratorInit; @@ -13,3 +15,7 @@ pub fn get_world_gen(seed: Seed) -> Box { // TODO decide which WorldGenerator to pick based on config. Box::new(PlainsGenerator::new(seed)) } + +pub struct ChunkRandom { + sample_count: i32, +} diff --git a/pumpkin-world/src/world_gen/noise.rs b/pumpkin-world/src/world_gen/noise.rs new file mode 100644 index 000000000..647f4462d --- /dev/null +++ b/pumpkin-world/src/world_gen/noise.rs @@ -0,0 +1,1158 @@ +use pumpkin_core::random::Random; + +pub fn lerp(delta: f64, start: f64, end: f64) -> f64 { + start + delta * (end - start) +} + +pub fn lerp2(delta_x: f64, delta_y: f64, x0y0: f64, x1y0: f64, x0y1: f64, x1y1: f64) -> f64 { + lerp( + delta_y, + lerp(delta_x, x0y0, x1y0), + lerp(delta_x, x0y1, x1y1), + ) +} + +pub fn lerp3( + delta_x: f64, + delta_y: f64, + delta_z: f64, + x0y0z0: f64, + x1y0z0: f64, + x0y1z0: f64, + x1y1z0: f64, + x0y0z1: f64, + x1y0z1: f64, + x0y1z1: f64, + x1y1z1: f64, +) -> f64 { + lerp( + delta_z, + lerp2(delta_x, delta_y, x0y0z0, x1y0z0, x0y1z0, x1y1z0), + lerp2(delta_x, delta_y, x0y0z1, x1y0z1, x0y1z1, x1y1z1), + ) +} + +struct Gradient { + x: i32, + y: i32, + z: i32, +} + +pub struct SimplexNoiseSampler { + permutation: Box<[u8]>, + x_origin: f64, + y_origin: f64, + z_origin: f64, +} + +impl SimplexNoiseSampler { + const GRADIENTS: [Gradient; 16] = [ + Gradient { x: 1, y: 1, z: 0 }, + Gradient { x: -1, y: 1, z: 0 }, + Gradient { x: 1, y: -1, z: 0 }, + Gradient { x: -1, y: -1, z: 0 }, + Gradient { x: 1, y: 0, z: 1 }, + Gradient { x: -1, y: 0, z: 1 }, + Gradient { x: 1, y: 0, z: -1 }, + Gradient { x: -1, y: 0, z: -1 }, + Gradient { x: 0, y: 1, z: 1 }, + Gradient { x: 0, y: -1, z: 1 }, + Gradient { x: 0, y: 1, z: -1 }, + Gradient { x: 0, y: -1, z: -1 }, + Gradient { x: 1, y: 1, z: 0 }, + Gradient { x: 0, y: -1, z: 1 }, + Gradient { x: -1, y: 1, z: 0 }, + Gradient { x: 0, y: -1, z: -1 }, + ]; + + const SQRT_3: f64 = 1.732050807568877293527446341505872367f64; + const SKEW_FACTOR_2D: f64 = 0.5f64 * (Self::SQRT_3 - 1f64); + const UNSKEW_FACTOR_2D: f64 = (3f64 - Self::SQRT_3) / 6f64; + + pub fn new(random: &mut impl Random) -> Self { + let x_origin = random.next_f64() * 256f64; + let y_origin = random.next_f64() * 256f64; + let z_origin = random.next_f64() * 256f64; + + let mut permutation = [0u8; 256]; + + permutation + .iter_mut() + .enumerate() + .for_each(|(i, x)| *x = i as u8); + + for i in 0..256 { + let j = random.next_bounded_i32((256 - i) as i32) as usize; + permutation.swap(i, i + j); + } + + Self { + permutation: Box::new(permutation), + x_origin, + y_origin, + z_origin, + } + } + + fn map(&self, input: i32) -> i32 { + self.permutation[(input & 0xFF) as usize] as i32 + } + + fn dot(gradient: &Gradient, x: f64, y: f64, z: f64) -> f64 { + gradient.x as f64 * x + gradient.y as f64 * y + gradient.z as f64 * z + } + + fn grad(gradient_index: usize, x: f64, y: f64, z: f64, distance: f64) -> f64 { + let d = distance - x * x - y * y - z * z; + if d < 0f64 { + 0f64 + } else { + let d = d * d; + d * d * Self::dot(&Self::GRADIENTS[gradient_index], x, y, z) + } + } + + pub fn sample_2d(&self, x: f64, y: f64) -> f64 { + let d = (x + y) * Self::SKEW_FACTOR_2D; + let i = (x + d).floor() as i32; + let j = (y + d).floor() as i32; + + let e = (i.wrapping_add(j)) as f64 * Self::UNSKEW_FACTOR_2D; + let f = i as f64 - e; + let g = j as f64 - e; + + let h = x - f; + let k = y - g; + + let (l, m) = if h > k { (1, 0) } else { (0, 1) }; + + let n = h - l as f64 + Self::UNSKEW_FACTOR_2D; + let o = k - m as f64 + Self::UNSKEW_FACTOR_2D; + let p = h - 1f64 + 2f64 * Self::UNSKEW_FACTOR_2D; + let q = k - 1f64 + 2f64 * Self::UNSKEW_FACTOR_2D; + + let r = i & 0xFF; + let s = j & 0xFF; + + let t = self.map(r + self.map(s)) % 12; + let u = self.map(r.wrapping_add(l).wrapping_add(self.map(s.wrapping_add(m)))) % 12; + let v = self.map(r.wrapping_add(1).wrapping_add(self.map(s.wrapping_add(1)))) % 12; + + let w = Self::grad(t as usize, h, k, 0f64, 0.5f64); + let z = Self::grad(u as usize, n, o, 0f64, 0.5f64); + let aa = Self::grad(v as usize, p, q, 0f64, 0.5f64); + + 70f64 * (w + z + aa) + } + + pub fn sample_3d(&self, x: f64, y: f64, z: f64) -> f64 { + let e = (x + y + z) * 0.3333333333333333f64; + + let i = (x + e).floor() as i32; + let j = (y + e).floor() as i32; + let k = (z + e).floor() as i32; + + let g = (i.wrapping_add(j).wrapping_add(k)) as f64 * 0.16666666666666666f64; + let h = i as f64 - g; + let l = j as f64 - g; + let m = k as f64 - g; + + let n = x - h; + let o = y - l; + let p = z - m; + + let (q, r, s, t, u, v) = if n >= o { + if o >= p { + (1, 0, 0, 1, 1, 0) + } else if n >= p { + (1, 0, 0, 1, 0, 1) + } else { + (0, 0, 1, 1, 0, 1) + } + } else if o < p { + (0, 0, 1, 0, 1, 1) + } else if n < p { + (0, 1, 0, 0, 1, 1) + } else { + (0, 1, 0, 1, 1, 0) + }; + + let w = n - q as f64 + 0.16666666666666666f64; + let aa = o - r as f64 + 0.16666666666666666f64; + let ab = p - s as f64 + 0.16666666666666666f64; + + let ac = n - t as f64 + 0.3333333333333333f64; + let ad = o - u as f64 + 0.3333333333333333f64; + let ae = p - v as f64 + 0.3333333333333333f64; + + let af = n - 1f64 + 0.5f64; + let ag = o - 1f64 + 0.5f64; + let ah = p - 1f64 + 0.5f64; + + let ai = i & 0xFF; + let aj = j & 0xFF; + let ak = k & 0xFF; + + let al = self.map(ai.wrapping_add(self.map(aj.wrapping_add(self.map(ak))))) % 12; + let am = self.map( + ai.wrapping_add(q).wrapping_add( + self.map( + aj.wrapping_add(r) + .wrapping_add(self.map(ak.wrapping_add(s))), + ), + ), + ) % 12; + let an = self.map( + ai.wrapping_add(t).wrapping_add( + self.map( + aj.wrapping_add(u) + .wrapping_add(self.map(ak.wrapping_add(v))), + ), + ), + ) % 12; + let ao = self.map( + ai.wrapping_add(1).wrapping_add( + self.map( + aj.wrapping_add(1) + .wrapping_add(self.map(ak.wrapping_add(1))), + ), + ), + ) % 12; + + let ap = Self::grad(al as usize, n, o, p, 0.6f64); + let aq = Self::grad(am as usize, w, aa, ab, 0.6f64); + let ar = Self::grad(an as usize, ac, ad, ae, 0.6f64); + let az = Self::grad(ao as usize, af, ag, ah, 0.6f64); + + 32f64 * (ap + aq + ar + az) + } +} + +pub struct PerlinNoiseSampler { + permutation: Box<[u8]>, + x_origin: f64, + y_origin: f64, + z_origin: f64, +} + +impl PerlinNoiseSampler { + pub fn new(random: &mut impl Random) -> Self { + let x_origin = random.next_f64() * 256f64; + let y_origin = random.next_f64() * 256f64; + let z_origin = random.next_f64() * 256f64; + + let mut permutation = [0u8; 256]; + + permutation + .iter_mut() + .enumerate() + .for_each(|(i, x)| *x = i as u8); + + for i in 0..256 { + let j = random.next_bounded_i32((256 - i) as i32) as usize; + permutation.swap(i, i + j); + } + + Self { + permutation: Box::new(permutation), + x_origin, + y_origin, + z_origin, + } + } + + pub fn sample_flat_y(&self, x: f64, y: f64, z: f64) -> f64 { + self.sample_no_fade(x, y, z, 0f64, 0f64) + } + + pub fn sample_no_fade(&self, x: f64, y: f64, z: f64, y_scale: f64, y_max: f64) -> f64 { + let trans_x = x + self.x_origin; + let trans_y = y + self.y_origin; + let trans_z = z + self.z_origin; + + let x_int = trans_x.floor() as i32; + let y_int = trans_y.floor() as i32; + let z_int = trans_z.floor() as i32; + + let x_dec = trans_x - x_int as f64; + let y_dec = trans_y - y_int as f64; + let z_dec = trans_z - z_int as f64; + + let y_noise = if y_scale != 0f64 { + let raw_y_dec = if y_max >= 0f64 && y_max < y_dec { + y_max + } else { + y_dec + }; + (raw_y_dec / y_scale + 1.0E-7f32 as f64).floor() * y_scale + } else { + 0f64 + }; + + self.sample(x_int, y_int, z_int, x_dec, y_dec - y_noise, z_dec, y_dec) + } + + fn grad(hash: i32, x: f64, y: f64, z: f64) -> f64 { + SimplexNoiseSampler::dot( + &SimplexNoiseSampler::GRADIENTS[(hash & 15) as usize], + x, + y, + z, + ) + } + + fn perlin_fade(value: f64) -> f64 { + value * value * value * (value * (value * 6f64 - 15f64) + 10f64) + } + + fn map(&self, input: i32) -> i32 { + (self.permutation[(input & 0xFF) as usize] & 0xFF) as i32 + } + + #[allow(clippy::too_many_arguments)] + fn sample( + &self, + x: i32, + y: i32, + z: i32, + local_x: f64, + local_y: f64, + local_z: f64, + fade_local_y: f64, + ) -> f64 { + let i = self.map(x); + let j = self.map(x.wrapping_add(1)); + let k = self.map(i.wrapping_add(y)); + + let l = self.map(i.wrapping_add(y).wrapping_add(1)); + let m = self.map(j.wrapping_add(y)); + let n = self.map(j.wrapping_add(y).wrapping_add(1)); + + let d = Self::grad(self.map(k.wrapping_add(z)), local_x, local_y, local_z); + let e = Self::grad( + self.map(m.wrapping_add(z)), + local_x - 1f64, + local_y, + local_z, + ); + let f = Self::grad( + self.map(l.wrapping_add(z)), + local_x, + local_y - 1f64, + local_z, + ); + let g = Self::grad( + self.map(n.wrapping_add(z)), + local_x - 1f64, + local_y - 1f64, + local_z, + ); + let h = Self::grad( + self.map(k.wrapping_add(z).wrapping_add(1)), + local_x, + local_y, + local_z - 1f64, + ); + let o = Self::grad( + self.map(m.wrapping_add(z).wrapping_add(1)), + local_x - 1f64, + local_y, + local_z - 1f64, + ); + let p = Self::grad( + self.map(l.wrapping_add(z).wrapping_add(1)), + local_x, + local_y - 1f64, + local_z - 1f64, + ); + let q = Self::grad( + self.map(n.wrapping_add(z).wrapping_add(1)), + local_x - 1f64, + local_y - 1f64, + local_z - 1f64, + ); + let r = Self::perlin_fade(local_x); + let s = Self::perlin_fade(fade_local_y); + let t = Self::perlin_fade(local_z); + + lerp3(r, s, t, d, e, f, g, h, o, p, q) + } +} + +struct OctavePerlinNoiseSampler { + octave_samplers: Box<[SimplexNoiseSampler]>, + persistence: f64, + lacunarity: f64, +} + +impl OctavePerlinNoiseSampler { + pub fn new(random: &mut impl Random, octaves: &[i32]) -> Self { + let mut octaves = Vec::from_iter(octaves); + octaves.sort(); + + let i = -**octaves.first().expect("Should have some octaves"); + let j = **octaves.last().expect("Should have some octaves"); + let k = i.wrapping_add(j).wrapping_add(1); + + let sampler = SimplexNoiseSampler::new(random); + let l = j; + let mut samplers: Vec = vec![]; + + if j >= 0 && j < k && octaves.contains(&&0) { + samplers[0] = sampler; + } + + for m in (j + 1)..k { + if m >= 0 && octaves.contains(&&(l - m)) { + samplers[m as usize] = SimplexNoiseSampler::new(random); + } else { + random.skip(262); + } + } + + if j > 0 { + let n = (sampler.sample_3d(sampler.x_origin, sampler.y_origin, sampler.z_origin) + * 9.223372E18f32 as f64) as i64; + } + } +} + +#[cfg(test)] +mod simplex_noise_sampler_test { + use std::ops::Deref; + + use pumpkin_core::random::{xoroshiro128::Xoroshiro, Random}; + + use crate::world_gen::noise::SimplexNoiseSampler; + + #[test] + fn test_create() { + let mut rand = Xoroshiro::from_seed(111); + assert_eq!(rand.next_i32(), -1467508761); + let sampler = SimplexNoiseSampler::new(&mut rand); + assert_eq!(sampler.x_origin, 48.58072036717974f64); + assert_eq!(sampler.y_origin, 110.73235882678037f64); + assert_eq!(sampler.z_origin, 65.26438852860176f64); + + let permutation: [u8; 256] = [ + 159, 113, 41, 143, 203, 123, 95, 177, 25, 79, 229, 219, 194, 60, 130, 14, 83, 99, 24, + 202, 207, 232, 167, 152, 220, 201, 29, 235, 87, 147, 74, 160, 155, 97, 111, 31, 85, + 205, 115, 50, 13, 171, 77, 237, 149, 116, 209, 174, 169, 109, 221, 9, 166, 84, 54, 216, + 121, 106, 211, 16, 69, 244, 65, 192, 183, 146, 124, 37, 56, 45, 193, 158, 126, 217, 36, + 255, 162, 163, 230, 103, 63, 90, 191, 214, 20, 138, 32, 39, 238, 67, 64, 105, 250, 140, + 148, 114, 68, 75, 200, 161, 239, 125, 227, 199, 101, 61, 175, 107, 129, 240, 170, 51, + 139, 86, 186, 145, 212, 178, 30, 251, 89, 226, 120, 153, 47, 141, 233, 2, 179, 236, 1, + 19, 98, 21, 164, 108, 11, 23, 91, 204, 119, 88, 165, 195, 168, 26, 48, 206, 128, 6, 52, + 118, 110, 180, 197, 231, 117, 7, 3, 135, 224, 58, 82, 78, 4, 59, 222, 18, 72, 57, 150, + 43, 246, 100, 122, 112, 53, 133, 93, 17, 27, 210, 142, 234, 245, 80, 22, 46, 185, 172, + 71, 248, 33, 173, 76, 35, 40, 92, 228, 127, 254, 70, 42, 208, 73, 104, 187, 62, 154, + 243, 189, 241, 34, 66, 249, 94, 8, 12, 134, 132, 102, 242, 196, 218, 181, 28, 38, 15, + 151, 157, 247, 223, 198, 55, 188, 96, 0, 182, 49, 190, 156, 10, 215, 252, 131, 137, + 184, 176, 136, 81, 44, 213, 253, 144, 225, 5, + ]; + assert_eq!(sampler.permutation.deref(), permutation); + } + + #[test] + fn test_sample_2d() { + let data1 = [ + ((-50000, 0), -0.013008608535752102), + ((-49999, 1000), 0.0), + ((-49998, 2000), -0.03787856584046271), + ((-49997, 3000), 0.0), + ((-49996, 4000), 0.5015373706471664), + ((-49995, 5000), -0.032797908620906514), + ((-49994, 6000), -0.19158655563621785), + ((-49993, 7000), 0.49893473629544977), + ((-49992, 8000), 0.31585737840402556), + ((-49991, 9000), 0.43909577227435836), + ]; + + let data2 = [ + ( + (-3.134738528791615E8, 5.676610095659718E7), + 0.018940199193618792, + ), + ( + (-1369026.560586418, 3.957311252810864E8), + -0.1417598930091471, + ), + ( + (6.439373693833767E8, -3.36218773041759E8), + 0.07129176668335062, + ), + ( + (1.353820060118252E8, -3.204701624793043E8), + 0.330648835988156, + ), + ( + (-6906850.625560562, 1.0153663948838013E8), + 0.46826928755778685, + ), + ( + (-7.108376621385525E7, -2.029413580824217E8), + -0.515950097501492, + ), + ( + (1.0591429119126628E8, -4.7911044364543396E8), + -0.5467822192664874, + ), + ( + (4.04615501401398E7, -3.074409286586152E8), + 0.7470460844090322, + ), + ( + (-4.8645283544246924E8, -3.922570151180015E8), + 0.8521699147242563, + ), + ( + (2.861710031285905E8, -1.8973201372718483E8), + 0.1889297962671115, + ), + ( + (2.885407603819252E8, -3.358708100884505E7), + 0.24006029504945695, + ), + ( + (3.6548491156354237E8, 7.995429702025633E7), + -0.8114171447379924, + ), + ( + (1.3298684552869435E8, 3.6743804723880893E8), + 0.07042306408164949, + ), + ( + (-1.3123184148036437E8, -2.722300890805201E8), + 0.5093850689193259, + ), + ( + (-5.56047682304707E8, 3.554803693060646E8), + -0.6343788467687929, + ), + ( + (5.638216625134594E8, -2.236907346192737E8), + 0.5848746152449286, + ), + ( + (-5.436956979127073E7, -1.129261611506945E8), + -0.05456282199582522, + ), + ( + (1.0915760091641709E8, 1.932642099859593E7), + -0.273739377096594, + ), + ( + (-6.73911758014991E8, -2.2147483413687566E8), + 0.05464681163741797, + ), + ( + (-2.4827386778136212E8, -2.6640208832089204E8), + -0.0902449424742273, + ), + ]; + + let mut rand = Xoroshiro::from_seed(111); + assert_eq!(rand.next_i32(), -1467508761); + + let sampler = SimplexNoiseSampler::new(&mut rand); + for ((x, y), sample) in data1 { + assert_eq!(sampler.sample_2d(x as f64, y as f64), sample); + } + + for ((x, y), sample) in data2 { + assert_eq!(sampler.sample_2d(x, y), sample); + } + } + + #[test] + fn test_sample_3d() { + let data = [ + ( + ( + -3.134738528791615E8, + 5.676610095659718E7, + 2.011711832498507E8, + ), + -0.07626353895981935, + ), + ( + (-1369026.560586418, 3.957311252810864E8, 6.797037355570006E8), + 0.0, + ), + ( + ( + 6.439373693833767E8, + -3.36218773041759E8, + -3.265494249695775E8, + ), + -0.5919400355725402, + ), + ( + ( + 1.353820060118252E8, + -3.204701624793043E8, + -4.612474746056331E8, + ), + -0.5220477236433517, + ), + ( + ( + -6906850.625560562, + 1.0153663948838013E8, + 2.4923185478305575E8, + ), + -0.39146687767898636, + ), + ( + ( + -7.108376621385525E7, + -2.029413580824217E8, + 2.5164602748045415E8, + ), + -0.629386846329711, + ), + ( + ( + 1.0591429119126628E8, + -4.7911044364543396E8, + -2918719.2277242197, + ), + 0.5427502531663232, + ), + ( + ( + 4.04615501401398E7, + -3.074409286586152E8, + 5.089118769334092E7, + ), + -0.4273080639878097, + ), + ( + ( + -4.8645283544246924E8, + -3.922570151180015E8, + 2.3741632952563038E8, + ), + 0.32129944093252394, + ), + ( + ( + 2.861710031285905E8, + -1.8973201372718483E8, + -3.2653143323982143E8, + ), + 0.35839032946039706, + ), + ( + ( + 2.885407603819252E8, + -3.358708100884505E7, + -1.4480399660676318E8, + ), + -0.02451312935907038, + ), + ( + ( + 3.6548491156354237E8, + 7.995429702025633E7, + 2.509991661702412E8, + ), + -0.36830526266318003, + ), + ( + ( + 1.3298684552869435E8, + 3.6743804723880893E8, + 5.791092458225288E7, + ), + -0.023683302916542803, + ), + ( + ( + -1.3123184148036437E8, + -2.722300890805201E8, + 2.1601883778132245E7, + ), + -0.261629562325043, + ), + ( + ( + -5.56047682304707E8, + 3.554803693060646E8, + 3.1647392358159083E8, + ), + -0.4959372930161496, + ), + ( + ( + 5.638216625134594E8, + -2.236907346192737E8, + -5.0562852022285646E8, + ), + -0.06079315675880484, + ), + ( + ( + -5.436956979127073E7, + -1.129261611506945E8, + -1.7909512156895646E8, + ), + -0.37726907424345196, + ), + ( + ( + 1.0915760091641709E8, + 1.932642099859593E7, + -3.405060533753616E8, + ), + 0.37747828159811136, + ), + ( + ( + -6.73911758014991E8, + -2.2147483413687566E8, + -4.531457195005102E7, + ), + -0.32929020207000603, + ), + ( + ( + -2.4827386778136212E8, + -2.6640208832089204E8, + -3.354675096522197E8, + ), + -0.3046390200444667, + ), + ]; + + let mut rand = Xoroshiro::from_seed(111); + assert_eq!(rand.next_i32(), -1467508761); + + let sampler = SimplexNoiseSampler::new(&mut rand); + for ((x, y, z), sample) in data { + assert_eq!(sampler.sample_3d(x, y, z), sample); + } + } +} + +#[cfg(test)] +mod perlin_noise_sampler_test { + use std::ops::Deref; + + use pumpkin_core::random::{xoroshiro128::Xoroshiro, Random}; + + use crate::world_gen::noise::PerlinNoiseSampler; + + #[test] + fn test_create() { + let mut rand = Xoroshiro::from_seed(111); + assert_eq!(rand.next_i32(), -1467508761); + + let sampler = PerlinNoiseSampler::new(&mut rand); + assert_eq!(sampler.x_origin, 48.58072036717974); + assert_eq!(sampler.y_origin, 110.73235882678037); + assert_eq!(sampler.z_origin, 65.26438852860176); + + let permutation: [u8; 256] = [ + 159, 113, 41, 143, 203, 123, 95, 177, 25, 79, 229, 219, 194, 60, 130, 14, 83, 99, 24, + 202, 207, 232, 167, 152, 220, 201, 29, 235, 87, 147, 74, 160, 155, 97, 111, 31, 85, + 205, 115, 50, 13, 171, 77, 237, 149, 116, 209, 174, 169, 109, 221, 9, 166, 84, 54, 216, + 121, 106, 211, 16, 69, 244, 65, 192, 183, 146, 124, 37, 56, 45, 193, 158, 126, 217, 36, + 255, 162, 163, 230, 103, 63, 90, 191, 214, 20, 138, 32, 39, 238, 67, 64, 105, 250, 140, + 148, 114, 68, 75, 200, 161, 239, 125, 227, 199, 101, 61, 175, 107, 129, 240, 170, 51, + 139, 86, 186, 145, 212, 178, 30, 251, 89, 226, 120, 153, 47, 141, 233, 2, 179, 236, 1, + 19, 98, 21, 164, 108, 11, 23, 91, 204, 119, 88, 165, 195, 168, 26, 48, 206, 128, 6, 52, + 118, 110, 180, 197, 231, 117, 7, 3, 135, 224, 58, 82, 78, 4, 59, 222, 18, 72, 57, 150, + 43, 246, 100, 122, 112, 53, 133, 93, 17, 27, 210, 142, 234, 245, 80, 22, 46, 185, 172, + 71, 248, 33, 173, 76, 35, 40, 92, 228, 127, 254, 70, 42, 208, 73, 104, 187, 62, 154, + 243, 189, 241, 34, 66, 249, 94, 8, 12, 134, 132, 102, 242, 196, 218, 181, 28, 38, 15, + 151, 157, 247, 223, 198, 55, 188, 96, 0, 182, 49, 190, 156, 10, 215, 252, 131, 137, + 184, 176, 136, 81, 44, 213, 253, 144, 225, 5, + ]; + assert_eq!(sampler.permutation.deref(), permutation); + } + + #[test] + fn test_no_y() { + let mut rand = Xoroshiro::from_seed(111); + assert_eq!(rand.next_i32(), -1467508761); + let sampler = PerlinNoiseSampler::new(&mut rand); + + let values = [ + ( + ( + -3.134738528791615E8, + 5.676610095659718E7, + 2.011711832498507E8, + ), + 0.38582139614602945, + ), + ( + (-1369026.560586418, 3.957311252810864E8, 6.797037355570006E8), + 0.15777501333157193, + ), + ( + ( + 6.439373693833767E8, + -3.36218773041759E8, + -3.265494249695775E8, + ), + -0.2806135912409497, + ), + ( + ( + 1.353820060118252E8, + -3.204701624793043E8, + -4.612474746056331E8, + ), + -0.15052865500837787, + ), + ( + ( + -6906850.625560562, + 1.0153663948838013E8, + 2.4923185478305575E8, + ), + -0.3079300694558318, + ), + ( + ( + -7.108376621385525E7, + -2.029413580824217E8, + 2.5164602748045415E8, + ), + 0.03051312670440398, + ), + ( + ( + 1.0591429119126628E8, + -4.7911044364543396E8, + -2918719.2277242197, + ), + -0.11775123159138573, + ), + ( + ( + 4.04615501401398E7, + -3.074409286586152E8, + 5.089118769334092E7, + ), + 0.08763639340713025, + ), + ( + ( + -4.8645283544246924E8, + -3.922570151180015E8, + 2.3741632952563038E8, + ), + 0.08857245482456311, + ), + ( + ( + 2.861710031285905E8, + -1.8973201372718483E8, + -3.2653143323982143E8, + ), + -0.2378339698793312, + ), + ( + ( + 2.885407603819252E8, + -3.358708100884505E7, + -1.4480399660676318E8, + ), + -0.46661747461279457, + ), + ( + ( + 3.6548491156354237E8, + 7.995429702025633E7, + 2.509991661702412E8, + ), + 0.1671543972176835, + ), + ( + ( + 1.3298684552869435E8, + 3.6743804723880893E8, + 5.791092458225288E7, + ), + -0.2704070746642889, + ), + ( + ( + -1.3123184148036437E8, + -2.722300890805201E8, + 2.1601883778132245E7, + ), + 0.05049887915906969, + ), + ( + ( + -5.56047682304707E8, + 3.554803693060646E8, + 3.1647392358159083E8, + ), + -0.21178547899422662, + ), + ( + ( + 5.638216625134594E8, + -2.236907346192737E8, + -5.0562852022285646E8, + ), + 0.03351245780858128, + ), + ( + ( + -5.436956979127073E7, + -1.129261611506945E8, + -1.7909512156895646E8, + ), + 0.31670010349494726, + ), + ( + ( + 1.0915760091641709E8, + 1.932642099859593E7, + -3.405060533753616E8, + ), + -0.13987439655026918, + ), + ( + ( + -6.73911758014991E8, + -2.2147483413687566E8, + -4.531457195005102E7, + ), + 0.07824440437151846, + ), + ( + ( + -2.4827386778136212E8, + -2.6640208832089204E8, + -3.354675096522197E8, + ), + -0.2989735599541437, + ), + ]; + + for ((x, y, z), sample) in values { + assert_eq!(sampler.sample_flat_y(x, y, z), sample); + } + } + + #[test] + fn test_no_fade() { + let mut rand = Xoroshiro::from_seed(111); + assert_eq!(rand.next_i32(), -1467508761); + let sampler = PerlinNoiseSampler::new(&mut rand); + + let values = [ + ( + ( + -3.134738528791615E8, + 5.676610095659718E7, + 2.011711832498507E8, + -1369026.560586418, + 3.957311252810864E8, + ), + 23234.47859421248, + ), + ( + ( + 6.797037355570006E8, + 6.439373693833767E8, + -3.36218773041759E8, + -3.265494249695775E8, + 1.353820060118252E8, + ), + -0.016403984198221984, + ), + ( + ( + -3.204701624793043E8, + -4.612474746056331E8, + -6906850.625560562, + 1.0153663948838013E8, + 2.4923185478305575E8, + ), + 0.3444286491766397, + ), + ( + ( + -7.108376621385525E7, + -2.029413580824217E8, + 2.5164602748045415E8, + 1.0591429119126628E8, + -4.7911044364543396E8, + ), + 0.03051312670440398, + ), + ( + ( + -2918719.2277242197, + 4.04615501401398E7, + -3.074409286586152E8, + 5.089118769334092E7, + -4.8645283544246924E8, + ), + 0.3434020232968479, + ), + ( + ( + -3.922570151180015E8, + 2.3741632952563038E8, + 2.861710031285905E8, + -1.8973201372718483E8, + -3.2653143323982143E8, + ), + -0.07935517045771859, + ), + ( + ( + 2.885407603819252E8, + -3.358708100884505E7, + -1.4480399660676318E8, + 3.6548491156354237E8, + 7.995429702025633E7, + ), + -0.46661747461279457, + ), + ( + ( + 2.509991661702412E8, + 1.3298684552869435E8, + 3.6743804723880893E8, + 5.791092458225288E7, + -1.3123184148036437E8, + ), + 0.0723439870279631, + ), + ( + ( + -2.722300890805201E8, + 2.1601883778132245E7, + -5.56047682304707E8, + 3.554803693060646E8, + 3.1647392358159083E8, + ), + -0.656560662515624, + ), + ( + ( + 5.638216625134594E8, + -2.236907346192737E8, + -5.0562852022285646E8, + -5.436956979127073E7, + -1.129261611506945E8, + ), + 0.03351245780858128, + ), + ( + ( + -1.7909512156895646E8, + 1.0915760091641709E8, + 1.932642099859593E7, + -3.405060533753616E8, + -6.73911758014991E8, + ), + -0.2089142558681482, + ), + ( + ( + -2.2147483413687566E8, + -4.531457195005102E7, + -2.4827386778136212E8, + -2.6640208832089204E8, + -3.354675096522197E8, + ), + 0.38250837565598395, + ), + ( + ( + 3.618095500266467E8, + -1.785261966631494E8, + 8.855575989580283E7, + -1.3702508894700047E8, + -3.564818414428105E8, + ), + 0.00883370523171791, + ), + ( + ( + 3.585592594479808E7, + 1.8822208340571395E8, + -386327.524558296, + -2.613548000006699E8, + 1995562.4304017993, + ), + -0.27653878487738676, + ), + ( + ( + 3.0800276873619422E7, + 1.166750302259058E7, + 8.502636255675305E7, + 4.347409652503064E8, + 1.0678086363325526E8, + ), + -0.13800758751097497, + ), + ( + ( + -2.797805968820768E8, + 9.446376468140173E7, + 2.2821543438325477E8, + -4.8176550369786626E8, + 7.316871126959312E7, + ), + 0.05505478945301634, + ), + ( + ( + -2.236596113898912E7, + 1.5296478602495643E8, + 3.903966235164034E8, + 9.40479475527148E7, + 1.0948229366673347E8, + ), + 0.1158678618158655, + ), + ( + ( + 3.5342596632385695E8, + 3.1584773170834744E8, + -2.1860087172846535E8, + -1.8126626716239208E8, + -2.5263456116162892E7, + ), + -0.354953975313882, + ), + ( + ( + -1.2711958434031656E8, + -4.541988855460623E7, + -1.375878074907788E8, + 6.72693784001799E7, + 6815739.665531283, + ), + -0.23849179316215247, + ), + ( + ( + 1.2660906027019228E8, + -3.3769609799741164E7, + -3.4331505330046E8, + -6.663866659430536E7, + -1.6603843763414428E8, + ), + 0.07974650858448407, + ), + ]; + + for ((x, y, z, y_scale, y_max), sample) in values { + assert_eq!(sampler.sample_no_fade(x, y, z, y_scale, y_max), sample); + } + } +} From 987d2cf87e5dd439ef253b8613e3654c2807020f Mon Sep 17 00:00:00 2001 From: kralverde Date: Wed, 11 Sep 2024 13:44:28 -0400 Subject: [PATCH 07/65] continue implementation of noise --- pumpkin-core/src/random/legacy_rand.rs | 19 ++- pumpkin-world/src/world_gen/mod.rs | 1 - pumpkin-world/src/world_gen/noise.rs | 203 +++++++++++++++++++++++-- 3 files changed, 205 insertions(+), 18 deletions(-) diff --git a/pumpkin-core/src/random/legacy_rand.rs b/pumpkin-core/src/random/legacy_rand.rs index f7f60ff41..2360ab5ca 100644 --- a/pumpkin-core/src/random/legacy_rand.rs +++ b/pumpkin-core/src/random/legacy_rand.rs @@ -2,7 +2,7 @@ use super::{ gaussian::GaussianGenerator, hash_block_pos, java_string_hash, Random, RandomSplitter, }; -struct LegacyRand { +pub struct LegacyRand { seed: u64, internal_next_gaussian: f64, internal_has_next_gaussian: bool, @@ -86,13 +86,13 @@ impl Random for LegacyRand { } fn next_bounded_i32(&mut self, bound: i32) -> i32 { - if bound & (bound - 1) == 0 { - (bound as u64).wrapping_mul(self.next(31) >> 31) as i32 + if (bound & bound.wrapping_sub(1)) == 0 { + ((bound as u64).wrapping_mul(self.next(31)) >> 31) as i32 } else { loop { let i = self.next(31) as i32; let j = i % bound; - if (i - j + (bound - 1)) > 0 { + if (i.wrapping_sub(j).wrapping_add(bound.wrapping_sub(1))) >= 0 { return j; } } @@ -163,6 +163,17 @@ mod test { for value in values { assert_eq!(rand.next_bounded_i32(0xf), value); } + + let mut rand = LegacyRand::from_seed(0); + for _ in 0..10 { + assert_eq!(rand.next_bounded_i32(1), 0); + } + + let mut rand = LegacyRand::from_seed(0); + let values = [1, 1, 0, 1, 1, 0, 1, 0, 1, 1]; + for value in values { + assert_eq!(rand.next_bounded_i32(2), value); + } } #[test] diff --git a/pumpkin-world/src/world_gen/mod.rs b/pumpkin-world/src/world_gen/mod.rs index db5470680..693132f5c 100644 --- a/pumpkin-world/src/world_gen/mod.rs +++ b/pumpkin-world/src/world_gen/mod.rs @@ -6,7 +6,6 @@ mod seed; pub use generator::WorldGenerator; use implementation::overworld::biome::plains::PlainsGenerator; -use pumpkin_core::random::Random; pub use seed::Seed; use generator::GeneratorInit; diff --git a/pumpkin-world/src/world_gen/noise.rs b/pumpkin-world/src/world_gen/noise.rs index 647f4462d..0f9fc0f33 100644 --- a/pumpkin-world/src/world_gen/noise.rs +++ b/pumpkin-world/src/world_gen/noise.rs @@ -1,4 +1,5 @@ -use pumpkin_core::random::Random; +use num_traits::Pow; +use pumpkin_core::random::{legacy_rand::LegacyRand, Random}; pub fn lerp(delta: f64, start: f64, end: f64) -> f64 { start + delta * (end - start) @@ -65,7 +66,7 @@ impl SimplexNoiseSampler { Gradient { x: 0, y: -1, z: -1 }, ]; - const SQRT_3: f64 = 1.732050807568877293527446341505872367f64; + const SQRT_3: f64 = 1.7320508075688772f64; const SKEW_FACTOR_2D: f64 = 0.5f64 * (Self::SQRT_3 - 1f64); const UNSKEW_FACTOR_2D: f64 = (3f64 - Self::SQRT_3) / 6f64; @@ -82,8 +83,8 @@ impl SimplexNoiseSampler { .for_each(|(i, x)| *x = i as u8); for i in 0..256 { - let j = random.next_bounded_i32((256 - i) as i32) as usize; - permutation.swap(i, i + j); + let j = random.next_bounded_i32(256 - i) as usize; + permutation.swap(i as usize, i as usize + j); } Self { @@ -134,7 +135,7 @@ impl SimplexNoiseSampler { let r = i & 0xFF; let s = j & 0xFF; - let t = self.map(r + self.map(s)) % 12; + let t = self.map(r.wrapping_add(self.map(s))) % 12; let u = self.map(r.wrapping_add(l).wrapping_add(self.map(s.wrapping_add(m)))) % 12; let v = self.map(r.wrapping_add(1).wrapping_add(self.map(s.wrapping_add(1)))) % 12; @@ -380,7 +381,7 @@ impl PerlinNoiseSampler { } struct OctavePerlinNoiseSampler { - octave_samplers: Box<[SimplexNoiseSampler]>, + octave_samplers: Vec>, persistence: f64, lacunarity: f64, } @@ -396,23 +397,199 @@ impl OctavePerlinNoiseSampler { let sampler = SimplexNoiseSampler::new(random); let l = j; - let mut samplers: Vec = vec![]; - - if j >= 0 && j < k && octaves.contains(&&0) { - samplers[0] = sampler; + let mut samplers: Vec> = Vec::with_capacity(k as usize); + for _ in 0..k { + samplers.push(None); } for m in (j + 1)..k { if m >= 0 && octaves.contains(&&(l - m)) { - samplers[m as usize] = SimplexNoiseSampler::new(random); + let sampler = SimplexNoiseSampler::new(random); + samplers[m as usize] = Some(sampler); } else { random.skip(262); } } if j > 0 { - let n = (sampler.sample_3d(sampler.x_origin, sampler.y_origin, sampler.z_origin) - * 9.223372E18f32 as f64) as i64; + let sample = sampler.sample_3d(sampler.x_origin, sampler.y_origin, sampler.z_origin); + let n = (sample * 9.223372E18f32 as f64) as i64; + let mut random = LegacyRand::from_seed(n as u64); + + for o in (0..=(l - 1)).rev() { + if o < k && octaves.contains(&&(l - o)) { + let sampler = SimplexNoiseSampler::new(&mut random); + samplers[o as usize] = Some(sampler); + } else { + random.skip(262); + } + } + } + + if j >= 0 && j < k && octaves.contains(&&0) { + samplers[j as usize] = Some(sampler); + } + + Self { + octave_samplers: samplers, + persistence: 1f64 / (2f64.pow(k) - 1f64), + lacunarity: 2f64.pow(j), + } + } + + pub fn sample(&self, x: f64, y: f64, use_origin: bool) -> f64 { + let mut d = 0f64; + let mut e = self.lacunarity; + let mut f = self.persistence; + + for sampler in self.octave_samplers.iter() { + if let Some(sampler) = sampler { + d += sampler.sample_2d( + x * e + if use_origin { sampler.x_origin } else { 0f64 }, + y * e + if use_origin { sampler.y_origin } else { 0f64 }, + ) * f; + } + + e /= 2f64; + f *= 2f64; + } + + d + } +} + +#[cfg(test)] +mod octave_perlin_noise_sampler_test { + use pumpkin_core::random::{xoroshiro128::Xoroshiro, Random}; + + use crate::world_gen::noise::OctavePerlinNoiseSampler; + + #[test] + fn test_new() { + let mut rand = Xoroshiro::from_seed(450); + assert_eq!(rand.next_i32(), 1394613419); + let sampler = OctavePerlinNoiseSampler::new(&mut rand, &[-1, 1, 0]); + + assert_eq!(sampler.lacunarity, 2f64); + assert_eq!(sampler.persistence, 0.14285714285714285); + + let values = [ + (33.48154133535127, 200.15584029786743, 239.82697852863149), + (115.65071632913913, 5.88805286077266, 184.4887403898897), + (64.69791492580848, 19.256055216755044, 97.01795462351956), + ]; + + assert_eq!(values.len(), sampler.octave_samplers.len()); + for (sampler, (x, y, z)) in sampler.octave_samplers.iter().zip(values) { + match sampler { + Some(sampler) => { + assert_eq!(sampler.x_origin, x); + assert_eq!(sampler.y_origin, y); + assert_eq!(sampler.z_origin, z); + } + None => panic!(), + } + } + } + + #[test] + fn test_sample() { + let mut rand = Xoroshiro::from_seed(450); + assert_eq!(rand.next_i32(), 1394613419); + let sampler = OctavePerlinNoiseSampler::new(&mut rand, &[-1, 1, 0]); + + let values_1 = [ + ( + (-1.3127900550351206E7, 792897.4979227383), + -0.4321152413690901, + ), + ( + (-1.6920637874404985E7, -2.7155569346339065E8), + -0.5262902093081003, + ), + ( + (4.3144247722741723E8, 5.681942883881191E8), + 0.11591369897395602, + ), + ( + (1.4302738270336467E8, -1.4548998886244193E8), + -0.3879951077548365, + ), + ( + (-3.9028350711219925E8, -5.213995559811158E7), + -0.7540785159288218, + ), + ( + (-1.3442750163759476E8, -6.725465365393716E8), + 0.31442035977402105, + ), + ( + (-1.1937282161424601E8, 3.2134650034986335E8), + 0.28218849676360336, + ), + ( + (-3.128475507865152E8, -3.014112871163455E8), + 0.593770404657594, + ), + ( + (1.2027011883589141E8, -5.045175636913682E8), + -0.2893240282016911, + ), + ( + (-9.065155753781198E7, 6106991.342893547), + -0.3402301205344082, + ), + ]; + + for ((x, y), sample) in values_1 { + assert_eq!(sampler.sample(x, y, false), sample); + } + + let values_2 = [ + ( + (-1.3127900550351206E7, 792897.4979227383), + 0.21834818545873672, + ), + ( + (-1.6920637874404985E7, -2.7155569346339065E8), + 0.025042742676442978, + ), + ( + (4.3144247722741723E8, 5.681942883881191E8), + 0.3738693783591451, + ), + ( + (1.4302738270336467E8, -1.4548998886244193E8), + -0.023113657524218345, + ), + ( + (-3.9028350711219925E8, -5.213995559811158E7), + 0.5195582376240916, + ), + ( + (-1.3442750163759476E8, -6.725465365393716E8), + 0.020366186088347903, + ), + ( + (-1.1937282161424601E8, 3.2134650034986335E8), + -0.10921072611129382, + ), + ( + (-3.128475507865152E8, -3.014112871163455E8), + 0.18066933648141983, + ), + ( + (1.2027011883589141E8, -5.045175636913682E8), + -0.36788084946294336, + ), + ( + (-9.065155753781198E7, 6106991.342893547), + -0.5677921377363926, + ), + ]; + + for ((x, y), sample) in values_2 { + assert_eq!(sampler.sample(x, y, true), sample); } } } From c6043189d3ab6803a34b1de389c954898f189e58 Mon Sep 17 00:00:00 2001 From: Asurar0 Date: Wed, 11 Sep 2024 22:39:53 +0200 Subject: [PATCH 08/65] Improved synchronization primitives - Replaced all `std::sync::Mutex` by `parking_lot::Mutex` and refactored implementation accordingly - Replaced all `Mutex where T: Copy` by `crossbeam::AtomicCell` and refactored implementation accordingly --- Cargo.lock | 84 +++++++++++++++++++++++++ Cargo.toml | 5 ++ pumpkin-core/src/math/position.rs | 1 + pumpkin-core/src/math/vector3.rs | 4 ++ pumpkin-inventory/Cargo.toml | 4 +- pumpkin-inventory/src/drag_handler.rs | 21 +++---- pumpkin-inventory/src/lib.rs | 1 + pumpkin-inventory/src/open_container.rs | 3 +- pumpkin-inventory/src/player.rs | 1 + pumpkin-protocol/src/lib.rs | 2 +- pumpkin-world/Cargo.toml | 2 + pumpkin-world/src/level.rs | 63 ++++++++++--------- pumpkin/Cargo.toml | 2 + pumpkin/src/client/client_packet.rs | 21 +++---- pumpkin/src/client/container.rs | 78 ++++++++++++----------- pumpkin/src/client/mod.rs | 34 +++++----- pumpkin/src/client/player_packet.rs | 70 ++++++++++----------- pumpkin/src/commands/cmd_echest.rs | 5 +- pumpkin/src/commands/cmd_gamemode.rs | 4 +- pumpkin/src/entity/mod.rs | 61 +++++++++--------- pumpkin/src/entity/player.rs | 56 +++++++++-------- pumpkin/src/main.rs | 4 +- pumpkin/src/proxy/velocity.rs | 2 +- pumpkin/src/server/mod.rs | 8 +-- pumpkin/src/world/mod.rs | 27 ++++---- pumpkin/src/world/player_chunker.rs | 19 +++--- 26 files changed, 338 insertions(+), 244 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1c1cbac2c..fff734d23 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -534,6 +534,28 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1137cd7e7fc0fb5d3c5a8678be38ec56e819125d8d7907411fe24ccb943faca8" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-epoch", + "crossbeam-queue", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-deque" version = "0.8.5" @@ -553,6 +575,15 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "crossbeam-queue" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.20" @@ -1409,6 +1440,16 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + [[package]] name = "log" version = "0.4.22" @@ -1671,6 +1712,29 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.52.6", +] + [[package]] name = "paste" version = "1.0.15" @@ -1858,6 +1922,7 @@ version = "0.1.0-dev" dependencies = [ "base64 0.22.1", "bytes", + "crossbeam", "ctrlc", "digest 0.11.0-pre.9", "hmac", @@ -1868,6 +1933,7 @@ dependencies = [ "num-bigint", "num-derive", "num-traits", + "parking_lot", "pumpkin-config", "pumpkin-core", "pumpkin-entity", @@ -1922,9 +1988,11 @@ version = "0.1.0" name = "pumpkin-inventory" version = "0.1.0" dependencies = [ + "crossbeam", "itertools 0.13.0", "num-derive", "num-traits", + "parking_lot", "pumpkin-world", "thiserror", ] @@ -1991,6 +2059,7 @@ dependencies = [ "noise", "num-derive", "num-traits", + "parking_lot", "pumpkin-core", "rand", "rayon", @@ -2117,6 +2186,15 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "redox_syscall" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" +dependencies = [ + "bitflags 2.6.0", +] + [[package]] name = "redox_users" version = "0.4.6" @@ -2376,6 +2454,12 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + [[package]] name = "semver" version = "1.0.23" diff --git a/Cargo.toml b/Cargo.toml index 38d79ccf3..9b13bf54b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,7 +32,12 @@ tokio = { version = "1.40", features = [ "io-util", "sync", ] } + +# Concurrency/Parallelism and Synchronization rayon = "1.10.0" +parking_lot = "0.12.3" +crossbeam = "0.8.4" + uuid = { version = "1.10.0", features = ["serde", "v3", "v4"] } derive_more = { version = "1.0.0", features = ["full"] } serde = { version = "1.0", features = ["derive"] } diff --git a/pumpkin-core/src/math/position.rs b/pumpkin-core/src/math/position.rs index c324e89df..7d44f029f 100644 --- a/pumpkin-core/src/math/position.rs +++ b/pumpkin-core/src/math/position.rs @@ -2,6 +2,7 @@ use serde::{Deserialize, Serialize}; use super::vector3::Vector3; +#[derive(Clone, Copy)] /// Aka Block Position pub struct WorldPosition(pub Vector3); diff --git a/pumpkin-core/src/math/vector3.rs b/pumpkin-core/src/math/vector3.rs index 65da454e6..00cbd06b5 100644 --- a/pumpkin-core/src/math/vector3.rs +++ b/pumpkin-core/src/math/vector3.rs @@ -93,12 +93,16 @@ impl Neg for Vector3 { } impl From<(T, T, T)> for Vector3 { + + #[inline(always)] fn from((x, y, z): (T, T, T)) -> Self { Vector3 { x, y, z } } } impl From> for (T, T, T) { + + #[inline(always)] fn from(vector: Vector3) -> Self { (vector.x, vector.y, vector.z) } diff --git a/pumpkin-inventory/Cargo.toml b/pumpkin-inventory/Cargo.toml index 3b6c8c972..29daefc7e 100644 --- a/pumpkin-inventory/Cargo.toml +++ b/pumpkin-inventory/Cargo.toml @@ -10,4 +10,6 @@ pumpkin-world = { path = "../pumpkin-world"} num-traits = "0.2" num-derive = "0.4" thiserror = "1.0.63" -itertools = "0.13.0" \ No newline at end of file +itertools = "0.13.0" +parking_lot.workspace = true +crossbeam.workspace = true diff --git a/pumpkin-inventory/src/drag_handler.rs b/pumpkin-inventory/src/drag_handler.rs index 11d8b2f82..8479c3635 100644 --- a/pumpkin-inventory/src/drag_handler.rs +++ b/pumpkin-inventory/src/drag_handler.rs @@ -2,9 +2,10 @@ use crate::container_click::MouseDragType; use crate::{Container, InventoryError}; use itertools::Itertools; use num_traits::Euclid; +use parking_lot::{Mutex, RwLock}; use pumpkin_world::item::ItemStack; use std::collections::HashMap; -use std::sync::{Arc, Mutex, RwLock}; +use std::sync::Arc; #[derive(Debug, Default)] pub struct DragHandler(RwLock>>>); @@ -23,10 +24,7 @@ impl DragHandler { drag_type, slots: vec![], }; - let mut drags = match self.0.write() { - Ok(drags) => drags, - Err(_) => Err(InventoryError::LockError)?, - }; + let mut drags = self.0.write(); drags.insert(container_id, Arc::new(Mutex::new(drag))); Ok(()) } @@ -37,13 +35,10 @@ impl DragHandler { player: i32, slot: usize, ) -> Result<(), InventoryError> { - let drags = match self.0.read() { - Ok(drags) => drags, - Err(_) => Err(InventoryError::LockError)?, - }; + let drags = self.0.read(); match drags.get(&container_id) { Some(drag) => { - let mut drag = drag.lock().unwrap(); + let mut drag = drag.lock(); if drag.player != player { Err(InventoryError::MultiplePlayersDragging)? } @@ -68,13 +63,11 @@ impl DragHandler { return Ok(()); } - let Ok(mut drags) = self.0.write() else { - Err(InventoryError::LockError)? - }; + let mut drags = self.0.write(); let Some((_, drag)) = drags.remove_entry(container_id) else { Err(InventoryError::OutOfOrderDragging)? }; - let drag = drag.lock().unwrap(); + let drag = drag.lock(); if player != drag.player { Err(InventoryError::MultiplePlayersDragging)? diff --git a/pumpkin-inventory/src/lib.rs b/pumpkin-inventory/src/lib.rs index e1e554fae..db1de481f 100644 --- a/pumpkin-inventory/src/lib.rs +++ b/pumpkin-inventory/src/lib.rs @@ -1,5 +1,6 @@ use crate::container_click::MouseClick; use crate::player::PlayerInventory; +use crossbeam::atomic::AtomicCell; use num_derive::{FromPrimitive, ToPrimitive}; use pumpkin_world::item::ItemStack; diff --git a/pumpkin-inventory/src/open_container.rs b/pumpkin-inventory/src/open_container.rs index 25207d018..8ba82ac89 100644 --- a/pumpkin-inventory/src/open_container.rs +++ b/pumpkin-inventory/src/open_container.rs @@ -1,6 +1,7 @@ use crate::{Container, WindowType}; use pumpkin_world::item::ItemStack; -use std::sync::{Arc, Mutex}; +use std::sync::Arc; +use parking_lot::Mutex; pub struct OpenContainer { players: Vec, diff --git a/pumpkin-inventory/src/player.rs b/pumpkin-inventory/src/player.rs index 999c9a9e1..09f8b2ff9 100644 --- a/pumpkin-inventory/src/player.rs +++ b/pumpkin-inventory/src/player.rs @@ -2,6 +2,7 @@ use std::sync::atomic::AtomicU32; use crate::container_click::MouseClick; use crate::{handle_item_change, Container, InventoryError, WindowType}; +use crossbeam::atomic::AtomicCell; use pumpkin_world::item::ItemStack; pub struct PlayerInventory { diff --git a/pumpkin-protocol/src/lib.rs b/pumpkin-protocol/src/lib.rs index dfabd2e99..a3e290c04 100644 --- a/pumpkin-protocol/src/lib.rs +++ b/pumpkin-protocol/src/lib.rs @@ -151,7 +151,7 @@ pub enum PacketError { MalformedLength, } -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, PartialEq, Clone, Copy)] pub enum ConnectionState { HandShake, Status, diff --git a/pumpkin-world/Cargo.toml b/pumpkin-world/Cargo.toml index 90be69d5c..56f6dadee 100644 --- a/pumpkin-world/Cargo.toml +++ b/pumpkin-world/Cargo.toml @@ -19,6 +19,8 @@ serde_json = "1.0" static_assertions = "1.1.0" log.workspace = true +parking_lot.workspace = true + noise = "0.9.0" rand = "0.8.5" diff --git a/pumpkin-world/src/level.rs b/pumpkin-world/src/level.rs index 8707912be..f19648414 100644 --- a/pumpkin-world/src/level.rs +++ b/pumpkin-world/src/level.rs @@ -3,11 +3,12 @@ use std::{ fs::OpenOptions, io::{Read, Seek}, path::PathBuf, - sync::{Arc, Mutex}, + sync::Arc, }; use flate2::{bufread::ZlibDecoder, read::GzDecoder}; use itertools::Itertools; +use parking_lot::Mutex; use pumpkin_core::math::vector2::Vector2; use rayon::prelude::*; use thiserror::Error; @@ -152,40 +153,40 @@ impl Level { dbg!("a"); return; } - if let Ok(mut loaded_chunks) = self.loaded_chunks.lock() { - let channel = channel.clone(); + let mut loaded_chunks = self.loaded_chunks.lock(); + let channel = channel.clone(); - // Check if chunks is already loaded - if loaded_chunks.contains_key(at) { - channel - .blocking_send(Ok(loaded_chunks.get(at).unwrap().clone())) - .expect("Failed sending ChunkData."); - return; - } - let at = *at; - let data = match &self.save_file { - Some(save_file) => { - match Self::read_chunk(save_file, at) { - Err(WorldError::ChunkNotGenerated(_)) => { - // This chunk was not generated yet. - Ok(self.world_gen.generate_chunk(at)) - } - // TODO this doesn't warn the user about the error. fix. - result => result, - } - } - None => { - // There is no savefile yet -> generate the chunks - Ok(self.world_gen.generate_chunk(at)) - } - } - .unwrap(); - let data = Arc::new(data); + // Check if chunks is already loaded + if loaded_chunks.contains_key(at) { channel - .blocking_send(Ok(data.clone())) + .blocking_send(Ok(loaded_chunks.get(at).unwrap().clone())) .expect("Failed sending ChunkData."); - loaded_chunks.insert(at, data); + return; } + let at = *at; + let data = match &self.save_file { + Some(save_file) => { + match Self::read_chunk(save_file, at) { + Err(WorldError::ChunkNotGenerated(_)) => { + // This chunk was not generated yet. + Ok(self.world_gen.generate_chunk(at)) + } + // TODO this doesn't warn the user about the error. fix. + result => result, + } + } + None => { + // There is no savefile yet -> generate the chunks + Ok(self.world_gen.generate_chunk(at)) + } + } + .unwrap(); + let data = Arc::new(data); + channel + .blocking_send(Ok(data.clone())) + .expect("Failed sending ChunkData."); + loaded_chunks.insert(at, data); + }) } diff --git a/pumpkin/Cargo.toml b/pumpkin/Cargo.toml index 29fa203dc..839947f0f 100644 --- a/pumpkin/Cargo.toml +++ b/pumpkin/Cargo.toml @@ -62,6 +62,8 @@ log.workspace = true # networking mio = { version = "1.0.2", features = ["os-poll", "net"]} +parking_lot.workspace = true +crossbeam.workspace = true uuid.workspace = true tokio.workspace = true rayon.workspace = true diff --git a/pumpkin/src/client/client_packet.rs b/pumpkin/src/client/client_packet.rs index 7fc6d11d1..e3b3bd754 100644 --- a/pumpkin/src/client/client_packet.rs +++ b/pumpkin/src/client/client_packet.rs @@ -42,10 +42,9 @@ impl Client { let version = handshake.protocol_version.0; self.protocol_version .store(version, std::sync::atomic::Ordering::Relaxed); - let mut connection_state = self.connection_state.lock().unwrap(); - *connection_state = handshake.next_state; - if *connection_state != ConnectionState::Status { + 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 => { @@ -85,7 +84,7 @@ impl Client { } // default game profile, when no online mode // TODO: make offline uuid - let mut gameprofile = self.gameprofile.lock().unwrap(); + let mut gameprofile = self.gameprofile.lock(); *gameprofile = Some(GameProfile { id: login_start.uuid, name: login_start.name, @@ -125,7 +124,7 @@ impl Client { self.enable_encryption(&shared_secret) .unwrap_or_else(|e| self.kick(&e.to_string())); - let mut gameprofile = self.gameprofile.lock().unwrap(); + let mut gameprofile = self.gameprofile.lock(); if BASIC_CONFIG.online_mode { let hash = Sha1::new() @@ -133,7 +132,7 @@ impl Client { .chain_update(&server.public_key_der) .finalize(); let hash = auth_digest(&hash); - let ip = self.address.lock().unwrap().ip(); + let ip = self.address.lock().ip(); match authentication::authenticate( &gameprofile.as_ref().unwrap().name, &hash, @@ -204,7 +203,7 @@ impl Client { server: &Arc, _login_acknowledged: SLoginAcknowledged, ) { - *self.connection_state.lock().unwrap() = ConnectionState::Config; + self.connection_state.store(ConnectionState::Config); server.send_brand(self); let resource_config = &ADVANCED_CONFIG.resource_pack; @@ -239,8 +238,8 @@ impl Client { _server: &Arc, client_information: SClientInformationConfig, ) { - dbg!("got client settings"); - *self.config.lock().unwrap() = Some(PlayerConfig { + dbg!("got client settings"); + *self.config.lock() = Some(PlayerConfig { locale: client_information.locale, view_distance: client_information.view_distance, chat_mode: ChatMode::from_i32(client_information.chat_mode.into()).unwrap(), @@ -258,7 +257,7 @@ impl Client { { dbg!("got a client brand"); match String::from_utf8(plugin_message.data) { - Ok(brand) => *self.brand.lock().unwrap() = Some(brand), + Ok(brand) => *self.brand.lock() = Some(brand), Err(e) => self.kick(&e.to_string()), } } @@ -283,7 +282,7 @@ impl Client { _config_acknowledged: SAcknowledgeFinishConfig, ) { dbg!("config acknowledged"); - *self.connection_state.lock().unwrap() = ConnectionState::Play; + self.connection_state.store(ConnectionState::Play); self.make_player .store(true, std::sync::atomic::Ordering::Relaxed); } diff --git a/pumpkin/src/client/container.rs b/pumpkin/src/client/container.rs index 86769642e..0df08864f 100644 --- a/pumpkin/src/client/container.rs +++ b/pumpkin/src/client/container.rs @@ -1,6 +1,7 @@ use crate::entity::player::Player; use crate::server::Server; use itertools::Itertools; +use parking_lot::Mutex; use pumpkin_core::text::TextComponent; use pumpkin_core::GameMode; use pumpkin_inventory::container_click::{ @@ -16,11 +17,11 @@ use pumpkin_protocol::client::play::{ use pumpkin_protocol::server::play::SClickContainer; use pumpkin_protocol::slot::Slot; use pumpkin_world::item::ItemStack; -use std::sync::{Arc, Mutex}; +use std::sync::Arc; impl Player { pub fn open_container(&self, server: &Arc, minecraft_menu_id: &str) { - let inventory = self.inventory.lock().unwrap(); + let inventory = self.inventory.lock(); inventory .state_id .store(0, std::sync::atomic::Ordering::Relaxed); @@ -28,7 +29,7 @@ impl Player { let container = self.get_open_container(server); let mut container = container .as_ref() - .map(|container| container.lock().unwrap()); + .map(|container| container.lock()); let menu_protocol_id = (*pumpkin_world::global_registry::REGISTRY .get("minecraft:menu") .unwrap() @@ -54,7 +55,7 @@ impl Player { } pub fn set_container_content(&self, container: Option<&mut Box>) { - let mut inventory = self.inventory.lock().unwrap(); + let mut inventory = self.inventory.lock(); let total_opened_containers = inventory.total_opened_containers; let container = OptionallyCombinedContainer::new(&mut inventory, container); @@ -66,7 +67,7 @@ impl Player { .collect_vec(); let carried_item = { - if let Some(item) = self.carried_item.lock().unwrap().as_ref() { + if let Some(item) = self.carried_item.load().as_ref() { item.into() } else { Slot::empty() @@ -87,7 +88,7 @@ impl Player { /// The official Minecraft client is weird, and will always just close *any* window that is opened when this gets sent pub fn close_container(&self) { - let mut inventory = self.inventory.lock().unwrap(); + let mut inventory = self.inventory.lock(); inventory.total_opened_containers += 1; self.client .send_packet(&CCloseContainer::new(inventory.total_opened_containers)) @@ -99,7 +100,7 @@ impl Player { ) { let (id, value) = window_property.into_tuple(); self.client.send_packet(&CSetContainerProperty::new( - self.inventory.lock().unwrap().total_opened_containers, + self.inventory.lock().total_opened_containers, id, value, )); @@ -113,13 +114,12 @@ impl Player { let opened_container = self.get_open_container(server); let mut opened_container = opened_container .as_ref() - .map(|container| container.lock().unwrap()); + .map(|container| container.lock()); let drag_handler = &server.drag_handler; let state_id = self .inventory .lock() - .unwrap() .state_id .load(std::sync::atomic::Ordering::Relaxed); // This is just checking for regular desync, client hasn't done anything malicious @@ -129,7 +129,7 @@ impl Player { } if opened_container.is_some() { - if packet.window_id != self.inventory.lock().unwrap().total_opened_containers { + if packet.window_id != self.inventory.lock().total_opened_containers { return Err(InventoryError::ClosedContainerInteract(self.entity_id())); } } else if packet.window_id != 0 { @@ -191,7 +191,7 @@ impl Player { drop(opened_container); self.send_whole_container_change(server).await?; } else if let container_click::Slot::Normal(slot_index) = click.slot { - let mut inventory = self.inventory.lock().unwrap(); + let mut inventory = self.inventory.lock(); let combined_container = OptionallyCombinedContainer::new(&mut inventory, Some(&mut opened_container)); if let Some(slot) = combined_container.get_slot_excluding_inventory(slot_index) { @@ -211,15 +211,20 @@ impl Player { mouse_click: MouseClick, slot: container_click::Slot, ) -> Result<(), InventoryError> { - let mut inventory = self.inventory.lock().unwrap(); + let mut inventory = self.inventory.lock(); let mut container = OptionallyCombinedContainer::new(&mut inventory, opened_container); match slot { - container_click::Slot::Normal(slot) => container.handle_item_change( - &mut self.carried_item.lock().unwrap(), - slot, - mouse_click, - ), + container_click::Slot::Normal(slot) => { + let mut carried_item = self.carried_item.load(); + let res = container.handle_item_change( + &mut carried_item, + slot, + mouse_click, + ); + self.carried_item.store(carried_item); + res + }, container_click::Slot::OutsideInventory => Ok(()), } } @@ -229,7 +234,7 @@ impl Player { opened_container: Option<&mut Box>, slot: container_click::Slot, ) -> Result<(), InventoryError> { - let mut inventory = self.inventory.lock().unwrap(); + let mut inventory = self.inventory.lock(); let mut container = OptionallyCombinedContainer::new(&mut inventory, opened_container); match slot { @@ -281,7 +286,7 @@ impl Player { KeyClick::Slot(slot) => slot, KeyClick::Offhand => 45, }; - let mut inventory = self.inventory.lock().unwrap(); + let mut inventory = self.inventory.lock(); let mut changing_item_slot = inventory.get_slot(changing_slot as usize)?.to_owned(); let mut container = OptionallyCombinedContainer::new(&mut inventory, opened_container); @@ -295,13 +300,13 @@ impl Player { opened_container: Option<&mut Box>, slot: usize, ) -> Result<(), InventoryError> { - if *self.gamemode.lock().unwrap() != GameMode::Creative { + if self.gamemode.load() != GameMode::Creative { return Err(InventoryError::PermissionError); } - let mut inventory = self.inventory.lock().unwrap(); + let mut inventory = self.inventory.lock(); let mut container = OptionallyCombinedContainer::new(&mut inventory, opened_container); if let Some(Some(item)) = container.all_slots().get_mut(slot) { - *self.carried_item.lock().unwrap() = Some(item.to_owned()) + self.carried_item.store(Some(item.to_owned())); } Ok(()) } @@ -311,7 +316,7 @@ impl Player { opened_container: Option<&mut Box>, slot: usize, ) -> Result<(), InventoryError> { - let mut inventory = self.inventory.lock().unwrap(); + let mut inventory = self.inventory.lock(); let mut container = OptionallyCombinedContainer::new(&mut inventory, opened_container); let mut slots = container.all_slots(); @@ -340,7 +345,7 @@ impl Player { } } } - *self.carried_item.lock().unwrap() = Some(carried_item); + self.carried_item.store(Some(carried_item)); Ok(()) } @@ -358,7 +363,7 @@ impl Player { match mouse_drag_state { MouseDragState::Start(drag_type) => { if drag_type == MouseDragType::Middle - && *self.gamemode.lock().unwrap() != GameMode::Creative + && self.gamemode.load() != GameMode::Creative { Err(InventoryError::PermissionError)? } @@ -366,15 +371,18 @@ impl Player { } MouseDragState::AddSlot(slot) => drag_handler.add_slot(container_id, player_id, slot), MouseDragState::End => { - let mut inventory = self.inventory.lock().unwrap(); + let mut inventory = self.inventory.lock(); let mut container = OptionallyCombinedContainer::new(&mut inventory, opened_container); - drag_handler.apply_drag( - &mut self.carried_item.lock().unwrap(), + let mut carried_item = self.carried_item.load(); + let res = drag_handler.apply_drag( + &mut carried_item, &mut container, &container_id, player_id, - ) + ); + self.carried_item.store(carried_item); + res } } } @@ -383,10 +391,9 @@ impl Player { let player_ids = { let open_containers = server .open_containers - .read() - .expect("open_containers is poisoned"); + .read(); open_containers - .get(&self.open_container.lock().unwrap().unwrap()) + .get(&self.open_container.load().unwrap()) .unwrap() .all_player_ids() .into_iter() @@ -403,7 +410,6 @@ impl Player { .world .current_players .lock() - .unwrap() .iter() .filter_map(|(token, player)| { if *token != player_token { @@ -428,7 +434,7 @@ impl Player { slot: Slot, ) -> Result<(), InventoryError> { for player in self.get_current_players_in_container(server).await { - let inventory = player.inventory.lock().unwrap(); + let inventory = player.inventory.lock(); let total_opened_containers = inventory.total_opened_containers; // Returns previous value @@ -451,14 +457,14 @@ impl Player { for player in players { let container = player.get_open_container(server); - let mut container = container.as_ref().map(|v| v.lock().unwrap()); + let mut container = container.as_ref().map(|v| v.lock()); player.set_container_content(container.as_deref_mut()); } Ok(()) } pub fn get_open_container(&self, server: &Server) -> Option>>> { - if let Some(id) = *self.open_container.lock().unwrap() { + if let Some(id) = self.open_container.load() { server.try_get_container(self.entity_id(), id) } else { None diff --git a/pumpkin/src/client/mod.rs b/pumpkin/src/client/mod.rs index f4e89a450..186b1a2ea 100644 --- a/pumpkin/src/client/mod.rs +++ b/pumpkin/src/client/mod.rs @@ -2,8 +2,7 @@ use std::{ io::{self, Write}, net::SocketAddr, sync::{ - atomic::{AtomicBool, AtomicI32}, - Arc, Mutex, + atomic::{AtomicBool, AtomicI32}, Arc, }, }; @@ -13,7 +12,9 @@ use crate::{ }; use authentication::GameProfile; +use crossbeam::atomic::AtomicCell; use mio::{event::Event, net::TcpStream, Token}; +use parking_lot::Mutex; use pumpkin_core::text::TextComponent; use pumpkin_protocol::{ bytebuf::{packet_id::Packet, DeserializerError}, @@ -71,7 +72,7 @@ pub struct Client { pub brand: Mutex>, pub protocol_version: AtomicI32, - pub connection_state: Mutex, + pub connection_state: AtomicCell, pub encryption: AtomicBool, pub closed: AtomicBool, pub token: Token, @@ -93,7 +94,7 @@ impl Client { brand: Mutex::new(None), token, address: Mutex::new(address), - connection_state: Mutex::new(ConnectionState::HandShake), + connection_state: AtomicCell::new(ConnectionState::HandShake), connection: Arc::new(Mutex::new(connection)), enc: Arc::new(Mutex::new(PacketEncoder::default())), dec: Arc::new(Mutex::new(PacketDecoder::default())), @@ -106,7 +107,7 @@ impl Client { /// adds a Incoming packet to the queue pub fn add_packet(&self, packet: RawPacket) { - let mut client_packets_queue = self.client_packets_queue.lock().unwrap(); + let mut client_packets_queue = self.client_packets_queue.lock(); client_packets_queue.push(packet); } @@ -120,8 +121,8 @@ impl Client { let crypt_key: [u8; 16] = shared_secret .try_into() .map_err(|_| EncryptionError::SharedWrongLength)?; - self.dec.lock().unwrap().enable_encryption(&crypt_key); - self.enc.lock().unwrap().enable_encryption(&crypt_key); + self.dec.lock().enable_encryption(&crypt_key); + self.enc.lock().enable_encryption(&crypt_key); Ok(()) } @@ -129,20 +130,18 @@ impl Client { pub fn set_compression(&self, compression: Option<(u32, u32)>) { self.dec .lock() - .unwrap() .set_compression(compression.map(|v| v.0)); - self.enc.lock().unwrap().set_compression(compression); + self.enc.lock().set_compression(compression); } /// Send a Clientbound Packet to the Client pub fn send_packet(&self, packet: &P) { // assert!(!self.closed); - let mut enc = self.enc.lock().unwrap(); + let mut enc = self.enc.lock(); enc.append_packet(packet) .unwrap_or_else(|e| self.kick(&e.to_string())); self.connection .lock() - .unwrap() .write_all(&enc.take()) .map_err(|_| PacketError::ConnectionWrite) .unwrap_or_else(|e| self.kick(&e.to_string())); @@ -151,18 +150,17 @@ impl Client { pub fn try_send_packet(&self, packet: &P) -> Result<(), PacketError> { // assert!(!self.closed); - let mut enc = self.enc.lock().unwrap(); + let mut enc = self.enc.lock(); enc.append_packet(packet)?; self.connection .lock() - .unwrap() .write_all(&enc.take()) .map_err(|_| PacketError::ConnectionWrite)?; Ok(()) } pub async fn process_packets(&self, server: &Arc) { - while let Some(mut packet) = self.client_packets_queue.lock().unwrap().pop() { + while let Some(mut packet) = self.client_packets_queue.lock().pop() { match self.handle_packet(server, &mut packet).await { Ok(_) => {} Err(e) => { @@ -182,7 +180,7 @@ impl Client { ) -> Result<(), DeserializerError> { // TODO: handle each packet's Error instead of calling .unwrap() let bytebuf = &mut packet.bytebuf; - let locked_state = self.connection_state.lock().unwrap(); + let locked_state = self.connection_state.load(); let state = locked_state.clone(); drop(locked_state); match state { @@ -291,7 +289,7 @@ impl Client { let mut bytes_read = 0; loop { let connection = self.connection.clone(); - let mut connection = connection.lock().unwrap(); + let mut connection = connection.lock(); match connection.read(&mut received_data[bytes_read..]) { Ok(0) => { // Reading 0 bytes means the other side has closed the @@ -313,7 +311,7 @@ impl Client { } if bytes_read != 0 { - let mut dec = self.dec.lock().unwrap(); + let mut dec = self.dec.lock(); dec.queue_slice(&received_data[..bytes_read]); match dec.decode() { Ok(packet) => { @@ -331,7 +329,7 @@ impl Client { /// Kicks the Client with a reason depending on the connection state pub fn kick(&self, reason: &str) { dbg!(reason); - match *self.connection_state.lock().unwrap() { + match self.connection_state.load() { ConnectionState::Login => { self.try_send_packet(&CLoginDisconnect::new( &serde_json::to_string_pretty(&reason).unwrap_or("".into()), diff --git a/pumpkin/src/client/player_packet.rs b/pumpkin/src/client/player_packet.rs index 7f342d872..549f628f5 100644 --- a/pumpkin/src/client/player_packet.rs +++ b/pumpkin/src/client/player_packet.rs @@ -9,7 +9,7 @@ use crate::{ use num_traits::FromPrimitive; use pumpkin_config::ADVANCED_CONFIG; use pumpkin_core::{ - math::{position::WorldPosition, wrap_degrees}, + math::{position::WorldPosition, vector3::Vector3, wrap_degrees}, text::TextComponent, GameMode, }; @@ -46,7 +46,7 @@ impl Player { _server: &Arc, confirm_teleport: SConfirmTeleport, ) { - let mut awaiting_teleport = self.awaiting_teleport.lock().unwrap(); + let mut awaiting_teleport = self.awaiting_teleport.lock(); if let Some((id, position)) = awaiting_teleport.as_ref() { if id == &confirm_teleport.teleport_id { // we should set the pos now to that we requested in the teleport packet, Is may fixed issues when the client sended position packets while being teleported @@ -82,14 +82,14 @@ impl Player { Self::clamp_vertical(position.feet_y), Self::clamp_horizontal(position.z), ); - let mut last_position = self.last_position.lock().unwrap(); - let pos = entity.pos.lock().unwrap(); - *last_position = *pos; + let pos = entity.pos.load(); + self.last_position.store(pos); + let last_position = self.last_position.load(); entity .on_ground .store(position.ground, std::sync::atomic::Ordering::Relaxed); let entity_id = entity.entity_id; - let (x, y, z) = (*pos).into(); + let Vector3 { x, y, z } = pos; let (lastx, lasty, lastz) = (last_position.x, last_position.y, last_position.z); let world = &entity.world; @@ -142,9 +142,9 @@ impl Player { Self::clamp_vertical(position_rotation.feet_y), Self::clamp_horizontal(position_rotation.z), ); - let mut last_position = self.last_position.lock().unwrap(); - let pos = entity.pos.lock().unwrap(); - *last_position = *pos; + let pos = entity.pos.load(); + self.last_position.store(pos); + let last_position = self.last_position.load(); entity.on_ground.store( position_rotation.ground, std::sync::atomic::Ordering::Relaxed, @@ -155,10 +155,10 @@ impl Player { ); let entity_id = entity.entity_id; - let (x, y, z) = (*pos).into(); + let Vector3 {x, y, z } = pos; let (lastx, lasty, lastz) = (last_position.x, last_position.y, last_position.z); - let yaw = modulus(*entity.yaw.lock().unwrap() * 256.0 / 360.0, 256.0); - let pitch = modulus(*entity.pitch.lock().unwrap() * 256.0 / 360.0, 256.0); + let yaw = modulus(entity.yaw.load() * 256.0 / 360.0, 256.0); + let pitch = modulus(entity.pitch.load() * 256.0 / 360.0, 256.0); // let head_yaw = (entity.head_yaw * 256.0 / 360.0).floor(); let world = &entity.world; @@ -210,8 +210,8 @@ impl Player { ); // send new position to all other players let entity_id = entity.entity_id; - let yaw = modulus(*entity.yaw.lock().unwrap() * 256.0 / 360.0, 256.0); - let pitch = modulus(*entity.pitch.lock().unwrap() * 256.0 / 360.0, 256.0); + let yaw = modulus(entity.yaw.load() * 256.0 / 360.0, 256.0); + let pitch = modulus(entity.pitch.load() * 256.0 / 360.0, 256.0); // let head_yaw = modulus(entity.head_yaw * 256.0 / 360.0, 256.0); let world = &entity.world; @@ -350,7 +350,7 @@ impl Player { Hand::from_i32(client_information.main_hand.into()), ChatMode::from_i32(client_information.chat_mode.into()), ) { - *self.config.lock().unwrap() = PlayerConfig { + *self.config.lock() = PlayerConfig { locale: client_information.locale, view_distance: client_information.view_distance, chat_mode, @@ -383,19 +383,19 @@ impl Player { if let Some(player) = attacked_player { let victem_entity = &player.entity; if config.protect_creative - && *player.gamemode.lock().unwrap() == GameMode::Creative + && player.gamemode.load() == GameMode::Creative { return; } if config.knockback { - let yaw = entity.yaw.lock().unwrap(); + let yaw = entity.yaw.load(); let strength = 1.0; - let mut victem_velocity = victem_entity.velocity.lock().unwrap(); - let saved_velo = *victem_velocity; + let victem_velocity = victem_entity.velocity.load(); + let saved_velo = victem_velocity; victem_entity.knockback( strength * 0.5, - (*yaw * (PI / 180.0)).sin() as f64, - -(*yaw * (PI / 180.0)).cos() as f64, + (yaw * (PI / 180.0)).sin() as f64, + -(yaw * (PI / 180.0)).cos() as f64, ); let packet = &CEntityVelocity::new( &entity_id, @@ -403,16 +403,16 @@ impl Player { victem_velocity.y as f32, victem_velocity.z as f32, ); - let mut velocity = entity.velocity.lock().unwrap(); - *velocity = velocity.multiply(0.6, 1.0, 0.6); + let velocity = entity.velocity.load(); + victem_entity.velocity.store(velocity.multiply(0.6, 1.0, 0.6)); - *victem_velocity = saved_velo; + victem_entity.velocity.store(saved_velo); player.client.send_packet(packet); } if config.hurt_animation { world.broadcast_packet_all(&CHurtAnimation::new( &entity_id, - *entity.yaw.lock().unwrap(), + entity.yaw.load(), )) } if config.swing {} @@ -441,7 +441,7 @@ impl Player { } // TODO: do validation // TODO: Config - if *self.gamemode.lock().unwrap() == GameMode::Creative { + if self.gamemode.load() == GameMode::Creative { let location = player_action.location; // Block break & block break sound // TODO: currently this is always dirt replace it @@ -509,7 +509,7 @@ impl Player { } if let Some(face) = BlockFace::from_i32(use_item_on.face.0) { - if let Some(item) = self.inventory.lock().unwrap().held_item() { + if let Some(item) = self.inventory.lock().held_item() { let minecraft_id = global_registry::find_minecraft_id( global_registry::ITEM_REGISTRY, item.item_id, @@ -545,7 +545,7 @@ impl Player { if !(0..=8).contains(&slot) { self.kick(TextComponent::text("Invalid held slot")) } - self.inventory.lock().unwrap().set_selected(slot as usize); + self.inventory.lock().set_selected(slot as usize); } pub fn handle_set_creative_slot( @@ -553,10 +553,10 @@ impl Player { _server: &Arc, packet: SSetCreativeSlot, ) -> Result<(), InventoryError> { - if *self.gamemode.lock().unwrap() != GameMode::Creative { + if self.gamemode.load() != GameMode::Creative { return Err(InventoryError::PermissionError); } - self.inventory.lock().unwrap().set_slot( + self.inventory.lock().set_slot( packet.slot as usize, packet.clicked_item.to_item(), false, @@ -570,19 +570,17 @@ impl Player { // window_id 0 represents both 9x1 Generic AND inventory here self.inventory .lock() - .unwrap() .state_id .store(0, std::sync::atomic::Ordering::Relaxed); - let mut open_container = self.open_container.lock().unwrap(); - if let Some(id) = *open_container { + let open_container = self.open_container.load(); + if let Some(id) = open_container { let mut open_containers = server .open_containers - .write() - .expect("open_containers got poisoned"); + .write(); if let Some(container) = open_containers.get_mut(&id) { container.remove_player(self.entity_id()) } - *open_container = None; + self.open_container.store(None); } let Some(_window_type) = WindowType::from_u8(packet.window_id) else { self.kick(TextComponent::text("Invalid window ID")); diff --git a/pumpkin/src/commands/cmd_echest.rs b/pumpkin/src/commands/cmd_echest.rs index fe55c9608..2d234e3cf 100644 --- a/pumpkin/src/commands/cmd_echest.rs +++ b/pumpkin/src/commands/cmd_echest.rs @@ -11,12 +11,11 @@ pub(crate) fn init_command_tree<'a>() -> CommandTree<'a> { CommandTree::new(NAMES, DESCRIPTION).execute(&|sender, server, _| { if let Some(player) = sender.as_mut_player() { let entity_id = player.entity_id(); - *player.open_container.lock().unwrap() = Some(0); + player.open_container.store(Some(0)); { let mut open_containers = server .open_containers - .write() - .expect("open_containers got poisoned"); + .write(); match open_containers.get_mut(&0) { Some(ender_chest) => { ender_chest.add_player(entity_id); diff --git a/pumpkin/src/commands/cmd_gamemode.rs b/pumpkin/src/commands/cmd_gamemode.rs index 92600d395..ce48f8d91 100644 --- a/pumpkin/src/commands/cmd_gamemode.rs +++ b/pumpkin/src/commands/cmd_gamemode.rs @@ -65,7 +65,7 @@ pub(crate) fn init_command_tree<'a>() -> CommandTree<'a> { let gamemode = parse_arg_gamemode(args)?; return if let Player(target) = sender { - if *target.gamemode.lock().unwrap() == gamemode { + if target.gamemode.load() == gamemode { target.send_system_message(TextComponent::text(&format!( "You already in {:?} gamemode", gamemode @@ -89,7 +89,7 @@ pub(crate) fn init_command_tree<'a>() -> CommandTree<'a> { let gamemode = parse_arg_gamemode(args)?; let target = parse_arg_player(sender, ARG_TARGET, args)?; - if *target.gamemode.lock().unwrap() == gamemode { + if target.gamemode.load() == gamemode { target.send_system_message(TextComponent::text(&format!( "You already in {:?} gamemode", gamemode diff --git a/pumpkin/src/entity/mod.rs b/pumpkin/src/entity/mod.rs index 132dfedf0..982de2ad1 100644 --- a/pumpkin/src/entity/mod.rs +++ b/pumpkin/src/entity/mod.rs @@ -1,5 +1,6 @@ -use std::sync::{atomic::AtomicBool, Arc, Mutex}; +use std::sync::{atomic::AtomicBool, Arc}; +use crossbeam::atomic::AtomicCell; use pumpkin_core::math::{ get_section_cord, position::WorldPosition, vector2::Vector2, vector3::Vector3, }; @@ -18,24 +19,24 @@ pub struct Entity { pub entity_type: EntityType, pub world: Arc, - pub pos: Mutex>, - pub block_pos: Mutex, - pub chunk_pos: Mutex>, + pub pos: AtomicCell>, + pub block_pos: AtomicCell, + pub chunk_pos: AtomicCell>, pub sneaking: AtomicBool, pub sprinting: AtomicBool, pub fall_flying: AtomicBool, - pub velocity: Mutex>, + pub velocity: AtomicCell>, // Should be not trusted pub on_ground: AtomicBool, - pub yaw: Mutex, - pub head_yaw: Mutex, - pub pitch: Mutex, + pub yaw: AtomicCell, + pub head_yaw: AtomicCell, + pub pitch: AtomicCell, // TODO: Change this in diffrent poses pub standing_eye_height: f32, - pub pose: Mutex, + pub pose: AtomicCell, } impl Entity { @@ -49,41 +50,41 @@ impl Entity { entity_id, entity_type, on_ground: AtomicBool::new(false), - pos: Mutex::new(Vector3::new(0.0, 0.0, 0.0)), - block_pos: Mutex::new(WorldPosition(Vector3::new(0, 0, 0))), - chunk_pos: Mutex::new(Vector2::new(0, 0)), + pos: AtomicCell::new(Vector3::new(0.0, 0.0, 0.0)), + block_pos: AtomicCell::new(WorldPosition(Vector3::new(0, 0, 0))), + chunk_pos: AtomicCell::new(Vector2::new(0, 0)), sneaking: AtomicBool::new(false), world, sprinting: AtomicBool::new(false), fall_flying: AtomicBool::new(false), - yaw: Mutex::new(0.0), - head_yaw: Mutex::new(0.0), - pitch: Mutex::new(0.0), - velocity: Mutex::new(Vector3::new(0.0, 0.0, 0.0)), + yaw: AtomicCell::new(0.0), + head_yaw: AtomicCell::new(0.0), + pitch: AtomicCell::new(0.0), + velocity: AtomicCell::new(Vector3::new(0.0, 0.0, 0.0)), standing_eye_height, - pose: Mutex::new(EntityPose::Standing), + pose: AtomicCell::new(EntityPose::Standing), } } pub fn set_pos(&self, x: f64, y: f64, z: f64) { - let mut pos = self.pos.lock().unwrap(); + let pos = self.pos.load(); if pos.x != x || pos.y != y || pos.z != z { - *pos = Vector3::new(x, y, z); + self.pos.store(Vector3::new(x, y, z)); let i = x.floor() as i32; let j = y.floor() as i32; let k = z.floor() as i32; - let mut block_pos = self.block_pos.lock().unwrap(); + let block_pos = self.block_pos.load(); let block_pos_vec = block_pos.0; if i != block_pos_vec.x || j != block_pos_vec.y || k != block_pos_vec.z { - *block_pos = WorldPosition(Vector3::new(i, j, k)); + self.block_pos.store(WorldPosition(Vector3::new(i, j, k))); - let mut chunk_pos = self.chunk_pos.lock().unwrap(); + let chunk_pos = self.chunk_pos.load(); if get_section_cord(i) != chunk_pos.x || get_section_cord(k) != chunk_pos.z { - *chunk_pos = Vector2::new( + self.chunk_pos.store(Vector2::new( get_section_cord(block_pos_vec.x), get_section_cord(block_pos_vec.z), - ); + )); } } } @@ -91,8 +92,8 @@ impl Entity { pub fn set_rotation(&self, yaw: f32, pitch: f32) { // TODO - *self.yaw.lock().unwrap() = yaw; - *self.pitch.lock().unwrap() = pitch + self.yaw.store(yaw); + self.pitch.store(pitch); } pub async fn remove(&mut self) { @@ -109,8 +110,8 @@ impl Entity { } let var8 = Vector3::new(x, 0.0, z).normalize() * strength; - let mut velocity = self.velocity.lock().unwrap(); - *velocity = Vector3::new( + let velocity = self.velocity.load(); + self.velocity.store(Vector3::new( velocity.x / 2.0 - var8.x, if self.on_ground.load(std::sync::atomic::Ordering::Relaxed) { (velocity.y / 2.0 + strength).min(0.4) @@ -118,7 +119,7 @@ impl Entity { velocity.y }, velocity.z / 2.0 - var8.z, - ); + )); } pub async fn set_sneaking(&self, sneaking: bool) { @@ -171,7 +172,7 @@ impl Entity { } pub async fn set_pose(&self, pose: EntityPose) { - *self.pose.lock().unwrap() = pose; + self.pose.store(pose); let pose = pose as i32; let packet = CSetEntityMetadata::::new( self.entity_id.into(), diff --git a/pumpkin/src/entity/player.rs b/pumpkin/src/entity/player.rs index 67ed05e8f..67f0ee774 100644 --- a/pumpkin/src/entity/player.rs +++ b/pumpkin/src/entity/player.rs @@ -1,10 +1,12 @@ use std::sync::{ atomic::{AtomicI32, AtomicU8}, - Arc, Mutex, + Arc, }; +use crossbeam::atomic::AtomicCell; use num_derive::FromPrimitive; use num_traits::ToPrimitive; +use parking_lot::Mutex; use pumpkin_core::{ math::{boundingbox::BoundingBox, position::WorldPosition, vector3::Vector3}, text::TextComponent, @@ -67,18 +69,18 @@ pub struct Player { pub client: Client, pub config: Mutex, /// Current gamemode - pub gamemode: Mutex, + pub gamemode: AtomicCell, // TODO: prbly should put this into an Living Entitiy or something - pub health: Mutex, + pub health: AtomicCell, pub food: AtomicI32, - pub food_saturation: Mutex, + pub food_saturation: AtomicCell, pub inventory: Mutex, - pub open_container: Mutex>, - pub carried_item: Mutex>, + pub open_container: AtomicCell>, + pub carried_item: AtomicCell>, /// send `send_abilties_update` when changed pub abilities: PlayerAbilities, - pub last_position: Mutex>, + pub last_position: AtomicCell>, // TODO: This is currently unused, We have to calculate the block breaking speed our own and then break the block our own if its done pub current_block_destroy_stage: AtomicU8, @@ -87,12 +89,12 @@ pub struct Player { // Current awaiting teleport id and location, None if did not teleport pub awaiting_teleport: Mutex)>>, - pub watched_section: Mutex>, + pub watched_section: AtomicCell>, } impl Player { pub fn new(client: Client, world: Arc, entity_id: EntityId, gamemode: GameMode) -> Self { - let gameprofile = match client.gameprofile.lock().unwrap().clone() { + let gameprofile = match client.gameprofile.lock().clone() { Some(profile) => profile, None => { log::error!("No gameprofile?. Impossible"); @@ -104,7 +106,7 @@ impl Player { } } }; - let config = client.config.lock().unwrap().clone().unwrap_or_default(); + let config = client.config.lock().clone().unwrap_or_default(); Self { entity: Entity::new(entity_id, world, EntityType::Player, 1.62), config: Mutex::new(config), @@ -112,18 +114,18 @@ impl Player { client, awaiting_teleport: Mutex::new(None), // TODO: Load this from previous instance - health: Mutex::new(20.0), + health: AtomicCell::new(20.0), food: AtomicI32::new(20), - food_saturation: Mutex::new(20.0), + food_saturation: AtomicCell::new(20.0), current_block_destroy_stage: AtomicU8::new(0), inventory: Mutex::new(PlayerInventory::new()), - open_container: Mutex::new(None), - carried_item: Mutex::new(None), + open_container: AtomicCell::new(None), + carried_item: AtomicCell::new(None), teleport_id_count: AtomicI32::new(0), abilities: PlayerAbilities::default(), - gamemode: Mutex::new(gamemode), - watched_section: Mutex::new(Vector3::new(0, 0, 0)), - last_position: Mutex::new(Vector3::new(0.0, 0.0, 0.0)), + gamemode: AtomicCell::new(gamemode), + watched_section: AtomicCell::new(Vector3::new(0, 0, 0)), + last_position: AtomicCell::new(Vector3::new(0.0, 0.0, 0.0)), } } @@ -173,7 +175,7 @@ impl Player { let entity = &self.entity; entity.set_pos(x, y, z); entity.set_rotation(yaw, pitch); - *self.awaiting_teleport.lock().unwrap() = Some((teleport_id.into(), Vector3::new(x, y, z))); + *self.awaiting_teleport.lock() = Some((teleport_id.into(), Vector3::new(x, y, z))); self.client.send_packet(&CSyncPlayerPosition::new( x, y, @@ -186,7 +188,7 @@ impl Player { } pub fn block_interaction_range(&self) -> f64 { - if *self.gamemode.lock().unwrap() == GameMode::Creative { + if self.gamemode.load() == GameMode::Creative { 5.0 } else { 4.5 @@ -196,7 +198,7 @@ impl Player { pub fn can_interact_with_block_at(&self, pos: &WorldPosition, additional_range: f64) -> bool { let d = self.block_interaction_range() + additional_range; let box_pos = BoundingBox::from_block(pos); - let entity_pos = self.entity.pos.lock().unwrap(); + let entity_pos = self.entity.pos.load(); let standing_eye_height = self.entity.standing_eye_height; box_pos.squared_magnitude(Vector3 { x: entity_pos.x, @@ -207,7 +209,7 @@ impl Player { /// Kicks the Client with a reason depending on the connection state pub fn kick(&self, reason: TextComponent) { - assert!(*self.client.connection_state.lock().unwrap() == ConnectionState::Play); + assert!(self.client.connection_state.load() == ConnectionState::Play); assert!(!self .client .closed @@ -225,19 +227,19 @@ impl Player { } pub fn update_health(&self, health: f32, food: i32, food_saturation: f32) { - *self.health.lock().unwrap() = health; + self.health.store(health); self.food.store(food, std::sync::atomic::Ordering::Relaxed); - *self.food_saturation.lock().unwrap() = food_saturation; + self.food_saturation.store(food_saturation); } pub fn set_gamemode(&self, gamemode: GameMode) { // We could send the same gamemode without problems. But why waste bandwidth ? - let mut current_gamemode = self.gamemode.lock().unwrap(); + let current_gamemode = self.gamemode.load(); assert!( - *current_gamemode != gamemode, + current_gamemode != gamemode, "Setting the same gamemode as already is" ); - *current_gamemode = gamemode; + self.gamemode.store(gamemode); // So a little story time. I actually made an abitlties_from_gamemode function. I looked at vanilla and they always send the abilties from the gamemode. But the funny thing actually is. That the client // does actually use the same method and set the abilties when receiving the CGameEvent gamemode packet. Just Mojang nonsense self.entity @@ -261,7 +263,7 @@ impl Player { impl Player { pub async fn process_packets(&self, server: &Arc) { - let mut packets = self.client.client_packets_queue.lock().unwrap(); + let mut packets = self.client.client_packets_queue.lock(); while let Some(mut packet) = packets.pop() { match self.handle_play_packet(server, &mut packet).await { Ok(_) => {} diff --git a/pumpkin/src/main.rs b/pumpkin/src/main.rs index 8a00ebbb4..79f5382e8 100644 --- a/pumpkin/src/main.rs +++ b/pumpkin/src/main.rs @@ -172,7 +172,7 @@ fn main() -> io::Result<()> { dbg!("a"); player.remove().await; dbg!("b"); - let connection = &mut player.client.connection.lock().unwrap(); + let connection = &mut player.client.connection.lock(); dbg!("c"); poll.registry().deregister(connection.by_ref())?; @@ -201,7 +201,7 @@ fn main() -> io::Result<()> { if done || make_player { if let Some(client) = clients.remove(&token) { if done { - let connection = &mut client.connection.lock().unwrap(); + let connection = &mut client.connection.lock(); poll.registry().deregister(connection.by_ref())?; } else if make_player { let token = client.token; diff --git a/pumpkin/src/proxy/velocity.rs b/pumpkin/src/proxy/velocity.rs index 869f470f2..e97e84c97 100644 --- a/pumpkin/src/proxy/velocity.rs +++ b/pumpkin/src/proxy/velocity.rs @@ -62,7 +62,7 @@ pub fn receive_plugin_response( } // TODO: no unwrap let addr: SocketAddr = buf.get_string().unwrap().parse().unwrap(); - *client.address.lock().unwrap() = addr; + *client.address.lock() = addr; todo!() } else { client.kick("This server requires you to connect with Velocity.") diff --git a/pumpkin/src/server/mod.rs b/pumpkin/src/server/mod.rs index 986842d60..2756f9194 100644 --- a/pumpkin/src/server/mod.rs +++ b/pumpkin/src/server/mod.rs @@ -1,6 +1,7 @@ use base64::{engine::general_purpose, Engine}; use image::GenericImageView; use mio::Token; +use parking_lot::{Mutex, RwLock}; use pumpkin_config::{BasicConfiguration, BASIC_CONFIG}; use pumpkin_core::GameMode; use pumpkin_entity::EntityId; @@ -11,13 +12,11 @@ use pumpkin_protocol::{ }; use pumpkin_world::dimension::Dimension; use std::collections::HashMap; -use std::sync::RwLock; use std::{ io::Cursor, path::Path, sync::{ - atomic::{AtomicI32, Ordering}, - Arc, Mutex, + atomic::{AtomicI32, Ordering}, Arc, }, time::Duration, }; @@ -143,8 +142,7 @@ impl Server { ) -> Option>>> { let open_containers = self .open_containers - .read() - .expect("open_containers is poisoned"); + .read(); open_containers .get(&container_id)? .try_open(player_id) diff --git a/pumpkin/src/world/mod.rs b/pumpkin/src/world/mod.rs index 253fa27b4..11e1bae85 100644 --- a/pumpkin/src/world/mod.rs +++ b/pumpkin/src/world/mod.rs @@ -1,12 +1,13 @@ use std::{ collections::HashMap, - sync::{Arc, Mutex}, + sync::Arc, }; pub mod player_chunker; use mio::Token; use num_traits::ToPrimitive; +use parking_lot::Mutex; use pumpkin_config::BasicConfiguration; use pumpkin_core::math::vector2::Vector2; use pumpkin_entity::{entity_type::EntityType, EntityId}; @@ -45,7 +46,7 @@ impl World { where P: ClientPacket, { - let current_players = self.current_players.lock().unwrap(); + let current_players = self.current_players.lock(); for (_, player) in current_players.iter() { player.client.send_packet(packet); } @@ -56,7 +57,7 @@ impl World { where P: ClientPacket, { - let current_players = self.current_players.lock().unwrap(); + let current_players = self.current_players.lock(); for (_, player) in current_players.iter().filter(|c| !except.contains(c.0)) { player.client.send_packet(packet); } @@ -65,7 +66,7 @@ impl World { pub async fn spawn_player(&self, base_config: &BasicConfiguration, player: Arc) { // This code follows the vanilla packet order let entity_id = player.entity_id(); - let gamemode = player.gamemode.lock().unwrap(); + let gamemode = player.gamemode.load(); log::debug!("spawning player, entity id {}", entity_id); // login packet for our new player @@ -126,7 +127,6 @@ impl World { for (_, playerr) in self .current_players .lock() - .unwrap() .iter() .filter(|(c, _)| **c != player.client.token) { @@ -173,12 +173,11 @@ impl World { for (_, existing_player) in self .current_players .lock() - .unwrap() .iter() .filter(|c| c.0 != &token) { let entity = &existing_player.entity; - let pos = entity.pos.lock().unwrap(); + let pos = entity.pos.load(); let gameprofile = &existing_player.gameprofile; player.client.send_packet(&CSpawnEntity::new( existing_player.entity_id().into(), @@ -187,9 +186,9 @@ impl World { pos.x, pos.y, pos.z, - *entity.yaw.lock().unwrap(), - *entity.pitch.lock().unwrap(), - *entity.head_yaw.lock().unwrap(), + entity.yaw.load(), + entity.pitch.load(), + entity.head_yaw.load(), 0.into(), 0.0, 0.0, @@ -198,7 +197,7 @@ impl World { } // entity meta data // set skin parts - if let Some(config) = player.client.config.lock().unwrap().as_ref() { + if let Some(config) = player.client.config.lock().as_ref() { let packet = CSetEntityMetadata::new( entity_id.into(), Metadata::new(17, VarInt(0), config.skin_parts), @@ -221,7 +220,7 @@ impl World { let closed = client.closed.load(std::sync::atomic::Ordering::Relaxed); let chunks = Arc::new(chunks); tokio::task::spawn_blocking(move || { - level.lock().unwrap().fetch_chunks(&chunks, sender, closed) + level.lock().fetch_chunks(&chunks, sender, closed) }); while let Some(chunk_data) = chunk_receiver.recv().await { @@ -254,7 +253,6 @@ impl World { for (_, player) in self .current_players .lock() - .unwrap() .iter() .filter(|c| c.0 != &from.client.token) { @@ -266,13 +264,12 @@ impl World { } pub fn add_player(&self, token: Token, player: Arc) { - self.current_players.lock().unwrap().insert(token, player); + self.current_players.lock().insert(token, player); } pub fn remove_player(&self, player: &Player) { self.current_players .lock() - .unwrap() .remove(&player.client.token) .unwrap(); let uuid = player.gameprofile.id; diff --git a/pumpkin/src/world/player_chunker.rs b/pumpkin/src/world/player_chunker.rs index 0a8464883..d6fb893aa 100644 --- a/pumpkin/src/world/player_chunker.rs +++ b/pumpkin/src/world/player_chunker.rs @@ -15,16 +15,15 @@ fn get_view_distance(player: &Player) -> i8 { player .config .lock() - .unwrap() .view_distance .clamp(2, BASIC_CONFIG.view_distance as i8) } pub async fn player_join(world: &World, player: Arc) { - let new_watched = chunk_section_from_pos(&player.entity.block_pos.lock().unwrap()); - let mut watched_section = player.watched_section.lock().unwrap(); - *watched_section = new_watched; - let chunk_pos = player.entity.chunk_pos.lock().unwrap(); + let new_watched = chunk_section_from_pos(&player.entity.block_pos.load()); + player.watched_section.store(new_watched); + let watched_section = new_watched; + let chunk_pos = player.entity.chunk_pos.load(); player.client.send_packet(&CCenterChunk { chunk_x: chunk_pos.x.into(), chunk_z: chunk_pos.z.into(), @@ -58,10 +57,10 @@ pub async fn player_join(world: &World, player: Arc) { } pub async fn update_position(entity: &Entity, player: &Player) { - let mut current_watched = player.watched_section.lock().unwrap(); - let new_watched = chunk_section_from_pos(&entity.block_pos.lock().unwrap()); - if *current_watched != new_watched { - let chunk_pos = entity.chunk_pos.lock().unwrap(); + let current_watched = player.watched_section.load(); + let new_watched = chunk_section_from_pos(&entity.block_pos.load()); + if current_watched != new_watched { + let chunk_pos = entity.chunk_pos.load(); player.client.send_packet(&CCenterChunk { chunk_x: chunk_pos.x.into(), chunk_z: chunk_pos.z.into(), @@ -74,7 +73,7 @@ pub async fn update_position(entity: &Entity, player: &Player) { ); let new_cylindrical = Cylindrical::new(Vector2::new(chunk_pos.x, chunk_pos.z), view_distance); - *current_watched = new_watched; + player.watched_section.store(new_watched); let mut loading_chunks = Vec::new(); Cylindrical::for_each_changed_chunk( old_cylindrical, From d176fbd37e546ff9e8a775721bd1c850c0f59633 Mon Sep 17 00:00:00 2001 From: Asurar0 Date: Wed, 11 Sep 2024 22:56:03 +0200 Subject: [PATCH 09/65] fix fmt --- pumpkin-core/src/math/vector3.rs | 2 -- pumpkin-inventory/src/open_container.rs | 2 +- pumpkin-world/src/level.rs | 1 - pumpkin/src/client/client_packet.rs | 2 +- pumpkin/src/client/container.rs | 23 ++++++----------------- pumpkin/src/client/mod.rs | 7 +++---- pumpkin/src/client/player_packet.rs | 18 ++++++++---------- pumpkin/src/commands/cmd_echest.rs | 4 +--- pumpkin/src/server/mod.rs | 7 +++---- pumpkin/src/world/mod.rs | 16 +++------------- 10 files changed, 26 insertions(+), 56 deletions(-) diff --git a/pumpkin-core/src/math/vector3.rs b/pumpkin-core/src/math/vector3.rs index 00cbd06b5..d8f0b3916 100644 --- a/pumpkin-core/src/math/vector3.rs +++ b/pumpkin-core/src/math/vector3.rs @@ -93,7 +93,6 @@ impl Neg for Vector3 { } impl From<(T, T, T)> for Vector3 { - #[inline(always)] fn from((x, y, z): (T, T, T)) -> Self { Vector3 { x, y, z } @@ -101,7 +100,6 @@ impl From<(T, T, T)> for Vector3 { } impl From> for (T, T, T) { - #[inline(always)] fn from(vector: Vector3) -> Self { (vector.x, vector.y, vector.z) diff --git a/pumpkin-inventory/src/open_container.rs b/pumpkin-inventory/src/open_container.rs index 8ba82ac89..261ba3b74 100644 --- a/pumpkin-inventory/src/open_container.rs +++ b/pumpkin-inventory/src/open_container.rs @@ -1,7 +1,7 @@ use crate::{Container, WindowType}; +use parking_lot::Mutex; use pumpkin_world::item::ItemStack; use std::sync::Arc; -use parking_lot::Mutex; pub struct OpenContainer { players: Vec, diff --git a/pumpkin-world/src/level.rs b/pumpkin-world/src/level.rs index f19648414..e44448bb8 100644 --- a/pumpkin-world/src/level.rs +++ b/pumpkin-world/src/level.rs @@ -186,7 +186,6 @@ impl Level { .blocking_send(Ok(data.clone())) .expect("Failed sending ChunkData."); loaded_chunks.insert(at, data); - }) } diff --git a/pumpkin/src/client/client_packet.rs b/pumpkin/src/client/client_packet.rs index e3b3bd754..45a016f02 100644 --- a/pumpkin/src/client/client_packet.rs +++ b/pumpkin/src/client/client_packet.rs @@ -238,7 +238,7 @@ impl Client { _server: &Arc, client_information: SClientInformationConfig, ) { - dbg!("got client settings"); + dbg!("got client settings"); *self.config.lock() = Some(PlayerConfig { locale: client_information.locale, view_distance: client_information.view_distance, diff --git a/pumpkin/src/client/container.rs b/pumpkin/src/client/container.rs index 0df08864f..dc15a21cd 100644 --- a/pumpkin/src/client/container.rs +++ b/pumpkin/src/client/container.rs @@ -27,9 +27,7 @@ impl Player { .store(0, std::sync::atomic::Ordering::Relaxed); let total_opened_containers = inventory.total_opened_containers; let container = self.get_open_container(server); - let mut container = container - .as_ref() - .map(|container| container.lock()); + let mut container = container.as_ref().map(|container| container.lock()); let menu_protocol_id = (*pumpkin_world::global_registry::REGISTRY .get("minecraft:menu") .unwrap() @@ -112,9 +110,7 @@ impl Player { packet: SClickContainer, ) -> Result<(), InventoryError> { let opened_container = self.get_open_container(server); - let mut opened_container = opened_container - .as_ref() - .map(|container| container.lock()); + let mut opened_container = opened_container.as_ref().map(|container| container.lock()); let drag_handler = &server.drag_handler; let state_id = self @@ -217,14 +213,10 @@ impl Player { match slot { container_click::Slot::Normal(slot) => { let mut carried_item = self.carried_item.load(); - let res = container.handle_item_change( - &mut carried_item, - slot, - mouse_click, - ); + let res = container.handle_item_change(&mut carried_item, slot, mouse_click); self.carried_item.store(carried_item); res - }, + } container_click::Slot::OutsideInventory => Ok(()), } } @@ -362,8 +354,7 @@ impl Player { .unwrap_or(player_id as u64); match mouse_drag_state { MouseDragState::Start(drag_type) => { - if drag_type == MouseDragType::Middle - && self.gamemode.load() != GameMode::Creative + if drag_type == MouseDragType::Middle && self.gamemode.load() != GameMode::Creative { Err(InventoryError::PermissionError)? } @@ -389,9 +380,7 @@ impl Player { async fn get_current_players_in_container(&self, server: &Server) -> Vec> { let player_ids = { - let open_containers = server - .open_containers - .read(); + let open_containers = server.open_containers.read(); open_containers .get(&self.open_container.load().unwrap()) .unwrap() diff --git a/pumpkin/src/client/mod.rs b/pumpkin/src/client/mod.rs index 186b1a2ea..b6201918a 100644 --- a/pumpkin/src/client/mod.rs +++ b/pumpkin/src/client/mod.rs @@ -2,7 +2,8 @@ use std::{ io::{self, Write}, net::SocketAddr, sync::{ - atomic::{AtomicBool, AtomicI32}, Arc, + atomic::{AtomicBool, AtomicI32}, + Arc, }, }; @@ -128,9 +129,7 @@ impl Client { // Compression threshold, Compression level pub fn set_compression(&self, compression: Option<(u32, u32)>) { - self.dec - .lock() - .set_compression(compression.map(|v| v.0)); + self.dec.lock().set_compression(compression.map(|v| v.0)); self.enc.lock().set_compression(compression); } diff --git a/pumpkin/src/client/player_packet.rs b/pumpkin/src/client/player_packet.rs index 549f628f5..0cb74053c 100644 --- a/pumpkin/src/client/player_packet.rs +++ b/pumpkin/src/client/player_packet.rs @@ -155,7 +155,7 @@ impl Player { ); let entity_id = entity.entity_id; - let Vector3 {x, y, z } = pos; + let Vector3 { x, y, z } = pos; let (lastx, lasty, lastz) = (last_position.x, last_position.y, last_position.z); let yaw = modulus(entity.yaw.load() * 256.0 / 360.0, 256.0); let pitch = modulus(entity.pitch.load() * 256.0 / 360.0, 256.0); @@ -404,7 +404,9 @@ impl Player { victem_velocity.z as f32, ); let velocity = entity.velocity.load(); - victem_entity.velocity.store(velocity.multiply(0.6, 1.0, 0.6)); + victem_entity + .velocity + .store(velocity.multiply(0.6, 1.0, 0.6)); victem_entity.velocity.store(saved_velo); player.client.send_packet(packet); @@ -556,11 +558,9 @@ impl Player { if self.gamemode.load() != GameMode::Creative { return Err(InventoryError::PermissionError); } - self.inventory.lock().set_slot( - packet.slot as usize, - packet.clicked_item.to_item(), - false, - ) + self.inventory + .lock() + .set_slot(packet.slot as usize, packet.clicked_item.to_item(), false) } // TODO: @@ -574,9 +574,7 @@ impl Player { .store(0, std::sync::atomic::Ordering::Relaxed); let open_container = self.open_container.load(); if let Some(id) = open_container { - let mut open_containers = server - .open_containers - .write(); + let mut open_containers = server.open_containers.write(); if let Some(container) = open_containers.get_mut(&id) { container.remove_player(self.entity_id()) } diff --git a/pumpkin/src/commands/cmd_echest.rs b/pumpkin/src/commands/cmd_echest.rs index 2d234e3cf..690fff41c 100644 --- a/pumpkin/src/commands/cmd_echest.rs +++ b/pumpkin/src/commands/cmd_echest.rs @@ -13,9 +13,7 @@ pub(crate) fn init_command_tree<'a>() -> CommandTree<'a> { let entity_id = player.entity_id(); player.open_container.store(Some(0)); { - let mut open_containers = server - .open_containers - .write(); + let mut open_containers = server.open_containers.write(); match open_containers.get_mut(&0) { Some(ender_chest) => { ender_chest.add_player(entity_id); diff --git a/pumpkin/src/server/mod.rs b/pumpkin/src/server/mod.rs index 2756f9194..04949582a 100644 --- a/pumpkin/src/server/mod.rs +++ b/pumpkin/src/server/mod.rs @@ -16,7 +16,8 @@ use std::{ io::Cursor, path::Path, sync::{ - atomic::{AtomicI32, Ordering}, Arc, + atomic::{AtomicI32, Ordering}, + Arc, }, time::Duration, }; @@ -140,9 +141,7 @@ impl Server { player_id: EntityId, container_id: u64, ) -> Option>>> { - let open_containers = self - .open_containers - .read(); + let open_containers = self.open_containers.read(); open_containers .get(&container_id)? .try_open(player_id) diff --git a/pumpkin/src/world/mod.rs b/pumpkin/src/world/mod.rs index 11e1bae85..49d6720b5 100644 --- a/pumpkin/src/world/mod.rs +++ b/pumpkin/src/world/mod.rs @@ -1,7 +1,4 @@ -use std::{ - collections::HashMap, - sync::Arc, -}; +use std::{collections::HashMap, sync::Arc}; pub mod player_chunker; @@ -170,12 +167,7 @@ impl World { ); // spawn players for our client let token = player.client.token; - for (_, existing_player) in self - .current_players - .lock() - .iter() - .filter(|c| c.0 != &token) - { + for (_, existing_player) in self.current_players.lock().iter().filter(|c| c.0 != &token) { let entity = &existing_player.entity; let pos = entity.pos.load(); let gameprofile = &existing_player.gameprofile; @@ -219,9 +211,7 @@ impl World { let level = self.level.clone(); let closed = client.closed.load(std::sync::atomic::Ordering::Relaxed); let chunks = Arc::new(chunks); - tokio::task::spawn_blocking(move || { - level.lock().fetch_chunks(&chunks, sender, closed) - }); + tokio::task::spawn_blocking(move || level.lock().fetch_chunks(&chunks, sender, closed)); while let Some(chunk_data) = chunk_receiver.recv().await { // dbg!(chunk_pos); From 2e99877a6cc15ce8024bd7dd1c7b21ac3f5f69cd Mon Sep 17 00:00:00 2001 From: Asurar0 Date: Wed, 11 Sep 2024 23:01:09 +0200 Subject: [PATCH 10/65] fix warnings --- pumpkin-inventory/src/lib.rs | 1 - pumpkin-inventory/src/player.rs | 1 - pumpkin/src/client/mod.rs | 5 +---- 3 files changed, 1 insertion(+), 6 deletions(-) diff --git a/pumpkin-inventory/src/lib.rs b/pumpkin-inventory/src/lib.rs index db1de481f..e1e554fae 100644 --- a/pumpkin-inventory/src/lib.rs +++ b/pumpkin-inventory/src/lib.rs @@ -1,6 +1,5 @@ use crate::container_click::MouseClick; use crate::player::PlayerInventory; -use crossbeam::atomic::AtomicCell; use num_derive::{FromPrimitive, ToPrimitive}; use pumpkin_world::item::ItemStack; diff --git a/pumpkin-inventory/src/player.rs b/pumpkin-inventory/src/player.rs index 09f8b2ff9..999c9a9e1 100644 --- a/pumpkin-inventory/src/player.rs +++ b/pumpkin-inventory/src/player.rs @@ -2,7 +2,6 @@ use std::sync::atomic::AtomicU32; use crate::container_click::MouseClick; use crate::{handle_item_change, Container, InventoryError, WindowType}; -use crossbeam::atomic::AtomicCell; use pumpkin_world::item::ItemStack; pub struct PlayerInventory { diff --git a/pumpkin/src/client/mod.rs b/pumpkin/src/client/mod.rs index b6201918a..bcdb72255 100644 --- a/pumpkin/src/client/mod.rs +++ b/pumpkin/src/client/mod.rs @@ -179,10 +179,7 @@ impl Client { ) -> Result<(), DeserializerError> { // TODO: handle each packet's Error instead of calling .unwrap() let bytebuf = &mut packet.bytebuf; - let locked_state = self.connection_state.load(); - let state = locked_state.clone(); - drop(locked_state); - match state { + match self.connection_state.load() { pumpkin_protocol::ConnectionState::HandShake => match packet.id.0 { SHandShake::PACKET_ID => { self.handle_handshake(server, SHandShake::read(bytebuf)?); From e49c46df322195c306fa232fe7befc8ebd192b3d Mon Sep 17 00:00:00 2001 From: kralverde Date: Wed, 11 Sep 2024 17:37:12 -0400 Subject: [PATCH 11/65] split code up --- pumpkin-world/src/world_gen/mod.rs | 4 - pumpkin-world/src/world_gen/noise/mod.rs | 63 ++ pumpkin-world/src/world_gen/noise/perlin.rs | 571 +++++++++++++++ .../world_gen/{noise.rs => noise/simplex.rs} | 651 +----------------- 4 files changed, 643 insertions(+), 646 deletions(-) create mode 100644 pumpkin-world/src/world_gen/noise/mod.rs create mode 100644 pumpkin-world/src/world_gen/noise/perlin.rs rename pumpkin-world/src/world_gen/{noise.rs => noise/simplex.rs} (52%) diff --git a/pumpkin-world/src/world_gen/mod.rs b/pumpkin-world/src/world_gen/mod.rs index 693132f5c..4712a36f3 100644 --- a/pumpkin-world/src/world_gen/mod.rs +++ b/pumpkin-world/src/world_gen/mod.rs @@ -14,7 +14,3 @@ pub fn get_world_gen(seed: Seed) -> Box { // TODO decide which WorldGenerator to pick based on config. Box::new(PlainsGenerator::new(seed)) } - -pub struct ChunkRandom { - sample_count: i32, -} diff --git a/pumpkin-world/src/world_gen/noise/mod.rs b/pumpkin-world/src/world_gen/noise/mod.rs new file mode 100644 index 000000000..206a47a30 --- /dev/null +++ b/pumpkin-world/src/world_gen/noise/mod.rs @@ -0,0 +1,63 @@ +mod perlin; +mod simplex; + +pub fn lerp(delta: f64, start: f64, end: f64) -> f64 { + start + delta * (end - start) +} + +pub fn lerp2(delta_x: f64, delta_y: f64, x0y0: f64, x1y0: f64, x0y1: f64, x1y1: f64) -> f64 { + lerp( + delta_y, + lerp(delta_x, x0y0, x1y0), + lerp(delta_x, x0y1, x1y1), + ) +} + +pub fn lerp3( + delta_x: f64, + delta_y: f64, + delta_z: f64, + x0y0z0: f64, + x1y0z0: f64, + x0y1z0: f64, + x1y1z0: f64, + x0y0z1: f64, + x1y0z1: f64, + x0y1z1: f64, + x1y1z1: f64, +) -> f64 { + lerp( + delta_z, + lerp2(delta_x, delta_y, x0y0z0, x1y0z0, x0y1z0, x1y1z0), + lerp2(delta_x, delta_y, x0y0z1, x1y0z1, x0y1z1, x1y1z1), + ) +} + +struct Gradient { + x: i32, + y: i32, + z: i32, +} + +const GRADIENTS: [Gradient; 16] = [ + Gradient { x: 1, y: 1, z: 0 }, + Gradient { x: -1, y: 1, z: 0 }, + Gradient { x: 1, y: -1, z: 0 }, + Gradient { x: -1, y: -1, z: 0 }, + Gradient { x: 1, y: 0, z: 1 }, + Gradient { x: -1, y: 0, z: 1 }, + Gradient { x: 1, y: 0, z: -1 }, + Gradient { x: -1, y: 0, z: -1 }, + Gradient { x: 0, y: 1, z: 1 }, + Gradient { x: 0, y: -1, z: 1 }, + Gradient { x: 0, y: 1, z: -1 }, + Gradient { x: 0, y: -1, z: -1 }, + Gradient { x: 1, y: 1, z: 0 }, + Gradient { x: 0, y: -1, z: 1 }, + Gradient { x: -1, y: 1, z: 0 }, + Gradient { x: 0, y: -1, z: -1 }, +]; + +fn dot(gradient: &Gradient, x: f64, y: f64, z: f64) -> f64 { + gradient.x as f64 * x + gradient.y as f64 * y + gradient.z as f64 * z +} diff --git a/pumpkin-world/src/world_gen/noise/perlin.rs b/pumpkin-world/src/world_gen/noise/perlin.rs new file mode 100644 index 000000000..a39caa6bc --- /dev/null +++ b/pumpkin-world/src/world_gen/noise/perlin.rs @@ -0,0 +1,571 @@ +use pumpkin_core::random::Random; + +use super::{dot, lerp3, GRADIENTS}; + +pub struct PerlinNoiseSampler { + permutation: Box<[u8]>, + x_origin: f64, + y_origin: f64, + z_origin: f64, +} + +impl PerlinNoiseSampler { + pub fn new(random: &mut impl Random) -> Self { + let x_origin = random.next_f64() * 256f64; + let y_origin = random.next_f64() * 256f64; + let z_origin = random.next_f64() * 256f64; + + let mut permutation = [0u8; 256]; + + permutation + .iter_mut() + .enumerate() + .for_each(|(i, x)| *x = i as u8); + + for i in 0..256 { + let j = random.next_bounded_i32((256 - i) as i32) as usize; + permutation.swap(i, i + j); + } + + Self { + permutation: Box::new(permutation), + x_origin, + y_origin, + z_origin, + } + } + + pub fn sample_flat_y(&self, x: f64, y: f64, z: f64) -> f64 { + self.sample_no_fade(x, y, z, 0f64, 0f64) + } + + pub fn sample_no_fade(&self, x: f64, y: f64, z: f64, y_scale: f64, y_max: f64) -> f64 { + let trans_x = x + self.x_origin; + let trans_y = y + self.y_origin; + let trans_z = z + self.z_origin; + + let x_int = trans_x.floor() as i32; + let y_int = trans_y.floor() as i32; + let z_int = trans_z.floor() as i32; + + let x_dec = trans_x - x_int as f64; + let y_dec = trans_y - y_int as f64; + let z_dec = trans_z - z_int as f64; + + let y_noise = if y_scale != 0f64 { + let raw_y_dec = if y_max >= 0f64 && y_max < y_dec { + y_max + } else { + y_dec + }; + (raw_y_dec / y_scale + 1.0E-7f32 as f64).floor() * y_scale + } else { + 0f64 + }; + + self.sample(x_int, y_int, z_int, x_dec, y_dec - y_noise, z_dec, y_dec) + } + + fn grad(hash: i32, x: f64, y: f64, z: f64) -> f64 { + dot(&GRADIENTS[(hash & 15) as usize], x, y, z) + } + + fn perlin_fade(value: f64) -> f64 { + value * value * value * (value * (value * 6f64 - 15f64) + 10f64) + } + + fn map(&self, input: i32) -> i32 { + (self.permutation[(input & 0xFF) as usize] & 0xFF) as i32 + } + + #[allow(clippy::too_many_arguments)] + fn sample( + &self, + x: i32, + y: i32, + z: i32, + local_x: f64, + local_y: f64, + local_z: f64, + fade_local_y: f64, + ) -> f64 { + let i = self.map(x); + let j = self.map(x.wrapping_add(1)); + let k = self.map(i.wrapping_add(y)); + + let l = self.map(i.wrapping_add(y).wrapping_add(1)); + let m = self.map(j.wrapping_add(y)); + let n = self.map(j.wrapping_add(y).wrapping_add(1)); + + let d = Self::grad(self.map(k.wrapping_add(z)), local_x, local_y, local_z); + let e = Self::grad( + self.map(m.wrapping_add(z)), + local_x - 1f64, + local_y, + local_z, + ); + let f = Self::grad( + self.map(l.wrapping_add(z)), + local_x, + local_y - 1f64, + local_z, + ); + let g = Self::grad( + self.map(n.wrapping_add(z)), + local_x - 1f64, + local_y - 1f64, + local_z, + ); + let h = Self::grad( + self.map(k.wrapping_add(z).wrapping_add(1)), + local_x, + local_y, + local_z - 1f64, + ); + let o = Self::grad( + self.map(m.wrapping_add(z).wrapping_add(1)), + local_x - 1f64, + local_y, + local_z - 1f64, + ); + let p = Self::grad( + self.map(l.wrapping_add(z).wrapping_add(1)), + local_x, + local_y - 1f64, + local_z - 1f64, + ); + let q = Self::grad( + self.map(n.wrapping_add(z).wrapping_add(1)), + local_x - 1f64, + local_y - 1f64, + local_z - 1f64, + ); + let r = Self::perlin_fade(local_x); + let s = Self::perlin_fade(fade_local_y); + let t = Self::perlin_fade(local_z); + + lerp3(r, s, t, d, e, f, g, h, o, p, q) + } +} + +#[cfg(test)] +mod perlin_noise_sampler_test { + use std::ops::Deref; + + use pumpkin_core::random::{xoroshiro128::Xoroshiro, Random}; + + use crate::world_gen::noise::perlin::PerlinNoiseSampler; + + #[test] + fn test_create() { + let mut rand = Xoroshiro::from_seed(111); + assert_eq!(rand.next_i32(), -1467508761); + + let sampler = PerlinNoiseSampler::new(&mut rand); + assert_eq!(sampler.x_origin, 48.58072036717974); + assert_eq!(sampler.y_origin, 110.73235882678037); + assert_eq!(sampler.z_origin, 65.26438852860176); + + let permutation: [u8; 256] = [ + 159, 113, 41, 143, 203, 123, 95, 177, 25, 79, 229, 219, 194, 60, 130, 14, 83, 99, 24, + 202, 207, 232, 167, 152, 220, 201, 29, 235, 87, 147, 74, 160, 155, 97, 111, 31, 85, + 205, 115, 50, 13, 171, 77, 237, 149, 116, 209, 174, 169, 109, 221, 9, 166, 84, 54, 216, + 121, 106, 211, 16, 69, 244, 65, 192, 183, 146, 124, 37, 56, 45, 193, 158, 126, 217, 36, + 255, 162, 163, 230, 103, 63, 90, 191, 214, 20, 138, 32, 39, 238, 67, 64, 105, 250, 140, + 148, 114, 68, 75, 200, 161, 239, 125, 227, 199, 101, 61, 175, 107, 129, 240, 170, 51, + 139, 86, 186, 145, 212, 178, 30, 251, 89, 226, 120, 153, 47, 141, 233, 2, 179, 236, 1, + 19, 98, 21, 164, 108, 11, 23, 91, 204, 119, 88, 165, 195, 168, 26, 48, 206, 128, 6, 52, + 118, 110, 180, 197, 231, 117, 7, 3, 135, 224, 58, 82, 78, 4, 59, 222, 18, 72, 57, 150, + 43, 246, 100, 122, 112, 53, 133, 93, 17, 27, 210, 142, 234, 245, 80, 22, 46, 185, 172, + 71, 248, 33, 173, 76, 35, 40, 92, 228, 127, 254, 70, 42, 208, 73, 104, 187, 62, 154, + 243, 189, 241, 34, 66, 249, 94, 8, 12, 134, 132, 102, 242, 196, 218, 181, 28, 38, 15, + 151, 157, 247, 223, 198, 55, 188, 96, 0, 182, 49, 190, 156, 10, 215, 252, 131, 137, + 184, 176, 136, 81, 44, 213, 253, 144, 225, 5, + ]; + assert_eq!(sampler.permutation.deref(), permutation); + } + + #[test] + fn test_no_y() { + let mut rand = Xoroshiro::from_seed(111); + assert_eq!(rand.next_i32(), -1467508761); + let sampler = PerlinNoiseSampler::new(&mut rand); + + let values = [ + ( + ( + -3.134738528791615E8, + 5.676610095659718E7, + 2.011711832498507E8, + ), + 0.38582139614602945, + ), + ( + (-1369026.560586418, 3.957311252810864E8, 6.797037355570006E8), + 0.15777501333157193, + ), + ( + ( + 6.439373693833767E8, + -3.36218773041759E8, + -3.265494249695775E8, + ), + -0.2806135912409497, + ), + ( + ( + 1.353820060118252E8, + -3.204701624793043E8, + -4.612474746056331E8, + ), + -0.15052865500837787, + ), + ( + ( + -6906850.625560562, + 1.0153663948838013E8, + 2.4923185478305575E8, + ), + -0.3079300694558318, + ), + ( + ( + -7.108376621385525E7, + -2.029413580824217E8, + 2.5164602748045415E8, + ), + 0.03051312670440398, + ), + ( + ( + 1.0591429119126628E8, + -4.7911044364543396E8, + -2918719.2277242197, + ), + -0.11775123159138573, + ), + ( + ( + 4.04615501401398E7, + -3.074409286586152E8, + 5.089118769334092E7, + ), + 0.08763639340713025, + ), + ( + ( + -4.8645283544246924E8, + -3.922570151180015E8, + 2.3741632952563038E8, + ), + 0.08857245482456311, + ), + ( + ( + 2.861710031285905E8, + -1.8973201372718483E8, + -3.2653143323982143E8, + ), + -0.2378339698793312, + ), + ( + ( + 2.885407603819252E8, + -3.358708100884505E7, + -1.4480399660676318E8, + ), + -0.46661747461279457, + ), + ( + ( + 3.6548491156354237E8, + 7.995429702025633E7, + 2.509991661702412E8, + ), + 0.1671543972176835, + ), + ( + ( + 1.3298684552869435E8, + 3.6743804723880893E8, + 5.791092458225288E7, + ), + -0.2704070746642889, + ), + ( + ( + -1.3123184148036437E8, + -2.722300890805201E8, + 2.1601883778132245E7, + ), + 0.05049887915906969, + ), + ( + ( + -5.56047682304707E8, + 3.554803693060646E8, + 3.1647392358159083E8, + ), + -0.21178547899422662, + ), + ( + ( + 5.638216625134594E8, + -2.236907346192737E8, + -5.0562852022285646E8, + ), + 0.03351245780858128, + ), + ( + ( + -5.436956979127073E7, + -1.129261611506945E8, + -1.7909512156895646E8, + ), + 0.31670010349494726, + ), + ( + ( + 1.0915760091641709E8, + 1.932642099859593E7, + -3.405060533753616E8, + ), + -0.13987439655026918, + ), + ( + ( + -6.73911758014991E8, + -2.2147483413687566E8, + -4.531457195005102E7, + ), + 0.07824440437151846, + ), + ( + ( + -2.4827386778136212E8, + -2.6640208832089204E8, + -3.354675096522197E8, + ), + -0.2989735599541437, + ), + ]; + + for ((x, y, z), sample) in values { + assert_eq!(sampler.sample_flat_y(x, y, z), sample); + } + } + + #[test] + fn test_no_fade() { + let mut rand = Xoroshiro::from_seed(111); + assert_eq!(rand.next_i32(), -1467508761); + let sampler = PerlinNoiseSampler::new(&mut rand); + + let values = [ + ( + ( + -3.134738528791615E8, + 5.676610095659718E7, + 2.011711832498507E8, + -1369026.560586418, + 3.957311252810864E8, + ), + 23234.47859421248, + ), + ( + ( + 6.797037355570006E8, + 6.439373693833767E8, + -3.36218773041759E8, + -3.265494249695775E8, + 1.353820060118252E8, + ), + -0.016403984198221984, + ), + ( + ( + -3.204701624793043E8, + -4.612474746056331E8, + -6906850.625560562, + 1.0153663948838013E8, + 2.4923185478305575E8, + ), + 0.3444286491766397, + ), + ( + ( + -7.108376621385525E7, + -2.029413580824217E8, + 2.5164602748045415E8, + 1.0591429119126628E8, + -4.7911044364543396E8, + ), + 0.03051312670440398, + ), + ( + ( + -2918719.2277242197, + 4.04615501401398E7, + -3.074409286586152E8, + 5.089118769334092E7, + -4.8645283544246924E8, + ), + 0.3434020232968479, + ), + ( + ( + -3.922570151180015E8, + 2.3741632952563038E8, + 2.861710031285905E8, + -1.8973201372718483E8, + -3.2653143323982143E8, + ), + -0.07935517045771859, + ), + ( + ( + 2.885407603819252E8, + -3.358708100884505E7, + -1.4480399660676318E8, + 3.6548491156354237E8, + 7.995429702025633E7, + ), + -0.46661747461279457, + ), + ( + ( + 2.509991661702412E8, + 1.3298684552869435E8, + 3.6743804723880893E8, + 5.791092458225288E7, + -1.3123184148036437E8, + ), + 0.0723439870279631, + ), + ( + ( + -2.722300890805201E8, + 2.1601883778132245E7, + -5.56047682304707E8, + 3.554803693060646E8, + 3.1647392358159083E8, + ), + -0.656560662515624, + ), + ( + ( + 5.638216625134594E8, + -2.236907346192737E8, + -5.0562852022285646E8, + -5.436956979127073E7, + -1.129261611506945E8, + ), + 0.03351245780858128, + ), + ( + ( + -1.7909512156895646E8, + 1.0915760091641709E8, + 1.932642099859593E7, + -3.405060533753616E8, + -6.73911758014991E8, + ), + -0.2089142558681482, + ), + ( + ( + -2.2147483413687566E8, + -4.531457195005102E7, + -2.4827386778136212E8, + -2.6640208832089204E8, + -3.354675096522197E8, + ), + 0.38250837565598395, + ), + ( + ( + 3.618095500266467E8, + -1.785261966631494E8, + 8.855575989580283E7, + -1.3702508894700047E8, + -3.564818414428105E8, + ), + 0.00883370523171791, + ), + ( + ( + 3.585592594479808E7, + 1.8822208340571395E8, + -386327.524558296, + -2.613548000006699E8, + 1995562.4304017993, + ), + -0.27653878487738676, + ), + ( + ( + 3.0800276873619422E7, + 1.166750302259058E7, + 8.502636255675305E7, + 4.347409652503064E8, + 1.0678086363325526E8, + ), + -0.13800758751097497, + ), + ( + ( + -2.797805968820768E8, + 9.446376468140173E7, + 2.2821543438325477E8, + -4.8176550369786626E8, + 7.316871126959312E7, + ), + 0.05505478945301634, + ), + ( + ( + -2.236596113898912E7, + 1.5296478602495643E8, + 3.903966235164034E8, + 9.40479475527148E7, + 1.0948229366673347E8, + ), + 0.1158678618158655, + ), + ( + ( + 3.5342596632385695E8, + 3.1584773170834744E8, + -2.1860087172846535E8, + -1.8126626716239208E8, + -2.5263456116162892E7, + ), + -0.354953975313882, + ), + ( + ( + -1.2711958434031656E8, + -4.541988855460623E7, + -1.375878074907788E8, + 6.72693784001799E7, + 6815739.665531283, + ), + -0.23849179316215247, + ), + ( + ( + 1.2660906027019228E8, + -3.3769609799741164E7, + -3.4331505330046E8, + -6.663866659430536E7, + -1.6603843763414428E8, + ), + 0.07974650858448407, + ), + ]; + + for ((x, y, z, y_scale, y_max), sample) in values { + assert_eq!(sampler.sample_no_fade(x, y, z, y_scale, y_max), sample); + } + } +} diff --git a/pumpkin-world/src/world_gen/noise.rs b/pumpkin-world/src/world_gen/noise/simplex.rs similarity index 52% rename from pumpkin-world/src/world_gen/noise.rs rename to pumpkin-world/src/world_gen/noise/simplex.rs index 0f9fc0f33..6c4567007 100644 --- a/pumpkin-world/src/world_gen/noise.rs +++ b/pumpkin-world/src/world_gen/noise/simplex.rs @@ -1,43 +1,7 @@ use num_traits::Pow; use pumpkin_core::random::{legacy_rand::LegacyRand, Random}; -pub fn lerp(delta: f64, start: f64, end: f64) -> f64 { - start + delta * (end - start) -} - -pub fn lerp2(delta_x: f64, delta_y: f64, x0y0: f64, x1y0: f64, x0y1: f64, x1y1: f64) -> f64 { - lerp( - delta_y, - lerp(delta_x, x0y0, x1y0), - lerp(delta_x, x0y1, x1y1), - ) -} - -pub fn lerp3( - delta_x: f64, - delta_y: f64, - delta_z: f64, - x0y0z0: f64, - x1y0z0: f64, - x0y1z0: f64, - x1y1z0: f64, - x0y0z1: f64, - x1y0z1: f64, - x0y1z1: f64, - x1y1z1: f64, -) -> f64 { - lerp( - delta_z, - lerp2(delta_x, delta_y, x0y0z0, x1y0z0, x0y1z0, x1y1z0), - lerp2(delta_x, delta_y, x0y0z1, x1y0z1, x0y1z1, x1y1z1), - ) -} - -struct Gradient { - x: i32, - y: i32, - z: i32, -} +use super::{dot, GRADIENTS}; pub struct SimplexNoiseSampler { permutation: Box<[u8]>, @@ -47,25 +11,6 @@ pub struct SimplexNoiseSampler { } impl SimplexNoiseSampler { - const GRADIENTS: [Gradient; 16] = [ - Gradient { x: 1, y: 1, z: 0 }, - Gradient { x: -1, y: 1, z: 0 }, - Gradient { x: 1, y: -1, z: 0 }, - Gradient { x: -1, y: -1, z: 0 }, - Gradient { x: 1, y: 0, z: 1 }, - Gradient { x: -1, y: 0, z: 1 }, - Gradient { x: 1, y: 0, z: -1 }, - Gradient { x: -1, y: 0, z: -1 }, - Gradient { x: 0, y: 1, z: 1 }, - Gradient { x: 0, y: -1, z: 1 }, - Gradient { x: 0, y: 1, z: -1 }, - Gradient { x: 0, y: -1, z: -1 }, - Gradient { x: 1, y: 1, z: 0 }, - Gradient { x: 0, y: -1, z: 1 }, - Gradient { x: -1, y: 1, z: 0 }, - Gradient { x: 0, y: -1, z: -1 }, - ]; - const SQRT_3: f64 = 1.7320508075688772f64; const SKEW_FACTOR_2D: f64 = 0.5f64 * (Self::SQRT_3 - 1f64); const UNSKEW_FACTOR_2D: f64 = (3f64 - Self::SQRT_3) / 6f64; @@ -99,17 +44,13 @@ impl SimplexNoiseSampler { self.permutation[(input & 0xFF) as usize] as i32 } - fn dot(gradient: &Gradient, x: f64, y: f64, z: f64) -> f64 { - gradient.x as f64 * x + gradient.y as f64 * y + gradient.z as f64 * z - } - fn grad(gradient_index: usize, x: f64, y: f64, z: f64, distance: f64) -> f64 { let d = distance - x * x - y * y - z * z; if d < 0f64 { 0f64 } else { let d = d * d; - d * d * Self::dot(&Self::GRADIENTS[gradient_index], x, y, z) + d * d * dot(&GRADIENTS[gradient_index], x, y, z) } } @@ -229,164 +170,13 @@ impl SimplexNoiseSampler { } } -pub struct PerlinNoiseSampler { - permutation: Box<[u8]>, - x_origin: f64, - y_origin: f64, - z_origin: f64, -} - -impl PerlinNoiseSampler { - pub fn new(random: &mut impl Random) -> Self { - let x_origin = random.next_f64() * 256f64; - let y_origin = random.next_f64() * 256f64; - let z_origin = random.next_f64() * 256f64; - - let mut permutation = [0u8; 256]; - - permutation - .iter_mut() - .enumerate() - .for_each(|(i, x)| *x = i as u8); - - for i in 0..256 { - let j = random.next_bounded_i32((256 - i) as i32) as usize; - permutation.swap(i, i + j); - } - - Self { - permutation: Box::new(permutation), - x_origin, - y_origin, - z_origin, - } - } - - pub fn sample_flat_y(&self, x: f64, y: f64, z: f64) -> f64 { - self.sample_no_fade(x, y, z, 0f64, 0f64) - } - - pub fn sample_no_fade(&self, x: f64, y: f64, z: f64, y_scale: f64, y_max: f64) -> f64 { - let trans_x = x + self.x_origin; - let trans_y = y + self.y_origin; - let trans_z = z + self.z_origin; - - let x_int = trans_x.floor() as i32; - let y_int = trans_y.floor() as i32; - let z_int = trans_z.floor() as i32; - - let x_dec = trans_x - x_int as f64; - let y_dec = trans_y - y_int as f64; - let z_dec = trans_z - z_int as f64; - - let y_noise = if y_scale != 0f64 { - let raw_y_dec = if y_max >= 0f64 && y_max < y_dec { - y_max - } else { - y_dec - }; - (raw_y_dec / y_scale + 1.0E-7f32 as f64).floor() * y_scale - } else { - 0f64 - }; - - self.sample(x_int, y_int, z_int, x_dec, y_dec - y_noise, z_dec, y_dec) - } - - fn grad(hash: i32, x: f64, y: f64, z: f64) -> f64 { - SimplexNoiseSampler::dot( - &SimplexNoiseSampler::GRADIENTS[(hash & 15) as usize], - x, - y, - z, - ) - } - - fn perlin_fade(value: f64) -> f64 { - value * value * value * (value * (value * 6f64 - 15f64) + 10f64) - } - - fn map(&self, input: i32) -> i32 { - (self.permutation[(input & 0xFF) as usize] & 0xFF) as i32 - } - - #[allow(clippy::too_many_arguments)] - fn sample( - &self, - x: i32, - y: i32, - z: i32, - local_x: f64, - local_y: f64, - local_z: f64, - fade_local_y: f64, - ) -> f64 { - let i = self.map(x); - let j = self.map(x.wrapping_add(1)); - let k = self.map(i.wrapping_add(y)); - - let l = self.map(i.wrapping_add(y).wrapping_add(1)); - let m = self.map(j.wrapping_add(y)); - let n = self.map(j.wrapping_add(y).wrapping_add(1)); - - let d = Self::grad(self.map(k.wrapping_add(z)), local_x, local_y, local_z); - let e = Self::grad( - self.map(m.wrapping_add(z)), - local_x - 1f64, - local_y, - local_z, - ); - let f = Self::grad( - self.map(l.wrapping_add(z)), - local_x, - local_y - 1f64, - local_z, - ); - let g = Self::grad( - self.map(n.wrapping_add(z)), - local_x - 1f64, - local_y - 1f64, - local_z, - ); - let h = Self::grad( - self.map(k.wrapping_add(z).wrapping_add(1)), - local_x, - local_y, - local_z - 1f64, - ); - let o = Self::grad( - self.map(m.wrapping_add(z).wrapping_add(1)), - local_x - 1f64, - local_y, - local_z - 1f64, - ); - let p = Self::grad( - self.map(l.wrapping_add(z).wrapping_add(1)), - local_x, - local_y - 1f64, - local_z - 1f64, - ); - let q = Self::grad( - self.map(n.wrapping_add(z).wrapping_add(1)), - local_x - 1f64, - local_y - 1f64, - local_z - 1f64, - ); - let r = Self::perlin_fade(local_x); - let s = Self::perlin_fade(fade_local_y); - let t = Self::perlin_fade(local_z); - - lerp3(r, s, t, d, e, f, g, h, o, p, q) - } -} - -struct OctavePerlinNoiseSampler { +pub struct OctaveSimplexNoiseSampler { octave_samplers: Vec>, persistence: f64, lacunarity: f64, } -impl OctavePerlinNoiseSampler { +impl OctaveSimplexNoiseSampler { pub fn new(random: &mut impl Random, octaves: &[i32]) -> Self { let mut octaves = Vec::from_iter(octaves); octaves.sort(); @@ -459,16 +249,16 @@ impl OctavePerlinNoiseSampler { } #[cfg(test)] -mod octave_perlin_noise_sampler_test { +mod octave_simplex_noise_sampler_test { use pumpkin_core::random::{xoroshiro128::Xoroshiro, Random}; - use crate::world_gen::noise::OctavePerlinNoiseSampler; + use crate::world_gen::noise::simplex::OctaveSimplexNoiseSampler; #[test] fn test_new() { let mut rand = Xoroshiro::from_seed(450); assert_eq!(rand.next_i32(), 1394613419); - let sampler = OctavePerlinNoiseSampler::new(&mut rand, &[-1, 1, 0]); + let sampler = OctaveSimplexNoiseSampler::new(&mut rand, &[-1, 1, 0]); assert_eq!(sampler.lacunarity, 2f64); assert_eq!(sampler.persistence, 0.14285714285714285); @@ -496,7 +286,7 @@ mod octave_perlin_noise_sampler_test { fn test_sample() { let mut rand = Xoroshiro::from_seed(450); assert_eq!(rand.next_i32(), 1394613419); - let sampler = OctavePerlinNoiseSampler::new(&mut rand, &[-1, 1, 0]); + let sampler = OctaveSimplexNoiseSampler::new(&mut rand, &[-1, 1, 0]); let values_1 = [ ( @@ -593,14 +383,13 @@ mod octave_perlin_noise_sampler_test { } } } - #[cfg(test)] mod simplex_noise_sampler_test { use std::ops::Deref; use pumpkin_core::random::{xoroshiro128::Xoroshiro, Random}; - use crate::world_gen::noise::SimplexNoiseSampler; + use crate::world_gen::noise::simplex::SimplexNoiseSampler; #[test] fn test_create() { @@ -911,425 +700,3 @@ mod simplex_noise_sampler_test { } } } - -#[cfg(test)] -mod perlin_noise_sampler_test { - use std::ops::Deref; - - use pumpkin_core::random::{xoroshiro128::Xoroshiro, Random}; - - use crate::world_gen::noise::PerlinNoiseSampler; - - #[test] - fn test_create() { - let mut rand = Xoroshiro::from_seed(111); - assert_eq!(rand.next_i32(), -1467508761); - - let sampler = PerlinNoiseSampler::new(&mut rand); - assert_eq!(sampler.x_origin, 48.58072036717974); - assert_eq!(sampler.y_origin, 110.73235882678037); - assert_eq!(sampler.z_origin, 65.26438852860176); - - let permutation: [u8; 256] = [ - 159, 113, 41, 143, 203, 123, 95, 177, 25, 79, 229, 219, 194, 60, 130, 14, 83, 99, 24, - 202, 207, 232, 167, 152, 220, 201, 29, 235, 87, 147, 74, 160, 155, 97, 111, 31, 85, - 205, 115, 50, 13, 171, 77, 237, 149, 116, 209, 174, 169, 109, 221, 9, 166, 84, 54, 216, - 121, 106, 211, 16, 69, 244, 65, 192, 183, 146, 124, 37, 56, 45, 193, 158, 126, 217, 36, - 255, 162, 163, 230, 103, 63, 90, 191, 214, 20, 138, 32, 39, 238, 67, 64, 105, 250, 140, - 148, 114, 68, 75, 200, 161, 239, 125, 227, 199, 101, 61, 175, 107, 129, 240, 170, 51, - 139, 86, 186, 145, 212, 178, 30, 251, 89, 226, 120, 153, 47, 141, 233, 2, 179, 236, 1, - 19, 98, 21, 164, 108, 11, 23, 91, 204, 119, 88, 165, 195, 168, 26, 48, 206, 128, 6, 52, - 118, 110, 180, 197, 231, 117, 7, 3, 135, 224, 58, 82, 78, 4, 59, 222, 18, 72, 57, 150, - 43, 246, 100, 122, 112, 53, 133, 93, 17, 27, 210, 142, 234, 245, 80, 22, 46, 185, 172, - 71, 248, 33, 173, 76, 35, 40, 92, 228, 127, 254, 70, 42, 208, 73, 104, 187, 62, 154, - 243, 189, 241, 34, 66, 249, 94, 8, 12, 134, 132, 102, 242, 196, 218, 181, 28, 38, 15, - 151, 157, 247, 223, 198, 55, 188, 96, 0, 182, 49, 190, 156, 10, 215, 252, 131, 137, - 184, 176, 136, 81, 44, 213, 253, 144, 225, 5, - ]; - assert_eq!(sampler.permutation.deref(), permutation); - } - - #[test] - fn test_no_y() { - let mut rand = Xoroshiro::from_seed(111); - assert_eq!(rand.next_i32(), -1467508761); - let sampler = PerlinNoiseSampler::new(&mut rand); - - let values = [ - ( - ( - -3.134738528791615E8, - 5.676610095659718E7, - 2.011711832498507E8, - ), - 0.38582139614602945, - ), - ( - (-1369026.560586418, 3.957311252810864E8, 6.797037355570006E8), - 0.15777501333157193, - ), - ( - ( - 6.439373693833767E8, - -3.36218773041759E8, - -3.265494249695775E8, - ), - -0.2806135912409497, - ), - ( - ( - 1.353820060118252E8, - -3.204701624793043E8, - -4.612474746056331E8, - ), - -0.15052865500837787, - ), - ( - ( - -6906850.625560562, - 1.0153663948838013E8, - 2.4923185478305575E8, - ), - -0.3079300694558318, - ), - ( - ( - -7.108376621385525E7, - -2.029413580824217E8, - 2.5164602748045415E8, - ), - 0.03051312670440398, - ), - ( - ( - 1.0591429119126628E8, - -4.7911044364543396E8, - -2918719.2277242197, - ), - -0.11775123159138573, - ), - ( - ( - 4.04615501401398E7, - -3.074409286586152E8, - 5.089118769334092E7, - ), - 0.08763639340713025, - ), - ( - ( - -4.8645283544246924E8, - -3.922570151180015E8, - 2.3741632952563038E8, - ), - 0.08857245482456311, - ), - ( - ( - 2.861710031285905E8, - -1.8973201372718483E8, - -3.2653143323982143E8, - ), - -0.2378339698793312, - ), - ( - ( - 2.885407603819252E8, - -3.358708100884505E7, - -1.4480399660676318E8, - ), - -0.46661747461279457, - ), - ( - ( - 3.6548491156354237E8, - 7.995429702025633E7, - 2.509991661702412E8, - ), - 0.1671543972176835, - ), - ( - ( - 1.3298684552869435E8, - 3.6743804723880893E8, - 5.791092458225288E7, - ), - -0.2704070746642889, - ), - ( - ( - -1.3123184148036437E8, - -2.722300890805201E8, - 2.1601883778132245E7, - ), - 0.05049887915906969, - ), - ( - ( - -5.56047682304707E8, - 3.554803693060646E8, - 3.1647392358159083E8, - ), - -0.21178547899422662, - ), - ( - ( - 5.638216625134594E8, - -2.236907346192737E8, - -5.0562852022285646E8, - ), - 0.03351245780858128, - ), - ( - ( - -5.436956979127073E7, - -1.129261611506945E8, - -1.7909512156895646E8, - ), - 0.31670010349494726, - ), - ( - ( - 1.0915760091641709E8, - 1.932642099859593E7, - -3.405060533753616E8, - ), - -0.13987439655026918, - ), - ( - ( - -6.73911758014991E8, - -2.2147483413687566E8, - -4.531457195005102E7, - ), - 0.07824440437151846, - ), - ( - ( - -2.4827386778136212E8, - -2.6640208832089204E8, - -3.354675096522197E8, - ), - -0.2989735599541437, - ), - ]; - - for ((x, y, z), sample) in values { - assert_eq!(sampler.sample_flat_y(x, y, z), sample); - } - } - - #[test] - fn test_no_fade() { - let mut rand = Xoroshiro::from_seed(111); - assert_eq!(rand.next_i32(), -1467508761); - let sampler = PerlinNoiseSampler::new(&mut rand); - - let values = [ - ( - ( - -3.134738528791615E8, - 5.676610095659718E7, - 2.011711832498507E8, - -1369026.560586418, - 3.957311252810864E8, - ), - 23234.47859421248, - ), - ( - ( - 6.797037355570006E8, - 6.439373693833767E8, - -3.36218773041759E8, - -3.265494249695775E8, - 1.353820060118252E8, - ), - -0.016403984198221984, - ), - ( - ( - -3.204701624793043E8, - -4.612474746056331E8, - -6906850.625560562, - 1.0153663948838013E8, - 2.4923185478305575E8, - ), - 0.3444286491766397, - ), - ( - ( - -7.108376621385525E7, - -2.029413580824217E8, - 2.5164602748045415E8, - 1.0591429119126628E8, - -4.7911044364543396E8, - ), - 0.03051312670440398, - ), - ( - ( - -2918719.2277242197, - 4.04615501401398E7, - -3.074409286586152E8, - 5.089118769334092E7, - -4.8645283544246924E8, - ), - 0.3434020232968479, - ), - ( - ( - -3.922570151180015E8, - 2.3741632952563038E8, - 2.861710031285905E8, - -1.8973201372718483E8, - -3.2653143323982143E8, - ), - -0.07935517045771859, - ), - ( - ( - 2.885407603819252E8, - -3.358708100884505E7, - -1.4480399660676318E8, - 3.6548491156354237E8, - 7.995429702025633E7, - ), - -0.46661747461279457, - ), - ( - ( - 2.509991661702412E8, - 1.3298684552869435E8, - 3.6743804723880893E8, - 5.791092458225288E7, - -1.3123184148036437E8, - ), - 0.0723439870279631, - ), - ( - ( - -2.722300890805201E8, - 2.1601883778132245E7, - -5.56047682304707E8, - 3.554803693060646E8, - 3.1647392358159083E8, - ), - -0.656560662515624, - ), - ( - ( - 5.638216625134594E8, - -2.236907346192737E8, - -5.0562852022285646E8, - -5.436956979127073E7, - -1.129261611506945E8, - ), - 0.03351245780858128, - ), - ( - ( - -1.7909512156895646E8, - 1.0915760091641709E8, - 1.932642099859593E7, - -3.405060533753616E8, - -6.73911758014991E8, - ), - -0.2089142558681482, - ), - ( - ( - -2.2147483413687566E8, - -4.531457195005102E7, - -2.4827386778136212E8, - -2.6640208832089204E8, - -3.354675096522197E8, - ), - 0.38250837565598395, - ), - ( - ( - 3.618095500266467E8, - -1.785261966631494E8, - 8.855575989580283E7, - -1.3702508894700047E8, - -3.564818414428105E8, - ), - 0.00883370523171791, - ), - ( - ( - 3.585592594479808E7, - 1.8822208340571395E8, - -386327.524558296, - -2.613548000006699E8, - 1995562.4304017993, - ), - -0.27653878487738676, - ), - ( - ( - 3.0800276873619422E7, - 1.166750302259058E7, - 8.502636255675305E7, - 4.347409652503064E8, - 1.0678086363325526E8, - ), - -0.13800758751097497, - ), - ( - ( - -2.797805968820768E8, - 9.446376468140173E7, - 2.2821543438325477E8, - -4.8176550369786626E8, - 7.316871126959312E7, - ), - 0.05505478945301634, - ), - ( - ( - -2.236596113898912E7, - 1.5296478602495643E8, - 3.903966235164034E8, - 9.40479475527148E7, - 1.0948229366673347E8, - ), - 0.1158678618158655, - ), - ( - ( - 3.5342596632385695E8, - 3.1584773170834744E8, - -2.1860087172846535E8, - -1.8126626716239208E8, - -2.5263456116162892E7, - ), - -0.354953975313882, - ), - ( - ( - -1.2711958434031656E8, - -4.541988855460623E7, - -1.375878074907788E8, - 6.72693784001799E7, - 6815739.665531283, - ), - -0.23849179316215247, - ), - ( - ( - 1.2660906027019228E8, - -3.3769609799741164E7, - -3.4331505330046E8, - -6.663866659430536E7, - -1.6603843763414428E8, - ), - 0.07974650858448407, - ), - ]; - - for ((x, y, z, y_scale, y_max), sample) in values { - assert_eq!(sampler.sample_no_fade(x, y, z, y_scale, y_max), sample); - } - } -} From a9031e48f9d1a8c5410e38f0d89f766e3ff49d69 Mon Sep 17 00:00:00 2001 From: kralverde Date: Wed, 11 Sep 2024 23:22:00 -0400 Subject: [PATCH 12/65] implement octave perlin noise sampler --- pumpkin-world/src/world_gen/noise/perlin.rs | 246 +++++++++++++++++++- 1 file changed, 245 insertions(+), 1 deletion(-) diff --git a/pumpkin-world/src/world_gen/noise/perlin.rs b/pumpkin-world/src/world_gen/noise/perlin.rs index a39caa6bc..7cfd2cb96 100644 --- a/pumpkin-world/src/world_gen/noise/perlin.rs +++ b/pumpkin-world/src/world_gen/noise/perlin.rs @@ -1,4 +1,5 @@ -use pumpkin_core::random::Random; +use num_traits::{Pow, WrappingSub}; +use pumpkin_core::random::{Random, RandomSplitter}; use super::{dot, lerp3, GRADIENTS}; @@ -148,6 +149,249 @@ impl PerlinNoiseSampler { } } +pub struct OctavePerlinNoiseSampler { + octave_samplers: Vec>, + amplitudes: Vec, + first_octave: i32, + persistence: f64, + lacunarity: f64, + max_value: f64, +} + +impl OctavePerlinNoiseSampler { + fn get_total_amplitude(scale: f64, persistence: f64, amplitudes: &Vec) -> f64 { + let mut d = 0f64; + let mut e = persistence; + + for amplitude in amplitudes.iter() { + if *amplitude != 0f64 { + d += amplitude * scale * e; + } + + e /= 2f64; + } + + d + } + + fn maintain_precision(value: f64) -> f64 { + value - (value / 3.3554432E7f64 + 0.5f64).floor() * 3.3554432E7f64 + } + + pub fn calculate_amplitudes(octaves: &[i32]) -> (i32, Vec) { + let mut octaves = Vec::from_iter(octaves); + octaves.sort(); + + let i = -**octaves.first().expect("we should have some octaves"); + let j = **octaves.last().expect("we should have some octaves"); + let k = i + j + 1; + + let mut double_list: Vec = Vec::with_capacity(k as usize); + for _ in 0..k { + double_list.push(0f64) + } + + for l in octaves { + double_list[(l + i) as usize] = 1f64; + } + + (-i, double_list) + } + + pub fn new(random: &mut impl Random, first_octave: i32, amplitudes: Vec) -> Self { + let i = amplitudes.len(); + let j = -first_octave; + + let mut samplers: Vec> = Vec::with_capacity(i); + for _ in 0..i { + samplers.push(None); + } + + let splitter = random.next_splitter(); + for k in 0..i { + if amplitudes[k] != 0f64 { + let l = first_octave + k as i32; + samplers[k] = Some(PerlinNoiseSampler::new( + &mut splitter.split_string(&format!("octave_{}", l)), + )); + } + } + + let persistence = 2f64.pow((i as i32).wrapping_sub(1) as f64) / (2f64.pow(i as f64) - 1f64); + let max_value = Self::get_total_amplitude(2f64, persistence, &litudes); + Self { + octave_samplers: samplers, + amplitudes, + first_octave, + persistence, + lacunarity: 2f64.pow((-j) as f64), + max_value, + } + } + + pub fn sample(&self, x: f64, y: f64, z: f64) -> f64 { + let mut d = 0f64; + let mut e = self.lacunarity; + let mut f = self.persistence; + + for (sampler, amplitude) in self.octave_samplers.iter().zip(self.amplitudes.iter()) { + if let Some(sampler) = sampler { + let g = sampler.sample_no_fade( + Self::maintain_precision(x * e), + Self::maintain_precision(y * e), + Self::maintain_precision(z * e), + 0f64, + 0f64, + ); + + d += amplitude * g * f; + } + + e *= 2f64; + f /= 2f64; + } + + d + } +} + +#[cfg(test)] +mod octave_perline_noise_sampler_test { + use pumpkin_core::random::{xoroshiro128::Xoroshiro, Random}; + + use super::OctavePerlinNoiseSampler; + + #[test] + fn test_create_xoroshiro() { + let mut rand = Xoroshiro::from_seed(513513513); + assert_eq!(rand.next_i32(), 404174895); + + let (start, amplitudes) = OctavePerlinNoiseSampler::calculate_amplitudes(&[1, 2, 3]); + assert_eq!(start, 1); + assert_eq!(amplitudes, [1f64, 1f64, 1f64]); + + let sampler = OctavePerlinNoiseSampler::new(&mut rand, start, amplitudes); + + assert_eq!(sampler.first_octave, 1); + assert_eq!(sampler.persistence, 0.5714285714285714f64); + assert_eq!(sampler.lacunarity, 2f64); + assert_eq!(sampler.max_value, 2f64); + + let coords = [ + (210.19539348148294, 203.08258445596215, 45.29925114984684), + (24.841250686920773, 181.62678157390076, 69.49871248131629), + (21.65886467061867, 97.80131502331685, 225.9273676334467), + ]; + + for (sampler, (x, y, z)) in sampler.octave_samplers.iter().zip(coords) { + match sampler { + Some(sampler) => { + assert_eq!(sampler.x_origin, x); + assert_eq!(sampler.y_origin, y); + assert_eq!(sampler.z_origin, z); + } + None => panic!(), + } + } + } + + #[test] + fn test_sample() { + let mut rand = Xoroshiro::from_seed(513513513); + assert_eq!(rand.next_i32(), 404174895); + + let (start, amplitudes) = OctavePerlinNoiseSampler::calculate_amplitudes(&[1, 2, 3]); + let sampler = OctavePerlinNoiseSampler::new(&mut rand, start, amplitudes); + + let values = [ + ( + ( + 1.4633897801218182E8, + 3.360929121402108E8, + -1.7376184515043163E8, + ), + -0.16510137639683028, + ), + ( + ( + -3.952093942501234E8, + -8.149682915016855E7, + 2.0761709535397574E8, + ), + -0.19865227457826365, + ), + ( + ( + 1.0603518812861493E8, + -1.6028050039630303E8, + 9.621510690305333E7, + ), + -0.16157548492944798, + ), + ( + ( + -2.2789281609860754E8, + 1.2416505757723756E8, + -3.047619296454517E8, + ), + -0.05762575118540847, + ), + ( + ( + -1.6361322604690066E8, + -1.862652364900794E8, + 9.03458926538596E7, + ), + 0.21589404036742288, + ), + ( + ( + -1.6074718857061076E8, + -4.816551924254624E8, + -9.930236785759543E7, + ), + 0.1888188057014473, + ), + ( + ( + -1.6848478115907547E8, + 1.9495247771890038E8, + 1.3780564333313772E8, + ), + 0.23114508298896774, + ), + ( + ( + 2.5355640846261957E8, + -2.5973376726076955E8, + 3.7834594620459855E7, + ), + -0.23703473310230702, + ), + ( + ( + -8.636649828254433E7, + 1.7017680431584623E8, + 2.941033134334743E8, + ), + -0.14050102207739693, + ), + ( + ( + -4.573784466442647E8, + 1.789046617664721E8, + -5.515223967099891E8, + ), + -0.1422470544720957, + ), + ]; + + for ((x, y, z), sample) in values { + assert_eq!(sampler.sample(x, y, z), sample); + } + } +} + #[cfg(test)] mod perlin_noise_sampler_test { use std::ops::Deref; From 0bebc6fc3111c7c5135fee4b7057af225f49f248 Mon Sep 17 00:00:00 2001 From: Snowiiii Date: Thu, 12 Sep 2024 12:33:33 +0200 Subject: [PATCH 13/65] Better doc --- pumpkin-config/README.md | 7 ++ pumpkin-entity/README.md | 0 pumpkin-protocol/README.md | 6 +- pumpkin-protocol/src/lib.rs | 26 +++++-- pumpkin-protocol/src/packet_decoder.rs | 3 + pumpkin-protocol/src/packet_encoder.rs | 3 + pumpkin-protocol/src/uuid.rs | 2 + pumpkin-world/README.md | 17 +++++ pumpkin-world/src/level.rs | 21 +++--- pumpkin/src/client/authentication.rs | 50 ++++++++++--- pumpkin/src/client/client_packet.rs | 6 +- pumpkin/src/client/mod.rs | 41 +++++++++-- pumpkin/src/entity/mod.rs | 73 +++++++++++++++---- pumpkin/src/entity/player.rs | 98 +++++++++++++++++--------- pumpkin/src/main.rs | 3 - pumpkin/src/server/mod.rs | 13 ++-- pumpkin/src/world/mod.rs | 25 ++++++- 17 files changed, 300 insertions(+), 94 deletions(-) create mode 100644 pumpkin-config/README.md create mode 100644 pumpkin-entity/README.md create mode 100644 pumpkin-world/README.md diff --git a/pumpkin-config/README.md b/pumpkin-config/README.md new file mode 100644 index 000000000..c8506926b --- /dev/null +++ b/pumpkin-config/README.md @@ -0,0 +1,7 @@ +### Pumpkin Configuration +Pumpkin offers a robust configuration system that allows users to customize various aspects of the server's behavior without relying on external plugins. This provides flexibility and control over the server's operation. + +#### Key Features: + - Extensive Customization: Configure server settings, player behavior, world generation, and more. + - Performance Optimization: Optimize server performance through configuration tweaks. + - Plugin-Free Customization: Achieve desired changes without the need for additional plugins. diff --git a/pumpkin-entity/README.md b/pumpkin-entity/README.md new file mode 100644 index 000000000..e69de29bb diff --git a/pumpkin-protocol/README.md b/pumpkin-protocol/README.md index 4ea5c5c71..9eec8c892 100644 --- a/pumpkin-protocol/README.md +++ b/pumpkin-protocol/README.md @@ -69,4 +69,8 @@ Thats a Serverbound packet pub struct CPlayDisconnect { reason: TextComponent, } -`` \ No newline at end of file +``` + +### Porting +You can compare difference in Protocol on wiki.vg https://wiki.vg/index.php?title=Protocol&action=history +Also change the `CURRENT_MC_PROTOCOL` in `src/lib.rs` \ No newline at end of file diff --git a/pumpkin-protocol/src/lib.rs b/pumpkin-protocol/src/lib.rs index dfabd2e99..426e02f98 100644 --- a/pumpkin-protocol/src/lib.rs +++ b/pumpkin-protocol/src/lib.rs @@ -12,6 +12,8 @@ pub mod server; pub mod slot; pub mod uuid; +/// To current Minecraft protocol +/// Don't forget to change this when porting pub const CURRENT_MC_PROTOCOL: u32 = 767; pub const MAX_PACKET_SIZE: i32 = 2097152; @@ -175,7 +177,6 @@ impl From for ConnectionState { } } } - pub struct RawPacket { pub id: VarInt, pub bytebuf: ByteBuffer, @@ -191,29 +192,42 @@ pub trait ServerPacket: Packet + Sized { #[derive(Serialize)] pub struct StatusResponse { - pub version: Version, - pub players: Players, + /// The version on which the Server is running. Optional + pub version: Option, + /// Informations about currently connected Players. Optional + pub players: Option, + /// The description displayed also called MOTD (Message of the day). Optional pub description: String, - pub favicon: Option, // data:image/png;base64, - // Players, favicon ... + /// The icon displayed, Optional + pub favicon: Option, + /// Players are forced to use Secure chat + pub enforece_secure_chat: bool, } #[derive(Serialize)] pub struct Version { + /// The current name of the Version (e.g. 1.21.1) pub name: String, + /// The current Protocol Version (e.g. 767) pub protocol: u32, } #[derive(Serialize)] pub struct Players { + /// The maximum Player count the server allows pub max: u32, + /// The current online player count pub online: u32, + /// Informations about currently connected players. + /// Note player can disable listing here. pub sample: Vec, } #[derive(Serialize)] pub struct Sample { + /// Players Name pub name: String, - pub id: String, // uuid + /// Players UUID + pub id: String, } // basicly game profile diff --git a/pumpkin-protocol/src/packet_decoder.rs b/pumpkin-protocol/src/packet_decoder.rs index 308716ccb..b001cea15 100644 --- a/pumpkin-protocol/src/packet_decoder.rs +++ b/pumpkin-protocol/src/packet_decoder.rs @@ -13,6 +13,8 @@ use crate::{ type Cipher = cfb8::Decryptor; // Decoder: Client -> Server +// Supports ZLib decoding/decompression +// Supports Aes128 Encyption #[derive(Default)] pub struct PacketDecoder { buf: BytesMut, @@ -105,6 +107,7 @@ impl PacketDecoder { self.cipher = Some(cipher); } + /// Enables ZLib Deompression pub fn set_compression(&mut self, compression: Option) { self.compression = compression; } diff --git a/pumpkin-protocol/src/packet_encoder.rs b/pumpkin-protocol/src/packet_encoder.rs index 1c545804b..9e0d9f525 100644 --- a/pumpkin-protocol/src/packet_encoder.rs +++ b/pumpkin-protocol/src/packet_encoder.rs @@ -13,6 +13,8 @@ use crate::{bytebuf::ByteBuffer, ClientPacket, PacketError, VarInt, MAX_PACKET_S type Cipher = cfb8::Encryptor; // Encoder: Server -> Client +// Supports ZLib endecoding/compression +// Supports Aes128 Encyption #[derive(Default)] pub struct PacketEncoder { buf: BytesMut, @@ -121,6 +123,7 @@ impl PacketEncoder { self.cipher = Some(Cipher::new_from_slices(key, key).expect("invalid key")); } + /// Enables ZLib Compression pub fn set_compression(&mut self, compression: Option<(u32, u32)>) { self.compression = compression; } diff --git a/pumpkin-protocol/src/uuid.rs b/pumpkin-protocol/src/uuid.rs index ab70ef83a..a187a083a 100644 --- a/pumpkin-protocol/src/uuid.rs +++ b/pumpkin-protocol/src/uuid.rs @@ -1,6 +1,8 @@ use serde::Serialize; #[derive(Clone)] +/// Wrapper around uuid::UUID, Please use this in every Packet containing a UUID +/// We use this to we can do own Serializing pub struct UUID(pub uuid::Uuid); impl Serialize for UUID { diff --git a/pumpkin-world/README.md b/pumpkin-world/README.md new file mode 100644 index 000000000..40637233c --- /dev/null +++ b/pumpkin-world/README.md @@ -0,0 +1,17 @@ +### Pumpkin World +Contains everything World related for example + +- Loading Chunks (Anvil Format) +- Generating Chunks +- Loading Blocks/Items + +### Porting +When updating your Minecraft server to a newer version, you typically need to replace the files in the assets directory to ensure compatibility with the new version's resources. +Thankfully, vanilla Minecraft provides a way to extract these updated assets directly from the server JAR file itself. + +1. Download the latest Minecraft server JAR file for the version you want to upgrade to. +2. Run `java -DbundlerMainClass=net.minecraft.data.Main -jar .jar --reports`. +3. This command will create a new folder named `reports` in the same directory as the server JAR. This folder contains the updated "assets" directory for the new version. +4. Copy the assets folder from the reports folder and replace the existing assets directory within your server directory. + +For details see https://wiki.vg/Data_Generators diff --git a/pumpkin-world/src/level.rs b/pumpkin-world/src/level.rs index 8707912be..d86b989c7 100644 --- a/pumpkin-world/src/level.rs +++ b/pumpkin-world/src/level.rs @@ -18,7 +18,15 @@ use crate::{ world_gen::{get_world_gen, Seed, WorldGenerator}, }; -/// The Level represents a single Dimension. +/// The `Level` module provides functionality for working with chunks within or outside a Minecraft world. +/// +/// Key features include: +/// +/// - **Chunk Loading:** Efficiently loads chunks from disk (Anvil format). +/// - **Chunk Caching:** Stores accessed chunks in memory for faster access. +/// - **Chunk Generation:** Generates new chunks on-demand using a specified `WorldGenerator`. +/// +/// For more details on world generation, refer to the `WorldGenerator` module. pub struct Level { save_file: Option, loaded_chunks: Arc, Arc>>>, @@ -126,17 +134,6 @@ impl Level { } } - // /// Read one chunk in the world - // /// - // /// Do not use this function if reading many chunks is required, since in case those two chunks which are read separately using `.read_chunk` are in the same region file, it will need to be opened and closed separately for both of them, leading to a performance loss. - // pub async fn read_chunk(&self, chunk: (i32, i32)) -> Result { - // self.read_chunks(vec![chunk]) - // .await - // .pop() - // .expect("Read chunks must return a chunk") - // .1 - // } - /// Reads/Generates many chunks in a world /// MUST be called from a tokio runtime thread /// diff --git a/pumpkin/src/client/authentication.rs b/pumpkin/src/client/authentication.rs index 60d6c5523..9af1020ce 100644 --- a/pumpkin/src/client/authentication.rs +++ b/pumpkin/src/client/authentication.rs @@ -39,6 +39,19 @@ pub struct GameProfile { pub profile_actions: Option>, } +/// Sends a GET request to Mojang's authentication servers to verify a client's Minecraft account. +/// +/// **Purpose:** +/// +/// This function is used to ensure that a client connecting to the server has a valid, premium Minecraft account. It's a crucial step in preventing unauthorized access and maintaining server security. +/// +/// **How it Works:** +/// +/// 1. A client with a premium account sends a login request to the Mojang session server. +/// 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 . +/// +/// **Note:** This process helps prevent unauthorized access to the server and ensures that only legitimate Minecraft accounts can connect. pub async fn authenticate( username: &str, server_hash: &str, @@ -71,29 +84,34 @@ pub async fn authenticate( Ok(profile) } -pub fn unpack_textures(property: Property, config: &TextureConfig) { - // TODO: no unwrap - let from64 = general_purpose::STANDARD.decode(property.value).unwrap(); - let textures: ProfileTextures = serde_json::from_slice(&from64).unwrap(); +pub fn unpack_textures(property: Property, config: &TextureConfig) -> Result<(), TextureError> { + let from64 = general_purpose::STANDARD + .decode(property.value) + .map_err(|e| TextureError::DecodeError(e.to_string()))?; + let textures: ProfileTextures = + serde_json::from_slice(&from64).map_err(|e| TextureError::JSONError(e.to_string()))?; for texture in textures.textures { - is_texture_url_valid(Url::parse(&texture.1.url).unwrap(), config); + let url = + Url::parse(&texture.1.url).map_err(|e| TextureError::InvalidURL(e.to_string()))?; + is_texture_url_valid(url, config)? } + Ok(()) } pub fn auth_digest(bytes: &[u8]) -> String { BigInt::from_signed_bytes_be(bytes).to_str_radix(16) } -pub fn is_texture_url_valid(url: Url, config: &TextureConfig) -> bool { +pub fn is_texture_url_valid(url: Url, config: &TextureConfig) -> Result<(), TextureError> { let scheme = url.scheme(); if !config.allowed_url_schemes.contains(&scheme.to_string()) { - return false; + return Err(TextureError::DisallowedUrlScheme(scheme.to_string())); } let domain = url.domain().unwrap_or(""); if !config.allowed_url_domains.contains(&domain.to_string()) { - return false; + return Err(TextureError::DisallowedUrlDomain(domain.to_string())); } - true + Ok(()) } #[derive(Error, Debug)] @@ -109,3 +127,17 @@ pub enum AuthError { #[error("Unknown Status Code")] UnknownStatusCode(String), } + +#[derive(Error, Debug)] +pub enum TextureError { + #[error("Invalid URL")] + InvalidURL(String), + #[error("Invalid URL scheme for player texture: {0}")] + DisallowedUrlScheme(String), + #[error("Invalid URL domain for player texture: {0}")] + DisallowedUrlDomain(String), + #[error("Failed to decode base64 player texture: {0}")] + DecodeError(String), + #[error("Failed to parse JSON from player texture: {0}")] + JSONError(String), +} diff --git a/pumpkin/src/client/client_packet.rs b/pumpkin/src/client/client_packet.rs index 7fc6d11d1..3323de115 100644 --- a/pumpkin/src/client/client_packet.rs +++ b/pumpkin/src/client/client_packet.rs @@ -171,9 +171,9 @@ impl Client { Err(e) => self.kick(&e.to_string()), } } - for ele in gameprofile.as_ref().unwrap().properties.clone() { - // todo, use this - unpack_textures(ele, &ADVANCED_CONFIG.authentication.textures); + for property in gameprofile.as_ref().unwrap().properties.clone() { + unpack_textures(property, &ADVANCED_CONFIG.authentication.textures) + .unwrap_or_else(|e| self.kick(&e.to_string())); } // enable compression diff --git a/pumpkin/src/client/mod.rs b/pumpkin/src/client/mod.rs index f4e89a450..4018b410b 100644 --- a/pumpkin/src/client/mod.rs +++ b/pumpkin/src/client/mod.rs @@ -37,15 +37,30 @@ mod client_packet; mod container; pub mod player_packet; +/// Represents a player's configuration settings. +/// +/// This struct contains various options that can be customized by the player, affecting their gameplay experience. +/// +/// **Usage:** +/// +/// This struct is typically used to store and manage a player's preferences. It can be sent to the server when a player joins or when they change their settings. #[derive(Clone)] pub struct PlayerConfig { + /// The player's preferred language. pub locale: String, // 16 + /// The maximum distance at which chunks are rendered. pub view_distance: i8, + /// The player's chat mode settings pub chat_mode: ChatMode, + /// Whether chat colors are enabled. pub chat_colors: bool, + /// The player's skin configuration options. pub skin_parts: u8, + /// The player's dominant hand (left or right). pub main_hand: Hand, + /// Whether text filtering is enabled. pub text_filtering: bool, + /// Whether the player wants to appear in the server list. pub server_listing: bool, } @@ -64,23 +79,37 @@ impl Default for PlayerConfig { } } +/// Everything which makes a Conection with our Server is a `Client`. +/// Client will become Players when they reach the `Play` state pub struct Client { + /// The client's game profile information. pub gameprofile: Mutex>, - + /// The client's configuration settings, Optional pub config: Mutex>, + /// The client's brand or modpack information, Optional. pub brand: Mutex>, - + /// The minecraft protocol version used by the client. pub protocol_version: AtomicI32, + /// The current connection state of the client (e.g., Handshaking, Status, Play). pub connection_state: Mutex, + /// Whether encryption is enabled for the connection. pub encryption: AtomicBool, + /// Indicates if the client connection is closed. pub closed: AtomicBool, + /// A unique token identifying the client. pub token: Token, + /// The underlying TCP connection to the client. pub connection: Arc>, + /// The client's IP address. pub address: Mutex, + /// The packet encoder for outgoing packets. enc: Arc>, + /// The packet decoder for incoming packets. dec: Arc>, + /// A queue of raw packets received from the client, waiting to be processed. pub client_packets_queue: Arc>>, + /// Indicates whether the client should be converted into a player. pub make_player: AtomicBool, } @@ -104,13 +133,13 @@ impl Client { } } - /// adds a Incoming packet to the queue + /// Adds a Incoming packet to the queue pub fn add_packet(&self, packet: RawPacket) { let mut client_packets_queue = self.client_packets_queue.lock().unwrap(); client_packets_queue.push(packet); } - /// enables encryption + /// Enables encryption pub fn enable_encryption( &self, shared_secret: &[u8], // decrypted @@ -125,7 +154,7 @@ impl Client { Ok(()) } - // Compression threshold, Compression level + /// Compression threshold, Compression level pub fn set_compression(&self, compression: Option<(u32, u32)>) { self.dec .lock() @@ -161,6 +190,7 @@ impl Client { Ok(()) } + /// Processes all packets send by the client pub async fn process_packets(&self, server: &Arc) { while let Some(mut packet) = self.client_packets_queue.lock().unwrap().pop() { match self.handle_packet(server, &mut packet).await { @@ -330,7 +360,6 @@ impl Client { /// Kicks the Client with a reason depending on the connection state pub fn kick(&self, reason: &str) { - dbg!(reason); match *self.connection_state.lock().unwrap() { ConnectionState::Login => { self.try_send_packet(&CLoginDisconnect::new( diff --git a/pumpkin/src/entity/mod.rs b/pumpkin/src/entity/mod.rs index 132dfedf0..4d0ffe3be 100644 --- a/pumpkin/src/entity/mod.rs +++ b/pumpkin/src/entity/mod.rs @@ -1,5 +1,7 @@ use std::sync::{atomic::AtomicBool, Arc, Mutex}; +use num_derive::{FromPrimitive, ToPrimitive}; +use num_traits::ToPrimitive; use pumpkin_core::math::{ get_section_cord, position::WorldPosition, vector2::Vector2, vector3::Vector3, }; @@ -14,27 +16,44 @@ use crate::world::World; pub mod player; pub struct Entity { + /// A unique identifier for the entity pub entity_id: EntityId, + /// The type of entity (e.g., player, zombie, item) pub entity_type: EntityType, + /// The world in which the entity exists. pub world: Arc, + /// The entity's current health level. + pub health: Mutex, + /// The entity's current position in the world pub pos: Mutex>, + /// The entity's position rounded to the nearest block coordinates pub block_pos: Mutex, + /// The chunk coordinates of the entity's current position pub chunk_pos: Mutex>, + /// Indicates whether the entity is sneaking pub sneaking: AtomicBool, + /// Indicates whether the entity is sprinting pub sprinting: AtomicBool, + /// Indicates whether the entity is flying due to a fall pub fall_flying: AtomicBool, + /// The entity's current velocity vector, aka Knockback pub velocity: Mutex>, - // Should be not trusted + /// Indicates whether the entity is on the ground (may not always be accurate). pub on_ground: AtomicBool, + /// The entity's yaw rotation (horizontal rotation) ← → pub yaw: Mutex, + /// The entity's head yaw rotation (horizontal rotation of the head) pub head_yaw: Mutex, + /// The entity's pitch rotation (vertical rotation) ↑ ↓ pub pitch: Mutex, + /// The height of the entity's eyes from the ground. // TODO: Change this in diffrent poses pub standing_eye_height: f32, + /// The entity's current pose (e.g., standing, sitting, swimming). pub pose: Mutex, } @@ -54,6 +73,8 @@ impl Entity { chunk_pos: Mutex::new(Vector2::new(0, 0)), sneaking: AtomicBool::new(false), world, + // TODO: Load this from previous instance + health: Mutex::new(20.0), sprinting: AtomicBool::new(false), fall_flying: AtomicBool::new(false), yaw: Mutex::new(0.0), @@ -65,6 +86,9 @@ impl Entity { } } + /// Updates the entity's position, block position, and chunk position. + /// + /// This function calculates the new position, block position, and chunk position based on the provided coordinates. If any of these values change, the corresponding fields are updated. pub fn set_pos(&self, x: f64, y: f64, z: f64) { let mut pos = self.pos.lock().unwrap(); if pos.x != x || pos.y != y || pos.z != z { @@ -89,16 +113,21 @@ impl Entity { } } + /// Sets the Entity yaw & pitch Rotation pub fn set_rotation(&self, yaw: f32, pitch: f32) { // TODO *self.yaw.lock().unwrap() = yaw; *self.pitch.lock().unwrap() = pitch } + /// Removes the Entity from their current World pub async fn remove(&mut self) { self.world.remove_entity(self); } + /// Applies knockback to the entity, following vanilla Minecraft's mechanics. + /// + /// This function calculates the entity's new velocity based on the specified knockback strength and direction. pub fn knockback(&self, strength: f64, x: f64, z: f64) { // This has some vanilla magic let mut x = x; @@ -125,7 +154,7 @@ impl Entity { assert!(self.sneaking.load(std::sync::atomic::Ordering::Relaxed) != sneaking); self.sneaking .store(sneaking, std::sync::atomic::Ordering::Relaxed); - self.set_flag(Self::SNEAKING_FLAG_INDEX, sneaking).await; + self.set_flag(Flag::Sneaking, sneaking).await; // if sneaking { // self.set_pose(EntityPose::Crouching).await; // } else { @@ -137,7 +166,7 @@ impl Entity { assert!(self.sprinting.load(std::sync::atomic::Ordering::Relaxed) != sprinting); self.sprinting .store(sprinting, std::sync::atomic::Ordering::Relaxed); - self.set_flag(Self::SPRINTING_FLAG_INDEX, sprinting).await; + self.set_flag(Flag::Sprinting, sprinting).await; } pub fn check_fall_flying(&self) -> bool { @@ -148,18 +177,11 @@ impl Entity { assert!(self.fall_flying.load(std::sync::atomic::Ordering::Relaxed) != fall_flying); self.fall_flying .store(fall_flying, std::sync::atomic::Ordering::Relaxed); - self.set_flag(Self::FALL_FLYING_FLAG_INDEX, fall_flying) - .await; + self.set_flag(Flag::FallFlying, fall_flying).await; } - pub const ON_FIRE_FLAG_INDEX: u32 = 0; - pub const SNEAKING_FLAG_INDEX: u32 = 1; - pub const SPRINTING_FLAG_INDEX: u32 = 3; - pub const SWIMMING_FLAG_INDEX: u32 = 4; - pub const INVISIBLE_FLAG_INDEX: u32 = 5; - pub const GLOWING_FLAG_INDEX: u32 = 6; - pub const FALL_FLYING_FLAG_INDEX: u32 = 7; - async fn set_flag(&self, index: u32, value: bool) { + async fn set_flag(&self, flag: Flag, value: bool) { + let index = flag.to_u32().unwrap(); let mut b = 0i8; if value { b |= 1 << index; @@ -180,3 +202,28 @@ impl Entity { self.world.broadcast_packet_all(&packet) } } + +#[derive(Clone, Copy, Debug, PartialEq, Eq, FromPrimitive, ToPrimitive)] +/// Represents various entity flags that are sent in entity metadata. +/// +/// These flags are used by the client to modify the rendering of entities based on their current state. +/// +/// **Purpose:** +/// +/// This enum provides a more type-safe and readable way to represent entity flags compared to using raw integer values. +pub enum Flag { + /// Indicates if the entity is on fire. + OnFire, + /// Indicates if the entity is sneaking. + Sneaking, + /// Indicates if the entity is sprinting. + Sprinting, + /// Indicates if the entity is swimming. + Swimming, + /// Indicates if the entity is invisible. + Invisible, + /// Indicates if the entity is glowing. + Glowing, + /// Indicates if the entity is flying due to a fall. + FallFlying, +} diff --git a/pumpkin/src/entity/player.rs b/pumpkin/src/entity/player.rs index 67ed05e8f..8fd5c5f21 100644 --- a/pumpkin/src/entity/player.rs +++ b/pumpkin/src/entity/player.rs @@ -38,55 +38,49 @@ use crate::{ use super::Entity; -pub struct PlayerAbilities { - pub invulnerable: bool, - pub flying: bool, - pub allow_flying: bool, - pub creative: bool, - pub fly_speed: f32, - pub walk_speed_fov: f32, -} - -impl Default for PlayerAbilities { - fn default() -> Self { - Self { - invulnerable: false, - flying: false, - allow_flying: false, - creative: false, - fly_speed: 0.5, - walk_speed_fov: 0.1, - } - } -} - +/// Represents a Minecraft player entity. +/// +/// A `Player` is a special type of entity that represents a human player connected to the server. pub struct Player { + /// The underlying entity object that represents the player. pub entity: Entity, + /// The player's game profile information, including their username and UUID. pub gameprofile: GameProfile, + /// The client connection associated with the player. pub client: Client, + /// The player's configuration settings. Changes when the Player changes their settings. pub config: Mutex, - /// Current gamemode + /// The player's current gamemode (e.g., Survival, Creative, Adventure). pub gamemode: Mutex, - // TODO: prbly should put this into an Living Entitiy or something - pub health: Mutex, + /// The player's hunger level. pub food: AtomicI32, + /// The player's food saturation level. pub food_saturation: Mutex, + /// The player's inventory, containing items and equipment. pub inventory: Mutex, + /// The ID of the currently open container (if any). pub open_container: Mutex>, + /// The item currently being held by the player. pub carried_item: Mutex>, - - /// send `send_abilties_update` when changed + /// The player's abilities and special powers. + /// + /// This field represents the various abilities that the player possesses, such as flight, invulnerability, and other special effects. + /// + /// **Note:** When the `abilities` field is updated, the server should send a `send_abilities_update` packet to the client to notify them of the changes. pub abilities: PlayerAbilities, + /// The player's last known position. + /// + /// This field is used to calculate the player's movement delta for network synchronization and other purposes. pub last_position: Mutex>, - + /// The current stage of the block the player is breaking. // TODO: This is currently unused, We have to calculate the block breaking speed our own and then break the block our own if its done pub current_block_destroy_stage: AtomicU8, - + /// A counter for teleport IDs used to track pending teleports. pub teleport_id_count: AtomicI32, - // Current awaiting teleport id and location, None if did not teleport + /// The pending teleport information, including the teleport ID and target location. pub awaiting_teleport: Mutex)>>, - + /// The coordinates of the chunk section the player is currently watching. pub watched_section: Mutex>, } @@ -112,7 +106,6 @@ impl Player { client, awaiting_teleport: Mutex::new(None), // TODO: Load this from previous instance - health: Mutex::new(20.0), food: AtomicI32::new(20), food_saturation: Mutex::new(20.0), current_block_destroy_stage: AtomicU8::new(0), @@ -136,6 +129,7 @@ impl Player { self.entity.entity_id } + /// Updates the current abilities the Player has pub fn send_abilties_update(&mut self) { let mut b = 0i8; let abilities = &self.abilities; @@ -225,7 +219,7 @@ impl Player { } pub fn update_health(&self, health: f32, food: i32, food_saturation: f32) { - *self.health.lock().unwrap() = health; + *self.entity.health.lock().unwrap() = health; self.food.store(food, std::sync::atomic::Ordering::Relaxed); *self.food_saturation.lock().unwrap() = food_saturation; } @@ -378,15 +372,53 @@ impl Player { } } +/// Represents a player's abilities and special powers. +/// +/// This struct contains information about the player's current abilities, such as flight, invulnerability, and creative mode. +pub struct PlayerAbilities { + /// Indicates whether the player is invulnerable to damage. + pub invulnerable: bool, + /// Indicates whether the player is currently flying. + pub flying: bool, + /// Indicates whether the player is allowed to fly (if enabled). + pub allow_flying: bool, + /// Indicates whether the player is in creative mode. + pub creative: bool, + /// The player's flying speed. + pub fly_speed: f32, + /// The field of view adjustment when the player is walking or sprinting. + pub walk_speed_fov: f32, +} + +impl Default for PlayerAbilities { + fn default() -> Self { + Self { + invulnerable: false, + flying: false, + allow_flying: false, + creative: false, + fly_speed: 0.5, + walk_speed_fov: 0.1, + } + } +} + +/// Represents the player's dominant hand. #[derive(FromPrimitive, Clone)] pub enum Hand { + /// The player's primary hand (usually the right hand). Main, + /// The player's off-hand (usually the left hand). Off, } +/// Represents the player's chat mode settings. #[derive(FromPrimitive, Clone)] pub enum ChatMode { + /// Chat is enabled for the player. Enabled, + /// The player should only see chat messages from commands CommandsOnly, + /// All messages should be hidden Hidden, } diff --git a/pumpkin/src/main.rs b/pumpkin/src/main.rs index 8a00ebbb4..734fce13d 100644 --- a/pumpkin/src/main.rs +++ b/pumpkin/src/main.rs @@ -169,11 +169,8 @@ fn main() -> io::Result<()> { } if closed { if let Some(player) = players.remove(&token) { - dbg!("a"); player.remove().await; - dbg!("b"); let connection = &mut player.client.connection.lock().unwrap(); - dbg!("c"); poll.registry().deregister(connection.by_ref())?; } diff --git a/pumpkin/src/server/mod.rs b/pumpkin/src/server/mod.rs index 986842d60..71a0eae9c 100644 --- a/pumpkin/src/server/mod.rs +++ b/pumpkin/src/server/mod.rs @@ -161,7 +161,8 @@ impl Server { } } - // move to world + /// Generates a new entity id + /// This should be global pub fn new_entity_id(&self) -> EntityId { self.entity_id.fetch_add(1, Ordering::SeqCst) } @@ -191,20 +192,22 @@ impl Server { }; StatusResponse { - version: Version { + version: Some(Version { name: CURRENT_MC_VERSION.into(), protocol: CURRENT_MC_PROTOCOL, - }, - players: Players { + }), + players: Some(Players { max: config.max_players, online: 0, sample: vec![Sample { name: "".into(), id: "".into(), }], - }, + }), description: config.motd.clone(), favicon: icon, + // TODO + enforece_secure_chat: false, } } diff --git a/pumpkin/src/world/mod.rs b/pumpkin/src/world/mod.rs index 253fa27b4..4046075b5 100644 --- a/pumpkin/src/world/mod.rs +++ b/pumpkin/src/world/mod.rs @@ -26,10 +26,21 @@ use crate::{ entity::{player::Player, Entity}, }; +/// Represents a Minecraft world, containing entities, players, and the underlying level data. +/// +/// Each dimension (Overworld, Nether, End) typically has its own `World`. +/// +/// **Key Responsibilities:** +/// +/// - Manages the `Level` instance for handling chunk-related operations. +/// - Stores and tracks active `Player` entities within the world. +/// - Provides a central hub for interacting with the world's entities and environment. pub struct World { + /// The underlying level, responsible for chunk management and terrain generation. pub level: Arc>, + /// A map of active players within the world, keyed by their unique token. pub current_players: Arc>>>, - // entities, players... + // TODO: entities } impl World { @@ -40,7 +51,11 @@ impl World { } } - /// Sends a Packet to all Players in the World + /// Broadcasts a packet to all connected players within the world. + /// + /// Sends the specified packet to every player currently logged in to the server. + /// + /// **Note:** This function acquires a lock on the `current_players` map, ensuring thread safety. pub fn broadcast_packet_all

(&self, packet: &P) where P: ClientPacket, @@ -51,7 +66,11 @@ impl World { } } - /// Sends a Packet to all Players in the World, Expect the Players given the the expect parameter + /// Broadcasts a packet to all connected players within the world, excluding the specified players. + /// + /// Sends the specified packet to every player currently logged in to the server, excluding the players listed in the `except` parameter. + /// + /// **Note:** This function acquires a lock on the `current_players` map, ensuring thread safety. pub fn broadcast_packet_expect

(&self, except: &[Token], packet: &P) where P: ClientPacket, From f9bf750b65b75b9e40630f1bbb448bfe66c2733b Mon Sep 17 00:00:00 2001 From: Snowiiii Date: Thu, 12 Sep 2024 15:42:26 +0200 Subject: [PATCH 14/65] Add Clientbound Entity Status --- .../src/client/play/c_entity_status.rs | 18 ++++++++++++ .../src/client/play/c_game_event.rs | 26 +++++++++++++++-- pumpkin-protocol/src/client/play/mod.rs | 2 ++ pumpkin/src/client/player_packet.rs | 2 +- pumpkin/src/commands/arg_player.rs | 4 +++ pumpkin/src/commands/cmd_gamemode.rs | 4 +-- pumpkin/src/entity/mod.rs | 17 +++++++++-- pumpkin/src/entity/player.rs | 8 ++++-- pumpkin/src/main.rs | 3 -- pumpkin/src/server/mod.rs | 10 +++++++ pumpkin/src/world/mod.rs | 28 ++++++++++++------- 11 files changed, 99 insertions(+), 23 deletions(-) create mode 100644 pumpkin-protocol/src/client/play/c_entity_status.rs diff --git a/pumpkin-protocol/src/client/play/c_entity_status.rs b/pumpkin-protocol/src/client/play/c_entity_status.rs new file mode 100644 index 000000000..301fa0ee9 --- /dev/null +++ b/pumpkin-protocol/src/client/play/c_entity_status.rs @@ -0,0 +1,18 @@ +use pumpkin_macros::packet; +use serde::Serialize; + +#[derive(Serialize)] +#[packet(0x1F)] +pub struct CEntityStatus { + entity_id: i32, + entity_status: i8, +} + +impl CEntityStatus { + pub fn new(entity_id: i32, entity_status: i8) -> Self { + Self { + entity_id, + entity_status, + } + } +} diff --git a/pumpkin-protocol/src/client/play/c_game_event.rs b/pumpkin-protocol/src/client/play/c_game_event.rs index 8873ab210..f8a01b5cf 100644 --- a/pumpkin-protocol/src/client/play/c_game_event.rs +++ b/pumpkin-protocol/src/client/play/c_game_event.rs @@ -8,8 +8,30 @@ pub struct CGameEvent { value: f32, } +/// Somewhere you need to implement all the random stuff right? impl CGameEvent { - pub fn new(event: u8, value: f32) -> Self { - Self { event, value } + pub fn new(event: GameEvent, value: f32) -> Self { + Self { + event: event as u8, + value, + } } } + +#[repr(u8)] +pub enum GameEvent { + NoRespawnBlockAvailable, + BeginRaining, + EndRaining, + ChangeGameMode, + WinGame, + DemoEvent, + ArrowHitPlayer, + RainLevelChange, + ThunderLevelChange, + PlayPufferfishStringSound, + PlayElderGuardianMobAppearance, + EnabledRespawnScreen, + LimitedCrafting, + StartWaitingChunks, +} diff --git a/pumpkin-protocol/src/client/play/mod.rs b/pumpkin-protocol/src/client/play/mod.rs index 90a405280..493ae921c 100644 --- a/pumpkin-protocol/src/client/play/mod.rs +++ b/pumpkin-protocol/src/client/play/mod.rs @@ -9,6 +9,7 @@ mod c_close_container; mod c_disguised_chat_message; mod c_entity_animation; mod c_entity_metadata; +mod c_entity_status; mod c_entity_velocity; mod c_game_event; mod c_head_rot; @@ -51,6 +52,7 @@ pub use c_close_container::*; pub use c_disguised_chat_message::*; pub use c_entity_animation::*; pub use c_entity_metadata::*; +pub use c_entity_status::*; pub use c_entity_velocity::*; pub use c_game_event::*; pub use c_head_rot::*; diff --git a/pumpkin/src/client/player_packet.rs b/pumpkin/src/client/player_packet.rs index 0cb74053c..f61457467 100644 --- a/pumpkin/src/client/player_packet.rs +++ b/pumpkin/src/client/player_packet.rs @@ -379,7 +379,7 @@ impl Player { let config = &ADVANCED_CONFIG.pvp; if config.enabled { let world = &entity.world; - let attacked_player = world.get_by_entityid(self, entity_id.0 as EntityId); + let attacked_player = world.get_player_by_entityid(entity_id.0 as EntityId); if let Some(player) = attacked_player { let victem_entity = &player.entity; if config.protect_creative diff --git a/pumpkin/src/commands/arg_player.rs b/pumpkin/src/commands/arg_player.rs index 98a71fde7..11e1fe3b6 100644 --- a/pumpkin/src/commands/arg_player.rs +++ b/pumpkin/src/commands/arg_player.rs @@ -1,8 +1,11 @@ +use std::sync::Arc; + use crate::commands::dispatcher::InvalidTreeError; use crate::commands::dispatcher::InvalidTreeError::InvalidConsumptionError; use crate::commands::tree::{ConsumedArgs, RawArgs}; use crate::commands::CommandSender; use crate::commands::CommandSender::Player; +use crate::server::Server; /// todo: implement (so far only own name + @s/@p is implemented) pub fn consume_arg_player(src: &CommandSender, args: &mut RawArgs) -> Option { @@ -29,6 +32,7 @@ pub fn consume_arg_player(src: &CommandSender, args: &mut RawArgs) -> Option( src: &'a mut CommandSender, + _server: &Arc, arg_name: &str, consumed_args: &ConsumedArgs, ) -> Result<&'a crate::entity::player::Player, InvalidTreeError> { diff --git a/pumpkin/src/commands/cmd_gamemode.rs b/pumpkin/src/commands/cmd_gamemode.rs index ce48f8d91..c950df438 100644 --- a/pumpkin/src/commands/cmd_gamemode.rs +++ b/pumpkin/src/commands/cmd_gamemode.rs @@ -85,9 +85,9 @@ pub(crate) fn init_command_tree<'a>() -> CommandTree<'a> { }), ) .with_child(argument(ARG_TARGET, consume_arg_player).execute( - &|sender, _, args| { + &|sender, server, args| { let gamemode = parse_arg_gamemode(args)?; - let target = parse_arg_player(sender, ARG_TARGET, args)?; + let target = parse_arg_player(sender, server, ARG_TARGET, args)?; if target.gamemode.load() == gamemode { target.send_system_message(TextComponent::text(&format!( diff --git a/pumpkin/src/entity/mod.rs b/pumpkin/src/entity/mod.rs index d1a62bb6e..d80b2244d 100644 --- a/pumpkin/src/entity/mod.rs +++ b/pumpkin/src/entity/mod.rs @@ -8,7 +8,7 @@ use pumpkin_core::math::{ }; use pumpkin_entity::{entity_type::EntityType, pose::EntityPose, EntityId}; use pumpkin_protocol::{ - client::play::{CSetEntityMetadata, Metadata}, + client::play::{CEntityStatus, CSetEntityMetadata, Metadata}, VarInt, }; @@ -121,8 +121,21 @@ impl Entity { self.pitch.store(pitch); } + /// Kills the Entity + /// + /// This is simliar to `kill` but Spawn Particles, Animation and plays death sound + pub fn kill(&self) { + // Spawns death smoke particles + self.world + .broadcast_packet_all(&CEntityStatus::new(self.entity_id, 60)); + // Plays the death sound and death animation + self.world + .broadcast_packet_all(&CEntityStatus::new(self.entity_id, 3)); + self.remove(); + } + /// Removes the Entity from their current World - pub async fn remove(&mut self) { + pub fn remove(&self) { self.world.remove_entity(self); } diff --git a/pumpkin/src/entity/player.rs b/pumpkin/src/entity/player.rs index 175b8d8b5..252d4dcc4 100644 --- a/pumpkin/src/entity/player.rs +++ b/pumpkin/src/entity/player.rs @@ -18,7 +18,7 @@ use pumpkin_protocol::{ bytebuf::{packet_id::Packet, DeserializerError}, client::play::{ CGameEvent, CPlayDisconnect, CPlayerAbilities, CPlayerInfoUpdate, CSyncPlayerPosition, - CSystemChatMessage, PlayerAction, + CSystemChatMessage, GameEvent, PlayerAction, }, server::play::{ SChatCommand, SChatMessage, SClickContainer, SClientInformationPlay, SConfirmTeleport, @@ -248,8 +248,10 @@ impl Player { actions: vec![PlayerAction::UpdateGameMode((gamemode as i32).into())], }], )); - self.client - .send_packet(&CGameEvent::new(3, gamemode.to_f32().unwrap())); + self.client.send_packet(&CGameEvent::new( + GameEvent::ChangeGameMode, + gamemode.to_f32().unwrap(), + )); } pub fn send_system_message(&self, text: TextComponent) { diff --git a/pumpkin/src/main.rs b/pumpkin/src/main.rs index a4cb4f174..5f27a7607 100644 --- a/pumpkin/src/main.rs +++ b/pumpkin/src/main.rs @@ -170,10 +170,7 @@ fn main() -> io::Result<()> { if closed { if let Some(player) = players.remove(&token) { player.remove().await; - dbg!("b"); let connection = &mut player.client.connection.lock(); - dbg!("c"); - poll.registry().deregister(connection.by_ref())?; } } diff --git a/pumpkin/src/server/mod.rs b/pumpkin/src/server/mod.rs index 292e3345a..86c18851b 100644 --- a/pumpkin/src/server/mod.rs +++ b/pumpkin/src/server/mod.rs @@ -158,6 +158,16 @@ impl Server { } } + /// Searches every world for a player by name + pub fn get_player_by_name(&self, name: &str) -> Option> { + for world in self.worlds.iter() { + if let Some(player) = world.get_player_by_name(name) { + return Some(player); + } + } + None + } + /// Generates a new entity id /// This should be global pub fn new_entity_id(&self) -> EntityId { diff --git a/pumpkin/src/world/mod.rs b/pumpkin/src/world/mod.rs index b65ad3720..18a21bec5 100644 --- a/pumpkin/src/world/mod.rs +++ b/pumpkin/src/world/mod.rs @@ -11,7 +11,7 @@ use pumpkin_entity::{entity_type::EntityType, EntityId}; use pumpkin_protocol::{ client::play::{ CChunkData, CGameEvent, CLogin, CPlayerAbilities, CPlayerInfoUpdate, CRemoveEntities, - CRemovePlayerInfo, CSetEntityMetadata, CSpawnEntity, Metadata, PlayerAction, + CRemovePlayerInfo, CSetEntityMetadata, CSpawnEntity, GameEvent, Metadata, PlayerAction, }, uuid::UUID, ClientPacket, VarInt, @@ -216,8 +216,10 @@ impl World { self.broadcast_packet_all(&packet) } - // Start waiting for level chunks - player.client.send_packet(&CGameEvent::new(13, 0.0)); + // Start waiting for level chunks, Sets the "Loading Terrain" screen + player + .client + .send_packet(&CGameEvent::new(GameEvent::StartWaitingChunks, 0.0)); // Spawn in inital chunks player_chunker::player_join(self, player.clone()).await; @@ -258,13 +260,9 @@ impl World { dbg!("DONE CHUNKS", inst.elapsed()); } - pub fn get_by_entityid(&self, from: &Player, id: EntityId) -> Option> { - for (_, player) in self - .current_players - .lock() - .iter() - .filter(|c| c.0 != &from.client.token) - { + /// Gets a Player by entity id + pub fn get_player_by_entityid(&self, id: EntityId) -> Option> { + for (_, player) in self.current_players.lock().iter() { if player.entity_id() == id { return Some(player.clone()); } @@ -272,6 +270,16 @@ impl World { None } + /// Gets a Player by name + pub fn get_player_by_name(&self, name: &str) -> Option> { + for (_, player) in self.current_players.lock().iter() { + if player.gameprofile.name == name { + return Some(player.clone()); + } + } + None + } + pub fn add_player(&self, token: Token, player: Arc) { self.current_players.lock().insert(token, player); } From e7fc40632d78ebc392fd6a8bfa8a1e76def239d5 Mon Sep 17 00:00:00 2001 From: StripedMonkey Date: Mon, 9 Sep 2024 21:43:01 -0400 Subject: [PATCH 15/65] extract authentication client v2 --- pumpkin/src/client/authentication.rs | 8 +-- pumpkin/src/client/client_packet.rs | 33 +++-------- pumpkin/src/server/bikeshed_key_store.rs | 74 ++++++++++++++++++++++++ pumpkin/src/server/mod.rs | 52 ++++++++--------- 4 files changed, 108 insertions(+), 59 deletions(-) create mode 100644 pumpkin/src/server/bikeshed_key_store.rs diff --git a/pumpkin/src/client/authentication.rs b/pumpkin/src/client/authentication.rs index 9af1020ce..0d3a537c4 100644 --- a/pumpkin/src/client/authentication.rs +++ b/pumpkin/src/client/authentication.rs @@ -1,7 +1,6 @@ use std::{collections::HashMap, net::IpAddr, sync::Arc}; use base64::{engine::general_purpose, Engine}; -use num_bigint::BigInt; use pumpkin_config::{auth::TextureConfig, ADVANCED_CONFIG}; use pumpkin_core::ProfileAction; use pumpkin_protocol::Property; @@ -93,15 +92,10 @@ pub fn unpack_textures(property: Property, config: &TextureConfig) -> Result<(), for texture in textures.textures { let url = Url::parse(&texture.1.url).map_err(|e| TextureError::InvalidURL(e.to_string()))?; - is_texture_url_valid(url, config)? - } + is_texture_url_valid(url, config)? } Ok(()) } -pub fn auth_digest(bytes: &[u8]) -> String { - BigInt::from_signed_bytes_be(bytes).to_str_radix(16) -} - pub fn is_texture_url_valid(url: Url, config: &TextureConfig) -> Result<(), TextureError> { let scheme = url.scheme(); if !config.allowed_url_schemes.contains(&scheme.to_string()) { diff --git a/pumpkin/src/client/client_packet.rs b/pumpkin/src/client/client_packet.rs index c8aa6a8bd..817adb245 100644 --- a/pumpkin/src/client/client_packet.rs +++ b/pumpkin/src/client/client_packet.rs @@ -6,7 +6,7 @@ use pumpkin_core::text::TextComponent; use pumpkin_protocol::{ client::{ config::{CConfigAddResourcePack, CFinishConfig, CKnownPacks, CRegistryData}, - login::{CEncryptionRequest, CLoginSuccess, CSetCompression}, + login::{CLoginSuccess, CSetCompression}, status::{CPingResponse, CStatusResponse}, }, server::{ @@ -17,8 +17,6 @@ use pumpkin_protocol::{ }, ConnectionState, KnownPack, CURRENT_MC_PROTOCOL, }; -use rsa::Pkcs1v15Encrypt; -use sha1::{Digest, Sha1}; use crate::{ client::authentication::{self, GameProfile}, @@ -27,10 +25,7 @@ use crate::{ server::{Server, CURRENT_MC_VERSION}, }; -use super::{ - authentication::{auth_digest, unpack_textures}, - Client, EncryptionError, PlayerConfig, -}; +use super::{authentication::unpack_textures, Client, PlayerConfig}; /// Processes incoming Packets from the Client to the Server /// Implements the `Client` Packets @@ -101,14 +96,7 @@ impl Client { // TODO: check config for encryption let verify_token: [u8; 4] = rand::random(); - let public_key_der = &server.public_key_der; - let packet = CEncryptionRequest::new( - "", - public_key_der, - &verify_token, - BASIC_CONFIG.online_mode, // TODO - ); - self.send_packet(&packet); + self.send_packet(&server.encryption_request(&verify_token, BASIC_CONFIG.online_mode)); } pub async fn handle_encryption_response( @@ -116,22 +104,15 @@ impl Client { server: &Arc, encryption_response: SEncryptionResponse, ) { - let shared_secret = server - .private_key - .decrypt(Pkcs1v15Encrypt, &encryption_response.shared_secret) - .map_err(|_| EncryptionError::FailedDecrypt) - .unwrap(); + let shared_secret = server.decrypt(&encryption_response.shared_secret).unwrap(); + self.enable_encryption(&shared_secret) .unwrap_or_else(|e| self.kick(&e.to_string())); let mut gameprofile = self.gameprofile.lock(); if BASIC_CONFIG.online_mode { - let hash = Sha1::new() - .chain_update(&shared_secret) - .chain_update(&server.public_key_der) - .finalize(); - let hash = auth_digest(&hash); + let hash = server.digest_secret(&shared_secret); let ip = self.address.lock().ip(); match authentication::authenticate( &gameprofile.as_ref().unwrap().name, @@ -231,7 +212,7 @@ impl Client { id: "core", version: "1.21", }])); - dbg!("login achnowlaged"); + dbg!("login acknowledged"); } pub fn handle_client_information_config( &self, diff --git a/pumpkin/src/server/bikeshed_key_store.rs b/pumpkin/src/server/bikeshed_key_store.rs new file mode 100644 index 000000000..e3b447af6 --- /dev/null +++ b/pumpkin/src/server/bikeshed_key_store.rs @@ -0,0 +1,74 @@ +use num_bigint::BigInt; +use pumpkin_protocol::client::login::CEncryptionRequest; +use rsa::{traits::PublicKeyParts as _, Pkcs1v15Encrypt, RsaPrivateKey, RsaPublicKey}; +use sha1::Sha1; +use sha2::Digest; + +use crate::client::EncryptionError; + +pub struct BikeShedKeyStore { + pub _public_key: RsaPublicKey, + pub private_key: RsaPrivateKey, + pub public_key_der: Box<[u8]>, +} + +impl BikeShedKeyStore { + pub fn new() -> Self { + log::debug!("Creating encryption keys..."); + let (public_key, private_key) = Self::generate_keys(); + + let public_key_der = rsa_der::public_key_to_der( + &private_key.n().to_bytes_be(), + &private_key.e().to_bytes_be(), + ) + .into_boxed_slice(); + BikeShedKeyStore { + _public_key: public_key, + private_key, + public_key_der, + } + } + + fn generate_keys() -> (RsaPublicKey, RsaPrivateKey) { + let mut rng = rand::thread_rng(); + + let priv_key = RsaPrivateKey::new(&mut rng, 1024).expect("failed to generate a key"); + let pub_key = RsaPublicKey::from(&priv_key); + (pub_key, priv_key) + } + + pub fn encryption_request<'a>( + &'a self, + server_id: &'a str, + verification_token: &'a [u8; 4], + should_authenticate: bool, + ) -> CEncryptionRequest<'_> { + CEncryptionRequest::new( + server_id, + &self.public_key_der, + verification_token, + should_authenticate, + ) + } + + pub fn decrypt(&self, data: &[u8]) -> Result, EncryptionError> { + let decrypted = self + .private_key + .decrypt(Pkcs1v15Encrypt, data) + .map_err(|_| EncryptionError::FailedDecrypt)?; + Ok(decrypted) + } + + pub fn get_digest(&self, secret: &[u8]) -> String { + auth_digest( + &Sha1::new() + .chain_update(secret) + .chain_update(&self.public_key_der) + .finalize(), + ) + } +} + +pub fn auth_digest(bytes: &[u8]) -> String { + BigInt::from_signed_bytes_be(bytes).to_str_radix(16) +} diff --git a/pumpkin/src/server/mod.rs b/pumpkin/src/server/mod.rs index 86c18851b..9569e14c4 100644 --- a/pumpkin/src/server/mod.rs +++ b/pumpkin/src/server/mod.rs @@ -1,4 +1,5 @@ use base64::{engine::general_purpose, Engine}; +use bikeshed_key_store::BikeShedKeyStore; use image::GenericImageView; use mio::Token; use parking_lot::{Mutex, RwLock}; @@ -6,6 +7,7 @@ use pumpkin_config::{BasicConfiguration, BASIC_CONFIG}; use pumpkin_core::GameMode; use pumpkin_entity::EntityId; use pumpkin_plugin::PluginLoader; +use pumpkin_protocol::client::login::CEncryptionRequest; use pumpkin_protocol::{ client::config::CPluginMessage, ClientPacket, Players, Sample, StatusResponse, VarInt, Version, CURRENT_MC_PROTOCOL, @@ -25,8 +27,8 @@ use std::{ use pumpkin_inventory::drag_handler::DragHandler; use pumpkin_inventory::{Container, OpenContainer}; use pumpkin_registry::Registry; -use rsa::{traits::PublicKeyParts, RsaPrivateKey, RsaPublicKey}; +use crate::client::EncryptionError; use crate::{ client::Client, commands::{default_dispatcher, dispatcher::CommandDispatcher}, @@ -34,13 +36,11 @@ use crate::{ world::World, }; +mod bikeshed_key_store; pub const CURRENT_MC_VERSION: &str = "1.21.1"; pub struct Server { - pub public_key: RsaPublicKey, - pub private_key: RsaPrivateKey, - pub public_key_der: Box<[u8]>, - + key_store: BikeShedKeyStore, pub plugin_loader: PluginLoader, pub command_dispatcher: Arc>, @@ -74,14 +74,7 @@ impl Server { let cached_server_brand = Self::build_brand(); // TODO: only create when needed - log::debug!("Creating encryption keys..."); - let (public_key, private_key) = Self::generate_keys(); - - let public_key_der = rsa_der::public_key_to_der( - &private_key.n().to_bytes_be(), - &private_key.e().to_bytes_be(), - ) - .into_boxed_slice(); + let key_store = BikeShedKeyStore::new(); let auth_client = if BASIC_CONFIG.online_mode { Some( reqwest::Client::builder() @@ -110,14 +103,12 @@ impl Server { // 0 is invalid entity_id: 2.into(), worlds: vec![Arc::new(world)], - public_key, - cached_server_brand, - private_key, command_dispatcher: Arc::new(command_dispatcher), + auth_client, + key_store, status_response, status_response_json, - public_key_der, - auth_client, + cached_server_brand, } } @@ -174,6 +165,23 @@ impl Server { self.entity_id.fetch_add(1, Ordering::SeqCst) } + pub fn encryption_request<'a>( + &'a self, + verification_token: &'a [u8; 4], + should_authenticate: bool, + ) -> CEncryptionRequest<'_> { + self.key_store + .encryption_request("", verification_token, should_authenticate) + } + + pub fn decrypt(&self, data: &[u8]) -> Result, EncryptionError> { + self.key_store.decrypt(data) + } + + pub fn digest_secret(&self, secret: &[u8]) -> String { + self.key_store.get_digest(secret) + } + pub fn build_brand() -> Vec { let brand = "Pumpkin"; let mut buf = vec![]; @@ -233,12 +241,4 @@ impl Server { general_purpose::STANDARD.encode_string(image, &mut result); result } - - pub fn generate_keys() -> (RsaPublicKey, RsaPrivateKey) { - let mut rng = rand::thread_rng(); - - let priv_key = RsaPrivateKey::new(&mut rng, 1024).expect("failed to generate a key"); - let pub_key = RsaPublicKey::from(&priv_key); - (pub_key, priv_key) - } } From c4dc376bc94ac4e9824350e462baab0c26997080 Mon Sep 17 00:00:00 2001 From: StripedMonkey Date: Mon, 9 Sep 2024 22:00:54 -0400 Subject: [PATCH 16/65] extract server status publishing into its own file --- .../src/client/config/c_add_resource_pack.rs | 4 +- pumpkin-protocol/src/uuid.rs | 6 + pumpkin/src/client/authentication.rs | 3 +- pumpkin/src/client/client_packet.rs | 28 ++--- pumpkin/src/server/bikeshed_server_listing.rs | 94 ++++++++++++++++ pumpkin/src/server/mod.rs | 103 +++--------------- 6 files changed, 136 insertions(+), 102 deletions(-) create mode 100644 pumpkin/src/server/bikeshed_server_listing.rs diff --git a/pumpkin-protocol/src/client/config/c_add_resource_pack.rs b/pumpkin-protocol/src/client/config/c_add_resource_pack.rs index 824141e25..926fb6e39 100644 --- a/pumpkin-protocol/src/client/config/c_add_resource_pack.rs +++ b/pumpkin-protocol/src/client/config/c_add_resource_pack.rs @@ -16,14 +16,14 @@ pub struct CConfigAddResourcePack<'a> { impl<'a> CConfigAddResourcePack<'a> { pub fn new( - uuid: UUID, + uuid: uuid::Uuid, url: &'a str, hash: &'a str, forced: bool, prompt_message: Option>, ) -> Self { Self { - uuid, + uuid: UUID(uuid), url, hash, forced, diff --git a/pumpkin-protocol/src/uuid.rs b/pumpkin-protocol/src/uuid.rs index a187a083a..39988ae40 100644 --- a/pumpkin-protocol/src/uuid.rs +++ b/pumpkin-protocol/src/uuid.rs @@ -13,3 +13,9 @@ impl Serialize for UUID { serializer.serialize_bytes(self.0.as_bytes()) } } + +impl UUID { + pub fn new(data: &[u8]) -> Self { + Self(uuid::Uuid::new_v3(&uuid::Uuid::NAMESPACE_DNS, data)) + } +} diff --git a/pumpkin/src/client/authentication.rs b/pumpkin/src/client/authentication.rs index 0d3a537c4..20a3c28e7 100644 --- a/pumpkin/src/client/authentication.rs +++ b/pumpkin/src/client/authentication.rs @@ -92,7 +92,8 @@ pub fn unpack_textures(property: Property, config: &TextureConfig) -> Result<(), for texture in textures.textures { let url = Url::parse(&texture.1.url).map_err(|e| TextureError::InvalidURL(e.to_string()))?; - is_texture_url_valid(url, config)? } + is_texture_url_valid(url, config)? + } Ok(()) } diff --git a/pumpkin/src/client/client_packet.rs b/pumpkin/src/client/client_packet.rs index 817adb245..9c4391405 100644 --- a/pumpkin/src/client/client_packet.rs +++ b/pumpkin/src/client/client_packet.rs @@ -7,7 +7,7 @@ use pumpkin_protocol::{ client::{ config::{CConfigAddResourcePack, CFinishConfig, CKnownPacks, CRegistryData}, login::{CLoginSuccess, CSetCompression}, - status::{CPingResponse, CStatusResponse}, + status::CPingResponse, }, server::{ config::{SAcknowledgeFinishConfig, SClientInformationConfig, SKnownPacks, SPluginMessage}, @@ -17,6 +17,7 @@ use pumpkin_protocol::{ }, ConnectionState, KnownPack, CURRENT_MC_PROTOCOL, }; +use uuid::Uuid; use crate::{ client::authentication::{self, GameProfile}, @@ -54,7 +55,7 @@ impl Client { } pub fn handle_status_request(&self, server: &Arc, _status_request: SStatusRequest) { - self.send_packet(&CStatusResponse::new(&server.status_response_json)); + self.send_packet(&server.get_status()); } pub fn handle_ping_request(&self, _server: &Arc, ping_request: SStatusPingRequest) { @@ -185,25 +186,26 @@ impl Client { _login_acknowledged: SLoginAcknowledged, ) { self.connection_state.store(ConnectionState::Config); - server.send_brand(self); + self.send_packet(&server.get_branding()); let resource_config = &ADVANCED_CONFIG.resource_pack; if resource_config.enabled { - let prompt_message = if resource_config.prompt_message.is_empty() { - None - } else { - Some(TextComponent::text(&resource_config.prompt_message)) - }; - self.send_packet(&CConfigAddResourcePack::new( - pumpkin_protocol::uuid::UUID(uuid::Uuid::new_v3( + let resource_pack = CConfigAddResourcePack::new( + Uuid::new_v3( &uuid::Uuid::NAMESPACE_DNS, resource_config.resource_pack_url.as_bytes(), - )), + ), &resource_config.resource_pack_url, &resource_config.resource_pack_sha1, resource_config.force, - prompt_message, - )); + if !resource_config.prompt_message.is_empty() { + Some(TextComponent::text(&resource_config.prompt_message)) + } else { + None + }, + ); + + self.send_packet(&resource_pack); } // known data packs diff --git a/pumpkin/src/server/bikeshed_server_listing.rs b/pumpkin/src/server/bikeshed_server_listing.rs new file mode 100644 index 000000000..57ca00fc7 --- /dev/null +++ b/pumpkin/src/server/bikeshed_server_listing.rs @@ -0,0 +1,94 @@ +use std::{io::Cursor, path::Path}; + +use base64::{engine::general_purpose, Engine as _}; +use image::GenericImageView as _; +use pumpkin_config::{BasicConfiguration, BASIC_CONFIG}; +use pumpkin_protocol::{ + client::{config::CPluginMessage, status::CStatusResponse}, + Players, Sample, StatusResponse, VarInt, Version, CURRENT_MC_PROTOCOL, +}; + +use super::CURRENT_MC_VERSION; + +pub struct BikeShedServerListing { + _status_response: StatusResponse, + // We cache the json response here so we don't parse it every time someone makes a Status request. + // Keep in mind that we must parse this again, when the StatusResponse changes which usually happen when a player joins or leaves + status_response_json: String, + /// Cached Server brand buffer so we don't have to rebuild them every time a player joins + cached_server_brand: Vec, +} + +impl BikeShedServerListing { + pub fn new() -> Self { + let status_response = Self::build_response(&BASIC_CONFIG); + let status_response_json = serde_json::to_string(&status_response) + .expect("Failed to parse Status response into JSON"); + let cached_server_brand = Self::build_brand(); + + BikeShedServerListing { + _status_response: status_response, + status_response_json, + cached_server_brand, + } + } + + pub fn get_branding(&self) -> CPluginMessage { + CPluginMessage::new("minecraft:brand", &self.cached_server_brand) + } + + pub fn get_status(&self) -> CStatusResponse<'_> { + CStatusResponse::new(&self.status_response_json) + } + + pub fn build_response(config: &BasicConfiguration) -> StatusResponse { + let icon_path = concat!(env!("CARGO_MANIFEST_DIR"), "/icon.png"); + let icon = if Path::new(icon_path).exists() { + Some(Self::load_icon(icon_path)) + } else { + None + }; + + StatusResponse { + version: Some(Version { + name: CURRENT_MC_VERSION.into(), + protocol: CURRENT_MC_PROTOCOL, + }), + players: Some(Players { + max: config.max_players, + online: 0, + sample: vec![Sample { + name: "".into(), + id: "".into(), + }], + }), + description: config.motd.clone(), + favicon: icon, + enforece_secure_chat: false, + } + } + + fn load_icon(path: &str) -> String { + let icon = match image::open(path).map_err(|e| panic!("error loading icon: {}", e)) { + Ok(icon) => icon, + Err(_) => return "".into(), + }; + let dimension = icon.dimensions(); + assert!(dimension.0 == 64, "Icon width must be 64"); + assert!(dimension.1 == 64, "Icon height must be 64"); + let mut image = Vec::with_capacity(64 * 64 * 4); + icon.write_to(&mut Cursor::new(&mut image), image::ImageFormat::Png) + .unwrap(); + let mut result = "data:image/png;base64,".to_owned(); + general_purpose::STANDARD.encode_string(image, &mut result); + result + } + + fn build_brand() -> Vec { + let brand = "Pumpkin"; + let mut buf = vec![]; + let _ = VarInt(brand.len() as i32).encode(&mut buf); + buf.extend_from_slice(brand.as_bytes()); + buf + } +} diff --git a/pumpkin/src/server/mod.rs b/pumpkin/src/server/mod.rs index 9569e14c4..ca43f5211 100644 --- a/pumpkin/src/server/mod.rs +++ b/pumpkin/src/server/mod.rs @@ -1,22 +1,17 @@ -use base64::{engine::general_purpose, Engine}; use bikeshed_key_store::BikeShedKeyStore; -use image::GenericImageView; +use bikeshed_server_listing::BikeShedServerListing; use mio::Token; use parking_lot::{Mutex, RwLock}; -use pumpkin_config::{BasicConfiguration, BASIC_CONFIG}; +use pumpkin_config::BASIC_CONFIG; use pumpkin_core::GameMode; use pumpkin_entity::EntityId; use pumpkin_plugin::PluginLoader; use pumpkin_protocol::client::login::CEncryptionRequest; -use pumpkin_protocol::{ - client::config::CPluginMessage, ClientPacket, Players, Sample, StatusResponse, VarInt, Version, - CURRENT_MC_PROTOCOL, -}; +use pumpkin_protocol::client::status::CStatusResponse; +use pumpkin_protocol::{client::config::CPluginMessage, ClientPacket}; use pumpkin_world::dimension::Dimension; use std::collections::HashMap; use std::{ - io::Cursor, - path::Path, sync::{ atomic::{AtomicI32, Ordering}, Arc, @@ -37,22 +32,16 @@ use crate::{ }; mod bikeshed_key_store; +mod bikeshed_server_listing; pub const CURRENT_MC_VERSION: &str = "1.21.1"; pub struct Server { key_store: BikeShedKeyStore, + server_listing: BikeShedServerListing, pub plugin_loader: PluginLoader, pub command_dispatcher: Arc>, - pub worlds: Vec>, - pub status_response: StatusResponse, - // We cache the json response here so we don't parse it every time someone makes a Status request. - // Keep in mind that we must parse this again, when the StatusResponse changes which usally happen when a player joins or leaves - pub status_response_json: String, - - /// Cache the Server brand buffer so we don't have to rebuild them every time a player joins - pub cached_server_brand: Vec, /// Cache the registry so we don't have to parse it every time a player joins pub cached_registry: Vec, @@ -68,13 +57,9 @@ pub struct Server { impl Server { #[allow(clippy::new_without_default)] pub fn new() -> Self { - let status_response = Self::build_response(&BASIC_CONFIG); - let status_response_json = serde_json::to_string(&status_response) - .expect("Failed to parse Status response into JSON"); - let cached_server_brand = Self::build_brand(); - // TODO: only create when needed let key_store = BikeShedKeyStore::new(); + let server_listing = BikeShedServerListing::new(); let auth_client = if BASIC_CONFIG.online_mode { Some( reqwest::Client::builder() @@ -106,9 +91,7 @@ impl Server { command_dispatcher: Arc::new(command_dispatcher), auth_client, key_store, - status_response, - status_response_json, - cached_server_brand, + server_listing, } } @@ -118,7 +101,7 @@ impl Server { GameMode::Undefined => GameMode::Survival, game_mode => game_mode, }; - // Basicly the default world + // Basically the default world // TODO: select default from config let world = self.worlds[0].clone(); @@ -165,6 +148,14 @@ impl Server { self.entity_id.fetch_add(1, Ordering::SeqCst) } + pub fn get_branding(&self) -> CPluginMessage<'_> { + self.server_listing.get_branding() + } + + pub fn get_status(&self) -> CStatusResponse<'_> { + self.server_listing.get_status() + } + pub fn encryption_request<'a>( &'a self, verification_token: &'a [u8; 4], @@ -181,64 +172,4 @@ impl Server { pub fn digest_secret(&self, secret: &[u8]) -> String { self.key_store.get_digest(secret) } - - pub fn build_brand() -> Vec { - let brand = "Pumpkin"; - let mut buf = vec![]; - let _ = VarInt(brand.len() as i32).encode(&mut buf); - buf.extend_from_slice(brand.as_bytes()); - buf - } - - pub fn send_brand(&self, client: &Client) { - // send server brand - client.send_packet(&CPluginMessage::new( - "minecraft:brand", - &self.cached_server_brand, - )); - } - - pub fn build_response(config: &BasicConfiguration) -> StatusResponse { - let icon_path = concat!(env!("CARGO_MANIFEST_DIR"), "/icon.png"); - let icon = if Path::new(icon_path).exists() { - Some(Self::load_icon(icon_path)) - } else { - None - }; - - StatusResponse { - version: Some(Version { - name: CURRENT_MC_VERSION.into(), - protocol: CURRENT_MC_PROTOCOL, - }), - players: Some(Players { - max: config.max_players, - online: 0, - sample: vec![Sample { - name: "".into(), - id: "".into(), - }], - }), - description: config.motd.clone(), - favicon: icon, - // TODO - enforece_secure_chat: false, - } - } - - pub fn load_icon(path: &str) -> String { - let icon = match image::open(path).map_err(|e| panic!("error loading icon: {}", e)) { - Ok(icon) => icon, - Err(_) => return "".into(), - }; - let dimension = icon.dimensions(); - assert!(dimension.0 == 64, "Icon width must be 64"); - assert!(dimension.1 == 64, "Icon height must be 64"); - let mut image = Vec::with_capacity(64 * 64 * 4); - icon.write_to(&mut Cursor::new(&mut image), image::ImageFormat::Png) - .unwrap(); - let mut result = "data:image/png;base64,".to_owned(); - general_purpose::STANDARD.encode_string(image, &mut result); - result - } } From c13261ba2020a9de753ead464e3ef0011c467aaa Mon Sep 17 00:00:00 2001 From: StripedMonkey Date: Tue, 10 Sep 2024 23:54:21 -0400 Subject: [PATCH 17/65] standardize use of compact serialization for UUIDs. --- .../src/client/config/c_add_resource_pack.rs | 6 ++---- .../src/client/play/c_player_chat_message.rs | 7 ++++--- .../src/client/play/c_player_remove.rs | 20 ++++++++++++++---- .../src/client/play/c_spawn_player.rs | 7 ++++--- pumpkin-protocol/src/lib.rs | 1 - pumpkin-protocol/src/uuid.rs | 21 ------------------- pumpkin/src/client/player_packet.rs | 2 +- pumpkin/src/world/mod.rs | 7 +++---- 8 files changed, 30 insertions(+), 41 deletions(-) delete mode 100644 pumpkin-protocol/src/uuid.rs diff --git a/pumpkin-protocol/src/client/config/c_add_resource_pack.rs b/pumpkin-protocol/src/client/config/c_add_resource_pack.rs index 926fb6e39..48b011faa 100644 --- a/pumpkin-protocol/src/client/config/c_add_resource_pack.rs +++ b/pumpkin-protocol/src/client/config/c_add_resource_pack.rs @@ -2,12 +2,10 @@ use pumpkin_core::text::TextComponent; use pumpkin_macros::packet; use serde::Serialize; -use crate::uuid::UUID; - #[derive(Serialize)] #[packet(0x09)] pub struct CConfigAddResourcePack<'a> { - uuid: UUID, + uuid: uuid::Uuid, url: &'a str, hash: &'a str, // max 40 forced: bool, @@ -23,7 +21,7 @@ impl<'a> CConfigAddResourcePack<'a> { prompt_message: Option>, ) -> Self { Self { - uuid: UUID(uuid), + uuid, url, hash, forced, 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 8ec95ddca..7d5e7c7ce 100644 --- a/pumpkin-protocol/src/client/play/c_player_chat_message.rs +++ b/pumpkin-protocol/src/client/play/c_player_chat_message.rs @@ -2,11 +2,12 @@ use pumpkin_core::text::TextComponent; use pumpkin_macros::packet; use serde::Serialize; -use crate::{uuid::UUID, BitSet, VarInt}; +use crate::{BitSet, VarInt}; #[derive(Serialize)] #[packet(0x39)] pub struct CPlayerChatMessage<'a> { - sender: UUID, + #[serde(with = "uuid::serde::compact")] + sender: uuid::Uuid, index: VarInt, message_signature: Option<&'a [u8]>, message: &'a str, @@ -24,7 +25,7 @@ pub struct CPlayerChatMessage<'a> { impl<'a> CPlayerChatMessage<'a> { #[expect(clippy::too_many_arguments)] pub fn new( - sender: UUID, + sender: uuid::Uuid, index: VarInt, message_signature: Option<&'a [u8]>, message: &'a str, diff --git a/pumpkin-protocol/src/client/play/c_player_remove.rs b/pumpkin-protocol/src/client/play/c_player_remove.rs index 4d128a816..20b7ba218 100644 --- a/pumpkin-protocol/src/client/play/c_player_remove.rs +++ b/pumpkin-protocol/src/client/play/c_player_remove.rs @@ -1,20 +1,32 @@ use pumpkin_macros::packet; -use serde::Serialize; +use serde::{ser::SerializeSeq, Serialize}; -use crate::{uuid::UUID, VarInt}; +use crate::VarInt; #[derive(Serialize)] #[packet(0x3D)] pub struct CRemovePlayerInfo<'a> { players_count: VarInt, - players: &'a [UUID], + #[serde(serialize_with = "serialize_slice_uuids")] + players: &'a [uuid::Uuid], } impl<'a> CRemovePlayerInfo<'a> { - pub fn new(players_count: VarInt, players: &'a [UUID]) -> Self { + pub fn new(players_count: VarInt, players: &'a [uuid::Uuid]) -> Self { Self { players_count, players, } } } + +fn serialize_slice_uuids( + uuids: &[uuid::Uuid], + serializer: S, +) -> Result { + let mut seq = serializer.serialize_seq(Some(uuids.len()))?; + for uuid in uuids { + seq.serialize_element(uuid.as_bytes())?; + } + seq.end() +} diff --git a/pumpkin-protocol/src/client/play/c_spawn_player.rs b/pumpkin-protocol/src/client/play/c_spawn_player.rs index 73d0f8541..c9c65b92d 100644 --- a/pumpkin-protocol/src/client/play/c_spawn_player.rs +++ b/pumpkin-protocol/src/client/play/c_spawn_player.rs @@ -1,13 +1,14 @@ use pumpkin_macros::packet; use serde::Serialize; -use crate::{uuid::UUID, VarInt}; +use crate::VarInt; #[derive(Serialize)] #[packet(0x01)] pub struct CSpawnEntity { entity_id: VarInt, - entity_uuid: UUID, + #[serde(with = "uuid::serde::compact")] + entity_uuid: uuid::Uuid, typ: VarInt, x: f64, y: f64, @@ -25,7 +26,7 @@ impl CSpawnEntity { #[expect(clippy::too_many_arguments)] pub fn new( entity_id: VarInt, - entity_uuid: UUID, + entity_uuid: uuid::Uuid, typ: VarInt, x: f64, y: f64, diff --git a/pumpkin-protocol/src/lib.rs b/pumpkin-protocol/src/lib.rs index c2fee6f50..4381934b4 100644 --- a/pumpkin-protocol/src/lib.rs +++ b/pumpkin-protocol/src/lib.rs @@ -10,7 +10,6 @@ pub mod packet_decoder; pub mod packet_encoder; pub mod server; pub mod slot; -pub mod uuid; /// To current Minecraft protocol /// Don't forget to change this when porting diff --git a/pumpkin-protocol/src/uuid.rs b/pumpkin-protocol/src/uuid.rs deleted file mode 100644 index 39988ae40..000000000 --- a/pumpkin-protocol/src/uuid.rs +++ /dev/null @@ -1,21 +0,0 @@ -use serde::Serialize; - -#[derive(Clone)] -/// Wrapper around uuid::UUID, Please use this in every Packet containing a UUID -/// We use this to we can do own Serializing -pub struct UUID(pub uuid::Uuid); - -impl Serialize for UUID { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - serializer.serialize_bytes(self.0.as_bytes()) - } -} - -impl UUID { - pub fn new(data: &[u8]) -> Self { - Self(uuid::Uuid::new_v3(&uuid::Uuid::NAMESPACE_DNS, data)) - } -} diff --git a/pumpkin/src/client/player_packet.rs b/pumpkin/src/client/player_packet.rs index f61457467..139c0b862 100644 --- a/pumpkin/src/client/player_packet.rs +++ b/pumpkin/src/client/player_packet.rs @@ -316,7 +316,7 @@ impl Player { let entity = &self.entity; let world = &entity.world; world.broadcast_packet_all(&CPlayerChatMessage::new( - pumpkin_protocol::uuid::UUID(gameprofile.id), + gameprofile.id, 1.into(), chat_message.signature.as_deref(), &message, diff --git a/pumpkin/src/world/mod.rs b/pumpkin/src/world/mod.rs index 18a21bec5..b61ddc868 100644 --- a/pumpkin/src/world/mod.rs +++ b/pumpkin/src/world/mod.rs @@ -13,7 +13,6 @@ use pumpkin_protocol::{ CChunkData, CGameEvent, CLogin, CPlayerAbilities, CPlayerInfoUpdate, CRemoveEntities, CRemovePlayerInfo, CSetEntityMetadata, CSpawnEntity, GameEvent, Metadata, PlayerAction, }, - uuid::UUID, ClientPacket, VarInt, }; use pumpkin_world::level::Level; @@ -170,7 +169,7 @@ impl World { // TODO: add velo &CSpawnEntity::new( entity_id.into(), - UUID(gameprofile.id), + gameprofile.id, (EntityType::Player as i32).into(), x, y, @@ -192,7 +191,7 @@ impl World { let gameprofile = &existing_player.gameprofile; player.client.send_packet(&CSpawnEntity::new( existing_player.entity_id().into(), - UUID(gameprofile.id), + gameprofile.id, (EntityType::Player as i32).into(), pos.x, pos.y, @@ -292,7 +291,7 @@ impl World { let uuid = player.gameprofile.id; self.broadcast_packet_expect( &[player.client.token], - &CRemovePlayerInfo::new(1.into(), &[UUID(uuid)]), + &CRemovePlayerInfo::new(1.into(), &[uuid]), ); self.remove_entity(&player.entity); } From 8bf8ba14dacfe7f6351cc7fd4ffc5a7520cde103 Mon Sep 17 00:00:00 2001 From: StripedMonkey Date: Thu, 12 Sep 2024 18:48:22 -0400 Subject: [PATCH 18/65] settle on key_store for a name --- .../server/{bikeshed_key_store.rs => key_store.rs} | 6 +++--- pumpkin/src/server/mod.rs | 12 +++++------- 2 files changed, 8 insertions(+), 10 deletions(-) rename pumpkin/src/server/{bikeshed_key_store.rs => key_store.rs} (96%) diff --git a/pumpkin/src/server/bikeshed_key_store.rs b/pumpkin/src/server/key_store.rs similarity index 96% rename from pumpkin/src/server/bikeshed_key_store.rs rename to pumpkin/src/server/key_store.rs index e3b447af6..54393dccf 100644 --- a/pumpkin/src/server/bikeshed_key_store.rs +++ b/pumpkin/src/server/key_store.rs @@ -6,13 +6,13 @@ use sha2::Digest; use crate::client::EncryptionError; -pub struct BikeShedKeyStore { +pub struct KeyStore { pub _public_key: RsaPublicKey, pub private_key: RsaPrivateKey, pub public_key_der: Box<[u8]>, } -impl BikeShedKeyStore { +impl KeyStore { pub fn new() -> Self { log::debug!("Creating encryption keys..."); let (public_key, private_key) = Self::generate_keys(); @@ -22,7 +22,7 @@ impl BikeShedKeyStore { &private_key.e().to_bytes_be(), ) .into_boxed_slice(); - BikeShedKeyStore { + KeyStore { _public_key: public_key, private_key, public_key_der, diff --git a/pumpkin/src/server/mod.rs b/pumpkin/src/server/mod.rs index ca43f5211..364101500 100644 --- a/pumpkin/src/server/mod.rs +++ b/pumpkin/src/server/mod.rs @@ -1,4 +1,4 @@ -use bikeshed_key_store::BikeShedKeyStore; +use key_store::KeyStore; use bikeshed_server_listing::BikeShedServerListing; use mio::Token; use parking_lot::{Mutex, RwLock}; @@ -31,13 +31,11 @@ use crate::{ world::World, }; -mod bikeshed_key_store; -mod bikeshed_server_listing; +mod key_store; pub const CURRENT_MC_VERSION: &str = "1.21.1"; pub struct Server { - key_store: BikeShedKeyStore, - server_listing: BikeShedServerListing, + key_store: KeyStore, pub plugin_loader: PluginLoader, pub command_dispatcher: Arc>, @@ -58,7 +56,7 @@ impl Server { #[allow(clippy::new_without_default)] pub fn new() -> Self { // TODO: only create when needed - let key_store = BikeShedKeyStore::new(); + let server_listing = BikeShedServerListing::new(); let auth_client = if BASIC_CONFIG.online_mode { Some( @@ -90,7 +88,7 @@ impl Server { worlds: vec![Arc::new(world)], command_dispatcher: Arc::new(command_dispatcher), auth_client, - key_store, + key_store: KeyStore::new(), server_listing, } } From d68a25b169786dcc703e5885598eda3a4aee51ba Mon Sep 17 00:00:00 2001 From: StripedMonkey Date: Thu, 12 Sep 2024 18:50:26 -0400 Subject: [PATCH 19/65] adopt proposed alternative name for cached data --- ..._server_listing.rs => connection_cache.rs} | 42 +++++++++++-------- pumpkin/src/server/mod.rs | 11 +++-- 2 files changed, 32 insertions(+), 21 deletions(-) rename pumpkin/src/server/{bikeshed_server_listing.rs => connection_cache.rs} (94%) diff --git a/pumpkin/src/server/bikeshed_server_listing.rs b/pumpkin/src/server/connection_cache.rs similarity index 94% rename from pumpkin/src/server/bikeshed_server_listing.rs rename to pumpkin/src/server/connection_cache.rs index 57ca00fc7..a8268ba3a 100644 --- a/pumpkin/src/server/bikeshed_server_listing.rs +++ b/pumpkin/src/server/connection_cache.rs @@ -10,33 +10,49 @@ use pumpkin_protocol::{ use super::CURRENT_MC_VERSION; -pub struct BikeShedServerListing { +pub struct CachedStatus { _status_response: StatusResponse, // We cache the json response here so we don't parse it every time someone makes a Status request. // Keep in mind that we must parse this again, when the StatusResponse changes which usually happen when a player joins or leaves status_response_json: String, +} + +pub struct CachedBranding { /// Cached Server brand buffer so we don't have to rebuild them every time a player joins cached_server_brand: Vec, } -impl BikeShedServerListing { +impl CachedBranding { + pub fn new() -> CachedBranding { + let cached_server_brand = Self::build_brand(); + CachedBranding { + cached_server_brand, + } + } + pub fn get_branding(&self) -> CPluginMessage { + CPluginMessage::new("minecraft:brand", &self.cached_server_brand) + } + fn build_brand() -> Vec { + let brand = "Pumpkin"; + let mut buf = vec![]; + let _ = VarInt(brand.len() as i32).encode(&mut buf); + buf.extend_from_slice(brand.as_bytes()); + buf + } +} + +impl CachedStatus { pub fn new() -> Self { let status_response = Self::build_response(&BASIC_CONFIG); let status_response_json = serde_json::to_string(&status_response) .expect("Failed to parse Status response into JSON"); - let cached_server_brand = Self::build_brand(); - BikeShedServerListing { + CachedStatus { _status_response: status_response, status_response_json, - cached_server_brand, } } - pub fn get_branding(&self) -> CPluginMessage { - CPluginMessage::new("minecraft:brand", &self.cached_server_brand) - } - pub fn get_status(&self) -> CStatusResponse<'_> { CStatusResponse::new(&self.status_response_json) } @@ -83,12 +99,4 @@ impl BikeShedServerListing { general_purpose::STANDARD.encode_string(image, &mut result); result } - - fn build_brand() -> Vec { - let brand = "Pumpkin"; - let mut buf = vec![]; - let _ = VarInt(brand.len() as i32).encode(&mut buf); - buf.extend_from_slice(brand.as_bytes()); - buf - } } diff --git a/pumpkin/src/server/mod.rs b/pumpkin/src/server/mod.rs index 364101500..68dcd9501 100644 --- a/pumpkin/src/server/mod.rs +++ b/pumpkin/src/server/mod.rs @@ -1,5 +1,5 @@ +use connection_cache::{CachedBranding, CachedStatus}; use key_store::KeyStore; -use bikeshed_server_listing::BikeShedServerListing; use mio::Token; use parking_lot::{Mutex, RwLock}; use pumpkin_config::BASIC_CONFIG; @@ -31,11 +31,14 @@ use crate::{ world::World, }; +mod connection_cache; mod key_store; pub const CURRENT_MC_VERSION: &str = "1.21.1"; pub struct Server { key_store: KeyStore, + server_listing: CachedStatus, + server_branding: CachedBranding, pub plugin_loader: PluginLoader, pub command_dispatcher: Arc>, @@ -57,7 +60,6 @@ impl Server { pub fn new() -> Self { // TODO: only create when needed - let server_listing = BikeShedServerListing::new(); let auth_client = if BASIC_CONFIG.online_mode { Some( reqwest::Client::builder() @@ -89,7 +91,8 @@ impl Server { command_dispatcher: Arc::new(command_dispatcher), auth_client, key_store: KeyStore::new(), - server_listing, + server_listing: CachedStatus::new(), + server_branding: CachedBranding::new(), } } @@ -147,7 +150,7 @@ impl Server { } pub fn get_branding(&self) -> CPluginMessage<'_> { - self.server_listing.get_branding() + self.server_branding.get_branding() } pub fn get_status(&self) -> CStatusResponse<'_> { From c34b5db03adc8421125f92f2942e2b3f5959c18b Mon Sep 17 00:00:00 2001 From: kralverde Date: Thu, 12 Sep 2024 21:50:48 -0400 Subject: [PATCH 20/65] add double noise generator and clean up code --- pumpkin-world/src/world_gen/noise/mod.rs | 2 + pumpkin-world/src/world_gen/noise/perlin.rs | 361 ++++++++++++++++++- pumpkin-world/src/world_gen/noise/simplex.rs | 3 + 3 files changed, 349 insertions(+), 17 deletions(-) diff --git a/pumpkin-world/src/world_gen/noise/mod.rs b/pumpkin-world/src/world_gen/noise/mod.rs index 206a47a30..106a38f3b 100644 --- a/pumpkin-world/src/world_gen/noise/mod.rs +++ b/pumpkin-world/src/world_gen/noise/mod.rs @@ -13,6 +13,8 @@ pub fn lerp2(delta_x: f64, delta_y: f64, x0y0: f64, x1y0: f64, x0y1: f64, x1y1: ) } +#[allow(dead_code)] +#[allow(clippy::too_many_arguments)] pub fn lerp3( delta_x: f64, delta_y: f64, diff --git a/pumpkin-world/src/world_gen/noise/perlin.rs b/pumpkin-world/src/world_gen/noise/perlin.rs index 7cfd2cb96..e1c92bf88 100644 --- a/pumpkin-world/src/world_gen/noise/perlin.rs +++ b/pumpkin-world/src/world_gen/noise/perlin.rs @@ -1,5 +1,8 @@ -use num_traits::{Pow, WrappingSub}; -use pumpkin_core::random::{Random, RandomSplitter}; +use itertools::Itertools; +use num_traits::{Pow, Zero}; +use pumpkin_core::random::{ + legacy_rand::LegacyRand, xoroshiro128::Xoroshiro, Random, RandomSplitter, +}; use super::{dot, lerp3, GRADIENTS}; @@ -36,6 +39,7 @@ impl PerlinNoiseSampler { } } + #[allow(dead_code)] pub fn sample_flat_y(&self, x: f64, y: f64, z: f64) -> f64 { self.sample_no_fade(x, y, z, 0f64, 0f64) } @@ -76,7 +80,7 @@ impl PerlinNoiseSampler { } fn map(&self, input: i32) -> i32 { - (self.permutation[(input & 0xFF) as usize] & 0xFF) as i32 + self.permutation[(input & 0xFF) as usize] as i32 } #[allow(clippy::too_many_arguments)] @@ -149,9 +153,16 @@ impl PerlinNoiseSampler { } } +#[allow(dead_code)] +pub enum RandomGenerator<'a> { + Xoroshiro(&'a mut Xoroshiro), + Legacy(&'a mut LegacyRand), +} + pub struct OctavePerlinNoiseSampler { octave_samplers: Vec>, amplitudes: Vec, + #[allow(dead_code)] first_octave: i32, persistence: f64, lacunarity: f64, @@ -159,7 +170,7 @@ pub struct OctavePerlinNoiseSampler { } impl OctavePerlinNoiseSampler { - fn get_total_amplitude(scale: f64, persistence: f64, amplitudes: &Vec) -> f64 { + fn get_total_amplitude(scale: f64, persistence: f64, amplitudes: &[f64]) -> f64 { let mut d = 0f64; let mut e = persistence; @@ -178,6 +189,7 @@ impl OctavePerlinNoiseSampler { value - (value / 3.3554432E7f64 + 0.5f64).floor() * 3.3554432E7f64 } + #[allow(dead_code)] pub fn calculate_amplitudes(octaves: &[i32]) -> (i32, Vec) { let mut octaves = Vec::from_iter(octaves); octaves.sort(); @@ -198,7 +210,7 @@ impl OctavePerlinNoiseSampler { (-i, double_list) } - pub fn new(random: &mut impl Random, first_octave: i32, amplitudes: Vec) -> Self { + pub fn new(random: &mut RandomGenerator, first_octave: i32, amplitudes: &[f64]) -> Self { let i = amplitudes.len(); let j = -first_octave; @@ -207,21 +219,54 @@ impl OctavePerlinNoiseSampler { samplers.push(None); } - let splitter = random.next_splitter(); - for k in 0..i { - if amplitudes[k] != 0f64 { - let l = first_octave + k as i32; - samplers[k] = Some(PerlinNoiseSampler::new( - &mut splitter.split_string(&format!("octave_{}", l)), - )); + match random { + RandomGenerator::Xoroshiro(random) => { + let splitter = random.next_splitter(); + for k in 0..i { + if amplitudes[k] != 0f64 { + let l = first_octave + k as i32; + samplers[k] = Some(PerlinNoiseSampler::new( + &mut splitter.split_string(&format!("octave_{}", l)), + )); + } + } + } + RandomGenerator::Legacy(random) => { + let sampler = PerlinNoiseSampler::new(*random); + if j >= 0 && j < i as i32 { + let d = amplitudes[j as usize]; + if d != 0f64 { + samplers[j as usize] = Some(sampler); + } + } + + for kx in (0..j as usize).rev() { + if kx < i { + let e = amplitudes[kx]; + if e != 0f64 { + samplers[kx] = Some(PerlinNoiseSampler::new(*random)); + } else { + random.skip(262); + } + } else { + random.skip(262); + } + } + + 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); } } let persistence = 2f64.pow((i as i32).wrapping_sub(1) as f64) / (2f64.pow(i as f64) - 1f64); - let max_value = Self::get_total_amplitude(2f64, persistence, &litudes); + let max_value = Self::get_total_amplitude(2f64, persistence, amplitudes); Self { octave_samplers: samplers, - amplitudes, + amplitudes: amplitudes.to_vec(), first_octave, persistence, lacunarity: 2f64.pow((-j) as f64), @@ -255,9 +300,259 @@ impl OctavePerlinNoiseSampler { } } +pub struct DoublePerlinNoiseSampler { + first_sampler: OctavePerlinNoiseSampler, + second_sampler: OctavePerlinNoiseSampler, + amplitude: f64, + #[allow(dead_code)] + max_value: f64, +} + +impl DoublePerlinNoiseSampler { + fn create_amplitude(octaves: i32) -> f64 { + 0.1f64 * (1f64 + 1f64 / (octaves + 1) as f64) + } + + #[allow(dead_code)] + pub fn new(rand: &mut RandomGenerator, first_octave: i32, amplitudes: &[f64]) -> Self { + let first_sampler = OctavePerlinNoiseSampler::new(rand, first_octave, amplitudes); + let second_sampler = OctavePerlinNoiseSampler::new(rand, first_octave, amplitudes); + + let mut j = i32::MAX; + let mut k = i32::MIN; + + for (index, amplitude) in amplitudes.iter().enumerate() { + if *amplitude != 0f64 { + j = i32::min(j, index as i32); + k = i32::max(k, index as i32); + } + } + + let amplitude = 0.16666666666666666f64 / Self::create_amplitude(k - j); + let max_value = (first_sampler.max_value + second_sampler.max_value) * amplitude; + + Self { + first_sampler, + second_sampler, + amplitude, + max_value, + } + } + + #[allow(dead_code)] + pub fn sample(&self, x: f64, y: f64, z: f64) -> f64 { + let d = x * 1.0181268882175227f64; + let e = y * 1.0181268882175227f64; + let f = z * 1.0181268882175227f64; + + (self.first_sampler.sample(x, y, z) + self.second_sampler.sample(d, e, f)) * self.amplitude + } +} + +#[cfg(test)] +mod double_perlin_noise_sampler_test { + use pumpkin_core::random::{legacy_rand::LegacyRand, xoroshiro128::Xoroshiro, Random}; + + use crate::world_gen::noise::perlin::{DoublePerlinNoiseSampler, RandomGenerator}; + + #[test] + fn sample_legacy() { + let mut rand = LegacyRand::from_seed(513513513); + assert_eq!(rand.next_i32(), -1302745855); + + let mut rand_gen = RandomGenerator::Legacy(&mut rand); + let sampler = DoublePerlinNoiseSampler::new(&mut rand_gen, 0, &[4f64]); + + let values = [ + ( + ( + 3.7329617139221236E7, + 2.847228022372606E8, + -1.8244299064688918E8, + ), + -0.5044027150385925, + ), + ( + ( + 8.936597679535551E7, + 1.491954533221004E8, + 3.457494216166344E8, + ), + -1.0004671438756043, + ), + ( + ( + -2.2479845046034336E8, + -4.085449163378981E7, + 1.343082907470065E8, + ), + 2.1781128778536973, + ), + ( + ( + -1.9094944979652843E8, + 3.695081561625232E8, + 2.1566424798360935E8, + ), + -1.2571847948126453, + ), + ( + ( + 1.8486356004931596E8, + -4.148713734284534E8, + 4.8687219454012525E8, + ), + -0.550285244015363, + ), + ( + ( + 1.7115351141710258E8, + -1.8835885697652313E8, + 1.7031060329927653E8, + ), + -0.6953327750604766, + ), + ( + ( + 8.952317194270046E7, + -5.420942524023042E7, + -2.5987559023045145E7, + ), + 2.7361630914824393, + ), + ( + ( + -8.36195975247282E8, + -1.2167090318484206E8, + 2.1237199673286602E8, + ), + -1.5518675789351004, + ), + ( + ( + 3.333103540906928E8, + 5.088236187007203E8, + -3.521137809477999E8, + ), + 0.6928720433082317, + ), + ( + ( + 7.82760234776598E7, + -2.5204361464037597E7, + -1.6615974590937865E8, + ), + -0.5102124930620466, + ), + ]; + + for ((x, y, z), sample) in values { + assert_eq!(sampler.sample(x, y, z), sample) + } + } + + #[test] + fn sample_xoroshiro() { + let mut rand = Xoroshiro::from_seed(5); + assert_eq!(rand.next_i32(), -1678727252); + + let mut rand_gen = RandomGenerator::Xoroshiro(&mut rand); + let sampler = DoublePerlinNoiseSampler::new(&mut rand_gen, 1, &[2f64, 4f64]); + + let values = [ + ( + ( + -2.4823401687190732E8, + 1.6909869132832196E8, + 1.0510057123823991E8, + ), + -0.09627881756376819, + ), + ( + ( + 1.2971355215791291E8, + -3.614855223614046E8, + 1.9997149869463342E8, + ), + 0.4412466810560897, + ), + ( + ( + -1.9858224577678584E7, + 2.5103843334053648E8, + 2.253841390457064E8, + ), + -1.3086196098510068, + ), + ( + ( + 1.4243878295159304E8, + -1.9185612600051942E8, + 4.7736284830701286E8, + ), + 1.727683424808049, + ), + ( + ( + -9.411241394159131E7, + 4.4052130232611096E8, + 5.1042225596740514E8, + ), + -0.4651812519989636, + ), + ( + ( + 3.007670445405074E8, + 1.4630490674448165E8, + -1.681994537227527E8, + ), + -0.8607587886441551, + ), + ( + ( + -2.290369962944646E8, + -4.9627750061129004E8, + 9.751744069476394E7, + ), + -0.3592693708849225, + ), + ( + ( + -5.380825223911383E7, + 6.317706682942032E7, + -3.0105795661690116E8, + ), + 0.7372424991843702, + ), + ( + ( + -1.4261684559190175E8, + 9.987839104129419E7, + 3.3290027416415906E8, + ), + 0.27706980571082485, + ), + ( + ( + -8.881637146904664E7, + 1.1033687270820947E8, + -1.0014482192140123E8, + ), + -0.4602443245357103, + ), + ]; + + for ((x, y, z), sample) in values { + assert_eq!(sampler.sample(x, y, z), sample) + } + } +} + #[cfg(test)] mod octave_perline_noise_sampler_test { - use pumpkin_core::random::{xoroshiro128::Xoroshiro, Random}; + use pumpkin_core::random::{legacy_rand::LegacyRand, xoroshiro128::Xoroshiro, Random}; + + use crate::world_gen::noise::perlin::RandomGenerator; use super::OctavePerlinNoiseSampler; @@ -270,7 +565,8 @@ mod octave_perline_noise_sampler_test { assert_eq!(start, 1); assert_eq!(amplitudes, [1f64, 1f64, 1f64]); - let sampler = OctavePerlinNoiseSampler::new(&mut rand, start, amplitudes); + let mut rand_gen = RandomGenerator::Xoroshiro(&mut rand); + let sampler = OctavePerlinNoiseSampler::new(&mut rand_gen, start, &litudes); assert_eq!(sampler.first_octave, 1); assert_eq!(sampler.persistence, 0.5714285714285714f64); @@ -295,13 +591,44 @@ mod octave_perline_noise_sampler_test { } } + #[test] + fn test_create_legacy() { + let mut rand = LegacyRand::from_seed(513513513); + assert_eq!(rand.next_i32(), -1302745855); + + let (start, amplitudes) = OctavePerlinNoiseSampler::calculate_amplitudes(&[0]); + assert_eq!(start, 0); + assert_eq!(amplitudes, [1f64]); + + let mut rand_gen = RandomGenerator::Legacy(&mut rand); + let sampler = OctavePerlinNoiseSampler::new(&mut rand_gen, start, &litudes); + assert_eq!(sampler.first_octave, 0); + assert_eq!(sampler.persistence, 1f64); + assert_eq!(sampler.lacunarity, 1f64); + assert_eq!(sampler.max_value, 2f64); + + let coords = [(226.220117499588, 32.67924779023767, 202.84067325597647)]; + + for (sampler, (x, y, z)) in sampler.octave_samplers.iter().zip(coords) { + match sampler { + Some(sampler) => { + assert_eq!(sampler.x_origin, x); + assert_eq!(sampler.y_origin, y); + assert_eq!(sampler.z_origin, z); + } + None => panic!(), + } + } + } + #[test] fn test_sample() { let mut rand = Xoroshiro::from_seed(513513513); assert_eq!(rand.next_i32(), 404174895); let (start, amplitudes) = OctavePerlinNoiseSampler::calculate_amplitudes(&[1, 2, 3]); - let sampler = OctavePerlinNoiseSampler::new(&mut rand, start, amplitudes); + let mut rand_gen = RandomGenerator::Xoroshiro(&mut rand); + let sampler = OctavePerlinNoiseSampler::new(&mut rand_gen, start, &litudes); let values = [ ( diff --git a/pumpkin-world/src/world_gen/noise/simplex.rs b/pumpkin-world/src/world_gen/noise/simplex.rs index 6c4567007..a84ab460f 100644 --- a/pumpkin-world/src/world_gen/noise/simplex.rs +++ b/pumpkin-world/src/world_gen/noise/simplex.rs @@ -15,6 +15,7 @@ impl SimplexNoiseSampler { const SKEW_FACTOR_2D: f64 = 0.5f64 * (Self::SQRT_3 - 1f64); const UNSKEW_FACTOR_2D: f64 = (3f64 - Self::SQRT_3) / 6f64; + #[allow(dead_code)] pub fn new(random: &mut impl Random) -> Self { let x_origin = random.next_f64() * 256f64; let y_origin = random.next_f64() * 256f64; @@ -177,6 +178,7 @@ pub struct OctaveSimplexNoiseSampler { } impl OctaveSimplexNoiseSampler { + #[allow(dead_code)] pub fn new(random: &mut impl Random, octaves: &[i32]) -> Self { let mut octaves = Vec::from_iter(octaves); octaves.sort(); @@ -227,6 +229,7 @@ impl OctaveSimplexNoiseSampler { } } + #[allow(dead_code)] pub fn sample(&self, x: f64, y: f64, use_origin: bool) -> f64 { let mut d = 0f64; let mut e = self.lacunarity; From 41b9e90112375218030a0f309b40e4618d858f2e Mon Sep 17 00:00:00 2001 From: kralverde Date: Fri, 13 Sep 2024 13:08:51 -0400 Subject: [PATCH 21/65] add enum wrapper around random implementations --- pumpkin-core/src/random/gaussian.rs | 21 ++- pumpkin-core/src/random/legacy_rand.rs | 38 ++--- pumpkin-core/src/random/mod.rs | 138 ++++++++++++++++++- pumpkin-core/src/random/xoroshiro128.rs | 36 ++--- pumpkin-world/src/world_gen/noise/perlin.rs | 32 ++--- pumpkin-world/src/world_gen/noise/simplex.rs | 10 +- 6 files changed, 186 insertions(+), 89 deletions(-) diff --git a/pumpkin-core/src/random/gaussian.rs b/pumpkin-core/src/random/gaussian.rs index 8364cb910..35d4095cd 100644 --- a/pumpkin-core/src/random/gaussian.rs +++ b/pumpkin-core/src/random/gaussian.rs @@ -1,18 +1,14 @@ -use super::Random; +use super::RandomImpl; -pub trait GaussianGenerator: Random { - fn has_next_gaussian(&self) -> bool; +pub trait GaussianGenerator: RandomImpl { + fn stored_next_gaussian(&self) -> Option; - fn set_has_next_gaussian(&mut self, value: bool); - - fn stored_next_gaussian(&self) -> f64; - - fn set_stored_next_gaussian(&mut self, value: f64); + fn set_stored_next_gaussian(&mut self, value: Option); fn calculate_gaussian(&mut self) -> f64 { - if self.has_next_gaussian() { - self.set_has_next_gaussian(false); - self.stored_next_gaussian() + if let Some(gaussian) = self.stored_next_gaussian() { + self.set_stored_next_gaussian(None); + gaussian } else { loop { let d = 2f64 * self.next_f64() - 1f64; @@ -21,8 +17,7 @@ pub trait GaussianGenerator: Random { if f < 1f64 && f != 0f64 { let g = (-2f64 * f.ln() / f).sqrt(); - self.set_stored_next_gaussian(e * g); - self.set_has_next_gaussian(true); + self.set_stored_next_gaussian(Some(e * g)); return d * g; } } diff --git a/pumpkin-core/src/random/legacy_rand.rs b/pumpkin-core/src/random/legacy_rand.rs index 2360ab5ca..4a2cb1b98 100644 --- a/pumpkin-core/src/random/legacy_rand.rs +++ b/pumpkin-core/src/random/legacy_rand.rs @@ -1,11 +1,10 @@ use super::{ - gaussian::GaussianGenerator, hash_block_pos, java_string_hash, Random, RandomSplitter, + gaussian::GaussianGenerator, hash_block_pos, java_string_hash, RandomDeriverImpl, RandomImpl, }; pub struct LegacyRand { seed: u64, - internal_next_gaussian: f64, - internal_has_next_gaussian: bool, + internal_next_gaussian: Option, } impl LegacyRand { @@ -18,29 +17,20 @@ impl LegacyRand { } impl GaussianGenerator for LegacyRand { - fn has_next_gaussian(&self) -> bool { - self.internal_has_next_gaussian - } - - fn stored_next_gaussian(&self) -> f64 { + fn stored_next_gaussian(&self) -> Option { self.internal_next_gaussian } - fn set_has_next_gaussian(&mut self, value: bool) { - self.internal_has_next_gaussian = value; - } - - fn set_stored_next_gaussian(&mut self, value: f64) { + fn set_stored_next_gaussian(&mut self, value: Option) { self.internal_next_gaussian = value; } } -impl Random for LegacyRand { +impl RandomImpl for LegacyRand { fn from_seed(seed: u64) -> Self { LegacyRand { seed: (seed ^ 0x5DEECE66D) & 0xFFFFFFFFFFFF, - internal_has_next_gaussian: false, - internal_next_gaussian: 0f64, + internal_next_gaussian: None, } } @@ -77,7 +67,8 @@ impl Random for LegacyRand { self.next(1) != 0 } - fn next_splitter(&mut self) -> impl RandomSplitter { + #[allow(refining_impl_trait)] + fn next_splitter(&mut self) -> LegacySplitter { LegacySplitter::new(self.next_i64() as u64) } @@ -100,7 +91,7 @@ impl Random for LegacyRand { } } -struct LegacySplitter { +pub struct LegacySplitter { seed: u64, } @@ -110,17 +101,18 @@ impl LegacySplitter { } } -impl RandomSplitter for LegacySplitter { - fn split_u64(&self, seed: u64) -> impl Random { +#[allow(refining_impl_trait)] +impl RandomDeriverImpl for LegacySplitter { + fn split_u64(&self, seed: u64) -> LegacyRand { LegacyRand::from_seed(seed) } - fn split_string(&self, seed: &str) -> impl Random { + fn split_string(&self, seed: &str) -> LegacyRand { let string_hash = java_string_hash(seed); LegacyRand::from_seed((string_hash as u64) ^ self.seed) } - fn split_pos(&self, x: i32, y: i32, z: i32) -> impl Random { + fn split_pos(&self, x: i32, y: i32, z: i32) -> LegacyRand { let pos_hash = hash_block_pos(x, y, z); LegacyRand::from_seed((pos_hash as u64) ^ self.seed) } @@ -128,7 +120,7 @@ impl RandomSplitter for LegacySplitter { #[cfg(test)] mod test { - use crate::random::{Random, RandomSplitter}; + use crate::random::{RandomDeriverImpl, RandomImpl}; use super::LegacyRand; diff --git a/pumpkin-core/src/random/mod.rs b/pumpkin-core/src/random/mod.rs index b5389aa49..2bb937de2 100644 --- a/pumpkin-core/src/random/mod.rs +++ b/pumpkin-core/src/random/mod.rs @@ -1,13 +1,139 @@ +use legacy_rand::{LegacyRand, LegacySplitter}; +use xoroshiro128::{Xoroshiro, XoroshiroSplitter}; + mod gaussian; pub mod legacy_rand; pub mod xoroshiro128; -pub trait Random { +pub enum RandomGenerator { + Xoroshiro(Xoroshiro), + Legacy(LegacyRand), +} + +impl RandomGenerator { + pub fn split(&mut self) -> Self { + match self { + Self::Xoroshiro(rand) => Self::Xoroshiro(rand.split()), + Self::Legacy(rand) => Self::Legacy(rand.split()), + } + } + + pub fn next_splitter(&mut self) -> RandomDeriver { + match self { + Self::Xoroshiro(rand) => RandomDeriver::Xoroshiro(rand.next_splitter()), + Self::Legacy(rand) => RandomDeriver::Legacy(rand.next_splitter()), + } + } + + pub fn next(&mut self, bits: u64) -> u64 { + match self { + Self::Xoroshiro(rand) => rand.next(bits), + Self::Legacy(rand) => rand.next(bits), + } + } + + pub fn next_i32(&mut self) -> i32 { + match self { + Self::Xoroshiro(rand) => rand.next_i32(), + Self::Legacy(rand) => rand.next_i32(), + } + } + + pub fn next_bounded_i32(&mut self, bound: i32) -> i32 { + match self { + Self::Xoroshiro(rand) => rand.next_bounded_i32(bound), + Self::Legacy(rand) => rand.next_bounded_i32(bound), + } + } + + pub fn next_inbetween_i32(&mut self, min: i32, max: i32) -> i32 { + self.next_bounded_i32(max - min + 1) + min + } + + pub fn next_i64(&mut self) -> i64 { + match self { + Self::Xoroshiro(rand) => rand.next_i64(), + Self::Legacy(rand) => rand.next_i64(), + } + } + + pub fn next_bool(&mut self) -> bool { + match self { + Self::Xoroshiro(rand) => rand.next_bool(), + Self::Legacy(rand) => rand.next_bool(), + } + } + + pub fn next_f32(&mut self) -> f32 { + match self { + Self::Xoroshiro(rand) => rand.next_f32(), + Self::Legacy(rand) => rand.next_f32(), + } + } + + pub fn next_f64(&mut self) -> f64 { + match self { + Self::Xoroshiro(rand) => rand.next_f64(), + Self::Legacy(rand) => rand.next_f64(), + } + } + + pub fn next_gaussian(&mut self) -> f64 { + match self { + Self::Xoroshiro(rand) => rand.next_gaussian(), + Self::Legacy(rand) => rand.next_gaussian(), + } + } + + pub fn next_triangular(&mut self, mode: f64, deviation: f64) -> f64 { + mode + deviation * (self.next_f64() - self.next_f64()) + } + + pub fn skip(&mut self, count: i32) { + for _ in 0..count { + self.next_i64(); + } + } + + pub fn next_inbetween_i32_exclusive(&mut self, min: i32, max: i32) -> i32 { + min + self.next_bounded_i32(max - min) + } +} + +pub enum RandomDeriver { + Xoroshiro(XoroshiroSplitter), + Legacy(LegacySplitter), +} + +impl RandomDeriver { + pub fn split_string(&self, seed: &str) -> RandomGenerator { + match self { + Self::Xoroshiro(deriver) => RandomGenerator::Xoroshiro(deriver.split_string(seed)), + Self::Legacy(deriver) => RandomGenerator::Legacy(deriver.split_string(seed)), + } + } + + pub fn split_u64(&self, seed: u64) -> RandomGenerator { + match self { + Self::Xoroshiro(deriver) => RandomGenerator::Xoroshiro(deriver.split_u64(seed)), + Self::Legacy(deriver) => RandomGenerator::Legacy(deriver.split_u64(seed)), + } + } + + pub fn split_pos(&self, x: i32, y: i32, z: i32) -> RandomGenerator { + match self { + Self::Xoroshiro(deriver) => RandomGenerator::Xoroshiro(deriver.split_pos(x, y, z)), + Self::Legacy(deriver) => RandomGenerator::Legacy(deriver.split_pos(x, y, z)), + } + } +} + +pub trait RandomImpl { fn from_seed(seed: u64) -> Self; fn split(&mut self) -> Self; - fn next_splitter(&mut self) -> impl RandomSplitter; + fn next_splitter(&mut self) -> impl RandomDeriverImpl; fn next(&mut self, bits: u64) -> u64; @@ -44,12 +170,12 @@ pub trait Random { } } -pub trait RandomSplitter { - fn split_string(&self, seed: &str) -> impl Random; +pub trait RandomDeriverImpl { + fn split_string(&self, seed: &str) -> impl RandomImpl; - fn split_u64(&self, seed: u64) -> impl Random; + fn split_u64(&self, seed: u64) -> impl RandomImpl; - fn split_pos(&self, x: i32, y: i32, z: i32) -> impl Random; + fn split_pos(&self, x: i32, y: i32, z: i32) -> impl RandomImpl; } fn hash_block_pos(x: i32, y: i32, z: i32) -> i64 { diff --git a/pumpkin-core/src/random/xoroshiro128.rs b/pumpkin-core/src/random/xoroshiro128.rs index e8d7e4f12..a82fcf75c 100644 --- a/pumpkin-core/src/random/xoroshiro128.rs +++ b/pumpkin-core/src/random/xoroshiro128.rs @@ -1,10 +1,9 @@ -use super::{gaussian::GaussianGenerator, hash_block_pos, Random, RandomSplitter}; +use super::{gaussian::GaussianGenerator, hash_block_pos, RandomDeriverImpl, RandomImpl}; pub struct Xoroshiro { lo: u64, hi: u64, - internal_next_gaussian: f64, - internal_has_next_gaussian: bool, + internal_next_gaussian: Option, } impl Xoroshiro { @@ -17,8 +16,7 @@ impl Xoroshiro { Self { lo, hi, - internal_next_gaussian: 0f64, - internal_has_next_gaussian: false, + internal_next_gaussian: None, } } @@ -45,21 +43,13 @@ impl Xoroshiro { } impl GaussianGenerator for Xoroshiro { - fn stored_next_gaussian(&self) -> f64 { + fn stored_next_gaussian(&self) -> Option { self.internal_next_gaussian } - fn has_next_gaussian(&self) -> bool { - self.internal_has_next_gaussian - } - - fn set_stored_next_gaussian(&mut self, value: f64) { + fn set_stored_next_gaussian(&mut self, value: Option) { self.internal_next_gaussian = value; } - - fn set_has_next_gaussian(&mut self, value: bool) { - self.internal_has_next_gaussian = value; - } } fn mix_stafford_13(z: u64) -> u64 { @@ -68,7 +58,7 @@ fn mix_stafford_13(z: u64) -> u64 { z ^ (z >> 31) } -impl Random for Xoroshiro { +impl RandomImpl for Xoroshiro { fn from_seed(seed: u64) -> Self { let (lo, hi) = Self::mix_u64(seed); let lo = mix_stafford_13(lo); @@ -84,7 +74,8 @@ impl Random for Xoroshiro { self.next_random() >> (64 - bits) } - fn next_splitter(&mut self) -> impl RandomSplitter { + #[allow(refining_impl_trait)] + fn next_splitter(&mut self) -> XoroshiroSplitter { XoroshiroSplitter { lo: self.next_random(), hi: self.next_random(), @@ -137,18 +128,19 @@ pub struct XoroshiroSplitter { hi: u64, } -impl RandomSplitter for XoroshiroSplitter { - fn split_pos(&self, x: i32, y: i32, z: i32) -> impl Random { +#[allow(refining_impl_trait)] +impl RandomDeriverImpl for XoroshiroSplitter { + fn split_pos(&self, x: i32, y: i32, z: i32) -> Xoroshiro { let l = hash_block_pos(x, y, z) as u64; let m = l ^ self.lo; Xoroshiro::new(m, self.hi) } - fn split_u64(&self, seed: u64) -> impl Random { + fn split_u64(&self, seed: u64) -> Xoroshiro { Xoroshiro::new(seed ^ self.lo, seed ^ self.hi) } - fn split_string(&self, seed: &str) -> impl Random { + fn split_string(&self, seed: &str) -> Xoroshiro { let bytes = md5::compute(seed.as_bytes()); let l = u64::from_be_bytes(bytes[0..8].try_into().expect("incorrect length")); let m = u64::from_be_bytes(bytes[8..16].try_into().expect("incorrect length")); @@ -159,7 +151,7 @@ impl RandomSplitter for XoroshiroSplitter { #[cfg(test)] mod tests { - use crate::random::{Random, RandomSplitter}; + use crate::random::{RandomDeriverImpl, RandomImpl}; use super::{mix_stafford_13, Xoroshiro}; diff --git a/pumpkin-world/src/world_gen/noise/perlin.rs b/pumpkin-world/src/world_gen/noise/perlin.rs index e1c92bf88..71ebcf188 100644 --- a/pumpkin-world/src/world_gen/noise/perlin.rs +++ b/pumpkin-world/src/world_gen/noise/perlin.rs @@ -1,8 +1,6 @@ use itertools::Itertools; use num_traits::{Pow, Zero}; -use pumpkin_core::random::{ - legacy_rand::LegacyRand, xoroshiro128::Xoroshiro, Random, RandomSplitter, -}; +use pumpkin_core::random::{RandomDeriverImpl, RandomGenerator, RandomImpl}; use super::{dot, lerp3, GRADIENTS}; @@ -14,7 +12,7 @@ pub struct PerlinNoiseSampler { } impl PerlinNoiseSampler { - pub fn new(random: &mut impl Random) -> Self { + pub fn new(random: &mut impl RandomImpl) -> Self { let x_origin = random.next_f64() * 256f64; let y_origin = random.next_f64() * 256f64; let z_origin = random.next_f64() * 256f64; @@ -153,12 +151,6 @@ impl PerlinNoiseSampler { } } -#[allow(dead_code)] -pub enum RandomGenerator<'a> { - Xoroshiro(&'a mut Xoroshiro), - Legacy(&'a mut LegacyRand), -} - pub struct OctavePerlinNoiseSampler { octave_samplers: Vec>, amplitudes: Vec, @@ -232,7 +224,7 @@ impl OctavePerlinNoiseSampler { } } RandomGenerator::Legacy(random) => { - let sampler = PerlinNoiseSampler::new(*random); + let sampler = PerlinNoiseSampler::new(random); if j >= 0 && j < i as i32 { let d = amplitudes[j as usize]; if d != 0f64 { @@ -244,7 +236,7 @@ impl OctavePerlinNoiseSampler { if kx < i { let e = amplitudes[kx]; if e != 0f64 { - samplers[kx] = Some(PerlinNoiseSampler::new(*random)); + samplers[kx] = Some(PerlinNoiseSampler::new(random)); } else { random.skip(262); } @@ -351,7 +343,7 @@ impl DoublePerlinNoiseSampler { #[cfg(test)] mod double_perlin_noise_sampler_test { - use pumpkin_core::random::{legacy_rand::LegacyRand, xoroshiro128::Xoroshiro, Random}; + use pumpkin_core::random::{legacy_rand::LegacyRand, xoroshiro128::Xoroshiro, RandomImpl}; use crate::world_gen::noise::perlin::{DoublePerlinNoiseSampler, RandomGenerator}; @@ -360,7 +352,7 @@ mod double_perlin_noise_sampler_test { let mut rand = LegacyRand::from_seed(513513513); assert_eq!(rand.next_i32(), -1302745855); - let mut rand_gen = RandomGenerator::Legacy(&mut rand); + let mut rand_gen = RandomGenerator::Legacy(rand); let sampler = DoublePerlinNoiseSampler::new(&mut rand_gen, 0, &[4f64]); let values = [ @@ -456,7 +448,7 @@ mod double_perlin_noise_sampler_test { let mut rand = Xoroshiro::from_seed(5); assert_eq!(rand.next_i32(), -1678727252); - let mut rand_gen = RandomGenerator::Xoroshiro(&mut rand); + let mut rand_gen = RandomGenerator::Xoroshiro(rand); let sampler = DoublePerlinNoiseSampler::new(&mut rand_gen, 1, &[2f64, 4f64]); let values = [ @@ -550,7 +542,7 @@ mod double_perlin_noise_sampler_test { #[cfg(test)] mod octave_perline_noise_sampler_test { - use pumpkin_core::random::{legacy_rand::LegacyRand, xoroshiro128::Xoroshiro, Random}; + use pumpkin_core::random::{legacy_rand::LegacyRand, xoroshiro128::Xoroshiro, RandomImpl}; use crate::world_gen::noise::perlin::RandomGenerator; @@ -565,7 +557,7 @@ mod octave_perline_noise_sampler_test { assert_eq!(start, 1); assert_eq!(amplitudes, [1f64, 1f64, 1f64]); - let mut rand_gen = RandomGenerator::Xoroshiro(&mut rand); + let mut rand_gen = RandomGenerator::Xoroshiro(rand); let sampler = OctavePerlinNoiseSampler::new(&mut rand_gen, start, &litudes); assert_eq!(sampler.first_octave, 1); @@ -600,7 +592,7 @@ mod octave_perline_noise_sampler_test { assert_eq!(start, 0); assert_eq!(amplitudes, [1f64]); - let mut rand_gen = RandomGenerator::Legacy(&mut rand); + let mut rand_gen = RandomGenerator::Legacy(rand); let sampler = OctavePerlinNoiseSampler::new(&mut rand_gen, start, &litudes); assert_eq!(sampler.first_octave, 0); assert_eq!(sampler.persistence, 1f64); @@ -627,7 +619,7 @@ mod octave_perline_noise_sampler_test { assert_eq!(rand.next_i32(), 404174895); let (start, amplitudes) = OctavePerlinNoiseSampler::calculate_amplitudes(&[1, 2, 3]); - let mut rand_gen = RandomGenerator::Xoroshiro(&mut rand); + let mut rand_gen = RandomGenerator::Xoroshiro(rand); let sampler = OctavePerlinNoiseSampler::new(&mut rand_gen, start, &litudes); let values = [ @@ -723,7 +715,7 @@ mod octave_perline_noise_sampler_test { mod perlin_noise_sampler_test { use std::ops::Deref; - use pumpkin_core::random::{xoroshiro128::Xoroshiro, Random}; + use pumpkin_core::random::{xoroshiro128::Xoroshiro, RandomImpl}; use crate::world_gen::noise::perlin::PerlinNoiseSampler; diff --git a/pumpkin-world/src/world_gen/noise/simplex.rs b/pumpkin-world/src/world_gen/noise/simplex.rs index a84ab460f..f15d787e0 100644 --- a/pumpkin-world/src/world_gen/noise/simplex.rs +++ b/pumpkin-world/src/world_gen/noise/simplex.rs @@ -1,5 +1,5 @@ use num_traits::Pow; -use pumpkin_core::random::{legacy_rand::LegacyRand, Random}; +use pumpkin_core::random::{legacy_rand::LegacyRand, RandomImpl}; use super::{dot, GRADIENTS}; @@ -16,7 +16,7 @@ impl SimplexNoiseSampler { const UNSKEW_FACTOR_2D: f64 = (3f64 - Self::SQRT_3) / 6f64; #[allow(dead_code)] - pub fn new(random: &mut impl Random) -> Self { + pub fn new(random: &mut impl RandomImpl) -> Self { let x_origin = random.next_f64() * 256f64; let y_origin = random.next_f64() * 256f64; let z_origin = random.next_f64() * 256f64; @@ -179,7 +179,7 @@ pub struct OctaveSimplexNoiseSampler { impl OctaveSimplexNoiseSampler { #[allow(dead_code)] - pub fn new(random: &mut impl Random, octaves: &[i32]) -> Self { + pub fn new(random: &mut impl RandomImpl, octaves: &[i32]) -> Self { let mut octaves = Vec::from_iter(octaves); octaves.sort(); @@ -253,7 +253,7 @@ impl OctaveSimplexNoiseSampler { #[cfg(test)] mod octave_simplex_noise_sampler_test { - use pumpkin_core::random::{xoroshiro128::Xoroshiro, Random}; + use pumpkin_core::random::{xoroshiro128::Xoroshiro, RandomImpl}; use crate::world_gen::noise::simplex::OctaveSimplexNoiseSampler; @@ -390,7 +390,7 @@ mod octave_simplex_noise_sampler_test { mod simplex_noise_sampler_test { use std::ops::Deref; - use pumpkin_core::random::{xoroshiro128::Xoroshiro, Random}; + use pumpkin_core::random::{xoroshiro128::Xoroshiro, RandomImpl}; use crate::world_gen::noise::simplex::SimplexNoiseSampler; From f57ff1ab78f3d1e259d3258bbac2b49af9505e1a Mon Sep 17 00:00:00 2001 From: kralverde Date: Fri, 13 Sep 2024 13:21:03 -0400 Subject: [PATCH 22/65] add inline(always) --- pumpkin-core/src/random/mod.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/pumpkin-core/src/random/mod.rs b/pumpkin-core/src/random/mod.rs index 2bb937de2..9b238ce3e 100644 --- a/pumpkin-core/src/random/mod.rs +++ b/pumpkin-core/src/random/mod.rs @@ -11,6 +11,7 @@ pub enum RandomGenerator { } impl RandomGenerator { + #[inline(always)] pub fn split(&mut self) -> Self { match self { Self::Xoroshiro(rand) => Self::Xoroshiro(rand.split()), @@ -18,6 +19,7 @@ impl RandomGenerator { } } + #[inline(always)] pub fn next_splitter(&mut self) -> RandomDeriver { match self { Self::Xoroshiro(rand) => RandomDeriver::Xoroshiro(rand.next_splitter()), @@ -25,6 +27,7 @@ impl RandomGenerator { } } + #[inline(always)] pub fn next(&mut self, bits: u64) -> u64 { match self { Self::Xoroshiro(rand) => rand.next(bits), @@ -32,6 +35,7 @@ impl RandomGenerator { } } + #[inline(always)] pub fn next_i32(&mut self) -> i32 { match self { Self::Xoroshiro(rand) => rand.next_i32(), @@ -39,6 +43,7 @@ impl RandomGenerator { } } + #[inline(always)] pub fn next_bounded_i32(&mut self, bound: i32) -> i32 { match self { Self::Xoroshiro(rand) => rand.next_bounded_i32(bound), @@ -46,10 +51,12 @@ impl RandomGenerator { } } + #[inline(always)] pub fn next_inbetween_i32(&mut self, min: i32, max: i32) -> i32 { self.next_bounded_i32(max - min + 1) + min } + #[inline(always)] pub fn next_i64(&mut self) -> i64 { match self { Self::Xoroshiro(rand) => rand.next_i64(), @@ -57,6 +64,7 @@ impl RandomGenerator { } } + #[inline(always)] pub fn next_bool(&mut self) -> bool { match self { Self::Xoroshiro(rand) => rand.next_bool(), @@ -64,6 +72,7 @@ impl RandomGenerator { } } + #[inline(always)] pub fn next_f32(&mut self) -> f32 { match self { Self::Xoroshiro(rand) => rand.next_f32(), @@ -71,6 +80,7 @@ impl RandomGenerator { } } + #[inline(always)] pub fn next_f64(&mut self) -> f64 { match self { Self::Xoroshiro(rand) => rand.next_f64(), @@ -78,6 +88,7 @@ impl RandomGenerator { } } + #[inline(always)] pub fn next_gaussian(&mut self) -> f64 { match self { Self::Xoroshiro(rand) => rand.next_gaussian(), @@ -85,16 +96,19 @@ impl RandomGenerator { } } + #[inline(always)] pub fn next_triangular(&mut self, mode: f64, deviation: f64) -> f64 { mode + deviation * (self.next_f64() - self.next_f64()) } + #[inline(always)] pub fn skip(&mut self, count: i32) { for _ in 0..count { self.next_i64(); } } + #[inline(always)] pub fn next_inbetween_i32_exclusive(&mut self, min: i32, max: i32) -> i32 { min + self.next_bounded_i32(max - min) } @@ -106,6 +120,7 @@ pub enum RandomDeriver { } impl RandomDeriver { + #[inline(always)] pub fn split_string(&self, seed: &str) -> RandomGenerator { match self { Self::Xoroshiro(deriver) => RandomGenerator::Xoroshiro(deriver.split_string(seed)), @@ -113,6 +128,7 @@ impl RandomDeriver { } } + #[inline(always)] pub fn split_u64(&self, seed: u64) -> RandomGenerator { match self { Self::Xoroshiro(deriver) => RandomGenerator::Xoroshiro(deriver.split_u64(seed)), @@ -120,6 +136,7 @@ impl RandomDeriver { } } + #[inline(always)] pub fn split_pos(&self, x: i32, y: i32, z: i32) -> RandomGenerator { match self { Self::Xoroshiro(deriver) => RandomGenerator::Xoroshiro(deriver.split_pos(x, y, z)), From 642e3ee9f4b37148956e95ffc7c8d0c25b42b919 Mon Sep 17 00:00:00 2001 From: kralverde Date: Fri, 13 Sep 2024 18:51:04 -0400 Subject: [PATCH 23/65] implement some changes --- pumpkin-core/src/random/mod.rs | 34 ++++++++++----------- pumpkin-world/src/world_gen/noise/perlin.rs | 5 +-- 2 files changed, 18 insertions(+), 21 deletions(-) diff --git a/pumpkin-core/src/random/mod.rs b/pumpkin-core/src/random/mod.rs index 9b238ce3e..d0200ebc4 100644 --- a/pumpkin-core/src/random/mod.rs +++ b/pumpkin-core/src/random/mod.rs @@ -11,7 +11,7 @@ pub enum RandomGenerator { } impl RandomGenerator { - #[inline(always)] + #[inline] pub fn split(&mut self) -> Self { match self { Self::Xoroshiro(rand) => Self::Xoroshiro(rand.split()), @@ -19,7 +19,7 @@ impl RandomGenerator { } } - #[inline(always)] + #[inline] pub fn next_splitter(&mut self) -> RandomDeriver { match self { Self::Xoroshiro(rand) => RandomDeriver::Xoroshiro(rand.next_splitter()), @@ -27,7 +27,7 @@ impl RandomGenerator { } } - #[inline(always)] + #[inline] pub fn next(&mut self, bits: u64) -> u64 { match self { Self::Xoroshiro(rand) => rand.next(bits), @@ -35,7 +35,7 @@ impl RandomGenerator { } } - #[inline(always)] + #[inline] pub fn next_i32(&mut self) -> i32 { match self { Self::Xoroshiro(rand) => rand.next_i32(), @@ -43,7 +43,7 @@ impl RandomGenerator { } } - #[inline(always)] + #[inline] pub fn next_bounded_i32(&mut self, bound: i32) -> i32 { match self { Self::Xoroshiro(rand) => rand.next_bounded_i32(bound), @@ -51,12 +51,12 @@ impl RandomGenerator { } } - #[inline(always)] + #[inline] pub fn next_inbetween_i32(&mut self, min: i32, max: i32) -> i32 { self.next_bounded_i32(max - min + 1) + min } - #[inline(always)] + #[inline] pub fn next_i64(&mut self) -> i64 { match self { Self::Xoroshiro(rand) => rand.next_i64(), @@ -64,7 +64,7 @@ impl RandomGenerator { } } - #[inline(always)] + #[inline] pub fn next_bool(&mut self) -> bool { match self { Self::Xoroshiro(rand) => rand.next_bool(), @@ -72,7 +72,7 @@ impl RandomGenerator { } } - #[inline(always)] + #[inline] pub fn next_f32(&mut self) -> f32 { match self { Self::Xoroshiro(rand) => rand.next_f32(), @@ -80,7 +80,7 @@ impl RandomGenerator { } } - #[inline(always)] + #[inline] pub fn next_f64(&mut self) -> f64 { match self { Self::Xoroshiro(rand) => rand.next_f64(), @@ -88,7 +88,7 @@ impl RandomGenerator { } } - #[inline(always)] + #[inline] pub fn next_gaussian(&mut self) -> f64 { match self { Self::Xoroshiro(rand) => rand.next_gaussian(), @@ -96,19 +96,19 @@ impl RandomGenerator { } } - #[inline(always)] + #[inline] pub fn next_triangular(&mut self, mode: f64, deviation: f64) -> f64 { mode + deviation * (self.next_f64() - self.next_f64()) } - #[inline(always)] + #[inline] pub fn skip(&mut self, count: i32) { for _ in 0..count { self.next_i64(); } } - #[inline(always)] + #[inline] pub fn next_inbetween_i32_exclusive(&mut self, min: i32, max: i32) -> i32 { min + self.next_bounded_i32(max - min) } @@ -120,7 +120,7 @@ pub enum RandomDeriver { } impl RandomDeriver { - #[inline(always)] + #[inline] pub fn split_string(&self, seed: &str) -> RandomGenerator { match self { Self::Xoroshiro(deriver) => RandomGenerator::Xoroshiro(deriver.split_string(seed)), @@ -128,7 +128,7 @@ impl RandomDeriver { } } - #[inline(always)] + #[inline] pub fn split_u64(&self, seed: u64) -> RandomGenerator { match self { Self::Xoroshiro(deriver) => RandomGenerator::Xoroshiro(deriver.split_u64(seed)), @@ -136,7 +136,7 @@ impl RandomDeriver { } } - #[inline(always)] + #[inline] pub fn split_pos(&self, x: i32, y: i32, z: i32) -> RandomGenerator { match self { Self::Xoroshiro(deriver) => RandomGenerator::Xoroshiro(deriver.split_pos(x, y, z)), diff --git a/pumpkin-world/src/world_gen/noise/perlin.rs b/pumpkin-world/src/world_gen/noise/perlin.rs index 71ebcf188..2674ded78 100644 --- a/pumpkin-world/src/world_gen/noise/perlin.rs +++ b/pumpkin-world/src/world_gen/noise/perlin.rs @@ -167,10 +167,7 @@ impl OctavePerlinNoiseSampler { let mut e = persistence; for amplitude in amplitudes.iter() { - if *amplitude != 0f64 { - d += amplitude * scale * e; - } - + d += amplitude * scale * e; e /= 2f64; } From 56b5d85324c1fdfb66381acbaead46f0425863cf Mon Sep 17 00:00:00 2001 From: kralverde Date: Fri, 13 Sep 2024 20:28:39 -0400 Subject: [PATCH 24/65] change allow dead code --- pumpkin-world/src/world_gen/noise/mod.rs | 2 +- pumpkin-world/src/world_gen/noise/perlin.rs | 6 ------ pumpkin-world/src/world_gen/noise/simplex.rs | 3 --- 3 files changed, 1 insertion(+), 10 deletions(-) diff --git a/pumpkin-world/src/world_gen/noise/mod.rs b/pumpkin-world/src/world_gen/noise/mod.rs index 106a38f3b..ea5d51196 100644 --- a/pumpkin-world/src/world_gen/noise/mod.rs +++ b/pumpkin-world/src/world_gen/noise/mod.rs @@ -1,3 +1,4 @@ +#![allow(dead_code)] mod perlin; mod simplex; @@ -13,7 +14,6 @@ pub fn lerp2(delta_x: f64, delta_y: f64, x0y0: f64, x1y0: f64, x0y1: f64, x1y1: ) } -#[allow(dead_code)] #[allow(clippy::too_many_arguments)] pub fn lerp3( delta_x: f64, diff --git a/pumpkin-world/src/world_gen/noise/perlin.rs b/pumpkin-world/src/world_gen/noise/perlin.rs index 2674ded78..a96b1fc6c 100644 --- a/pumpkin-world/src/world_gen/noise/perlin.rs +++ b/pumpkin-world/src/world_gen/noise/perlin.rs @@ -37,7 +37,6 @@ impl PerlinNoiseSampler { } } - #[allow(dead_code)] pub fn sample_flat_y(&self, x: f64, y: f64, z: f64) -> f64 { self.sample_no_fade(x, y, z, 0f64, 0f64) } @@ -154,7 +153,6 @@ impl PerlinNoiseSampler { pub struct OctavePerlinNoiseSampler { octave_samplers: Vec>, amplitudes: Vec, - #[allow(dead_code)] first_octave: i32, persistence: f64, lacunarity: f64, @@ -178,7 +176,6 @@ impl OctavePerlinNoiseSampler { value - (value / 3.3554432E7f64 + 0.5f64).floor() * 3.3554432E7f64 } - #[allow(dead_code)] pub fn calculate_amplitudes(octaves: &[i32]) -> (i32, Vec) { let mut octaves = Vec::from_iter(octaves); octaves.sort(); @@ -293,7 +290,6 @@ pub struct DoublePerlinNoiseSampler { first_sampler: OctavePerlinNoiseSampler, second_sampler: OctavePerlinNoiseSampler, amplitude: f64, - #[allow(dead_code)] max_value: f64, } @@ -302,7 +298,6 @@ impl DoublePerlinNoiseSampler { 0.1f64 * (1f64 + 1f64 / (octaves + 1) as f64) } - #[allow(dead_code)] pub fn new(rand: &mut RandomGenerator, first_octave: i32, amplitudes: &[f64]) -> Self { let first_sampler = OctavePerlinNoiseSampler::new(rand, first_octave, amplitudes); let second_sampler = OctavePerlinNoiseSampler::new(rand, first_octave, amplitudes); @@ -328,7 +323,6 @@ impl DoublePerlinNoiseSampler { } } - #[allow(dead_code)] pub fn sample(&self, x: f64, y: f64, z: f64) -> f64 { let d = x * 1.0181268882175227f64; let e = y * 1.0181268882175227f64; diff --git a/pumpkin-world/src/world_gen/noise/simplex.rs b/pumpkin-world/src/world_gen/noise/simplex.rs index f15d787e0..8d6fba7fa 100644 --- a/pumpkin-world/src/world_gen/noise/simplex.rs +++ b/pumpkin-world/src/world_gen/noise/simplex.rs @@ -15,7 +15,6 @@ impl SimplexNoiseSampler { const SKEW_FACTOR_2D: f64 = 0.5f64 * (Self::SQRT_3 - 1f64); const UNSKEW_FACTOR_2D: f64 = (3f64 - Self::SQRT_3) / 6f64; - #[allow(dead_code)] pub fn new(random: &mut impl RandomImpl) -> Self { let x_origin = random.next_f64() * 256f64; let y_origin = random.next_f64() * 256f64; @@ -178,7 +177,6 @@ pub struct OctaveSimplexNoiseSampler { } impl OctaveSimplexNoiseSampler { - #[allow(dead_code)] pub fn new(random: &mut impl RandomImpl, octaves: &[i32]) -> Self { let mut octaves = Vec::from_iter(octaves); octaves.sort(); @@ -229,7 +227,6 @@ impl OctaveSimplexNoiseSampler { } } - #[allow(dead_code)] pub fn sample(&self, x: f64, y: f64, use_origin: bool) -> f64 { let mut d = 0f64; let mut e = self.lacunarity; From 2a9eff409e3e27736dbba896a619162a92520889 Mon Sep 17 00:00:00 2001 From: Snowiiii Date: Mon, 16 Sep 2024 21:29:15 +0200 Subject: [PATCH 25/65] Minior bytebuf improvements --- pumpkin-protocol/src/bytebuf/mod.rs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/pumpkin-protocol/src/bytebuf/mod.rs b/pumpkin-protocol/src/bytebuf/mod.rs index 4ec1dc5a6..3b46f2f90 100644 --- a/pumpkin-protocol/src/bytebuf/mod.rs +++ b/pumpkin-protocol/src/bytebuf/mod.rs @@ -72,19 +72,19 @@ impl ByteBuffer { } pub fn get_string(&mut self) -> Result { - self.get_string_len(32767) + self.get_string_len(i16::MAX as i32) } - pub fn get_string_len(&mut self, max_size: usize) -> Result { + pub fn get_string_len(&mut self, max_size: i32) -> Result { let size = self.get_var_int()?.0; - if size as usize > max_size { + if size > max_size { return Err(DeserializerError::Message( "String length is bigger than max size".to_string(), )); } let data = self.copy_to_bytes(size as usize)?; - if data.len() > max_size { + if data.len() as i32 > max_size { return Err(DeserializerError::Message( "String is bigger than max size".to_string(), )); @@ -125,6 +125,14 @@ impl ByteBuffer { } pub fn put_string(&mut self, val: &str) { + self.put_string_len(val, i16::MAX as i32); + } + + 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()); } From 810ecbeb1f87d3f8f4cdce2a2f1881670568fa44 Mon Sep 17 00:00:00 2001 From: lukas0008 Date: Tue, 17 Sep 2024 23:50:56 +0200 Subject: [PATCH 26/65] Make Pumpkin work again --- pumpkin/src/client/client_packet.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pumpkin/src/client/client_packet.rs b/pumpkin/src/client/client_packet.rs index 9c4391405..67b091230 100644 --- a/pumpkin/src/client/client_packet.rs +++ b/pumpkin/src/client/client_packet.rs @@ -153,8 +153,8 @@ impl Client { } } for property in gameprofile.as_ref().unwrap().properties.clone() { - unpack_textures(property, &ADVANCED_CONFIG.authentication.textures) - .unwrap_or_else(|e| self.kick(&e.to_string())); + // TODO: use this (this was the todo here before, ill add it again cuz its prob here for a reason) + let _ = unpack_textures(property, &ADVANCED_CONFIG.authentication.textures); } // enable compression From 467a02e0ae9054df147d199d422b9cdbd89e43f1 Mon Sep 17 00:00:00 2001 From: Asurar0 Date: Sun, 15 Sep 2024 20:37:23 +0200 Subject: [PATCH 27/65] Expanded ChunkNbt data structure and added serialization support --- Cargo.lock | 31 +++++++ Cargo.toml | 1 + pumpkin-world/Cargo.toml | 1 + pumpkin-world/src/chunk.rs | 162 +++++++++++++++++++++++++++++-------- 4 files changed, 163 insertions(+), 32 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1c1cbac2c..bb44370c4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1493,6 +1493,15 @@ dependencies = [ "rustix", ] +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + [[package]] name = "mime" version = "0.3.17" @@ -1996,6 +2005,7 @@ dependencies = [ "rayon", "serde", "serde_json", + "speedy", "static_assertions", "thiserror", "tokio", @@ -2567,6 +2577,27 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "speedy" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da1992073f0e55aab599f4483c460598219b4f9ff0affa124b33580ab511e25a" +dependencies = [ + "memoffset", + "speedy-derive", +] + +[[package]] +name = "speedy-derive" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "658f2ca5276b92c3dfd65fa88316b4e032ace68f88d7570b43967784c0bac5ac" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "spin" version = "0.9.8" diff --git a/Cargo.toml b/Cargo.toml index 38d79ccf3..0c9f0bec1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,7 @@ tokio = { version = "1.40", features = [ "io-util", "sync", ] } +speedy = "0.8.7" rayon = "1.10.0" uuid = { version = "1.10.0", features = ["serde", "v3", "v4"] } derive_more = { version = "1.0.0", features = ["full"] } diff --git a/pumpkin-world/Cargo.toml b/pumpkin-world/Cargo.toml index 90be69d5c..98e915000 100644 --- a/pumpkin-world/Cargo.toml +++ b/pumpkin-world/Cargo.toml @@ -7,6 +7,7 @@ edition.workspace = true pumpkin-core = { path = "../pumpkin-core"} fastnbt = { git = "https://github.com/owengage/fastnbt.git" } +speedy.workspace = true tokio.workspace = true rayon.workspace = true derive_more.workspace = true diff --git a/pumpkin-world/src/chunk.rs b/pumpkin-world/src/chunk.rs index dd5aa643b..08e87bf47 100644 --- a/pumpkin-world/src/chunk.rs +++ b/pumpkin-world/src/chunk.rs @@ -1,11 +1,19 @@ +//! ## Chunk +//! +//! This module defines a minecraft chunk data strcture. +//! + +// ========================= Imports ========================= + use std::cmp::max; use std::collections::HashMap; use std::ops::Index; use fastnbt::LongArray; -use pumpkin_core::math::vector2::Vector2; use serde::{Deserialize, Serialize}; +use pumpkin_core::math::vector2::Vector2; + use crate::{ block::BlockId, coordinates::{ChunkRelativeBlockCoordinates, Height}, @@ -13,10 +21,112 @@ use crate::{ WORLD_HEIGHT, }; +// ======================== Constants ======================== + const CHUNK_AREA: usize = 16 * 16; const SUBCHUNK_VOLUME: usize = CHUNK_AREA * 16; const CHUNK_VOLUME: usize = CHUNK_AREA * WORLD_HEIGHT; +// ======================== NBT Structure ======================== +// This section defines some data structure designed and used by Minecraft +// java implementation. They might not be used as defined by Pumpkin for +// its core working. +// + +#[derive(Serialize, Deserialize, Debug)] +#[allow(dead_code)] +#[serde(rename_all = "PascalCase")] +/// `ChunkNbt` +/// +/// This data structure stores a chunk information as described by a regional +/// Minecraft Anvil file. They are stored in NBT format and have been updated +/// for Minecraft 1.18. +pub struct ChunkNbt { + /// Version of the chunk NBT structure. + data_version: i32, + /// X position of the chunk (in chunks, from the origin, not relative to region). + #[serde(rename = "xPos")] + x_pos: i32, + /// Z position of the chunk (in chunks, from the origin, not relative to region). + #[serde(rename = "zPos")] + z_pos: i32, + /// Lowest Y section position in the chunk (e.g. -4 in 1.18). + #[serde(rename = "yPos")] + y_pos: i32, + /// Defines the world generation status of this chunk. + status: ChunkStatus, + /// Tick when the chunk was last saved. + last_update: i64, + /// List of compound tags, each tag is a section (also known as sub-chunk). All + /// ections in the world's height are present in this list, even those who are + /// empty (filled with air). + #[serde(rename = "sections")] + sections: Vec, + /// Each TAG_Compound in this list defines a block entity in the chunk. If this list is empty, it becomes a list of End tags. + #[serde(rename = "block_entities")] + #[serde(skip)] + block_entities: Vec, + /// Several different heightmaps corresponding to 256 values compacted at 9 bits per value + heightmaps: ChunkHeightmaps, + /// A List of 16 lists that store positions of light sources per chunk section as shorts, only for proto-chunks + #[serde(skip)] + lights: Vec, + /// A list of entities in the proto-chunks, used when generating. As of 1.17, this list is not present for fully generated chunks and entities are moved to a separated region files once the chunk is generated. + #[serde(skip)] + entities: Vec, + /// TODO + #[serde(rename = "fluid_ticks")] + #[serde(skip)] + fluid_ticks: (), + /// TODO + #[serde(rename = "block_ticks")] + #[serde(skip)] + block_ticks: (), + /// TODO + #[serde(skip)] + inhabited_time: i64, + /// TODO + #[serde(rename = "blending_data")] + #[serde(skip)] + blending_data: ChunkNbtBlendingData, + /// TODO + #[serde(skip)] + post_processing: (), + /// TODO + #[serde(skip)] + structures: (), +} + +#[derive(Serialize, Deserialize, Debug)] +/// A block entity (not related to entity) is used by Minecraft to store information +/// about a block that can't be stored in the block's block states. Also known as +/// *"tile entities"* in prior versions of the game. +pub enum BlockNbtEntity { + // TODO +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct ChunkNbtLight { + // TODO +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct ChunkNbtEntity { + // TODO +} + +#[derive(Serialize, Deserialize, Default, Debug)] +/// Biome blending data +pub struct ChunkNbtBlendingData { + min_section: i32, + max_section: i32, +} + +// ======================== Pumpkin Structure ======================== +// This section defines structures that are used by +// +// + pub struct ChunkData { pub blocks: ChunkBlocks, pub position: Vector2, @@ -33,75 +143,63 @@ pub struct ChunkBlocks { pub heightmap: ChunkHeightmaps, } -#[derive(Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] #[serde(rename_all = "PascalCase")] struct PaletteEntry { name: String, properties: Option>, } -#[derive(Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] struct ChunkSectionBlockStates { data: Option, palette: Vec, } -#[derive(Deserialize, Serialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] #[serde(rename_all = "UPPERCASE")] pub struct ChunkHeightmaps { motion_blocking: LongArray, world_surface: LongArray, } -#[derive(Deserialize, Debug)] -#[expect(dead_code)] +#[derive(Serialize, Deserialize, Debug)] struct ChunkSection { #[serde(rename = "Y")] y: i32, block_states: Option, } -#[derive(Deserialize, Debug)] -#[serde(rename_all = "PascalCase")] -struct ChunkNbt { - #[expect(dead_code)] - data_version: usize, - - #[serde(rename = "sections")] - sections: Vec, - - heightmaps: ChunkHeightmaps, -} - -#[derive(Deserialize, Debug, PartialEq, Eq)] +#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)] #[serde(tag = "Status")] +#[repr(u32)] enum ChunkStatus { #[serde(rename = "minecraft:empty")] - Empty, + Empty = 0, #[serde(rename = "minecraft:structure_starts")] - StructureStarts, + StructureStarts = 1, #[serde(rename = "minecraft:structure_references")] - StructureReferences, + StructureReferences = 2, #[serde(rename = "minecraft:biomes")] - Biomes, + Biomes = 3, #[serde(rename = "minecraft:noise")] - Noise, + Noise = 4, #[serde(rename = "minecraft:surface")] - Surface, + Surface = 5, #[serde(rename = "minecraft:carvers")] - Carvers, + Carvers = 6, #[serde(rename = "minecraft:liquid_carvers")] - LiquidCarvers, + LiquidCarvers = 7, #[serde(rename = "minecraft:features")] - Features, + Features = 8, #[serde(rename = "minecraft:initialize_light")] - Light, + Light = 9, #[serde(rename = "minecraft:spawn")] - Spawn, + Spawn = 10, #[serde(rename = "minecraft:heightmaps")] - Heightmaps, + Heightmaps = 11, #[serde(rename = "minecraft:full")] - Full, + Full = 12, } /// The Heightmap for a completely empty chunk From cf62babfe4e243841181cdacf81406de4f1dc363 Mon Sep 17 00:00:00 2001 From: lukas0008 Date: Wed, 18 Sep 2024 16:59:12 +0200 Subject: [PATCH 28/65] Make itertools a workspace dependency --- Cargo.lock | 2 ++ Cargo.toml | 2 ++ pumpkin-inventory/Cargo.toml | 2 +- pumpkin-protocol/Cargo.toml | 2 +- pumpkin-world/Cargo.toml | 2 +- pumpkin/Cargo.toml | 2 +- 6 files changed, 8 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fff734d23..1caeddee1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2003,6 +2003,8 @@ version = "0.1.0" dependencies = [ "proc-macro2", "quote", + "serde", + "serde_json", "syn", ] diff --git a/Cargo.toml b/Cargo.toml index 9b13bf54b..bf45f7dbb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,3 +41,5 @@ crossbeam = "0.8.4" uuid = { version = "1.10.0", features = ["serde", "v3", "v4"] } derive_more = { version = "1.0.0", features = ["full"] } serde = { version = "1.0", features = ["derive"] } + +itertools = "0.13.0" diff --git a/pumpkin-inventory/Cargo.toml b/pumpkin-inventory/Cargo.toml index 29daefc7e..dd6c45661 100644 --- a/pumpkin-inventory/Cargo.toml +++ b/pumpkin-inventory/Cargo.toml @@ -10,6 +10,6 @@ pumpkin-world = { path = "../pumpkin-world"} num-traits = "0.2" num-derive = "0.4" thiserror = "1.0.63" -itertools = "0.13.0" +itertools.workspace = true parking_lot.workspace = true crossbeam.workspace = true diff --git a/pumpkin-protocol/Cargo.toml b/pumpkin-protocol/Cargo.toml index 0c0ab1296..70059083a 100644 --- a/pumpkin-protocol/Cargo.toml +++ b/pumpkin-protocol/Cargo.toml @@ -25,5 +25,5 @@ num-derive = "0.4" aes = "0.8.4" cfb8 = "0.8.1" -itertools = "0.13.0" +itertools.workspace = true fastnbt = { git = "https://github.com/owengage/fastnbt.git" } diff --git a/pumpkin-world/Cargo.toml b/pumpkin-world/Cargo.toml index 56f6dadee..9244d9bad 100644 --- a/pumpkin-world/Cargo.toml +++ b/pumpkin-world/Cargo.toml @@ -10,7 +10,7 @@ fastnbt = { git = "https://github.com/owengage/fastnbt.git" } tokio.workspace = true rayon.workspace = true derive_more.workspace = true -itertools = "0.13.0" +itertools.workspace = true thiserror = "1.0" futures = "0.3" flate2 = "1.0" diff --git a/pumpkin/Cargo.toml b/pumpkin/Cargo.toml index 839947f0f..5e2948a11 100644 --- a/pumpkin/Cargo.toml +++ b/pumpkin/Cargo.toml @@ -19,7 +19,7 @@ pumpkin-entity = { path = "../pumpkin-entity"} pumpkin-protocol = { path = "../pumpkin-protocol"} pumpkin-registry = { path = "../pumpkin-registry"} -itertools = "0.13.0" +itertools.workspace = true # config serde.workspace = true From ee7d652bf1f803d18c42ba134b921956971e11ca Mon Sep 17 00:00:00 2001 From: lukas0008 Date: Wed, 18 Sep 2024 17:01:18 +0200 Subject: [PATCH 29/65] Move assets folder outside of pumpkin-world --- {pumpkin-world/assets => assets}/blocks.json | 0 {pumpkin-world/assets => assets}/items.json | 0 {pumpkin-world/assets => assets}/registries.json | 0 pumpkin-world/src/block/block_registry.rs | 2 +- pumpkin-world/src/global_registry.rs | 2 +- pumpkin-world/src/item/item_registry.rs | 2 +- 6 files changed, 3 insertions(+), 3 deletions(-) rename {pumpkin-world/assets => assets}/blocks.json (100%) rename {pumpkin-world/assets => assets}/items.json (100%) rename {pumpkin-world/assets => assets}/registries.json (100%) diff --git a/pumpkin-world/assets/blocks.json b/assets/blocks.json similarity index 100% rename from pumpkin-world/assets/blocks.json rename to assets/blocks.json diff --git a/pumpkin-world/assets/items.json b/assets/items.json similarity index 100% rename from pumpkin-world/assets/items.json rename to assets/items.json diff --git a/pumpkin-world/assets/registries.json b/assets/registries.json similarity index 100% rename from pumpkin-world/assets/registries.json rename to assets/registries.json diff --git a/pumpkin-world/src/block/block_registry.rs b/pumpkin-world/src/block/block_registry.rs index 7d7b3f69f..cd06e98de 100644 --- a/pumpkin-world/src/block/block_registry.rs +++ b/pumpkin-world/src/block/block_registry.rs @@ -5,7 +5,7 @@ use serde::Deserialize; use super::block_id::BlockId; pub static BLOCKS: LazyLock> = LazyLock::new(|| { - serde_json::from_str(include_str!("../../assets/blocks.json")) + serde_json::from_str(include_str!("../../../assets/blocks.json")) .expect("Could not parse block.json registry.") }); diff --git a/pumpkin-world/src/global_registry.rs b/pumpkin-world/src/global_registry.rs index 19f8a6e6f..f822b57ab 100644 --- a/pumpkin-world/src/global_registry.rs +++ b/pumpkin-world/src/global_registry.rs @@ -2,7 +2,7 @@ use std::{collections::HashMap, sync::LazyLock}; pub const ITEM_REGISTRY: &str = "minecraft:item"; -const REGISTRY_JSON: &str = include_str!("../assets/registries.json"); +const REGISTRY_JSON: &str = include_str!("../../assets/registries.json"); #[derive(serde::Deserialize, Debug, Clone, PartialEq, Eq)] pub struct RegistryElement { diff --git a/pumpkin-world/src/item/item_registry.rs b/pumpkin-world/src/item/item_registry.rs index f91dd300c..756c30b4e 100644 --- a/pumpkin-world/src/item/item_registry.rs +++ b/pumpkin-world/src/item/item_registry.rs @@ -3,7 +3,7 @@ use std::{collections::HashMap, sync::LazyLock}; use super::Rarity; use crate::global_registry::{self, ITEM_REGISTRY}; -const ITEMS_JSON: &str = include_str!("../../assets/items.json"); +const ITEMS_JSON: &str = include_str!("../../../assets/items.json"); pub static ITEMS: LazyLock> = LazyLock::new(|| { serde_json::from_str(ITEMS_JSON).expect("Could not parse items.json registry.") From 79c67303864dda9fb4a57acc536c78ff71219476 Mon Sep 17 00:00:00 2001 From: lukas0008 Date: Wed, 18 Sep 2024 17:11:45 +0200 Subject: [PATCH 30/65] Make block_id macro --- Cargo.lock | 1 + pumpkin-macros/Cargo.toml | 3 + pumpkin-macros/src/block_id.rs | 136 +++++++++++++++++++++++++++++++++ pumpkin-macros/src/lib.rs | 6 ++ 4 files changed, 146 insertions(+) create mode 100644 pumpkin-macros/src/block_id.rs diff --git a/Cargo.lock b/Cargo.lock index 1caeddee1..c7f798fb2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2001,6 +2001,7 @@ dependencies = [ name = "pumpkin-macros" version = "0.1.0" dependencies = [ + "itertools 0.13.0", "proc-macro2", "quote", "serde", diff --git a/pumpkin-macros/Cargo.toml b/pumpkin-macros/Cargo.toml index c677c7c01..5dca73b94 100644 --- a/pumpkin-macros/Cargo.toml +++ b/pumpkin-macros/Cargo.toml @@ -10,3 +10,6 @@ proc-macro = true proc-macro2 = "1.0" quote = "1.0" syn = "2.0" +serde.workspace = true +itertools.workspace = true +serde_json = "1.0.128" diff --git a/pumpkin-macros/src/block_id.rs b/pumpkin-macros/src/block_id.rs new file mode 100644 index 000000000..825de0b6f --- /dev/null +++ b/pumpkin-macros/src/block_id.rs @@ -0,0 +1,136 @@ +use std::{collections::HashMap, sync::LazyLock}; + +use itertools::Itertools; +use proc_macro::TokenStream; +use quote::quote; +use syn::parse::Parser; + +#[derive(serde::Deserialize, Debug, Clone, PartialEq, Eq)] +struct RegistryBlockDefinition { + /// e.g. minecraft:door or minecraft:button + #[serde(rename = "type")] + pub category: String, + + /// Specifies the variant of the blocks category. + /// e.g. minecraft:iron_door has the variant iron + #[serde(rename = "block_set_type")] + pub variant: Option, +} + +/// One possible state of a Block. +/// This could e.g. be an extended piston facing left. +#[derive(serde::Deserialize, Debug, Clone, PartialEq, Eq)] +struct RegistryBlockState { + pub id: i32, + + /// Whether this is the default state of the Block + #[serde(default, rename = "default")] + pub is_default: bool, + + /// The propertise active for this `BlockState`. + #[serde(default)] + pub properties: HashMap, +} + +/// A fully-fledged block definition. +/// Stores the category, variant, all of the possible states and all of the possible properties. +#[derive(serde::Deserialize, Debug, Clone, PartialEq, Eq)] +struct RegistryBlockType { + pub definition: RegistryBlockDefinition, + pub states: Vec, + + // TODO is this safe to remove? It's currently not used in the Project. @lukas0008 @Snowiiii + /// A list of valid property keys/values for a block. + #[serde(default, rename = "properties")] + valid_properties: HashMap>, +} + +static BLOCKS: LazyLock> = LazyLock::new(|| { + serde_json::from_str(include_str!("../../assets/blocks.json")) + .expect("Could not parse block.json registry.") +}); + +pub fn block_id_impl(item: TokenStream) -> TokenStream { + let data = syn::punctuated::Punctuated::::parse_terminated + .parse(item) + .unwrap(); + let block_name = data + .first() + .expect("The first argument should be a block name"); + + let block_name = match block_name { + syn::Expr::Lit(lit) => match &lit.lit { + syn::Lit::Str(name) => name.value(), + _ => panic!("The first argument should be a string"), + }, + _ => panic!("The first argument should be a string"), + }; + + let mut properties = HashMap::new(); + for expr_thingy in data.into_iter().skip(1) { + match expr_thingy { + syn::Expr::Assign(assign) => { + let left = match assign.left.as_ref() { + syn::Expr::Lit(lit) => match &lit.lit { + syn::Lit::Str(name) => name.value(), + _ => panic!( + "All not-first arguments should be assignments (\"foo\" = \"bar\")" + ), + }, + _ => { + panic!("All not-first arguments should be assignments (\"foo\" = \"bar\")") + } + }; + let right = match assign.right.as_ref() { + syn::Expr::Lit(lit) => match &lit.lit { + syn::Lit::Str(name) => name.value(), + _ => panic!( + "All not-first arguments should be assignments (\"foo\" = \"bar\")" + ), + }, + _ => { + panic!("All not-first arguments should be assignments (\"foo\" = \"bar\")") + } + }; + properties.insert(left, right); + } + _ => panic!("All not-first arguments should be assignments (\"foo\" = \"bar\")"), + } + } + + // panic!("{:?}", properties); + + let block_info = &BLOCKS + .get(&block_name) + .expect("Block with that name does not exist"); + + let id = if properties.is_empty() { + block_info + .states + .iter() + .find(|state| state.is_default) + .expect("Error inside blocks.json file: Every Block should have at least 1 default state") + .id + } else { + match block_info + .states + .iter() + .find(|state| state.properties == properties) + { + Some(state) => state.id, + None => panic!( + "Could not find block with these properties, the following are valid properties: \n{}", + block_info + .valid_properties + .iter() + .map(|(name, values)| format!("{name} = {}", values.join(" | "))) + .join("\n") + ), + } + }; + + quote! { + pumpkin_world::block::block_id::BlockId::from_id(#id as u16) + } + .into() +} diff --git a/pumpkin-macros/src/lib.rs b/pumpkin-macros/src/lib.rs index 346159229..da53c14be 100644 --- a/pumpkin-macros/src/lib.rs +++ b/pumpkin-macros/src/lib.rs @@ -22,3 +22,9 @@ pub fn packet(input: TokenStream, item: TokenStream) -> TokenStream { gen.into() } + +mod block_id; +#[proc_macro] +pub fn block_id(item: TokenStream) -> TokenStream { + block_id::block_id_impl(item) +} From d2f700ac1fe6eb95952848e546ae170fc929a23f Mon Sep 17 00:00:00 2001 From: lukas0008 Date: Wed, 18 Sep 2024 17:21:05 +0200 Subject: [PATCH 31/65] Cargo fmt --- pumpkin-macros/src/block_id.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pumpkin-macros/src/block_id.rs b/pumpkin-macros/src/block_id.rs index 825de0b6f..796eeca56 100644 --- a/pumpkin-macros/src/block_id.rs +++ b/pumpkin-macros/src/block_id.rs @@ -109,7 +109,9 @@ pub fn block_id_impl(item: TokenStream) -> TokenStream { .states .iter() .find(|state| state.is_default) - .expect("Error inside blocks.json file: Every Block should have at least 1 default state") + .expect( + "Error inside blocks.json file: Every Block should have at least 1 default state", + ) .id } else { match block_info From 6ec8d90d5d99eaeb430553ac4dd86d3406e6d6e1 Mon Sep 17 00:00:00 2001 From: Asurar0 Date: Wed, 18 Sep 2024 18:03:34 +0200 Subject: [PATCH 32/65] Review edits --- Cargo.lock | 31 ------------------------------- Cargo.toml | 1 - pumpkin-world/Cargo.toml | 1 - pumpkin-world/src/chunk.rs | 26 +++++++++++++------------- 4 files changed, 13 insertions(+), 46 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bb44370c4..1c1cbac2c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1493,15 +1493,6 @@ dependencies = [ "rustix", ] -[[package]] -name = "memoffset" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" -dependencies = [ - "autocfg", -] - [[package]] name = "mime" version = "0.3.17" @@ -2005,7 +1996,6 @@ dependencies = [ "rayon", "serde", "serde_json", - "speedy", "static_assertions", "thiserror", "tokio", @@ -2577,27 +2567,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "speedy" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da1992073f0e55aab599f4483c460598219b4f9ff0affa124b33580ab511e25a" -dependencies = [ - "memoffset", - "speedy-derive", -] - -[[package]] -name = "speedy-derive" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "658f2ca5276b92c3dfd65fa88316b4e032ace68f88d7570b43967784c0bac5ac" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "spin" version = "0.9.8" diff --git a/Cargo.toml b/Cargo.toml index 0c9f0bec1..38d79ccf3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,7 +32,6 @@ tokio = { version = "1.40", features = [ "io-util", "sync", ] } -speedy = "0.8.7" rayon = "1.10.0" uuid = { version = "1.10.0", features = ["serde", "v3", "v4"] } derive_more = { version = "1.0.0", features = ["full"] } diff --git a/pumpkin-world/Cargo.toml b/pumpkin-world/Cargo.toml index 98e915000..90be69d5c 100644 --- a/pumpkin-world/Cargo.toml +++ b/pumpkin-world/Cargo.toml @@ -7,7 +7,6 @@ edition.workspace = true pumpkin-core = { path = "../pumpkin-core"} fastnbt = { git = "https://github.com/owengage/fastnbt.git" } -speedy.workspace = true tokio.workspace = true rayon.workspace = true derive_more.workspace = true diff --git a/pumpkin-world/src/chunk.rs b/pumpkin-world/src/chunk.rs index 08e87bf47..6bbdb2210 100644 --- a/pumpkin-world/src/chunk.rs +++ b/pumpkin-world/src/chunk.rs @@ -175,31 +175,31 @@ struct ChunkSection { #[repr(u32)] enum ChunkStatus { #[serde(rename = "minecraft:empty")] - Empty = 0, + Empty, #[serde(rename = "minecraft:structure_starts")] - StructureStarts = 1, + StructureStarts, #[serde(rename = "minecraft:structure_references")] - StructureReferences = 2, + StructureReferences, #[serde(rename = "minecraft:biomes")] - Biomes = 3, + Biomes, #[serde(rename = "minecraft:noise")] - Noise = 4, + Noise, #[serde(rename = "minecraft:surface")] - Surface = 5, + Surface, #[serde(rename = "minecraft:carvers")] - Carvers = 6, + Carvers, #[serde(rename = "minecraft:liquid_carvers")] - LiquidCarvers = 7, + LiquidCarvers, #[serde(rename = "minecraft:features")] - Features = 8, + Features, #[serde(rename = "minecraft:initialize_light")] - Light = 9, + Light, #[serde(rename = "minecraft:spawn")] - Spawn = 10, + Spawn, #[serde(rename = "minecraft:heightmaps")] - Heightmaps = 11, + Heightmaps, #[serde(rename = "minecraft:full")] - Full = 12, + Full, } /// The Heightmap for a completely empty chunk From e9a55cf976c8fe1f110c2e231effcce12a1e07d5 Mon Sep 17 00:00:00 2001 From: StripedMonkey Date: Thu, 19 Sep 2024 03:04:01 -0400 Subject: [PATCH 33/65] misc spelling fixes in comments and variables --- pumpkin-protocol/src/lib.rs | 8 ++++---- pumpkin/src/client/mod.rs | 2 +- pumpkin/src/entity/mod.rs | 2 +- pumpkin/src/entity/player.rs | 2 +- pumpkin/src/server/connection_cache.rs | 2 +- pumpkin/src/world/mod.rs | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pumpkin-protocol/src/lib.rs b/pumpkin-protocol/src/lib.rs index 4381934b4..aea2e4891 100644 --- a/pumpkin-protocol/src/lib.rs +++ b/pumpkin-protocol/src/lib.rs @@ -193,14 +193,14 @@ pub trait ServerPacket: Packet + Sized { pub struct StatusResponse { /// The version on which the Server is running. Optional pub version: Option, - /// Informations about currently connected Players. Optional + /// Information about currently connected Players. Optional pub players: Option, /// The description displayed also called MOTD (Message of the day). Optional pub description: String, /// The icon displayed, Optional pub favicon: Option, /// Players are forced to use Secure chat - pub enforece_secure_chat: bool, + pub enforce_secure_chat: bool, } #[derive(Serialize)] pub struct Version { @@ -216,7 +216,7 @@ pub struct Players { pub max: u32, /// The current online player count pub online: u32, - /// Informations about currently connected players. + /// Information about currently connected players. /// Note player can disable listing here. pub sample: Vec, } @@ -229,7 +229,7 @@ pub struct Sample { pub id: String, } -// basicly game profile +// basically game profile #[derive(Serialize, Deserialize, Clone, Debug)] pub struct Property { pub name: String, diff --git a/pumpkin/src/client/mod.rs b/pumpkin/src/client/mod.rs index 9dc174072..af7f2ec9f 100644 --- a/pumpkin/src/client/mod.rs +++ b/pumpkin/src/client/mod.rs @@ -81,7 +81,7 @@ impl Default for PlayerConfig { } } -/// Everything which makes a Conection with our Server is a `Client`. +/// Everything which makes a Connection with our Server is a `Client`. /// Client will become Players when they reach the `Play` state pub struct Client { /// The client's game profile information. diff --git a/pumpkin/src/entity/mod.rs b/pumpkin/src/entity/mod.rs index d80b2244d..68c323a6b 100644 --- a/pumpkin/src/entity/mod.rs +++ b/pumpkin/src/entity/mod.rs @@ -123,7 +123,7 @@ impl Entity { /// Kills the Entity /// - /// This is simliar to `kill` but Spawn Particles, Animation and plays death sound + /// This is similar to `kill` but Spawn Particles, Animation and plays death sound pub fn kill(&self) { // Spawns death smoke particles self.world diff --git a/pumpkin/src/entity/player.rs b/pumpkin/src/entity/player.rs index 252d4dcc4..66be814de 100644 --- a/pumpkin/src/entity/player.rs +++ b/pumpkin/src/entity/player.rs @@ -237,7 +237,7 @@ impl Player { "Setting the same gamemode as already is" ); self.gamemode.store(gamemode); - // So a little story time. I actually made an abitlties_from_gamemode function. I looked at vanilla and they always send the abilties from the gamemode. But the funny thing actually is. That the client + // So a little story time. I actually made an abilties_from_gamemode function. I looked at vanilla and they always send the abilties from the gamemode. But the funny thing actually is. That the client // does actually use the same method and set the abilties when receiving the CGameEvent gamemode packet. Just Mojang nonsense self.entity .world diff --git a/pumpkin/src/server/connection_cache.rs b/pumpkin/src/server/connection_cache.rs index a8268ba3a..49a65c4c2 100644 --- a/pumpkin/src/server/connection_cache.rs +++ b/pumpkin/src/server/connection_cache.rs @@ -80,7 +80,7 @@ impl CachedStatus { }), description: config.motd.clone(), favicon: icon, - enforece_secure_chat: false, + enforce_secure_chat: false, } } diff --git a/pumpkin/src/world/mod.rs b/pumpkin/src/world/mod.rs index b61ddc868..308ce4abf 100644 --- a/pumpkin/src/world/mod.rs +++ b/pumpkin/src/world/mod.rs @@ -220,7 +220,7 @@ impl World { .client .send_packet(&CGameEvent::new(GameEvent::StartWaitingChunks, 0.0)); - // Spawn in inital chunks + // Spawn in initial chunks player_chunker::player_join(self, player.clone()).await; } From 229be05e98065530a2ab75492906a0f84abacbd3 Mon Sep 17 00:00:00 2001 From: StripedMonkey Date: Thu, 19 Sep 2024 03:10:26 -0400 Subject: [PATCH 34/65] run formatter on cargo.toml files --- Cargo.toml | 6 +++--- pumpkin-config/Cargo.toml | 1 - pumpkin-inventory/Cargo.toml | 2 +- pumpkin-protocol/Cargo.toml | 2 +- pumpkin-registry/Cargo.toml | 4 ++-- pumpkin-world/Cargo.toml | 2 +- pumpkin/Cargo.toml | 25 +++++++++++++++---------- 7 files changed, 23 insertions(+), 19 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9b13bf54b..3770fdc9d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,11 +25,11 @@ codegen-units = 1 [workspace.dependencies] log = "0.4" tokio = { version = "1.40", features = [ - "net", - "macros", - "rt-multi-thread", "fs", "io-util", + "macros", + "net", + "rt-multi-thread", "sync", ] } diff --git a/pumpkin-config/Cargo.toml b/pumpkin-config/Cargo.toml index eacb43380..d3ce22e43 100644 --- a/pumpkin-config/Cargo.toml +++ b/pumpkin-config/Cargo.toml @@ -9,4 +9,3 @@ serde.workspace = true log.workspace = true toml = "0.8" - diff --git a/pumpkin-inventory/Cargo.toml b/pumpkin-inventory/Cargo.toml index 29daefc7e..66c4c427a 100644 --- a/pumpkin-inventory/Cargo.toml +++ b/pumpkin-inventory/Cargo.toml @@ -5,7 +5,7 @@ edition.workspace = true [dependencies] # For items -pumpkin-world = { path = "../pumpkin-world"} +pumpkin-world = { path = "../pumpkin-world" } num-traits = "0.2" num-derive = "0.4" diff --git a/pumpkin-protocol/Cargo.toml b/pumpkin-protocol/Cargo.toml index 0c0ab1296..32d0a92bd 100644 --- a/pumpkin-protocol/Cargo.toml +++ b/pumpkin-protocol/Cargo.toml @@ -5,7 +5,7 @@ edition.workspace = true [dependencies] pumpkin-macros = { path = "../pumpkin-macros" } -pumpkin-world = { path = "../pumpkin-world" } +pumpkin-world = { path = "../pumpkin-world" } pumpkin-core = { path = "../pumpkin-core" } bytes = "1.7" diff --git a/pumpkin-registry/Cargo.toml b/pumpkin-registry/Cargo.toml index f4fe985d9..922cd5b06 100644 --- a/pumpkin-registry/Cargo.toml +++ b/pumpkin-registry/Cargo.toml @@ -4,8 +4,8 @@ version.workspace = true edition.workspace = true [dependencies] -pumpkin-protocol = { path = "../pumpkin-protocol"} -pumpkin-core = { path = "../pumpkin-core"} +pumpkin-protocol = { path = "../pumpkin-protocol" } +pumpkin-core = { path = "../pumpkin-core" } # nbt fastnbt = { git = "https://github.com/owengage/fastnbt.git" } diff --git a/pumpkin-world/Cargo.toml b/pumpkin-world/Cargo.toml index 56f6dadee..44ebeddaf 100644 --- a/pumpkin-world/Cargo.toml +++ b/pumpkin-world/Cargo.toml @@ -4,7 +4,7 @@ version.workspace = true edition.workspace = true [dependencies] -pumpkin-core = { path = "../pumpkin-core"} +pumpkin-core = { path = "../pumpkin-core" } fastnbt = { git = "https://github.com/owengage/fastnbt.git" } tokio.workspace = true diff --git a/pumpkin/Cargo.toml b/pumpkin/Cargo.toml index 839947f0f..931be7113 100644 --- a/pumpkin/Cargo.toml +++ b/pumpkin/Cargo.toml @@ -10,14 +10,14 @@ plugins = ["pumpkin-plugin/plugins"] [dependencies] # pumpkin -pumpkin-core = { path = "../pumpkin-core"} +pumpkin-core = { path = "../pumpkin-core" } pumpkin-config = { path = "../pumpkin-config" } -pumpkin-plugin = { path = "../pumpkin-plugin"} -pumpkin-inventory = { path = "../pumpkin-inventory"} -pumpkin-world = { path = "../pumpkin-world"} -pumpkin-entity = { path = "../pumpkin-entity"} -pumpkin-protocol = { path = "../pumpkin-protocol"} -pumpkin-registry = { path = "../pumpkin-registry"} +pumpkin-plugin = { path = "../pumpkin-plugin" } +pumpkin-inventory = { path = "../pumpkin-inventory" } +pumpkin-world = { path = "../pumpkin-world" } +pumpkin-entity = { path = "../pumpkin-entity" } +pumpkin-protocol = { path = "../pumpkin-protocol" } +pumpkin-registry = { path = "../pumpkin-registry" } itertools = "0.13.0" @@ -40,7 +40,12 @@ rsa = "0.9.6" rsa-der = "0.3.0" # authentication -reqwest = { version = "0.12.7", default-features= false, features = ["json", "rustls-tls", "http2", "macos-system-configuration"]} +reqwest = { version = "0.12.7", default-features = false, features = [ + "http2", + "json", + "macos-system-configuration", + "rustls-tls", +] } sha1 = "0.10.6" digest = "=0.11.0-pre.9" @@ -53,14 +58,14 @@ thiserror = "1.0" # icon loading base64 = "0.22.1" -image = { version = "0.25", default-features = false, features = ["png"]} +image = { version = "0.25", default-features = false, features = ["png"] } # logging simple_logger = "5.0.0" log.workspace = true # networking -mio = { version = "1.0.2", features = ["os-poll", "net"]} +mio = { version = "1.0.2", features = ["net", "os-poll"] } parking_lot.workspace = true crossbeam.workspace = true From 8112f4a6d90b3a2e472af2d0a784b5bba0a28cc6 Mon Sep 17 00:00:00 2001 From: lukas0008 Date: Thu, 19 Sep 2024 20:24:15 +0200 Subject: [PATCH 35/65] Make `block_id` macro work in pumpkin-world --- pumpkin-macros/src/block_id.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/pumpkin-macros/src/block_id.rs b/pumpkin-macros/src/block_id.rs index 796eeca56..a71e90df4 100644 --- a/pumpkin-macros/src/block_id.rs +++ b/pumpkin-macros/src/block_id.rs @@ -131,8 +131,14 @@ pub fn block_id_impl(item: TokenStream) -> TokenStream { } }; - quote! { - pumpkin_world::block::block_id::BlockId::from_id(#id as u16) + if std::env::var("CARGO_PKG_NAME").unwrap() == "pumpkin-world" { + quote! { + crate::block::block_id::BlockId::from_id(#id as u16) + } + } else { + quote! { + pumpkin_world::block::block_id::BlockId::from_id(#id as u16) + } } .into() } From 14a13435c1b8d8b9ef9dc39d1ddd765cd2f34bb6 Mon Sep 17 00:00:00 2001 From: Snowiiii Date: Thu, 19 Sep 2024 23:13:45 +0200 Subject: [PATCH 36/65] less clones --- .../src/client/play/c_player_info_update.rs | 6 +-- .../src/client/play/player_action.rs | 6 +-- pumpkin-protocol/src/packet_encoder.rs | 1 - pumpkin/src/client/authentication.rs | 8 ++-- pumpkin/src/client/client_packet.rs | 36 +++++++++------- pumpkin/src/client/mod.rs | 8 +--- pumpkin/src/client/player_packet.rs | 2 +- pumpkin/src/server/connection_cache.rs | 5 +-- pumpkin/src/world/mod.rs | 43 ++++++++++--------- 9 files changed, 56 insertions(+), 59 deletions(-) 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 b62108680..054ebb028 100644 --- a/pumpkin-protocol/src/client/play/c_player_info_update.rs +++ b/pumpkin-protocol/src/client/play/c_player_info_update.rs @@ -7,12 +7,12 @@ use super::PlayerAction; #[packet(0x3E)] pub struct CPlayerInfoUpdate<'a> { pub actions: i8, - pub players: &'a [Player], + pub players: &'a [Player<'a>], } -pub struct Player { +pub struct Player<'a> { pub uuid: uuid::Uuid, - pub actions: Vec, + pub actions: Vec>, } impl<'a> CPlayerInfoUpdate<'a> { diff --git a/pumpkin-protocol/src/client/play/player_action.rs b/pumpkin-protocol/src/client/play/player_action.rs index 3a5f2a80a..48baf4eca 100644 --- a/pumpkin-protocol/src/client/play/player_action.rs +++ b/pumpkin-protocol/src/client/play/player_action.rs @@ -1,9 +1,9 @@ use crate::{Property, VarInt}; -pub enum PlayerAction { +pub enum PlayerAction<'a> { AddPlayer { - name: String, - properties: Vec, + name: &'a str, + properties: &'a [Property], }, InitializeChat(u8), /// Gamemode ? diff --git a/pumpkin-protocol/src/packet_encoder.rs b/pumpkin-protocol/src/packet_encoder.rs index 9e0d9f525..55c4da7cc 100644 --- a/pumpkin-protocol/src/packet_encoder.rs +++ b/pumpkin-protocol/src/packet_encoder.rs @@ -26,7 +26,6 @@ pub struct PacketEncoder { impl PacketEncoder { pub fn append_packet(&mut self, packet: &P) -> Result<(), PacketError> { let start_len = self.buf.len(); - let mut writer = (&mut self.buf).writer(); let mut packet_buf = ByteBuffer::empty(); diff --git a/pumpkin/src/client/authentication.rs b/pumpkin/src/client/authentication.rs index 20a3c28e7..03853578b 100644 --- a/pumpkin/src/client/authentication.rs +++ b/pumpkin/src/client/authentication.rs @@ -77,15 +77,15 @@ pub async fn authenticate( match response.status() { StatusCode::OK => {} StatusCode::NO_CONTENT => Err(AuthError::UnverifiedUsername)?, - other => Err(AuthError::UnknownStatusCode(other.as_str().to_string()))?, + other => Err(AuthError::UnknownStatusCode(other))?, } let profile: GameProfile = response.json().await.map_err(|_| AuthError::FailedParse)?; Ok(profile) } -pub fn unpack_textures(property: Property, config: &TextureConfig) -> Result<(), TextureError> { +pub fn unpack_textures(property: &Property, config: &TextureConfig) -> Result<(), TextureError> { let from64 = general_purpose::STANDARD - .decode(property.value) + .decode(&property.value) .map_err(|e| TextureError::DecodeError(e.to_string()))?; let textures: ProfileTextures = serde_json::from_slice(&from64).map_err(|e| TextureError::JSONError(e.to_string()))?; @@ -120,7 +120,7 @@ pub enum AuthError { #[error("Failed to parse JSON into Game Profile")] FailedParse, #[error("Unknown Status Code")] - UnknownStatusCode(String), + UnknownStatusCode(StatusCode), } #[derive(Error, Debug)] diff --git a/pumpkin/src/client/client_packet.rs b/pumpkin/src/client/client_packet.rs index 67b091230..a38b4059c 100644 --- a/pumpkin/src/client/client_packet.rs +++ b/pumpkin/src/client/client_packet.rs @@ -135,13 +135,12 @@ impl Client { self.kick("Your account can't join"); } } else { - for allowed in ADVANCED_CONFIG + for allowed in &ADVANCED_CONFIG .authentication .player_profile .allowed_actions - .clone() { - if !p.contains(&allowed) { + if !p.contains(allowed) { self.kick("Your account can't join"); } } @@ -152,7 +151,7 @@ impl Client { Err(e) => self.kick(&e.to_string()), } } - for property in gameprofile.as_ref().unwrap().properties.clone() { + for property in &gameprofile.as_ref().unwrap().properties { // TODO: use this (this was the todo here before, ill add it again cuz its prob here for a reason) let _ = unpack_textures(property, &ADVANCED_CONFIG.authentication.textures); } @@ -165,7 +164,7 @@ impl Client { self.set_compression(Some((threshold, level))); } - if let Some(profile) = gameprofile.as_ref().cloned() { + if let Some(profile) = gameprofile.as_ref() { let packet = CLoginSuccess::new(&profile.id, &profile.name, &profile.properties, false); self.send_packet(&packet); } else { @@ -222,16 +221,23 @@ impl Client { client_information: SClientInformationConfig, ) { dbg!("got client settings"); - *self.config.lock() = Some(PlayerConfig { - locale: client_information.locale, - view_distance: client_information.view_distance, - chat_mode: ChatMode::from_i32(client_information.chat_mode.into()).unwrap(), - chat_colors: client_information.chat_colors, - skin_parts: client_information.skin_parts, - main_hand: Hand::from_i32(client_information.main_hand.into()).unwrap(), - text_filtering: client_information.text_filtering, - server_listing: client_information.server_listing, - }); + 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() = Some(PlayerConfig { + locale: client_information.locale, + view_distance: client_information.view_distance, + 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") + } } pub fn handle_plugin_message(&self, _server: &Arc, plugin_message: SPluginMessage) { diff --git a/pumpkin/src/client/mod.rs b/pumpkin/src/client/mod.rs index af7f2ec9f..7178841c0 100644 --- a/pumpkin/src/client/mod.rs +++ b/pumpkin/src/client/mod.rs @@ -16,10 +16,9 @@ use authentication::GameProfile; use crossbeam::atomic::AtomicCell; use mio::{event::Event, net::TcpStream, Token}; use parking_lot::Mutex; -use pumpkin_core::text::TextComponent; use pumpkin_protocol::{ bytebuf::{packet_id::Packet, DeserializerError}, - client::{config::CConfigDisconnect, login::CLoginDisconnect, play::CPlayDisconnect}, + client::{config::CConfigDisconnect, login::CLoginDisconnect}, packet_decoder::PacketDecoder, packet_encoder::PacketEncoder, server::{ @@ -366,11 +365,6 @@ impl Client { self.try_send_packet(&CConfigDisconnect::new(reason)) .unwrap_or_else(|_| self.close()); } - // So we can also kick on errors, but generally should use Player::kick - ConnectionState::Play => { - self.try_send_packet(&CPlayDisconnect::new(&TextComponent::text(reason))) - .unwrap_or_else(|_| self.close()); - } _ => { log::warn!("Can't kick in {:?} State", self.connection_state) } diff --git a/pumpkin/src/client/player_packet.rs b/pumpkin/src/client/player_packet.rs index 139c0b862..759d4cb79 100644 --- a/pumpkin/src/client/player_packet.rs +++ b/pumpkin/src/client/player_packet.rs @@ -326,7 +326,7 @@ impl Player { Some(TextComponent::text(&message)), FilterType::PassThrough, 1.into(), - TextComponent::text(&gameprofile.name.clone()), + TextComponent::text(&gameprofile.name), None, )) diff --git a/pumpkin/src/server/connection_cache.rs b/pumpkin/src/server/connection_cache.rs index 49a65c4c2..6fdd618dd 100644 --- a/pumpkin/src/server/connection_cache.rs +++ b/pumpkin/src/server/connection_cache.rs @@ -85,10 +85,7 @@ impl CachedStatus { } fn load_icon(path: &str) -> String { - let icon = match image::open(path).map_err(|e| panic!("error loading icon: {}", e)) { - Ok(icon) => icon, - Err(_) => return "".into(), - }; + let icon = image::open(path).expect("Failed to load icon"); let dimension = icon.dimensions(); assert!(dimension.0 == 64, "Icon width must be 64"); assert!(dimension.1 == 64, "Icon height must be 64"); diff --git a/pumpkin/src/world/mod.rs b/pumpkin/src/world/mod.rs index 308ce4abf..8fe4eae04 100644 --- a/pumpkin/src/world/mod.rs +++ b/pumpkin/src/world/mod.rs @@ -129,8 +129,8 @@ impl World { uuid: gameprofile.id, actions: vec![ PlayerAction::AddPlayer { - name: gameprofile.name.clone(), - properties: gameprofile.properties.clone(), + name: &gameprofile.name, + properties: &gameprofile.properties, }, PlayerAction::UpdateListed(true), ], @@ -139,27 +139,28 @@ impl World { // here we send all the infos of already joined players let mut entries = Vec::new(); - for (_, playerr) in self - .current_players - .lock() - .iter() - .filter(|(c, _)| **c != player.client.token) { - let gameprofile = &playerr.gameprofile; - entries.push(pumpkin_protocol::client::play::Player { - uuid: gameprofile.id, - actions: vec![ - PlayerAction::AddPlayer { - name: gameprofile.name.clone(), - properties: gameprofile.properties.clone(), - }, - PlayerAction::UpdateListed(true), - ], - }) + let current_players = self.current_players.lock(); + for (_, playerr) in current_players + .iter() + .filter(|(c, _)| **c != player.client.token) + { + let gameprofile = &playerr.gameprofile; + entries.push(pumpkin_protocol::client::play::Player { + uuid: gameprofile.id, + actions: vec![ + PlayerAction::AddPlayer { + name: &gameprofile.name, + properties: &gameprofile.properties, + }, + PlayerAction::UpdateListed(true), + ], + }) + } + player + .client + .send_packet(&CPlayerInfoUpdate::new(0x01 | 0x08, &entries)); } - player - .client - .send_packet(&CPlayerInfoUpdate::new(0x01 | 0x08, &entries)); let gameprofile = &player.gameprofile; From cda86fd59e3f044c7fd9aff2a76cc2d5397c2d0f Mon Sep 17 00:00:00 2001 From: Edvin Bryntesson Date: Sun, 22 Sep 2024 17:59:56 +0200 Subject: [PATCH 37/65] add keep alive packets --- .../src/client/play/c_keep_alive.rs | 8 ++++ pumpkin-protocol/src/client/play/mod.rs | 2 + pumpkin-protocol/src/server/play/mod.rs | 2 + .../src/server/play/s_keep_alive.rs | 8 ++++ pumpkin/src/client/mod.rs | 13 +++++- pumpkin/src/entity/player.rs | 19 ++++++-- pumpkin/src/main.rs | 46 +++++++++++++++++-- pumpkin/src/server/mod.rs | 9 ++-- pumpkin/src/world/mod.rs | 9 ++-- 9 files changed, 97 insertions(+), 19 deletions(-) create mode 100644 pumpkin-protocol/src/client/play/c_keep_alive.rs create mode 100644 pumpkin-protocol/src/server/play/s_keep_alive.rs diff --git a/pumpkin-protocol/src/client/play/c_keep_alive.rs b/pumpkin-protocol/src/client/play/c_keep_alive.rs new file mode 100644 index 000000000..c1d95f094 --- /dev/null +++ b/pumpkin-protocol/src/client/play/c_keep_alive.rs @@ -0,0 +1,8 @@ +use pumpkin_macros::packet; +use serde::Serialize; + +#[packet(0x26)] +#[derive(Serialize)] +pub struct CKeepAlive { + pub keep_alive_id: i64, +} diff --git a/pumpkin-protocol/src/client/play/mod.rs b/pumpkin-protocol/src/client/play/mod.rs index 493ae921c..ff9dd2461 100644 --- a/pumpkin-protocol/src/client/play/mod.rs +++ b/pumpkin-protocol/src/client/play/mod.rs @@ -14,6 +14,7 @@ mod c_entity_velocity; mod c_game_event; mod c_head_rot; mod c_hurt_animation; +mod c_keep_alive; mod c_login; mod c_open_screen; mod c_particle; @@ -57,6 +58,7 @@ pub use c_entity_velocity::*; pub use c_game_event::*; pub use c_head_rot::*; pub use c_hurt_animation::*; +pub use c_keep_alive::*; pub use c_login::*; pub use c_open_screen::*; pub use c_particle::*; diff --git a/pumpkin-protocol/src/server/play/mod.rs b/pumpkin-protocol/src/server/play/mod.rs index 59a8fc3f7..4723a6b57 100644 --- a/pumpkin-protocol/src/server/play/mod.rs +++ b/pumpkin-protocol/src/server/play/mod.rs @@ -5,6 +5,7 @@ mod s_client_information; mod s_close_container; mod s_confirm_teleport; mod s_interact; +mod s_keep_alive; mod s_ping_request; mod s_player_action; mod s_player_command; @@ -25,6 +26,7 @@ pub use s_client_information::*; pub use s_close_container::*; pub use s_confirm_teleport::*; pub use s_interact::*; +pub use s_keep_alive::*; pub use s_ping_request::*; pub use s_player_action::*; pub use s_player_command::*; diff --git a/pumpkin-protocol/src/server/play/s_keep_alive.rs b/pumpkin-protocol/src/server/play/s_keep_alive.rs new file mode 100644 index 000000000..449f00b06 --- /dev/null +++ b/pumpkin-protocol/src/server/play/s_keep_alive.rs @@ -0,0 +1,8 @@ +use pumpkin_macros::packet; +use serde::Deserialize; + +#[packet(0x18)] +#[derive(Deserialize)] +pub struct SKeepAlive { + pub keep_alive_id: i64, +} diff --git a/pumpkin/src/client/mod.rs b/pumpkin/src/client/mod.rs index 9dc174072..df40d5d80 100644 --- a/pumpkin/src/client/mod.rs +++ b/pumpkin/src/client/mod.rs @@ -113,10 +113,19 @@ pub struct Client { /// Indicates whether the client should be converted into a player. pub make_player: AtomicBool, + /// Sends each keep alive packet that the server receives for a player to here, which gets picked up in a tokio task + pub keep_alive_sender: Arc>, + /// Stores the last time it was confirmed that the client is alive + pub last_alive_received: AtomicCell, } impl Client { - pub fn new(token: Token, connection: TcpStream, address: SocketAddr) -> Self { + pub fn new( + token: Token, + connection: TcpStream, + address: SocketAddr, + keep_alive_sender: Arc>, + ) -> Self { Self { protocol_version: AtomicI32::new(0), gameprofile: Mutex::new(None), @@ -132,6 +141,8 @@ impl Client { closed: AtomicBool::new(false), client_packets_queue: Arc::new(Mutex::new(Vec::new())), make_player: AtomicBool::new(false), + keep_alive_sender, + last_alive_received: AtomicCell::new(std::time::Instant::now()), } } diff --git a/pumpkin/src/entity/player.rs b/pumpkin/src/entity/player.rs index 252d4dcc4..848f4f51f 100644 --- a/pumpkin/src/entity/player.rs +++ b/pumpkin/src/entity/player.rs @@ -29,7 +29,7 @@ use pumpkin_protocol::{ ConnectionState, RawPacket, ServerPacket, VarInt, }; -use pumpkin_protocol::server::play::SCloseContainer; +use pumpkin_protocol::server::play::{SCloseContainer, SKeepAlive}; use pumpkin_world::item::ItemStack; use crate::{ @@ -50,7 +50,7 @@ pub struct Player { /// The player's game profile information, including their username and UUID. pub gameprofile: GameProfile, /// The client connection associated with the player. - pub client: Client, + pub client: Arc, /// The player's configuration settings. Changes when the Player changes their settings. pub config: Mutex, /// The player's current gamemode (e.g., Survival, Creative, Adventure). @@ -90,7 +90,12 @@ pub struct Player { } impl Player { - pub fn new(client: Client, world: Arc, entity_id: EntityId, gamemode: GameMode) -> Self { + pub fn new( + client: Arc, + world: Arc, + entity_id: EntityId, + gamemode: GameMode, + ) -> Self { let gameprofile = match client.gameprofile.lock().clone() { Some(profile) => profile, None => { @@ -371,6 +376,14 @@ impl Player { self.handle_close_container(server, SCloseContainer::read(bytebuf)?); Ok(()) } + SKeepAlive::PACKET_ID => { + self.client + .keep_alive_sender + .send(SKeepAlive::read(bytebuf)?.keep_alive_id) + .await + .unwrap(); + Ok(()) + } _ => { log::error!("Failed to handle player packet id {:#04x}", packet.id.0); Ok(()) diff --git a/pumpkin/src/main.rs b/pumpkin/src/main.rs index 5f27a7607..bb709c22c 100644 --- a/pumpkin/src/main.rs +++ b/pumpkin/src/main.rs @@ -6,11 +6,13 @@ compile_error!("Compiling for WASI targets is not supported!"); use mio::net::TcpListener; use mio::{Events, Interest, Poll, Token}; +use client::{interrupted, Client}; +use pumpkin_protocol::client::play::CKeepAlive; +use pumpkin_protocol::ConnectionState; +use server::Server; use std::collections::HashMap; use std::io::{self, Read}; - -use client::{interrupted, Client}; -use server::Server; +use std::time::Duration; // Setup some tokens to allow us to identify which event is for which socket. @@ -78,7 +80,7 @@ fn main() -> io::Result<()> { let use_console = ADVANCED_CONFIG.commands.use_console; let rcon = ADVANCED_CONFIG.rcon.clone(); - let mut clients: HashMap = HashMap::new(); + let mut clients: HashMap> = HashMap::new(); let mut players: HashMap> = HashMap::new(); let server = Arc::new(Server::new()); @@ -152,7 +154,41 @@ fn main() -> io::Result<()> { token, Interest::READABLE.add(Interest::WRITABLE), )?; - let client = Client::new(token, connection, addr); + let keep_alive = tokio::sync::mpsc::channel(1024); + let client = + Arc::new(Client::new(token, connection, addr, keep_alive.0.into())); + + { + let client = client.clone(); + let mut receiver = keep_alive.1; + tokio::spawn(async move { + let mut interval = tokio::time::interval(Duration::from_secs(1)); + loop { + interval.tick().await; + let now = std::time::Instant::now(); + if client.connection_state.load() == ConnectionState::Play { + if now.duration_since(client.last_alive_received.load()) + >= Duration::from_secs(15) + { + dbg!("no keep alive"); + client.kick("No keep alive received"); + break; + } + let random = rand::random::(); + client.send_packet(&CKeepAlive { + keep_alive_id: random, + }); + if let Some(id) = receiver.recv().await { + if id == random { + client.last_alive_received.store(now); + } + } + } else { + client.last_alive_received.store(now); + } + } + }); + } clients.insert(token, client); }, diff --git a/pumpkin/src/server/mod.rs b/pumpkin/src/server/mod.rs index 68dcd9501..b34d756c9 100644 --- a/pumpkin/src/server/mod.rs +++ b/pumpkin/src/server/mod.rs @@ -5,10 +5,13 @@ use parking_lot::{Mutex, RwLock}; use pumpkin_config::BASIC_CONFIG; use pumpkin_core::GameMode; use pumpkin_entity::EntityId; +use pumpkin_inventory::drag_handler::DragHandler; +use pumpkin_inventory::{Container, OpenContainer}; use pumpkin_plugin::PluginLoader; use pumpkin_protocol::client::login::CEncryptionRequest; use pumpkin_protocol::client::status::CStatusResponse; use pumpkin_protocol::{client::config::CPluginMessage, ClientPacket}; +use pumpkin_registry::Registry; use pumpkin_world::dimension::Dimension; use std::collections::HashMap; use std::{ @@ -19,10 +22,6 @@ use std::{ time::Duration, }; -use pumpkin_inventory::drag_handler::DragHandler; -use pumpkin_inventory::{Container, OpenContainer}; -use pumpkin_registry::Registry; - use crate::client::EncryptionError; use crate::{ client::Client, @@ -96,7 +95,7 @@ impl Server { } } - pub async fn add_player(&self, token: Token, client: Client) -> (Arc, Arc) { + pub async fn add_player(&self, token: Token, client: Arc) -> (Arc, Arc) { let entity_id = self.new_entity_id(); let gamemode = match BASIC_CONFIG.default_gamemode { GameMode::Undefined => GameMode::Survival, diff --git a/pumpkin/src/world/mod.rs b/pumpkin/src/world/mod.rs index b61ddc868..b3eb8a595 100644 --- a/pumpkin/src/world/mod.rs +++ b/pumpkin/src/world/mod.rs @@ -2,6 +2,10 @@ use std::{collections::HashMap, sync::Arc}; pub mod player_chunker; +use crate::{ + client::Client, + entity::{player::Player, Entity}, +}; use mio::Token; use num_traits::ToPrimitive; use parking_lot::Mutex; @@ -18,11 +22,6 @@ use pumpkin_protocol::{ use pumpkin_world::level::Level; use tokio::sync::mpsc; -use crate::{ - client::Client, - entity::{player::Player, Entity}, -}; - /// Represents a Minecraft world, containing entities, players, and the underlying level data. /// /// Each dimension (Overworld, Nether, End) typically has its own `World`. From 92fef82bdad6d1a21260c0d6a4b27829149cff51 Mon Sep 17 00:00:00 2001 From: Snowiiii Date: Thu, 26 Sep 2024 23:27:31 +0200 Subject: [PATCH 38/65] use HashMap::values --- pumpkin/src/world/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pumpkin/src/world/mod.rs b/pumpkin/src/world/mod.rs index 0268f5664..97672a0ca 100644 --- a/pumpkin/src/world/mod.rs +++ b/pumpkin/src/world/mod.rs @@ -57,7 +57,7 @@ impl World { P: ClientPacket, { let current_players = self.current_players.lock(); - for (_, player) in current_players.iter() { + for player in current_players.values() { player.client.send_packet(packet); } } @@ -261,7 +261,7 @@ impl World { /// Gets a Player by entity id pub fn get_player_by_entityid(&self, id: EntityId) -> Option> { - for (_, player) in self.current_players.lock().iter() { + for player in self.current_players.lock().values() { if player.entity_id() == id { return Some(player.clone()); } @@ -271,7 +271,7 @@ impl World { /// Gets a Player by name pub fn get_player_by_name(&self, name: &str) -> Option> { - for (_, player) in self.current_players.lock().iter() { + for player in self.current_players.lock().values() { if player.gameprofile.name == name { return Some(player.clone()); } From 1ddaca1406b8005a9595b91f4581769cf728cecc Mon Sep 17 00:00:00 2001 From: Snowiiii Date: Sat, 28 Sep 2024 13:03:31 +0200 Subject: [PATCH 39/65] Allow disable encryption --- Cargo.lock | 1 + pumpkin-config/src/compression.rs | 25 +++++++++++--- pumpkin-config/src/lib.rs | 2 +- pumpkin-protocol/Cargo.toml | 1 + pumpkin-protocol/src/packet_decoder.rs | 27 ++++++++++------ pumpkin-protocol/src/packet_encoder.rs | 24 +++++++++----- pumpkin-world/src/block/block_id.rs | 7 ++-- pumpkin-world/src/level.rs | 43 ++++++++---------------- pumpkin/src/client/client_packet.rs | 24 +++++++------- pumpkin/src/client/mod.rs | 45 ++++++++++++++------------ pumpkin/src/client/player_packet.rs | 2 +- pumpkin/src/rcon/mod.rs | 11 +++---- pumpkin/src/server/connection_cache.rs | 2 +- pumpkin/src/server/mod.rs | 4 +-- 14 files changed, 117 insertions(+), 101 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c7f798fb2..4f66bfda4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2031,6 +2031,7 @@ dependencies = [ "log", "num-derive", "num-traits", + "pumpkin-config", "pumpkin-core", "pumpkin-macros", "pumpkin-world", diff --git a/pumpkin-config/src/compression.rs b/pumpkin-config/src/compression.rs index 544c8d31d..9bdd733d4 100644 --- a/pumpkin-config/src/compression.rs +++ b/pumpkin-config/src/compression.rs @@ -1,24 +1,39 @@ use serde::{Deserialize, Serialize}; #[derive(Deserialize, Serialize)] -// Packet compression +/// Packet compression pub struct CompressionConfig { /// Is compression enabled ? pub enabled: bool, + #[serde(flatten)] + pub compression_info: CompressionInfo, +} + +#[derive(Deserialize, Serialize, Clone)] +/// We have this in a Seperate struct so we can use it outside of the Config +pub struct CompressionInfo { /// The compression threshold used when compression is enabled - pub compression_threshold: u32, + pub threshold: u32, /// A value between 0..9 /// 1 = Optimize for the best speed of encoding. /// 9 = Optimize for the size of data being encoded. - pub compression_level: u32, + pub level: u32, +} + +impl Default for CompressionInfo { + fn default() -> Self { + Self { + threshold: 256, + level: 4, + } + } } impl Default for CompressionConfig { fn default() -> Self { Self { enabled: true, - compression_threshold: 256, - compression_level: 4, + compression_info: Default::default(), } } } diff --git a/pumpkin-config/src/lib.rs b/pumpkin-config/src/lib.rs index f24ca2810..0679bf5ed 100644 --- a/pumpkin-config/src/lib.rs +++ b/pumpkin-config/src/lib.rs @@ -20,7 +20,7 @@ pub use pvp::PVPConfig; pub use rcon::RCONConfig; mod commands; -mod compression; +pub mod compression; mod pvp; mod rcon; diff --git a/pumpkin-protocol/Cargo.toml b/pumpkin-protocol/Cargo.toml index 3ae52b6eb..956c7021c 100644 --- a/pumpkin-protocol/Cargo.toml +++ b/pumpkin-protocol/Cargo.toml @@ -4,6 +4,7 @@ version.workspace = true edition.workspace = true [dependencies] +pumpkin-config = { path = "../pumpkin-config" } pumpkin-macros = { path = "../pumpkin-macros" } pumpkin-world = { path = "../pumpkin-world" } pumpkin-core = { path = "../pumpkin-core" } diff --git a/pumpkin-protocol/src/packet_decoder.rs b/pumpkin-protocol/src/packet_decoder.rs index b001cea15..f3aa9e41f 100644 --- a/pumpkin-protocol/src/packet_decoder.rs +++ b/pumpkin-protocol/src/packet_decoder.rs @@ -19,7 +19,7 @@ type Cipher = cfb8::Decryptor; pub struct PacketDecoder { buf: BytesMut, decompress_buf: BytesMut, - compression: Option, + compression: bool, cipher: Option, } @@ -45,7 +45,7 @@ impl PacketDecoder { let packet_len_len = VarInt(packet_len).written_size(); let mut data; - if self.compression.is_some() { + if self.compression { r = &r[..packet_len as usize]; let data_len = VarInt::decode(&mut r).map_err(|_| PacketError::TooLong)?.0; @@ -96,19 +96,26 @@ impl PacketDecoder { })) } - pub fn enable_encryption(&mut self, key: &[u8; 16]) { - assert!(self.cipher.is_none(), "encryption is already enabled"); + 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"); + 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); + // Don't forget to decrypt the data we already have. - self.cipher = Some(cipher); + 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; + } } - /// Enables ZLib Deompression - pub fn set_compression(&mut self, compression: Option) { + /// Sets ZLib Deompression + pub fn set_compression(&mut self, compression: bool) { self.compression = compression; } diff --git a/pumpkin-protocol/src/packet_encoder.rs b/pumpkin-protocol/src/packet_encoder.rs index 55c4da7cc..947fdf81e 100644 --- a/pumpkin-protocol/src/packet_encoder.rs +++ b/pumpkin-protocol/src/packet_encoder.rs @@ -2,6 +2,7 @@ use std::io::Write; use aes::cipher::{generic_array::GenericArray, BlockEncryptMut, BlockSizeUser, KeyIvInit}; use bytes::{BufMut, BytesMut}; +use pumpkin_config::compression::CompressionInfo; use std::io::Read; @@ -19,7 +20,7 @@ type Cipher = cfb8::Encryptor; pub struct PacketEncoder { buf: BytesMut, compress_buf: Vec, - compression: Option<(u32, u32)>, + compression: Option, cipher: Option, } @@ -40,10 +41,10 @@ impl PacketEncoder { let data_len = self.buf.len() - start_len; - if let Some((threshold, compression_level)) = self.compression { - if data_len > threshold as usize { + if let Some(compression) = &self.compression { + if data_len > compression.threshold as usize { let mut z = - ZlibEncoder::new(&self.buf[start_len..], Compression::new(compression_level)); + ZlibEncoder::new(&self.buf[start_len..], Compression::new(compression.level)); self.compress_buf.clear(); @@ -117,13 +118,20 @@ impl PacketEncoder { Ok(()) } - pub fn enable_encryption(&mut self, key: &[u8; 16]) { - assert!(self.cipher.is_none(), "encryption is already enabled"); - self.cipher = Some(Cipher::new_from_slices(key, key).expect("invalid key")); + pub fn set_encryption(&mut self, key: Option<&[u8; 16]>) { + if let Some(key) = key { + assert!(self.cipher.is_none(), "encryption is already enabled"); + + self.cipher = Some(Cipher::new_from_slices(key, key).expect("invalid key")); + } else { + assert!(self.cipher.is_some(), "encryption is disabled"); + + self.cipher = None; + } } /// Enables ZLib Compression - pub fn set_compression(&mut self, compression: Option<(u32, u32)>) { + pub fn set_compression(&mut self, compression: Option) { self.compression = compression; } diff --git a/pumpkin-world/src/block/block_id.rs b/pumpkin-world/src/block/block_id.rs index 80380842c..747daff2a 100644 --- a/pumpkin-world/src/block/block_id.rs +++ b/pumpkin-world/src/block/block_id.rs @@ -26,10 +26,9 @@ impl BlockId { .iter(); let block_state = match properties { - Some(properties) => match block_states.find(|state| &state.properties == properties) { - Some(state) => state, - None => return Err(WorldError::BlockStateIdNotFound), - }, + Some(properties) => block_states + .find(|state| &state.properties == properties) + .ok_or_else(|| WorldError::BlockStateIdNotFound)?, None => block_states .find(|state| state.is_default) .expect("Every Block should have at least 1 default state"), diff --git a/pumpkin-world/src/level.rs b/pumpkin-world/src/level.rs index d97ebe523..28c991124 100644 --- a/pumpkin-world/src/level.rs +++ b/pumpkin-world/src/level.rs @@ -135,6 +135,8 @@ impl Level { } } + pub fn get_block() {} + /// Reads/Generates many chunks in a world /// MUST be called from a tokio runtime thread /// @@ -147,7 +149,6 @@ impl Level { ) { chunks.into_par_iter().for_each(|at| { if is_alive { - dbg!("a"); return; } let mut loaded_chunks = self.loaded_chunks.lock(); @@ -235,29 +236,21 @@ impl Level { // Read the file using the offset and size let mut file_buf = { - let seek_result = region_file.seek(std::io::SeekFrom::Start(offset)); - if seek_result.is_err() { - return Err(WorldError::RegionIsInvalid); - } + region_file + .seek(std::io::SeekFrom::Start(offset)) + .map_err(|_| WorldError::RegionIsInvalid)?; let mut out = vec![0; size]; - let read_result = region_file.read_exact(&mut out); - if read_result.is_err() { - return Err(WorldError::RegionIsInvalid); - } + region_file + .read_exact(&mut out) + .map_err(|_| WorldError::RegionIsInvalid)?; out }; // TODO: check checksum to make sure chunk is not corrupted let header = file_buf.drain(0..5).collect_vec(); - let compression = match Compression::from_byte(header[4]) { - Some(c) => c, - None => { - return Err(WorldError::Compression( - CompressionError::UnknownCompression, - )) - } - }; + let compression = Compression::from_byte(header[4]) + .ok_or_else(|| WorldError::Compression(CompressionError::UnknownCompression))?; let size = u32::from_be_bytes(header[..4].try_into().unwrap()); @@ -277,23 +270,15 @@ impl Level { Compression::Gzip => { let mut z = GzDecoder::new(&compressed_data[..]); let mut chunk_data = Vec::with_capacity(compressed_data.len()); - match z.read_to_end(&mut chunk_data) { - Ok(_) => {} - Err(e) => { - return Err(CompressionError::GZipError(e)); - } - } + z.read_to_end(&mut chunk_data) + .map_err(CompressionError::GZipError)?; Ok(chunk_data) } Compression::Zlib => { let mut z = ZlibDecoder::new(&compressed_data[..]); let mut chunk_data = Vec::with_capacity(compressed_data.len()); - match z.read_to_end(&mut chunk_data) { - Ok(_) => {} - Err(e) => { - return Err(CompressionError::ZlibError(e)); - } - } + z.read_to_end(&mut chunk_data) + .map_err(CompressionError::ZlibError)?; Ok(chunk_data) } Compression::None => Ok(compressed_data), diff --git a/pumpkin/src/client/client_packet.rs b/pumpkin/src/client/client_packet.rs index a38b4059c..bcdb541dd 100644 --- a/pumpkin/src/client/client_packet.rs +++ b/pumpkin/src/client/client_packet.rs @@ -107,7 +107,7 @@ impl Client { ) { let shared_secret = server.decrypt(&encryption_response.shared_secret).unwrap(); - self.enable_encryption(&shared_secret) + self.set_encryption(Some(&shared_secret)) .unwrap_or_else(|e| self.kick(&e.to_string())); let mut gameprofile = self.gameprofile.lock(); @@ -115,6 +115,7 @@ impl Client { if BASIC_CONFIG.online_mode { let hash = server.digest_secret(&shared_secret); let ip = self.address.lock().ip(); + match authentication::authenticate( &gameprofile.as_ref().unwrap().name, &hash, @@ -123,15 +124,15 @@ impl Client { ) .await { - Ok(p) => { + Ok(profile) => { // Check if player should join - if let Some(p) = &p.profile_actions { + if let Some(actions) = &profile.profile_actions { if !ADVANCED_CONFIG .authentication .player_profile .allow_banned_players { - if !p.is_empty() { + if !actions.is_empty() { self.kick("Your account can't join"); } } else { @@ -140,28 +141,27 @@ impl Client { .player_profile .allowed_actions { - if !p.contains(allowed) { + if !actions.contains(allowed) { self.kick("Your account can't join"); } } } } - *gameprofile = Some(p); + *gameprofile = Some(profile); } Err(e) => self.kick(&e.to_string()), } } for property in &gameprofile.as_ref().unwrap().properties { - // TODO: use this (this was the todo here before, ill add it again cuz its prob here for a reason) - let _ = unpack_textures(property, &ADVANCED_CONFIG.authentication.textures); + unpack_textures(property, &ADVANCED_CONFIG.authentication.textures) + .unwrap_or_else(|e| self.kick(&e.to_string())); } // enable compression if ADVANCED_CONFIG.packet_compression.enabled { - let threshold = ADVANCED_CONFIG.packet_compression.compression_threshold; - let level = ADVANCED_CONFIG.packet_compression.compression_level; - self.send_packet(&CSetCompression::new(threshold.into())); - self.set_compression(Some((threshold, level))); + let compression = ADVANCED_CONFIG.packet_compression.compression_info.clone(); + self.send_packet(&CSetCompression::new(compression.threshold.into())); + self.set_compression(Some(compression)); } if let Some(profile) = gameprofile.as_ref() { diff --git a/pumpkin/src/client/mod.rs b/pumpkin/src/client/mod.rs index 7acd396bd..ca744d1b5 100644 --- a/pumpkin/src/client/mod.rs +++ b/pumpkin/src/client/mod.rs @@ -16,6 +16,7 @@ use authentication::GameProfile; use crossbeam::atomic::AtomicCell; use mio::{event::Event, net::TcpStream, Token}; use parking_lot::Mutex; +use pumpkin_config::compression::CompressionInfo; use pumpkin_protocol::{ bytebuf::{packet_id::Packet, DeserializerError}, client::{config::CConfigDisconnect, login::CLoginDisconnect}, @@ -151,24 +152,29 @@ impl Client { client_packets_queue.push(packet); } - /// Enables encryption - pub fn enable_encryption( + /// Sets the Packet encryption + pub fn set_encryption( &self, - shared_secret: &[u8], // decrypted + shared_secret: Option<&[u8]>, // decrypted ) -> Result<(), EncryptionError> { - self.encryption - .store(true, std::sync::atomic::Ordering::Relaxed); - let crypt_key: [u8; 16] = shared_secret - .try_into() - .map_err(|_| EncryptionError::SharedWrongLength)?; - self.dec.lock().enable_encryption(&crypt_key); - self.enc.lock().enable_encryption(&crypt_key); + 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)?; + self.dec.lock().set_encryption(Some(&crypt_key)); + self.enc.lock().set_encryption(Some(&crypt_key)); + } else { + self.dec.lock().set_encryption(None); + self.enc.lock().set_encryption(None); + } Ok(()) } - /// Compression threshold, Compression level - pub fn set_compression(&self, compression: Option<(u32, u32)>) { - self.dec.lock().set_compression(compression.map(|v| v.0)); + /// Sets the Packet compression + pub fn set_compression(&self, compression: Option) { + self.dec.lock().set_compression(compression.is_some()); self.enc.lock().set_compression(compression); } @@ -200,14 +206,11 @@ impl Client { /// Processes all packets send by the client pub async fn process_packets(&self, server: &Arc) { while let Some(mut packet) = self.client_packets_queue.lock().pop() { - match self.handle_packet(server, &mut packet).await { - Ok(_) => {} - Err(e) => { - let text = format!("Error while reading incoming packet {}", e); - log::error!("{}", text); - self.kick(&text) - } - }; + let _ = self.handle_packet(server, &mut packet).await.map_err(|e| { + let text = format!("Error while reading incoming packet {}", e); + log::error!("{}", text); + self.kick(&text) + }); } } diff --git a/pumpkin/src/client/player_packet.rs b/pumpkin/src/client/player_packet.rs index 759d4cb79..420d3589b 100644 --- a/pumpkin/src/client/player_packet.rs +++ b/pumpkin/src/client/player_packet.rs @@ -131,7 +131,7 @@ impl Player { self.kick(TextComponent::text("Invalid movement")); return; } - if !position_rotation.yaw.is_finite() || !position_rotation.pitch.is_finite() { + if position_rotation.yaw.is_infinite() || position_rotation.pitch.is_infinite() { self.kick(TextComponent::text("Invalid rotation")); return; } diff --git a/pumpkin/src/rcon/mod.rs b/pumpkin/src/rcon/mod.rs index a2cf8dfc2..af5944e03 100644 --- a/pumpkin/src/rcon/mod.rs +++ b/pumpkin/src/rcon/mod.rs @@ -140,13 +140,10 @@ impl RCONClient { } } // If we get a close here, we might have a reply, which we still want to write. - match self.poll(server, password).await { - Ok(()) => {} - Err(e) => { - log::error!("rcon error: {e}"); - self.closed = true; - } - } + let _ = self.poll(server, password).await.map_err(|e| { + log::error!("rcon error: {e}"); + self.closed = true; + }); } self.closed } diff --git a/pumpkin/src/server/connection_cache.rs b/pumpkin/src/server/connection_cache.rs index 6fdd618dd..58181c214 100644 --- a/pumpkin/src/server/connection_cache.rs +++ b/pumpkin/src/server/connection_cache.rs @@ -58,7 +58,7 @@ impl CachedStatus { } pub fn build_response(config: &BasicConfiguration) -> StatusResponse { - let icon_path = concat!(env!("CARGO_MANIFEST_DIR"), "/icon.png"); + let icon_path = "/icon.png"; let icon = if Path::new(icon_path).exists() { Some(Self::load_icon(icon_path)) } else { diff --git a/pumpkin/src/server/mod.rs b/pumpkin/src/server/mod.rs index b34d756c9..4f9ada322 100644 --- a/pumpkin/src/server/mod.rs +++ b/pumpkin/src/server/mod.rs @@ -103,11 +103,11 @@ impl Server { }; // Basically the default world // TODO: select default from config - let world = self.worlds[0].clone(); + let world = &self.worlds[0]; let player = Arc::new(Player::new(client, world.clone(), entity_id, gamemode)); world.add_player(token, player.clone()); - (player, world) + (player, world.clone()) } pub fn try_get_container( From 62cf03e37cfc4121000ac89e423e801162bd5317 Mon Sep 17 00:00:00 2001 From: Snowiiii Date: Sat, 28 Sep 2024 13:22:53 +0200 Subject: [PATCH 40/65] Revert "Merge pull request #97 from Asurar0/chunk-data" This reverts commit 49c36416b695a2b06909940cb587152b339bad15, reversing changes made to 5823f328e659293ff1084f3b86969147d86bc70c. --- pumpkin-world/src/chunk.rs | 136 ++++++------------------------------- 1 file changed, 19 insertions(+), 117 deletions(-) diff --git a/pumpkin-world/src/chunk.rs b/pumpkin-world/src/chunk.rs index 6bbdb2210..dd5aa643b 100644 --- a/pumpkin-world/src/chunk.rs +++ b/pumpkin-world/src/chunk.rs @@ -1,18 +1,10 @@ -//! ## Chunk -//! -//! This module defines a minecraft chunk data strcture. -//! - -// ========================= Imports ========================= - use std::cmp::max; use std::collections::HashMap; use std::ops::Index; use fastnbt::LongArray; -use serde::{Deserialize, Serialize}; - use pumpkin_core::math::vector2::Vector2; +use serde::{Deserialize, Serialize}; use crate::{ block::BlockId, @@ -21,112 +13,10 @@ use crate::{ WORLD_HEIGHT, }; -// ======================== Constants ======================== - const CHUNK_AREA: usize = 16 * 16; const SUBCHUNK_VOLUME: usize = CHUNK_AREA * 16; const CHUNK_VOLUME: usize = CHUNK_AREA * WORLD_HEIGHT; -// ======================== NBT Structure ======================== -// This section defines some data structure designed and used by Minecraft -// java implementation. They might not be used as defined by Pumpkin for -// its core working. -// - -#[derive(Serialize, Deserialize, Debug)] -#[allow(dead_code)] -#[serde(rename_all = "PascalCase")] -/// `ChunkNbt` -/// -/// This data structure stores a chunk information as described by a regional -/// Minecraft Anvil file. They are stored in NBT format and have been updated -/// for Minecraft 1.18. -pub struct ChunkNbt { - /// Version of the chunk NBT structure. - data_version: i32, - /// X position of the chunk (in chunks, from the origin, not relative to region). - #[serde(rename = "xPos")] - x_pos: i32, - /// Z position of the chunk (in chunks, from the origin, not relative to region). - #[serde(rename = "zPos")] - z_pos: i32, - /// Lowest Y section position in the chunk (e.g. -4 in 1.18). - #[serde(rename = "yPos")] - y_pos: i32, - /// Defines the world generation status of this chunk. - status: ChunkStatus, - /// Tick when the chunk was last saved. - last_update: i64, - /// List of compound tags, each tag is a section (also known as sub-chunk). All - /// ections in the world's height are present in this list, even those who are - /// empty (filled with air). - #[serde(rename = "sections")] - sections: Vec, - /// Each TAG_Compound in this list defines a block entity in the chunk. If this list is empty, it becomes a list of End tags. - #[serde(rename = "block_entities")] - #[serde(skip)] - block_entities: Vec, - /// Several different heightmaps corresponding to 256 values compacted at 9 bits per value - heightmaps: ChunkHeightmaps, - /// A List of 16 lists that store positions of light sources per chunk section as shorts, only for proto-chunks - #[serde(skip)] - lights: Vec, - /// A list of entities in the proto-chunks, used when generating. As of 1.17, this list is not present for fully generated chunks and entities are moved to a separated region files once the chunk is generated. - #[serde(skip)] - entities: Vec, - /// TODO - #[serde(rename = "fluid_ticks")] - #[serde(skip)] - fluid_ticks: (), - /// TODO - #[serde(rename = "block_ticks")] - #[serde(skip)] - block_ticks: (), - /// TODO - #[serde(skip)] - inhabited_time: i64, - /// TODO - #[serde(rename = "blending_data")] - #[serde(skip)] - blending_data: ChunkNbtBlendingData, - /// TODO - #[serde(skip)] - post_processing: (), - /// TODO - #[serde(skip)] - structures: (), -} - -#[derive(Serialize, Deserialize, Debug)] -/// A block entity (not related to entity) is used by Minecraft to store information -/// about a block that can't be stored in the block's block states. Also known as -/// *"tile entities"* in prior versions of the game. -pub enum BlockNbtEntity { - // TODO -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct ChunkNbtLight { - // TODO -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct ChunkNbtEntity { - // TODO -} - -#[derive(Serialize, Deserialize, Default, Debug)] -/// Biome blending data -pub struct ChunkNbtBlendingData { - min_section: i32, - max_section: i32, -} - -// ======================== Pumpkin Structure ======================== -// This section defines structures that are used by -// -// - pub struct ChunkData { pub blocks: ChunkBlocks, pub position: Vector2, @@ -143,36 +33,48 @@ pub struct ChunkBlocks { pub heightmap: ChunkHeightmaps, } -#[derive(Serialize, Deserialize, Debug, Clone)] +#[derive(Deserialize, Debug, Clone)] #[serde(rename_all = "PascalCase")] struct PaletteEntry { name: String, properties: Option>, } -#[derive(Serialize, Deserialize, Debug, Clone)] +#[derive(Deserialize, Debug, Clone)] struct ChunkSectionBlockStates { data: Option, palette: Vec, } -#[derive(Serialize, Deserialize, Debug, Clone)] +#[derive(Deserialize, Serialize, Debug, Clone)] #[serde(rename_all = "UPPERCASE")] pub struct ChunkHeightmaps { motion_blocking: LongArray, world_surface: LongArray, } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Deserialize, Debug)] +#[expect(dead_code)] struct ChunkSection { #[serde(rename = "Y")] y: i32, block_states: Option, } -#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)] +#[derive(Deserialize, Debug)] +#[serde(rename_all = "PascalCase")] +struct ChunkNbt { + #[expect(dead_code)] + data_version: usize, + + #[serde(rename = "sections")] + sections: Vec, + + heightmaps: ChunkHeightmaps, +} + +#[derive(Deserialize, Debug, PartialEq, Eq)] #[serde(tag = "Status")] -#[repr(u32)] enum ChunkStatus { #[serde(rename = "minecraft:empty")] Empty, From fff7357756a4e8f7a4954e121146939602af78c3 Mon Sep 17 00:00:00 2001 From: Snowiiii Date: Sat, 28 Sep 2024 13:23:08 +0200 Subject: [PATCH 41/65] Fix: Auth --- pumpkin-config/src/lib.rs | 14 ++------------ pumpkin/src/client/authentication.rs | 13 +++++++++++-- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/pumpkin-config/src/lib.rs b/pumpkin-config/src/lib.rs index 0679bf5ed..a65caa444 100644 --- a/pumpkin-config/src/lib.rs +++ b/pumpkin-config/src/lib.rs @@ -27,9 +27,6 @@ mod rcon; use proxy::ProxyConfig; use resource_pack::ResourcePackConfig; -/// Current Config version of the Base Config -const CURRENT_BASE_VERSION: &str = "1.0.0"; - pub static ADVANCED_CONFIG: LazyLock = LazyLock::new(AdvancedConfiguration::load); @@ -53,8 +50,6 @@ pub struct AdvancedConfiguration { #[derive(Serialize, Deserialize)] pub struct BasicConfiguration { - /// A version identifier for the configuration format. - pub config_version: String, /// The address to bind the server to. pub server_address: SocketAddr, /// The seed for world generation. @@ -84,7 +79,6 @@ pub struct BasicConfiguration { impl Default for BasicConfiguration { fn default() -> Self { Self { - config_version: CURRENT_BASE_VERSION.to_string(), server_address: SocketAddr::new(Ipv4Addr::new(0, 0, 0, 0).into(), 25565), seed: "".to_string(), max_players: 100000, @@ -114,7 +108,7 @@ trait LoadConfiguration { toml::from_str(&file_content).unwrap_or_else(|err| { panic!( - "Couldn't parse config at {:?}. Reason: {}", + "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, err.message() ) @@ -124,7 +118,7 @@ trait LoadConfiguration { if let Err(err) = fs::write(path, toml::to_string(&content).unwrap()) { warn!( - "Couldn't write default config to {:?}. Reason: {}", + "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 ); } @@ -157,10 +151,6 @@ impl LoadConfiguration for BasicConfiguration { } fn validate(&self) { - assert_eq!( - self.config_version, CURRENT_BASE_VERSION, - "Config version does not match used Config version. Please update your config" - ); assert!(self.view_distance >= 2, "View distance must be at least 2"); assert!( self.view_distance <= 32, diff --git a/pumpkin/src/client/authentication.rs b/pumpkin/src/client/authentication.rs index 03853578b..062c01b43 100644 --- a/pumpkin/src/client/authentication.rs +++ b/pumpkin/src/client/authentication.rs @@ -99,11 +99,20 @@ pub fn unpack_textures(property: &Property, config: &TextureConfig) -> Result<() pub fn is_texture_url_valid(url: Url, config: &TextureConfig) -> Result<(), TextureError> { let scheme = url.scheme(); - if !config.allowed_url_schemes.contains(&scheme.to_string()) { + if !config + .allowed_url_schemes + .iter() + .any(|allowed_scheme| scheme.ends_with(allowed_scheme)) + { return Err(TextureError::DisallowedUrlScheme(scheme.to_string())); } let domain = url.domain().unwrap_or(""); - if !config.allowed_url_domains.contains(&domain.to_string()) { + dbg!(domain); + if !config + .allowed_url_domains + .iter() + .any(|allowed_domain| domain.ends_with(allowed_domain)) + { return Err(TextureError::DisallowedUrlDomain(domain.to_string())); } Ok(()) From b7b975a064128d785411299b0cc7748ef313b955 Mon Sep 17 00:00:00 2001 From: kralverde Date: Sat, 28 Sep 2024 17:39:11 -0400 Subject: [PATCH 42/65] rebase block_id --- Cargo.lock | 1 + .../src/{block_id.rs => block_state.rs} | 135 ++++++++++++++++-- pumpkin-macros/src/lib.rs | 18 ++- pumpkin-world/Cargo.toml | 1 + pumpkin-world/src/block/block_id.rs | 57 -------- pumpkin-world/src/block/block_registry.rs | 33 ++++- pumpkin-world/src/block/block_state.rs | 73 ++++++++++ pumpkin-world/src/block/mod.rs | 6 +- pumpkin-world/src/chunk.rs | 9 +- pumpkin-world/src/world_gen/generator.rs | 6 +- .../src/world_gen/generic_generator.rs | 12 +- .../implementation/overworld/biome/plains.rs | 14 +- .../src/world_gen/implementation/superflat.rs | 13 +- pumpkin/src/client/player_packet.rs | 4 +- 14 files changed, 285 insertions(+), 97 deletions(-) rename pumpkin-macros/src/{block_id.rs => block_state.rs} (54%) delete mode 100644 pumpkin-world/src/block/block_id.rs create mode 100644 pumpkin-world/src/block/block_state.rs diff --git a/Cargo.lock b/Cargo.lock index 4f66bfda4..5ff8491d3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2065,6 +2065,7 @@ dependencies = [ "num-traits", "parking_lot", "pumpkin-core", + "pumpkin-macros", "rand", "rayon", "serde", diff --git a/pumpkin-macros/src/block_id.rs b/pumpkin-macros/src/block_state.rs similarity index 54% rename from pumpkin-macros/src/block_id.rs rename to pumpkin-macros/src/block_state.rs index a71e90df4..3f10f3943 100644 --- a/pumpkin-macros/src/block_id.rs +++ b/pumpkin-macros/src/block_state.rs @@ -1,4 +1,7 @@ -use std::{collections::HashMap, sync::LazyLock}; +use std::{ + collections::{HashMap, HashSet}, + sync::LazyLock, +}; use itertools::Itertools; use proc_macro::TokenStream; @@ -50,7 +53,108 @@ static BLOCKS: LazyLock> = LazyLock::new(|| { .expect("Could not parse block.json registry.") }); -pub fn block_id_impl(item: TokenStream) -> TokenStream { +fn pascal_case(original: &str) -> String { + let mut pascal = String::new(); + let mut capitalize = true; + for ch in original.chars() { + if ch == '_' { + capitalize = true; + } else if capitalize { + pascal.push(ch.to_ascii_uppercase()); + capitalize = false; + } else { + pascal.push(ch); + } + } + pascal +} + +pub fn block_type_enum_impl() -> TokenStream { + let categories: &HashSet<&str> = &BLOCKS + .values() + .map(|val| val.definition.category.as_str()) + .collect(); + + let original_and_converted_stream = categories.iter().map(|key| { + ( + key, + pascal_case(key.split_once(':').expect("Bad minecraft id").1), + ) + }); + let new_names: proc_macro2::TokenStream = original_and_converted_stream + .clone() + .map(|(_, x)| x) + .join(",\n") + .parse() + .unwrap(); + + let from_string: proc_macro2::TokenStream = original_and_converted_stream + .clone() + .map(|(original, converted)| format!("\"{}\" => BlockCategory::{},", original, converted)) + .join("\n") + .parse() + .unwrap(); + + // I;ve never used macros before so call me out on this lol + quote! { + #[derive(PartialEq, Clone)] + pub enum BlockCategory { + #new_names + } + + impl BlockCategory { + pub fn from_registry_id(id: &str) -> BlockCategory { + match id { + #from_string + _ => panic!("Not a valid block type id"), + } + } + } + } + .into() +} + +pub fn block_enum_impl() -> TokenStream { + let original_and_converted_stream = &BLOCKS.keys().map(|key| { + ( + key, + pascal_case(key.split_once(':').expect("Bad minecraft id").1), + ) + }); + let new_names: proc_macro2::TokenStream = original_and_converted_stream + .clone() + .map(|(_, x)| x) + .join(",\n") + .parse() + .unwrap(); + + let from_string: proc_macro2::TokenStream = original_and_converted_stream + .clone() + .map(|(original, converted)| format!("\"{}\" => Block::{},", original, converted)) + .join("\n") + .parse() + .unwrap(); + + // I;ve never used macros before so call me out on this lol + quote! { + #[derive(PartialEq, Clone)] + pub enum Block { + #new_names + } + + impl Block { + pub fn from_registry_id(id: &str) -> Block { + match id { + #from_string + _ => panic!("Not a valid block id"), + } + } + } + } + .into() +} + +pub fn block_state_impl(item: TokenStream) -> TokenStream { let data = syn::punctuated::Punctuated::::parse_terminated .parse(item) .unwrap(); @@ -61,9 +165,12 @@ pub fn block_id_impl(item: TokenStream) -> TokenStream { let block_name = match block_name { syn::Expr::Lit(lit) => match &lit.lit { syn::Lit::Str(name) => name.value(), - _ => panic!("The first argument should be a string"), + _ => panic!("The first argument should be a string, have: {:?}", lit), }, - _ => panic!("The first argument should be a string"), + _ => panic!( + "The first argument should be a string, have: {:?}", + block_name + ), }; let mut properties = HashMap::new(); @@ -104,7 +211,7 @@ pub fn block_id_impl(item: TokenStream) -> TokenStream { .get(&block_name) .expect("Block with that name does not exist"); - let id = if properties.is_empty() { + let state = if properties.is_empty() { block_info .states .iter() @@ -112,14 +219,13 @@ pub fn block_id_impl(item: TokenStream) -> TokenStream { .expect( "Error inside blocks.json file: Every Block should have at least 1 default state", ) - .id } else { match block_info .states .iter() .find(|state| state.properties == properties) { - Some(state) => state.id, + Some(state) => state, None => panic!( "Could not find block with these properties, the following are valid properties: \n{}", block_info @@ -131,13 +237,24 @@ pub fn block_id_impl(item: TokenStream) -> TokenStream { } }; + let id = state.id; + let category_name = block_info.definition.category.clone(); + if std::env::var("CARGO_PKG_NAME").unwrap() == "pumpkin-world" { quote! { - crate::block::block_id::BlockId::from_id(#id as u16) + crate::block::block_state::BlockState::new_unchecked( + #id as u16, + crate::block::Block::from_registry_id(#block_name), + crate::block::BlockCategory::from_registry_id(#category_name), + ) } } else { quote! { - pumpkin_world::block::block_id::BlockId::from_id(#id as u16) + pumpkin_world::block::block_id::BlockStateId::new_unchecked( + #id as u16, + pumpkin_world::block::Block::from_registry_id(#block_name), + pumpkin_world::block::BlockCategory::from_registry_id(#category_name), + ) } } .into() diff --git a/pumpkin-macros/src/lib.rs b/pumpkin-macros/src/lib.rs index da53c14be..3c27f91a2 100644 --- a/pumpkin-macros/src/lib.rs +++ b/pumpkin-macros/src/lib.rs @@ -23,8 +23,20 @@ pub fn packet(input: TokenStream, item: TokenStream) -> TokenStream { gen.into() } -mod block_id; +mod block_state; #[proc_macro] -pub fn block_id(item: TokenStream) -> TokenStream { - block_id::block_id_impl(item) +pub fn block(item: TokenStream) -> TokenStream { + block_state::block_state_impl(item) +} + +#[proc_macro] +/// Creates an enum for all block types. Should only be used once +pub fn blocks_enum(_item: TokenStream) -> TokenStream { + block_state::block_enum_impl() +} + +#[proc_macro] +/// Creates an enum for all block categories. Should only be used once +pub fn block_categories_enum(_item: TokenStream) -> TokenStream { + block_state::block_type_enum_impl() } diff --git a/pumpkin-world/Cargo.toml b/pumpkin-world/Cargo.toml index 43d335c13..453842903 100644 --- a/pumpkin-world/Cargo.toml +++ b/pumpkin-world/Cargo.toml @@ -5,6 +5,7 @@ edition.workspace = true [dependencies] pumpkin-core = { path = "../pumpkin-core" } +pumpkin-macros = { path = "../pumpkin-macros" } fastnbt = { git = "https://github.com/owengage/fastnbt.git" } tokio.workspace = true diff --git a/pumpkin-world/src/block/block_id.rs b/pumpkin-world/src/block/block_id.rs deleted file mode 100644 index 747daff2a..000000000 --- a/pumpkin-world/src/block/block_id.rs +++ /dev/null @@ -1,57 +0,0 @@ -use std::collections::HashMap; - -use serde::Deserialize; - -use super::block_registry::BLOCKS; -use crate::level::WorldError; - -// 0 is air -> reasonable default -#[derive(Default, Deserialize, Debug, Hash, Clone, Copy, PartialEq, Eq)] -#[serde(transparent)] -pub struct BlockId { - data: u16, -} - -impl BlockId { - pub const AIR: Self = Self::from_id(0); - - pub fn new( - text_id: &str, - properties: Option<&HashMap>, - ) -> Result { - let mut block_states = BLOCKS - .get(text_id) - .ok_or(WorldError::BlockIdentifierNotFound)? - .states - .iter(); - - let block_state = match properties { - Some(properties) => block_states - .find(|state| &state.properties == properties) - .ok_or_else(|| WorldError::BlockStateIdNotFound)?, - None => block_states - .find(|state| state.is_default) - .expect("Every Block should have at least 1 default state"), - }; - - Ok(block_state.id) - } - - pub const fn from_id(id: u16) -> Self { - // TODO: add check if the id is actually valid - Self { data: id } - } - - pub fn is_air(&self) -> bool { - self.data == 0 || self.data == 12959 || self.data == 12958 - } - - pub fn get_id(&self) -> u16 { - self.data - } - - /// An i32 is the way mojang internally represents their Blocks - pub fn get_id_mojang_repr(&self) -> i32 { - self.data as i32 - } -} diff --git a/pumpkin-world/src/block/block_registry.rs b/pumpkin-world/src/block/block_registry.rs index cd06e98de..896efd8b2 100644 --- a/pumpkin-world/src/block/block_registry.rs +++ b/pumpkin-world/src/block/block_registry.rs @@ -2,13 +2,16 @@ use std::{collections::HashMap, sync::LazyLock}; use serde::Deserialize; -use super::block_id::BlockId; +use super::BlockState; pub static BLOCKS: LazyLock> = LazyLock::new(|| { serde_json::from_str(include_str!("../../../assets/blocks.json")) .expect("Could not parse block.json registry.") }); +pumpkin_macros::blocks_enum!(); +pumpkin_macros::block_categories_enum!(); + #[derive(Deserialize, Debug, Clone, PartialEq, Eq)] pub struct RegistryBlockDefinition { /// e.g. minecraft:door or minecraft:button @@ -48,3 +51,31 @@ pub struct RegistryBlockType { #[serde(default, rename = "properties")] valid_properties: HashMap>, } + +#[derive(Default, Copy, Deserialize, Debug, Clone, PartialEq, Eq, Hash)] +#[serde(transparent)] +pub struct BlockId { + pub data: u16, +} + +impl BlockId { + pub fn is_air(&self) -> bool { + self.data == 0 || self.data == 12959 || self.data == 12958 + } + + pub fn get_id_mojang_repr(&self) -> i32 { + self.data as i32 + } + + pub fn get_id(&self) -> u16 { + self.data + } +} + +impl From for BlockId { + fn from(value: BlockState) -> Self { + Self { + data: value.get_id(), + } + } +} diff --git a/pumpkin-world/src/block/block_state.rs b/pumpkin-world/src/block/block_state.rs new file mode 100644 index 000000000..437349df2 --- /dev/null +++ b/pumpkin-world/src/block/block_state.rs @@ -0,0 +1,73 @@ +use std::collections::HashMap; + +use crate::level::WorldError; + +use super::block_registry::{Block, BlockCategory, BLOCKS}; + +#[derive(Clone)] +pub struct BlockState { + state_id: u16, + block: Block, + category: BlockCategory, +} + +impl BlockState { + pub const AIR: BlockState = BlockState { + state_id: 0, + block: Block::Air, + category: BlockCategory::Air, + }; + + pub fn new( + registry_id: &str, + properties: Option<&HashMap>, + ) -> Result { + let block_registry = BLOCKS + .get(registry_id) + .ok_or(WorldError::BlockIdentifierNotFound)?; + let mut block_states = block_registry.states.iter(); + + let block_state = match properties { + Some(properties) => block_states + .find(|state| &state.properties == properties) + .ok_or_else(|| WorldError::BlockStateIdNotFound)?, + None => block_states + .find(|state| state.is_default) + .expect("Every Block should have at least 1 default state"), + }; + + Ok(Self { + state_id: block_state.id.data, + block: Block::from_registry_id(registry_id), + category: BlockCategory::from_registry_id(&block_registry.definition.category), + }) + } + + pub const fn new_unchecked(state_id: u16, block: Block, category: BlockCategory) -> Self { + Self { + state_id, + block, + category, + } + } + + pub fn is_air(&self) -> bool { + self.category == BlockCategory::Air + } + + pub fn get_id(&self) -> u16 { + self.state_id + } + + pub fn get_id_mojang_repr(&self) -> i32 { + self.state_id as i32 + } + + pub fn of_block(&self, block: Block) -> bool { + self.block == block + } + + pub fn of_category(&self, category: BlockCategory) -> bool { + self.category == category + } +} diff --git a/pumpkin-world/src/block/mod.rs b/pumpkin-world/src/block/mod.rs index f5fbab664..8a838dedc 100644 --- a/pumpkin-world/src/block/mod.rs +++ b/pumpkin-world/src/block/mod.rs @@ -1,11 +1,13 @@ use num_derive::FromPrimitive; -pub mod block_id; mod block_registry; +pub mod block_state; -pub use block_id::BlockId; use pumpkin_core::math::vector3::Vector3; +pub use block_registry::{Block, BlockCategory, BlockId}; +pub use block_state::BlockState; + #[derive(FromPrimitive)] pub enum BlockFace { Bottom = 0, diff --git a/pumpkin-world/src/chunk.rs b/pumpkin-world/src/chunk.rs index dd5aa643b..2037bf5fa 100644 --- a/pumpkin-world/src/chunk.rs +++ b/pumpkin-world/src/chunk.rs @@ -7,7 +7,7 @@ use pumpkin_core::math::vector2::Vector2; use serde::{Deserialize, Serialize}; use crate::{ - block::BlockId, + block::{BlockId, BlockState}, coordinates::{ChunkRelativeBlockCoordinates, Height}, level::{ChunkNotGeneratedError, WorldError}, WORLD_HEIGHT, @@ -215,7 +215,12 @@ impl ChunkData { let palette = block_states .palette .iter() - .map(|entry| BlockId::new(&entry.name, entry.properties.as_ref())) + .map( + |entry| match BlockState::new(&entry.name, entry.properties.as_ref()) { + Err(e) => Err(e), + Ok(state) => Ok(state.into()), + }, + ) .collect::, _>>()?; let block_data = match block_states.data { diff --git a/pumpkin-world/src/world_gen/generator.rs b/pumpkin-world/src/world_gen/generator.rs index 0a7d043d6..36f3f9553 100644 --- a/pumpkin-world/src/world_gen/generator.rs +++ b/pumpkin-world/src/world_gen/generator.rs @@ -3,7 +3,7 @@ use pumpkin_core::math::vector2::Vector2; use static_assertions::assert_obj_safe; use crate::biome::Biome; -use crate::block::BlockId; +use crate::block::block_state::BlockState; use crate::chunk::ChunkData; use crate::coordinates::{BlockCoordinates, XZBlockCoordinates}; use crate::world_gen::Seed; @@ -26,12 +26,12 @@ pub(crate) trait TerrainGenerator: Sync + Send { fn prepare_chunk(&self, at: &Vector2); /// Is static - fn generate_block(&self, at: BlockCoordinates, biome: Biome) -> BlockId; + fn generate_block(&self, at: BlockCoordinates, biome: Biome) -> BlockState; } pub(crate) trait PerlinTerrainGenerator: Sync + Send { fn prepare_chunk(&self, at: &Vector2, perlin: &Perlin); /// Dependens on the perlin noise height - fn generate_block(&self, at: BlockCoordinates, chunk_height: i16, biome: Biome) -> BlockId; + fn generate_block(&self, at: BlockCoordinates, chunk_height: i16, biome: Biome) -> BlockState; } diff --git a/pumpkin-world/src/world_gen/generic_generator.rs b/pumpkin-world/src/world_gen/generic_generator.rs index 8bdc17135..1f0a17657 100644 --- a/pumpkin-world/src/world_gen/generic_generator.rs +++ b/pumpkin-world/src/world_gen/generic_generator.rs @@ -62,11 +62,13 @@ impl WorldGenerator for GenericGen blocks.set_block( coordinates, - self.terrain_generator.generate_block( - coordinates.with_chunk_coordinates(at), - chunk_height as i16, - biome, - ), + self.terrain_generator + .generate_block( + coordinates.with_chunk_coordinates(at), + chunk_height as i16, + biome, + ) + .into(), ); } } diff --git a/pumpkin-world/src/world_gen/implementation/overworld/biome/plains.rs b/pumpkin-world/src/world_gen/implementation/overworld/biome/plains.rs index cb3f3e981..001c71be7 100644 --- a/pumpkin-world/src/world_gen/implementation/overworld/biome/plains.rs +++ b/pumpkin-world/src/world_gen/implementation/overworld/biome/plains.rs @@ -3,7 +3,7 @@ use pumpkin_core::math::vector2::Vector2; use crate::{ biome::Biome, - block::BlockId, + block::block_state::BlockState, coordinates::{BlockCoordinates, XZBlockCoordinates}, world_gen::{ generator::{BiomeGenerator, GeneratorInit, PerlinTerrainGenerator}, @@ -40,21 +40,21 @@ impl GeneratorInit for PlainsTerrainGenerator { impl PerlinTerrainGenerator for PlainsTerrainGenerator { fn prepare_chunk(&self, _at: &Vector2, _perlin: &Perlin) {} // TODO allow specifying which blocks should be at which height in the config. - fn generate_block(&self, at: BlockCoordinates, chunk_height: i16, _: Biome) -> BlockId { + fn generate_block(&self, at: BlockCoordinates, chunk_height: i16, _: Biome) -> BlockState { let begin_stone_height = chunk_height - 5; let begin_dirt_height = chunk_height - 1; let y = *at.y; if y == -64 { - BlockId::from_id(79) // BEDROCK + pumpkin_macros::block!("minecraft:bedrock") } else if y >= -63 && y <= begin_stone_height { - return BlockId::from_id(1); // STONE + pumpkin_macros::block!("minecraft:stone") } else if y >= begin_stone_height && y < begin_dirt_height { - return BlockId::from_id(10); // DIRT; + pumpkin_macros::block!("minecraft:dirt") } else if y == chunk_height - 1 { - return BlockId::from_id(9); // GRASS BLOCK + pumpkin_macros::block!("minecraft:grass_block") } else { - BlockId::AIR + BlockState::AIR } } } diff --git a/pumpkin-world/src/world_gen/implementation/superflat.rs b/pumpkin-world/src/world_gen/implementation/superflat.rs index 3acf32495..872c31515 100644 --- a/pumpkin-world/src/world_gen/implementation/superflat.rs +++ b/pumpkin-world/src/world_gen/implementation/superflat.rs @@ -1,8 +1,9 @@ use pumpkin_core::math::vector2::Vector2; +use pumpkin_macros::block; use crate::{ biome::Biome, - block::BlockId, + block::block_state::BlockState, coordinates::{BlockCoordinates, XZBlockCoordinates}, world_gen::{ generator::{BiomeGenerator, GeneratorInit, TerrainGenerator}, @@ -40,12 +41,12 @@ impl GeneratorInit for SuperflatTerrainGenerator { impl TerrainGenerator for SuperflatTerrainGenerator { fn prepare_chunk(&self, _at: &Vector2) {} // TODO allow specifying which blocks should be at which height in the config. - fn generate_block(&self, at: BlockCoordinates, _: Biome) -> BlockId { + fn generate_block(&self, at: BlockCoordinates, _: Biome) -> BlockState { match *at.y { - -64 => BlockId::from_id(79), // Bedrock - -63..=-62 => BlockId::from_id(10), // Dirt - -61 => BlockId::from_id(9), // Grass - _ => BlockId::AIR, + -64 => block!("minecraft:bedrock"), + -63..=-62 => block!("minecraft:dirt"), + -61 => block!("minecraft:grass_block"), + _ => BlockState::AIR, } } } diff --git a/pumpkin/src/client/player_packet.rs b/pumpkin/src/client/player_packet.rs index 420d3589b..5e2e48da7 100644 --- a/pumpkin/src/client/player_packet.rs +++ b/pumpkin/src/client/player_packet.rs @@ -29,7 +29,7 @@ use pumpkin_protocol::{ SUseItemOn, Status, }, }; -use pumpkin_world::block::{BlockFace, BlockId}; +use pumpkin_world::block::{BlockFace, BlockState}; use pumpkin_world::global_registry; use super::PlayerConfig; @@ -517,7 +517,7 @@ impl Player { item.item_id, ) .expect("All item ids are in the global registry"); - if let Ok(block_state_id) = BlockId::new(minecraft_id, None) { + if let Ok(block_state_id) = BlockState::new(minecraft_id, None) { let entity = &self.entity; let world = &entity.world; world.broadcast_packet_all(&CBlockUpdate::new( From 0590a4f87d45839783810bceef93e418693a2c6a Mon Sep 17 00:00:00 2001 From: Alerty2 Date: Sun, 29 Sep 2024 17:06:23 +0200 Subject: [PATCH 43/65] adds the /kill command --- pumpkin/src/commands/cmd_kill.rs | 35 ++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 pumpkin/src/commands/cmd_kill.rs diff --git a/pumpkin/src/commands/cmd_kill.rs b/pumpkin/src/commands/cmd_kill.rs new file mode 100644 index 000000000..747b7e96e --- /dev/null +++ b/pumpkin/src/commands/cmd_kill.rs @@ -0,0 +1,35 @@ +use pumpkin_core::text::{color::NamedColor, TextComponent}; +use crate::commands::tree::CommandTree; +use crate::commands::arg_player::{consume_arg_player, parse_arg_player}; +use crate::commands::tree::RawArgs; +use crate::commands::CommandSender; +use crate::commands::tree_builder::argument; + +const NAMES: [&str; 1] = ["kill"]; +const DESCRIPTION: &str = "Kills a target player."; + +const ARG_TARGET: &str = "target"; + +pub fn consume_arg_target(_src: &CommandSender, args: &mut RawArgs) -> Option { + consume_arg_player(_src, args) +} + +pub(crate) fn init_command_tree<'a>() -> CommandTree<'a> { + CommandTree::new(NAMES, DESCRIPTION) + .with_child( + argument(ARG_TARGET, consume_arg_target) + .execute(&|sender, server, args| { + let target = parse_arg_player(sender, server, ARG_TARGET, args)?; + target.entity.kill(); + target.send_system_message(TextComponent::text( + "You have been killed." + ).color_named(NamedColor::Red)); + + sender.send_message(TextComponent::text( + "Player has been killed." + ).color_named(NamedColor::Blue)); + + Ok(()) + }) + ) +} From 100f313c491ff039536fa3e663bf5a47462f2f33 Mon Sep 17 00:00:00 2001 From: Alerty2 Date: Sun, 29 Sep 2024 17:15:10 +0200 Subject: [PATCH 44/65] adds the /kill command --- pumpkin/src/commands/mod.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pumpkin/src/commands/mod.rs b/pumpkin/src/commands/mod.rs index 17393346e..f3e260add 100644 --- a/pumpkin/src/commands/mod.rs +++ b/pumpkin/src/commands/mod.rs @@ -13,6 +13,7 @@ mod cmd_gamemode; mod cmd_help; mod cmd_pumpkin; mod cmd_stop; +mod cmd_kill; pub mod dispatcher; mod tree; mod tree_builder; @@ -75,6 +76,7 @@ pub fn default_dispatcher<'a>() -> CommandDispatcher<'a> { dispatcher.register(cmd_stop::init_command_tree()); dispatcher.register(cmd_help::init_command_tree()); dispatcher.register(cmd_echest::init_command_tree()); + dispatcher.register(cmd_kill::init_command_tree()); dispatcher } From f15db29d2966727da35a813eb7e0aaa4ba62a6d0 Mon Sep 17 00:00:00 2001 From: Alerty2 Date: Mon, 30 Sep 2024 20:50:00 +0200 Subject: [PATCH 45/65] adds the /kill command --- pumpkin/src/commands/cmd_kill.rs | 34 +++++++++++++++----------------- pumpkin/src/commands/mod.rs | 2 +- 2 files changed, 17 insertions(+), 19 deletions(-) diff --git a/pumpkin/src/commands/cmd_kill.rs b/pumpkin/src/commands/cmd_kill.rs index 747b7e96e..9f1e3f205 100644 --- a/pumpkin/src/commands/cmd_kill.rs +++ b/pumpkin/src/commands/cmd_kill.rs @@ -1,9 +1,9 @@ -use pumpkin_core::text::{color::NamedColor, TextComponent}; -use crate::commands::tree::CommandTree; use crate::commands::arg_player::{consume_arg_player, parse_arg_player}; +use crate::commands::tree::CommandTree; use crate::commands::tree::RawArgs; -use crate::commands::CommandSender; use crate::commands::tree_builder::argument; +use crate::commands::CommandSender; +use pumpkin_core::text::{color::NamedColor, TextComponent}; const NAMES: [&str; 1] = ["kill"]; const DESCRIPTION: &str = "Kills a target player."; @@ -15,21 +15,19 @@ pub fn consume_arg_target(_src: &CommandSender, args: &mut RawArgs) -> Option() -> CommandTree<'a> { - CommandTree::new(NAMES, DESCRIPTION) - .with_child( - argument(ARG_TARGET, consume_arg_target) - .execute(&|sender, server, args| { - let target = parse_arg_player(sender, server, ARG_TARGET, args)?; - target.entity.kill(); - target.send_system_message(TextComponent::text( - "You have been killed." - ).color_named(NamedColor::Red)); + CommandTree::new(NAMES, DESCRIPTION).with_child( + argument(ARG_TARGET, consume_arg_target).execute(&|sender, server, args| { + let target = parse_arg_player(sender, server, ARG_TARGET, args)?; + target.entity.kill(); + target.send_system_message( + TextComponent::text("You have been killed.").color_named(NamedColor::Red), + ); - sender.send_message(TextComponent::text( - "Player has been killed." - ).color_named(NamedColor::Blue)); + sender.send_message( + TextComponent::text("Player has been killed.").color_named(NamedColor::Blue), + ); - Ok(()) - }) - ) + Ok(()) + }), + ) } diff --git a/pumpkin/src/commands/mod.rs b/pumpkin/src/commands/mod.rs index f3e260add..912b03183 100644 --- a/pumpkin/src/commands/mod.rs +++ b/pumpkin/src/commands/mod.rs @@ -11,9 +11,9 @@ mod arg_player; mod cmd_echest; mod cmd_gamemode; mod cmd_help; +mod cmd_kill; mod cmd_pumpkin; mod cmd_stop; -mod cmd_kill; pub mod dispatcher; mod tree; mod tree_builder; From f8740479e045e740ebaeaf85b38fdae08ca04a9c Mon Sep 17 00:00:00 2001 From: Alerty2 Date: Tue, 1 Oct 2024 21:19:29 +0200 Subject: [PATCH 46/65] adds the /kick command --- pumpkin/src/commands/cmd_kill.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/pumpkin/src/commands/cmd_kill.rs b/pumpkin/src/commands/cmd_kill.rs index 9f1e3f205..d3b8135e6 100644 --- a/pumpkin/src/commands/cmd_kill.rs +++ b/pumpkin/src/commands/cmd_kill.rs @@ -19,9 +19,6 @@ pub(crate) fn init_command_tree<'a>() -> CommandTree<'a> { argument(ARG_TARGET, consume_arg_target).execute(&|sender, server, args| { let target = parse_arg_player(sender, server, ARG_TARGET, args)?; target.entity.kill(); - target.send_system_message( - TextComponent::text("You have been killed.").color_named(NamedColor::Red), - ); sender.send_message( TextComponent::text("Player has been killed.").color_named(NamedColor::Blue), From ff125c048a266abe0a8b13e7e53f0be9ba8174d6 Mon Sep 17 00:00:00 2001 From: Snowiiii Date: Thu, 3 Oct 2024 19:00:16 +0200 Subject: [PATCH 47/65] Fix: https://github.com/Snowiiii/Pumpkin/issues/108 Fix: https://github.com/Snowiiii/Pumpkin/issues/108 --- pumpkin-protocol/src/server/login/s_plugin_response.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/pumpkin-protocol/src/server/login/s_plugin_response.rs b/pumpkin-protocol/src/server/login/s_plugin_response.rs index 44360bde2..8b2dd0052 100644 --- a/pumpkin-protocol/src/server/login/s_plugin_response.rs +++ b/pumpkin-protocol/src/server/login/s_plugin_response.rs @@ -9,7 +9,6 @@ use crate::{ #[packet(0x02)] pub struct SLoginPluginResponse { pub message_id: VarInt, - pub successful: bool, pub data: Option, } @@ -17,7 +16,6 @@ impl ServerPacket for SLoginPluginResponse { fn read(bytebuf: &mut ByteBuffer) -> Result { Ok(Self { message_id: bytebuf.get_var_int()?, - successful: bytebuf.get_bool()?, data: bytebuf.get_option(|v| Ok(v.get_slice()))?, }) } From ec668e7eb2f0b69972e30af5bad1db494d13b419 Mon Sep 17 00:00:00 2001 From: ThePaulo1 <63714475+ThePaulo1@users.noreply.github.com> Date: Sun, 6 Oct 2024 12:57:47 +0200 Subject: [PATCH 48/65] allow assets folder Fix failing build due to assets not found --- .dockerignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.dockerignore b/.dockerignore index 5c5187748..0269ba472 100644 --- a/.dockerignore +++ b/.dockerignore @@ -6,6 +6,7 @@ # Allow the source code folders !/pumpkin*/ +!/assets # Dependencies !Cargo.lock From a0b47852690e8d9f07f9ed1190a32b13790b4fbc Mon Sep 17 00:00:00 2001 From: Alexander Medvedev <71594357+Snowiiii@users.noreply.github.com> Date: Mon, 7 Oct 2024 20:47:08 +0100 Subject: [PATCH 49/65] Move from `image` to `png` crate We used the famous and good `image` crate to load the favicon. Its a good crate but its in fact has features we don't need, Its in fact a "An Image Processing Library" like it is mentioned on their README. Its also supports multiple image formats which we don't need. The `png` is a PNG & APNG Decoder & Encoder, Exactly what we need. Its about Half the size of the image crate (while only having png feature) --- Cargo.lock | 28 ++++--------------- .../src/client/play/c_entity_animation.rs | 3 +- pumpkin/Cargo.toml | 2 +- pumpkin/src/server/connection_cache.rs | 23 ++++++++------- 4 files changed, 20 insertions(+), 36 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5ff8491d3..611057e57 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -195,12 +195,6 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" -[[package]] -name = "byteorder-lite" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" - [[package]] name = "bytes" version = "1.7.1" @@ -1281,18 +1275,6 @@ dependencies = [ "unicode-normalization", ] -[[package]] -name = "image" -version = "0.25.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99314c8a2152b8ddb211f924cdae532d8c5e4c8bb54728e12fff1b0cd5963a10" -dependencies = [ - "bytemuck", - "byteorder-lite", - "num-traits", - "png", -] - [[package]] name = "indexmap" version = "2.5.0" @@ -1547,7 +1529,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" dependencies = [ "adler", - "simd-adler32", ] [[package]] @@ -1557,6 +1538,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" dependencies = [ "adler2", + "simd-adler32", ] [[package]] @@ -1817,15 +1799,15 @@ checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] name = "png" -version = "0.17.13" +version = "0.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06e4b0d3d1312775e782c86c91a111aa1f910cbb65e1337f9975b5f9a554b5e1" +checksum = "52f9d46a34a05a6a57566bc2bfae066ef07585a6e3fa30fbbdff5936380623f0" dependencies = [ "bitflags 1.3.2", "crc32fast", "fdeflate", "flate2", - "miniz_oxide 0.7.4", + "miniz_oxide 0.8.0", ] [[package]] @@ -1926,7 +1908,6 @@ dependencies = [ "ctrlc", "digest 0.11.0-pre.9", "hmac", - "image", "itertools 0.13.0", "log", "mio", @@ -1934,6 +1915,7 @@ dependencies = [ "num-derive", "num-traits", "parking_lot", + "png", "pumpkin-config", "pumpkin-core", "pumpkin-entity", diff --git a/pumpkin-protocol/src/client/play/c_entity_animation.rs b/pumpkin-protocol/src/client/play/c_entity_animation.rs index 28542fac4..5618374ae 100644 --- a/pumpkin-protocol/src/client/play/c_entity_animation.rs +++ b/pumpkin-protocol/src/client/play/c_entity_animation.rs @@ -1,4 +1,3 @@ -use num_derive::ToPrimitive; use pumpkin_macros::packet; use serde::Serialize; @@ -21,7 +20,7 @@ impl CEntityAnimation { } } -#[derive(ToPrimitive)] +#[repr(u8)] pub enum Animation { SwingMainArm, LeaveBed, diff --git a/pumpkin/Cargo.toml b/pumpkin/Cargo.toml index 2b6a35929..401417a60 100644 --- a/pumpkin/Cargo.toml +++ b/pumpkin/Cargo.toml @@ -58,7 +58,7 @@ thiserror = "1.0" # icon loading base64 = "0.22.1" -image = { version = "0.25", default-features = false, features = ["png"] } +png = "0.17.14" # logging simple_logger = "5.0.0" diff --git a/pumpkin/src/server/connection_cache.rs b/pumpkin/src/server/connection_cache.rs index 58181c214..76aa3b076 100644 --- a/pumpkin/src/server/connection_cache.rs +++ b/pumpkin/src/server/connection_cache.rs @@ -1,7 +1,6 @@ -use std::{io::Cursor, path::Path}; +use std::{fs::File, path::Path}; use base64::{engine::general_purpose, Engine as _}; -use image::GenericImageView as _; use pumpkin_config::{BasicConfiguration, BASIC_CONFIG}; use pumpkin_protocol::{ client::{config::CPluginMessage, status::CStatusResponse}, @@ -85,15 +84,19 @@ impl CachedStatus { } fn load_icon(path: &str) -> String { - let icon = image::open(path).expect("Failed to load icon"); - let dimension = icon.dimensions(); - assert!(dimension.0 == 64, "Icon width must be 64"); - assert!(dimension.1 == 64, "Icon height must be 64"); - let mut image = Vec::with_capacity(64 * 64 * 4); - icon.write_to(&mut Cursor::new(&mut image), image::ImageFormat::Png) - .unwrap(); + let icon = png::Decoder::new(File::open(path).expect("Failed to load icon")); + let mut reader = icon.read_info().unwrap(); + let info = reader.info(); + assert!(info.width == 64, "Icon width must be 64"); + assert!(info.height == 64, "Icon height must be 64"); + // Allocate the output buffer. + let mut buf = vec![0; reader.output_buffer_size()]; + // Read the next frame. An APNG might contain multiple frames. + let info = reader.next_frame(&mut buf).unwrap(); + // Grab the bytes of the image. + let bytes = &buf[..info.buffer_size()]; let mut result = "data:image/png;base64,".to_owned(); - general_purpose::STANDARD.encode_string(image, &mut result); + general_purpose::STANDARD.encode_string(bytes, &mut result); result } } From ef0911c31decaa8feb7dd083c7e331684d36d945 Mon Sep 17 00:00:00 2001 From: Alexander Medvedev <71594357+Snowiiii@users.noreply.github.com> Date: Mon, 7 Oct 2024 21:12:30 +0100 Subject: [PATCH 50/65] Update docker alphine --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 69631ab81..20e2faaa5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM rust:1-alpine3.19 AS builder +FROM rust:1-alpine3.20 AS builder ENV RUSTFLAGS="-C target-feature=-crt-static -C target-cpu=native" RUN apk add --no-cache musl-dev WORKDIR /pumpkin @@ -6,7 +6,7 @@ COPY . /pumpkin RUN cargo build --release RUN strip target/release/pumpkin -FROM alpine:3.19 +FROM alpine:3.20 WORKDIR /pumpkin RUN apk add --no-cache libgcc COPY --from=builder /pumpkin/target/release/pumpkin /pumpkin/pumpkin From 2194f149810da2460a015e7a5123620b26874c5a Mon Sep 17 00:00:00 2001 From: Alexander Medvedev <71594357+Snowiiii@users.noreply.github.com> Date: Mon, 7 Oct 2024 23:00:25 +0100 Subject: [PATCH 51/65] Improve RCON Remove all unwraps when handling packets. Also improve split Packets into Client & Server bound removing ugly match statement where we are reading Clientboud Packets (which is impossible). --- Cargo.lock | 391 ++++++++++++++++-------------------- pumpkin-config/src/rcon.rs | 7 + pumpkin-protocol/Cargo.toml | 2 +- pumpkin/src/rcon/mod.rs | 50 +++-- pumpkin/src/rcon/packet.rs | 127 ++++++------ 5 files changed, 277 insertions(+), 300 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 611057e57..6ddf14516 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13,19 +13,13 @@ dependencies = [ [[package]] name = "addr2line" -version = "0.22.0" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ - "gimli 0.29.0", + "gimli 0.31.1", ] -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - [[package]] name = "adler2" version = "2.0.0" @@ -81,9 +75,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.86" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" +checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" [[package]] name = "arbitrary" @@ -93,9 +87,9 @@ checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" [[package]] name = "async-trait" -version = "0.1.82" +version = "0.1.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a27b8a3a6e1a44fa4c8baf1f653e4172e81486d4941f2237e20dc2d0cf4ddff1" +checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ "proc-macro2", "quote", @@ -110,23 +104,23 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "backtrace" -version = "0.3.73" +version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" dependencies = [ - "addr2line 0.22.0", - "cc", + "addr2line 0.24.2", "cfg-if", "libc", - "miniz_oxide 0.7.4", + "miniz_oxide", "object", "rustc-demangle", + "windows-targets 0.52.6", ] [[package]] @@ -170,11 +164,11 @@ dependencies = [ [[package]] name = "block-buffer" -version = "0.11.0-rc.1" +version = "0.11.0-rc.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8969801e57d15e15bc4d7cdc5600dc15ca06a9a62b622bd4871c2d21d8aeb42d" +checksum = "939c0e62efa052fb0b2db2c0f7c479ad32e364c192c3aab605a7641de265a1a7" dependencies = [ - "crypto-common 0.2.0-rc.1", + "hybrid-array", ] [[package]] @@ -197,15 +191,15 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.7.1" +version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" +checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" [[package]] name = "cap-fs-ext" -version = "3.2.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb23061fc1c4ead4e45ca713080fe768e6234e959f5a5c399c39eb41aa34e56e" +checksum = "712695628f77a28acd7c9135b9f05f9c1563f8eb91b317f63876bac550032403" dependencies = [ "cap-primitives", "cap-std", @@ -215,9 +209,9 @@ dependencies = [ [[package]] name = "cap-primitives" -version = "3.2.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d00bd8d26c4270d950eaaa837387964a2089a1c3c349a690a1fa03221d29531" +checksum = "ff5bcbaf57897c8f14098cc9ad48a78052930a9948119eea01b80ca224070fa6" dependencies = [ "ambient-authority", "fs-set-times", @@ -232,9 +226,9 @@ dependencies = [ [[package]] name = "cap-rand" -version = "3.2.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbcb16a619d8b8211ed61f42bd290d2a1ac71277a69cf8417ec0996fa92f5211" +checksum = "e7c780812948b31f362c3bab82d23b902529c26705d0e094888bc7fdb9656908" dependencies = [ "ambient-authority", "rand", @@ -242,9 +236,9 @@ dependencies = [ [[package]] name = "cap-std" -version = "3.2.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19eb8e3d71996828751c1ed3908a439639752ac6bdc874e41469ef7fc15fbd7f" +checksum = "e6cf1a22e6eab501e025a9953532b1e95efb8a18d6364bf8a4a7547b30c49186" dependencies = [ "cap-primitives", "io-extras", @@ -254,9 +248,9 @@ dependencies = [ [[package]] name = "cap-time-ext" -version = "3.2.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61142dc51e25b7acc970ca578ce2c3695eac22bbba46c1073f5f583e78957725" +checksum = "1e1547a95cd071db92382c649260bcc6721879ef5d1f0f442af33bff75003dd7" dependencies = [ "ambient-authority", "cap-primitives", @@ -286,9 +280,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.1.16" +version = "1.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9d013ecb737093c0e86b151a7b837993cf9ec6c502946cfb44bedc392421e0b" +checksum = "2e80e3b6a3ab07840e1cae9b0666a63970dc28e8ed5ffbcdacbfc760c281bfc1" dependencies = [ "jobserver", "libc", @@ -390,9 +384,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51e852e6dc9a5bed1fae92dd2375037bf2b768725bf3be87811edee3249d09ad" +checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" dependencies = [ "libc", ] @@ -682,7 +676,7 @@ version = "0.11.0-pre.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf2e3d6615d99707295a9673e889bf363a04b2a466bd320c65a72536f7577379" dependencies = [ - "block-buffer 0.11.0-rc.1", + "block-buffer 0.11.0-rc.2", "crypto-common 0.2.0-rc.1", ] @@ -772,9 +766,9 @@ dependencies = [ [[package]] name = "extism" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74614574b03f716c9d1439d46dbd993dc9ed778ebd149bec40a2d32142297b78" +checksum = "c352d53d63c58d66868b65246ec491dab9c78b7425d2d221f0219d97765a3548" dependencies = [ "anyhow", "cbindgen", @@ -798,9 +792,9 @@ dependencies = [ [[package]] name = "extism-convert" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "573b553cad4f82bd5625825803744815f40de4524fac5a6a6d1f137ab60c878b" +checksum = "6b2042dab1fdb408d7504446cfb10079815da8b45e192a8954bf568c8a43e65d" dependencies = [ "anyhow", "base64 0.22.1", @@ -814,9 +808,9 @@ dependencies = [ [[package]] name = "extism-convert-macros" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dd2e4b0608d189ded6694a6c2c2da7522b54564ca143996b69cdda0ae56b7ac" +checksum = "8bb2f0038f2c3b14daa95b132f4fc1bad786a92946c7c9c06e120985a1fc4028" dependencies = [ "manyhow", "proc-macro-crate", @@ -827,9 +821,9 @@ dependencies = [ [[package]] name = "extism-manifest" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6be4814f095a74d0547175bd9d8747106a03b21cfbe675f73457c0677a97042b" +checksum = "675c0d7e15bb5e6e2a520ea26c4309c047c30b16de852f26373de1906677a58d" dependencies = [ "base64 0.22.1", "serde", @@ -872,21 +866,21 @@ dependencies = [ [[package]] name = "fdeflate" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f9bfee30e4dedf0ab8b422f03af778d9612b63f502710fc500a334ebe2de645" +checksum = "d8090f921a24b04994d9929e204f50b498a33ea6ba559ffaa05e04f7ee7fb5ab" dependencies = [ "simd-adler32", ] [[package]] name = "flate2" -version = "1.0.33" +version = "1.0.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "324a1be68054ef05ad64b861cc9eaf1d623d2d8cb25b4bf2cb9cdd902b4bf253" +checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0" dependencies = [ "crc32fast", - "miniz_oxide 0.8.0", + "miniz_oxide", ] [[package]] @@ -895,6 +889,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2" + [[package]] name = "form_urlencoded" version = "1.2.1" @@ -917,9 +917,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ "futures-channel", "futures-core", @@ -932,9 +932,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", "futures-sink", @@ -942,15 +942,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-executor" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" dependencies = [ "futures-core", "futures-task", @@ -959,15 +959,15 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-macro" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", @@ -976,21 +976,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-util" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-channel", "futures-core", @@ -1060,9 +1060,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.29.0" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "glob" @@ -1108,6 +1108,15 @@ dependencies = [ "serde", ] +[[package]] +name = "hashbrown" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" +dependencies = [ + "foldhash", +] + [[package]] name = "heck" version = "0.4.1" @@ -1165,15 +1174,15 @@ dependencies = [ [[package]] name = "httparse" -version = "1.9.4" +version = "1.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" [[package]] name = "hybrid-array" -version = "0.2.0-rc.9" +version = "0.2.0-rc.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d306b679262030ad8813a82d4915fc04efff97776e4db7f8eb5137039d56400" +checksum = "a5a41e5b0754cae5aaf7915f1df1147ba8d316fc6e019cfcc00fbaba96d5e030" dependencies = [ "typenum", ] @@ -1218,9 +1227,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.7" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cde7055719c54e36e95e8719f95883f22072a48ede39db7fc17a4e1d5281e9b9" +checksum = "41296eb09f183ac68eec06e03cdbea2e759633d4067b2f6552fc2e009bcad08b" dependencies = [ "bytes", "futures-channel", @@ -1231,16 +1240,15 @@ dependencies = [ "pin-project-lite", "socket2", "tokio", - "tower", "tower-service", "tracing", ] [[package]] name = "iana-time-zone" -version = "0.1.60" +version = "0.1.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -1277,12 +1285,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", - "hashbrown 0.14.5", + "hashbrown 0.15.0", "serde", ] @@ -1313,9 +1321,9 @@ checksum = "5a611371471e98973dbcab4e0ec66c31a10bc356eeb4d54a0e05eac8158fe38c" [[package]] name = "ipnet" -version = "2.9.0" +version = "2.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" [[package]] name = "itertools" @@ -1396,9 +1404,9 @@ checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" [[package]] name = "libc" -version = "0.2.158" +version = "0.2.159" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" +checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" [[package]] name = "libm" @@ -1522,15 +1530,6 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" -[[package]] -name = "miniz_oxide" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" -dependencies = [ - "adler", -] - [[package]] name = "miniz_oxide" version = "0.8.0" @@ -1672,21 +1671,21 @@ dependencies = [ [[package]] name = "object" -version = "0.36.4" +version = "0.36.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "084f1a5821ac4c651660a94a7153d27ac9d8a53736203f58b31945ded098070a" +checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" dependencies = [ "crc32fast", - "hashbrown 0.14.5", + "hashbrown 0.15.0", "indexmap", "memchr", ] [[package]] name = "once_cell" -version = "1.19.0" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "overload" @@ -1738,26 +1737,6 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" -[[package]] -name = "pin-project" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "pin-project-lite" version = "0.2.14" @@ -1793,9 +1772,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" [[package]] name = "png" @@ -1807,7 +1786,7 @@ dependencies = [ "crc32fast", "fdeflate", "flate2", - "miniz_oxide 0.8.0", + "miniz_oxide", ] [[package]] @@ -1859,18 +1838,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "b3e4daa0dcf6feba26f985457cdf104d4b4256fc5a09547140f3631bb076b19a" dependencies = [ "unicode-ident", ] [[package]] name = "prost" -version = "0.13.2" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b2ecbe40f08db5c006b5764a2645f7f3f141ce756412ac9e1dd6087e6d32995" +checksum = "7b0487d90e047de87f984913713b85c601c05609aad5b0df4b4573fbf69aa13f" dependencies = [ "bytes", "prost-derive", @@ -1878,9 +1857,9 @@ dependencies = [ [[package]] name = "prost-derive" -version = "0.13.2" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acf0c195eebb4af52c752bec4f52f645da98b6e92077a04110c7f349477ae5ac" +checksum = "e9552f850d5f0964a4e4d0bf306459ac29323ddfbae05e35a7c0d35cb0803cc5" dependencies = [ "anyhow", "itertools 0.13.0", @@ -2175,9 +2154,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.3" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" dependencies = [ "bitflags 2.6.0", ] @@ -2208,14 +2187,14 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.6" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.7", - "regex-syntax 0.8.4", + "regex-automata 0.4.8", + "regex-syntax 0.8.5", ] [[package]] @@ -2229,13 +2208,13 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.4", + "regex-syntax 0.8.5", ] [[package]] @@ -2246,15 +2225,15 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "reqwest" -version = "0.12.7" +version = "0.12.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8f4955649ef5c38cc7f9e8aa41761d48fb9677197daea9984dc54f56aad5e63" +checksum = "f713147fbe92361e52392c73b8c9e48c04c6625bce969ef54dc901e58e042a7b" dependencies = [ "base64 0.22.1", "bytes", @@ -2380,9 +2359,9 @@ checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" [[package]] name = "rustix" -version = "0.38.36" +version = "0.38.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f55e80d50763938498dd5ebb18647174e0c76dc38c5505294bb224624f30f36" +checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" dependencies = [ "bitflags 2.6.0", "errno", @@ -2395,9 +2374,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.12" +version = "0.23.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c58f8c84392efc0a126acce10fa59ff7b3d2ac06ab451a33f2741989b806b044" +checksum = "415d9944693cb90382053259f89fbb077ea730ad7273047ec63b19bc9b160ba8" dependencies = [ "log", "once_cell", @@ -2410,25 +2389,24 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "2.1.3" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" dependencies = [ - "base64 0.22.1", "rustls-pki-types", ] [[package]] name = "rustls-pki-types" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0" +checksum = "0e696e35370c65c9c541198af4543ccd580cf17fc25d8e05c5a242b202488c55" [[package]] name = "rustls-webpki" -version = "0.102.7" +version = "0.102.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84678086bd54edf2b415183ed7a94d0efb049f1b646a33e22a36f3794be6ae56" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" dependencies = [ "ring", "rustls-pki-types", @@ -2458,9 +2436,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.209" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" dependencies = [ "serde_derive", ] @@ -2476,9 +2454,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.209" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", @@ -2499,9 +2477,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.7" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" dependencies = [ "serde", ] @@ -2680,9 +2658,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.77" +version = "2.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" dependencies = [ "proc-macro2", "quote", @@ -2743,9 +2721,9 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "tempfile" -version = "3.12.0" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" +checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" dependencies = [ "cfg-if", "fastrand", @@ -2765,18 +2743,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.63" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.63" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" dependencies = [ "proc-macro2", "quote", @@ -2915,9 +2893,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.20" +version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ "indexmap", "serde", @@ -2926,27 +2904,6 @@ dependencies = [ "winnow", ] -[[package]] -name = "tower" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" -dependencies = [ - "futures-core", - "futures-util", - "pin-project", - "pin-project-lite", - "tokio", - "tower-layer", - "tower-service", -] - -[[package]] -name = "tower-layer" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" - [[package]] name = "tower-service" version = "0.3.3" @@ -3029,42 +2986,42 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "unicode-bidi" -version = "0.3.15" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" +checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] name = "unicode-normalization" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" dependencies = [ "tinyvec", ] [[package]] name = "unicode-segmentation" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" [[package]] name = "unicode-width" -version = "0.1.13" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] name = "unicode-xid" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "229730647fbc343e3a80e463c1db7f78f3855d3f3739bee0dda773c9a037c90a" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "untrusted" @@ -3241,9 +3198,9 @@ dependencies = [ [[package]] name = "wasm-encoder" -version = "0.216.0" +version = "0.218.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04c23aebea22c8a75833ae08ed31ccc020835b12a41999e58c31464271b94a88" +checksum = "22b896fa8ceb71091ace9bcb81e853f54043183a1c9667cf93422c40252ffa0a" dependencies = [ "leb128", ] @@ -3540,24 +3497,24 @@ dependencies = [ [[package]] name = "wast" -version = "216.0.0" +version = "218.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7eb1f2eecd913fdde0dc6c3439d0f24530a98ac6db6cb3d14d92a5328554a08" +checksum = "8a53cd1f0fa505df97557e36a58bddb8296e2fcdcd089529545ebfdb18a1b9d7" dependencies = [ "bumpalo", "leb128", "memchr", "unicode-width", - "wasm-encoder 0.216.0", + "wasm-encoder 0.218.0", ] [[package]] name = "wat" -version = "1.216.0" +version = "1.218.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac0409090fb5154f95fb5ba3235675fd9e579e731524d63b6a2f653e1280c82a" +checksum = "4f87f8e14e776762e07927c27c2054d2cf678aab9aae2d431a79b3e31e4dd391" dependencies = [ - "wast 216.0.0", + "wast 218.0.0", ] [[package]] @@ -3572,9 +3529,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.26.5" +version = "0.26.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bd24728e5af82c6c4ec1b66ac4844bdf8156257fccda846ec58b42cd0cdbe6a" +checksum = "841c67bff177718f1d4dfefde8d8f0e78f9b6589319ba88312f567fc5841a958" dependencies = [ "rustls-pki-types", ] @@ -3859,9 +3816,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.6.18" +version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" dependencies = [ "memchr", ] diff --git a/pumpkin-config/src/rcon.rs b/pumpkin-config/src/rcon.rs index af5aa6ac0..f15fa655e 100644 --- a/pumpkin-config/src/rcon.rs +++ b/pumpkin-config/src/rcon.rs @@ -4,9 +4,15 @@ use serde::{Deserialize, Serialize}; #[derive(Deserialize, Serialize, Clone)] pub struct RCONConfig { + /// Is RCON Enabled? pub enabled: bool, + /// The network address and port where the RCON server will listen for connections. pub address: SocketAddr, + /// The password required for RCON authentication. pub password: String, + /// The maximum number of concurrent RCON connections allowed. + /// If 0 there is no limit + pub max_connections: u32, } impl Default for RCONConfig { @@ -15,6 +21,7 @@ impl Default for RCONConfig { enabled: false, address: SocketAddr::new(Ipv4Addr::new(0, 0, 0, 0).into(), 25575), password: "".to_string(), + max_connections: 0, } } } diff --git a/pumpkin-protocol/Cargo.toml b/pumpkin-protocol/Cargo.toml index 956c7021c..61d69ffe0 100644 --- a/pumpkin-protocol/Cargo.toml +++ b/pumpkin-protocol/Cargo.toml @@ -15,7 +15,7 @@ uuid.workspace = true serde.workspace = true -flate2 = "1.0.33" +flate2 = "1.0" thiserror = "1.0" log.workspace = true diff --git a/pumpkin/src/rcon/mod.rs b/pumpkin/src/rcon/mod.rs index af5944e03..3aad9732b 100644 --- a/pumpkin/src/rcon/mod.rs +++ b/pumpkin/src/rcon/mod.rs @@ -1,6 +1,6 @@ use std::{ collections::HashMap, - io::{self, Read}, + io::{self, Read, Write}, sync::Arc, }; @@ -8,7 +8,7 @@ use mio::{ net::{TcpListener, TcpStream}, Events, Interest, Poll, Token, }; -use packet::{Packet, PacketError, PacketType}; +use packet::{ClientboundPacket, Packet, PacketError, ServerboundPacket}; use pumpkin_config::RCONConfig; use thiserror::Error; @@ -72,6 +72,12 @@ impl RCONServer { } }; log::info!("Accepted connection from: {}", address); + if config.max_connections != 0 + && connections.len() >= config.max_connections as usize + { + log::warn!("Max RCON connections reached"); + break; + } let token = Self::next(&mut unique_token); poll.registry() @@ -156,27 +162,21 @@ impl RCONClient { }; match packet.get_type() { - PacketType::Auth => { + ServerboundPacket::Auth => { let body = packet.get_body(); if !body.is_empty() && packet.get_body() == password { - self.send(&mut Packet::new( - packet.get_id(), - PacketType::AuthResponse, - "".into(), - )) - .await - .unwrap(); + self.send(ClientboundPacket::AuthResponse, packet.get_id(), "".into()) + .await?; log::info!("RCON Client logged in successfully"); self.logged_in = true; } else { log::warn!("RCON Client has tried wrong password"); - self.send(&mut Packet::new(-1, PacketType::AuthResponse, "".into())) - .await - .unwrap(); - return Err(PacketError::WrongPassword); + self.send(ClientboundPacket::AuthResponse, -1, "".into()) + .await?; + self.closed = true; } } - PacketType::ExecCommand => { + ServerboundPacket::ExecCommand => { if self.logged_in { let mut output = Vec::new(); let dispatcher = server.command_dispatcher.clone(); @@ -186,14 +186,11 @@ impl RCONClient { packet.get_body(), ); for line in output { - self.send(&mut Packet::new(packet.get_id(), PacketType::Output, line)) - .await - .unwrap(); + self.send(ClientboundPacket::Output, packet.get_id(), line) + .await?; } } } - PacketType::Output => todo!(), - PacketType::AuthResponse => unreachable!(), } } } @@ -208,8 +205,17 @@ impl RCONClient { Ok(false) } - async fn send(&mut self, packet: &mut Packet) -> io::Result<()> { - packet.send_packet(&mut self.connection).await + async fn send( + &mut self, + packet: ClientboundPacket, + id: i32, + body: String, + ) -> Result<(), PacketError> { + let buf = packet.write_buf(id, body); + self.connection + .write(&buf) + .map_err(PacketError::FailedSend)?; + Ok(()) } async fn receive_packet(&mut self) -> Result, PacketError> { diff --git a/pumpkin/src/rcon/packet.rs b/pumpkin/src/rcon/packet.rs index 7ed2a91a9..7c3edac83 100644 --- a/pumpkin/src/rcon/packet.rs +++ b/pumpkin/src/rcon/packet.rs @@ -1,91 +1,98 @@ -use std::io::{self, BufRead, Cursor, Write}; +use std::{ + io::{BufRead, Cursor}, + string::FromUtf8Error, +}; use bytes::{BufMut, BytesMut}; -use mio::net::TcpStream; use thiserror::Error; use tokio::io::AsyncReadExt; +/// Client -> Server #[derive(Debug, Clone, Copy, PartialEq)] -pub enum PacketType { - Auth, - AuthResponse, - ExecCommand, - Output, +#[repr(i32)] +pub enum ServerboundPacket { + /// Typically, the first packet sent by the client, which is used to authenticate the connection with the server. + Auth = 2, + /// This packet type represents a command issued to the server by a client. This can be a ConCommand such as kill or weather clear. + /// The response will vary depending on the command issued. + ExecCommand = 3, +} + +impl ServerboundPacket { + pub fn from_i32(n: i32) -> Self { + match n { + 3 => Self::Auth, + 2 => Self::ExecCommand, + _ => Self::Auth, + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq)] +#[repr(i32)] +/// Server -> Client +pub enum ClientboundPacket { + /// This packet is a notification of the connection's current auth status. When the server receives an auth request, it will respond with an empty SERVERDATA_RESPONSE_VALUE, + /// followed immediately by a SERVERDATA_AUTH_RESPONSE indicating whether authentication succeeded or failed. Note that the status code is returned in the packet id field, so when pairing the response with the original auth request, you may need to look at the packet id of the preceeding SERVERDATA_RESPONSE_VALUE. + AuthResponse = 2, + /// A SERVERDATA_RESPONSE packet is the response to a SERVERDATA_EXECCOMMAND request. + Output = 0, +} + +impl ClientboundPacket { + pub fn write_buf(&self, id: i32, body: String) -> BytesMut { + // let len = outgoing.len() as u64; + let mut buf = BytesMut::new(); + // 10 is for 4 bytes ty, 4 bytes id, and 2 terminating nul bytes. + buf.put_i32_le(10 + body.len() as i32); + buf.put_i32_le(id); + buf.put_i32_le(*self as i32); + let bytes = body.as_bytes(); + buf.put_slice(bytes); + buf.put_u8(0); + buf.put_u8(0); + buf + } } #[derive(Error, Debug)] pub enum PacketError { #[error("invalid length")] InvalidLength, - #[error("expected terminating NUL byte")] - NoNullTermination, - #[error("wrong password")] - WrongPassword, -} - -impl PacketType { - fn to_i32(self) -> i32 { - match self { - PacketType::Auth => 3, - PacketType::AuthResponse => 2, - PacketType::ExecCommand => 2, - PacketType::Output => 0, - } - } - - pub fn from_i32(n: i32) -> PacketType { - match n { - 3 => PacketType::Auth, - 2 => PacketType::ExecCommand, - _ => PacketType::Output, - } - } + #[error("failed to read packet")] + FailedRead(std::io::Error), + #[error("failed to send packet")] + FailedSend(std::io::Error), + #[error("invalid Packet String body")] + InvalidBody(FromUtf8Error), } #[derive(Debug)] +/// Serverbound Packet pub struct Packet { id: i32, - ptype: PacketType, + ptype: ServerboundPacket, body: String, } impl Packet { - pub fn new(id: i32, ptype: PacketType, body: String) -> Packet { - Packet { id, ptype, body } - } - - pub async fn send_packet(&mut self, connection: &mut TcpStream) -> io::Result<()> { - // let len = outgoing.len() as u64; - let mut buf = BytesMut::new(); - // 10 is for 4 bytes ty, 4 bytes id, and 2 terminating nul bytes. - buf.put_i32_le(10 + self.get_body().len() as i32); - buf.put_i32_le(self.id); - buf.put_i32_le(self.get_type().to_i32()); - let bytes = self.get_body().as_bytes(); - buf.put_slice(bytes); - buf.put_u8(0); - buf.put_u8(0); - let _ = connection.write(&buf).unwrap(); - Ok(()) - } - pub async fn deserialize(incoming: &mut Vec) -> Result, PacketError> { if incoming.len() < 4 { return Ok(None); } let mut buf = Cursor::new(&incoming); - let len = buf.read_i32_le().await.unwrap() + 4; + let len = buf.read_i32_le().await.map_err(PacketError::FailedRead)? + 4; if !(0..=1460).contains(&len) { return Err(PacketError::InvalidLength); } - let id = buf.read_i32_le().await.unwrap(); - let ty = buf.read_i32_le().await.unwrap(); + let id = buf.read_i32_le().await.map_err(PacketError::FailedRead)?; + let ty = buf.read_i32_le().await.map_err(PacketError::FailedRead)?; let mut payload = vec![]; - let _ = buf.read_until(b'\0', &mut payload).unwrap(); + let _ = buf + .read_until(b'\0', &mut payload) + .map_err(PacketError::FailedRead)?; payload.pop(); - if buf.read_u8().await.unwrap() != 0 { - return Err(PacketError::NoNullTermination); - } + buf.read_u8().await.map_err(PacketError::FailedRead)?; if buf.position() != len as u64 { return Err(PacketError::InvalidLength); } @@ -93,8 +100,8 @@ impl Packet { let packet = Packet { id, - ptype: PacketType::from_i32(ty), - body: String::from_utf8(payload).unwrap(), + ptype: ServerboundPacket::from_i32(ty), + body: String::from_utf8(payload).map_err(PacketError::InvalidBody)?, }; Ok(Some(packet)) @@ -103,7 +110,7 @@ impl Packet { &self.body } - pub fn get_type(&self) -> PacketType { + pub fn get_type(&self) -> ServerboundPacket { self.ptype } From ac4223f89b8672eec73eddaea6759c78ea4bbab7 Mon Sep 17 00:00:00 2001 From: paul Date: Tue, 8 Oct 2024 19:24:33 +0200 Subject: [PATCH 52/65] expose docker container port --- Dockerfile | 1 + README.md | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 20e2faaa5..f1fb3ddd9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,4 +10,5 @@ FROM alpine:3.20 WORKDIR /pumpkin RUN apk add --no-cache libgcc COPY --from=builder /pumpkin/target/release/pumpkin /pumpkin/pumpkin +EXPOSE 25565 ENTRYPOINT ["/pumpkin/pumpkin"] diff --git a/README.md b/README.md index e45ccd31e..e6d4be5d5 100644 --- a/README.md +++ b/README.md @@ -111,7 +111,7 @@ docker build . -t pumpkin To run it use the following command: ```shell -docker run --rm -v "./world:/pumpkin/world" pumpkin +docker run --rm -p 25565:25565 -v "./world:/pumpkin/world" pumpkin ``` ## Contributions From 4967e9aa374b89e3f09f391ea621714aa64029a7 Mon Sep 17 00:00:00 2001 From: Alexander Medvedev <71594357+Snowiiii@users.noreply.github.com> Date: Tue, 8 Oct 2024 20:02:56 +0100 Subject: [PATCH 53/65] New clipps lints --- pumpkin/src/client/container.rs | 25 +++++++++++-------------- pumpkin/src/client/mod.rs | 2 +- pumpkin/src/client/player_packet.rs | 12 ++++++------ pumpkin/src/commands/cmd_echest.rs | 2 +- pumpkin/src/commands/cmd_gamemode.rs | 2 +- pumpkin/src/commands/cmd_help.rs | 2 +- pumpkin/src/commands/cmd_kill.rs | 2 +- pumpkin/src/commands/cmd_pumpkin.rs | 2 +- pumpkin/src/commands/cmd_stop.rs | 2 +- pumpkin/src/commands/mod.rs | 6 +++--- pumpkin/src/commands/tree.rs | 14 +++++++------- pumpkin/src/commands/tree_builder.rs | 4 ++-- pumpkin/src/entity/mod.rs | 2 +- pumpkin/src/entity/player.rs | 12 ++++++------ pumpkin/src/main.rs | 13 +++++++++++++ pumpkin/src/rcon/mod.rs | 4 ++-- pumpkin/src/rcon/packet.rs | 14 +++++++------- pumpkin/src/server/connection_cache.rs | 6 +++--- pumpkin/src/server/key_store.rs | 2 +- 19 files changed, 69 insertions(+), 59 deletions(-) diff --git a/pumpkin/src/client/container.rs b/pumpkin/src/client/container.rs index dc15a21cd..bb3a01d1a 100644 --- a/pumpkin/src/client/container.rs +++ b/pumpkin/src/client/container.rs @@ -40,7 +40,7 @@ impl Player { let window_title = container .as_ref() .map(|container| container.window_name()) - .unwrap_or(inventory.window_name()); + .unwrap_or_else(|| inventory.window_name()); let title = TextComponent::text(window_title); self.client.send_packet(&COpenScreen::new( @@ -64,13 +64,12 @@ impl Player { .map(Slot::from) .collect_vec(); - let carried_item = { - if let Some(item) = self.carried_item.load().as_ref() { - item.into() - } else { - Slot::empty() - } - }; + let carried_item = self + .carried_item + .load() + .as_ref() + .map_or_else(Slot::empty, |item| item.into()); + // Gets the previous value let i = inventory .state_id @@ -378,7 +377,7 @@ impl Player { } } - async fn get_current_players_in_container(&self, server: &Server) -> Vec> { + async fn get_current_players_in_container(&self, server: &Server) -> Vec> { let player_ids = { let open_containers = server.open_containers.read(); open_containers @@ -453,10 +452,8 @@ impl Player { } pub fn get_open_container(&self, server: &Server) -> Option>>> { - if let Some(id) = self.open_container.load() { - server.try_get_container(self.entity_id(), id) - } else { - None - } + self.open_container + .load() + .map_or_else(|| None, |id| server.try_get_container(self.entity_id(), id)) } } diff --git a/pumpkin/src/client/mod.rs b/pumpkin/src/client/mod.rs index ca744d1b5..d88889070 100644 --- a/pumpkin/src/client/mod.rs +++ b/pumpkin/src/client/mod.rs @@ -371,7 +371,7 @@ impl Client { match self.connection_state.load() { ConnectionState::Login => { self.try_send_packet(&CLoginDisconnect::new( - &serde_json::to_string_pretty(&reason).unwrap_or("".into()), + &serde_json::to_string_pretty(&reason).unwrap_or_else(|_| "".into()), )) .unwrap_or_else(|_| self.close()); } diff --git a/pumpkin/src/client/player_packet.rs b/pumpkin/src/client/player_packet.rs index 5e2e48da7..a9a881c0c 100644 --- a/pumpkin/src/client/player_packet.rs +++ b/pumpkin/src/client/player_packet.rs @@ -110,9 +110,9 @@ impl Player { &[self.client.token], &CUpdateEntityPos::new( entity_id.into(), - (x * 4096.0 - lastx * 4096.0) as i16, - (y * 4096.0 - lasty * 4096.0) as i16, - (z * 4096.0 - lastz * 4096.0) as i16, + x.mul_add(4096.0, -(lastx * 4096.0)) as i16, + y.mul_add(4096.0, -(lasty * 4096.0)) as i16, + z.mul_add(4096.0, -(lastz * 4096.0)) as i16, position.ground, ), ); @@ -180,9 +180,9 @@ impl Player { &[self.client.token], &CUpdateEntityPosRot::new( entity_id.into(), - (x * 4096.0 - lastx * 4096.0) as i16, - (y * 4096.0 - lasty * 4096.0) as i16, - (z * 4096.0 - lastz * 4096.0) as i16, + x.mul_add(4096.0, -(lastx * 4096.0)) as i16, + y.mul_add(4096.0, -(lasty * 4096.0)) as i16, + z.mul_add(4096.0, -(lastz * 4096.0)) as i16, yaw as u8, pitch as u8, position_rotation.ground, diff --git a/pumpkin/src/commands/cmd_echest.rs b/pumpkin/src/commands/cmd_echest.rs index 690fff41c..e793ccb8a 100644 --- a/pumpkin/src/commands/cmd_echest.rs +++ b/pumpkin/src/commands/cmd_echest.rs @@ -7,7 +7,7 @@ const NAMES: [&str; 2] = ["echest", "enderchest"]; const DESCRIPTION: &str = "Show your personal enderchest (this command is used for testing container behaviour)"; -pub(crate) fn init_command_tree<'a>() -> CommandTree<'a> { +pub fn init_command_tree<'a>() -> CommandTree<'a> { CommandTree::new(NAMES, DESCRIPTION).execute(&|sender, server, _| { if let Some(player) = sender.as_mut_player() { let entity_id = player.entity_id(); diff --git a/pumpkin/src/commands/cmd_gamemode.rs b/pumpkin/src/commands/cmd_gamemode.rs index c950df438..484d85c0e 100644 --- a/pumpkin/src/commands/cmd_gamemode.rs +++ b/pumpkin/src/commands/cmd_gamemode.rs @@ -56,7 +56,7 @@ pub fn parse_arg_gamemode(consumed_args: &ConsumedArgs) -> Result() -> CommandTree<'a> { +pub fn init_command_tree<'a>() -> CommandTree<'a> { CommandTree::new(NAMES, DESCRIPTION).with_child( require(&|sender| sender.permission_lvl() >= 2).with_child( argument(ARG_GAMEMODE, consume_arg_gamemode) diff --git a/pumpkin/src/commands/cmd_help.rs b/pumpkin/src/commands/cmd_help.rs index 59788b221..a955f4c8c 100644 --- a/pumpkin/src/commands/cmd_help.rs +++ b/pumpkin/src/commands/cmd_help.rs @@ -32,7 +32,7 @@ fn parse_arg_command<'a>( .map_err(|_| InvalidConsumptionError(Some(command_name.into()))) } -pub(crate) fn init_command_tree<'a>() -> CommandTree<'a> { +pub fn init_command_tree<'a>() -> CommandTree<'a> { CommandTree::new(NAMES, DESCRIPTION) .with_child( argument(ARG_COMMAND, consume_arg_command).execute(&|sender, server, args| { diff --git a/pumpkin/src/commands/cmd_kill.rs b/pumpkin/src/commands/cmd_kill.rs index d3b8135e6..3b0bdf3fa 100644 --- a/pumpkin/src/commands/cmd_kill.rs +++ b/pumpkin/src/commands/cmd_kill.rs @@ -14,7 +14,7 @@ pub fn consume_arg_target(_src: &CommandSender, args: &mut RawArgs) -> Option() -> CommandTree<'a> { +pub fn init_command_tree<'a>() -> CommandTree<'a> { CommandTree::new(NAMES, DESCRIPTION).with_child( argument(ARG_TARGET, consume_arg_target).execute(&|sender, server, args| { let target = parse_arg_player(sender, server, ARG_TARGET, args)?; diff --git a/pumpkin/src/commands/cmd_pumpkin.rs b/pumpkin/src/commands/cmd_pumpkin.rs index ad0b3561e..0d8d7565f 100644 --- a/pumpkin/src/commands/cmd_pumpkin.rs +++ b/pumpkin/src/commands/cmd_pumpkin.rs @@ -8,7 +8,7 @@ const NAMES: [&str; 1] = ["pumpkin"]; const DESCRIPTION: &str = "Display information about Pumpkin."; -pub(crate) fn init_command_tree<'a>() -> CommandTree<'a> { +pub fn init_command_tree<'a>() -> CommandTree<'a> { CommandTree::new(NAMES, DESCRIPTION).execute(&|sender, _, _| { let version = env!("CARGO_PKG_VERSION"); let description = env!("CARGO_PKG_DESCRIPTION"); diff --git a/pumpkin/src/commands/cmd_stop.rs b/pumpkin/src/commands/cmd_stop.rs index 5e12ccb48..2d90ae707 100644 --- a/pumpkin/src/commands/cmd_stop.rs +++ b/pumpkin/src/commands/cmd_stop.rs @@ -8,7 +8,7 @@ const NAMES: [&str; 1] = ["stop"]; const DESCRIPTION: &str = "Stop the server."; -pub(crate) fn init_command_tree<'a>() -> CommandTree<'a> { +pub fn init_command_tree<'a>() -> CommandTree<'a> { CommandTree::new(NAMES, DESCRIPTION).with_child( require(&|sender| sender.permission_lvl() >= 4).execute(&|sender, _, _args| { sender diff --git a/pumpkin/src/commands/mod.rs b/pumpkin/src/commands/mod.rs index 912b03183..322b96202 100644 --- a/pumpkin/src/commands/mod.rs +++ b/pumpkin/src/commands/mod.rs @@ -35,7 +35,7 @@ impl<'a> CommandSender<'a> { } } - pub fn is_player(&self) -> bool { + pub const fn is_player(&self) -> bool { match self { CommandSender::Console => false, CommandSender::Player(_) => true, @@ -43,7 +43,7 @@ impl<'a> CommandSender<'a> { } } - pub fn is_console(&self) -> bool { + pub const fn is_console(&self) -> bool { match self { CommandSender::Console => true, CommandSender::Player(_) => false, @@ -59,7 +59,7 @@ impl<'a> CommandSender<'a> { } /// todo: implement - pub fn permission_lvl(&self) -> i32 { + pub const fn permission_lvl(&self) -> i32 { match self { CommandSender::Rcon(_) => 4, CommandSender::Console => 4, diff --git a/pumpkin/src/commands/tree.rs b/pumpkin/src/commands/tree.rs index 6f1d8517f..ebbe3d801 100644 --- a/pumpkin/src/commands/tree.rs +++ b/pumpkin/src/commands/tree.rs @@ -3,20 +3,20 @@ use crate::commands::CommandSender; use std::collections::{HashMap, VecDeque}; /// see [crate::commands::tree_builder::argument] -pub(crate) type RawArgs<'a> = Vec<&'a str>; +pub type RawArgs<'a> = Vec<&'a str>; /// see [crate::commands::tree_builder::argument] and [CommandTree::execute]/[crate::commands::tree_builder::NonLeafNodeBuilder::execute] -pub(crate) type ConsumedArgs<'a> = HashMap<&'a str, String>; +pub type ConsumedArgs<'a> = HashMap<&'a str, String>; /// see [crate::commands::tree_builder::argument] -pub(crate) type ArgumentConsumer<'a> = fn(&CommandSender, &mut RawArgs) -> Option; +pub type ArgumentConsumer<'a> = fn(&CommandSender, &mut RawArgs) -> Option; -pub(crate) struct Node<'a> { +pub struct Node<'a> { pub(crate) children: Vec, pub(crate) node_type: NodeType<'a>, } -pub(crate) enum NodeType<'a> { +pub enum NodeType<'a> { ExecuteLeaf { run: &'a RunFunctionType, }, @@ -32,12 +32,12 @@ pub(crate) enum NodeType<'a> { }, } -pub(crate) enum Command<'a> { +pub enum Command<'a> { Tree(CommandTree<'a>), Alias(&'a str), } -pub(crate) struct CommandTree<'a> { +pub struct CommandTree<'a> { pub(crate) nodes: Vec>, pub(crate) children: Vec, pub(crate) names: Vec<&'a str>, diff --git a/pumpkin/src/commands/tree_builder.rs b/pumpkin/src/commands/tree_builder.rs index 00a5449f1..ecb79880e 100644 --- a/pumpkin/src/commands/tree_builder.rs +++ b/pumpkin/src/commands/tree_builder.rs @@ -101,7 +101,7 @@ impl<'a> NodeBuilder<'a> for NonLeafNodeBuilder<'a> { impl<'a> NonLeafNodeBuilder<'a> { /// Add a child [Node] to this one. - pub fn with_child(mut self, child: NonLeafNodeBuilder<'a>) -> Self { + pub fn with_child(mut self, child: Self) -> Self { self.child_nodes.push(child); self } @@ -124,7 +124,7 @@ impl<'a> NonLeafNodeBuilder<'a> { /// Matches a sting literal. #[expect(dead_code)] // todo: remove (so far no commands requiring this are implemented) -pub fn literal(string: &str) -> NonLeafNodeBuilder { +pub const fn literal(string: &str) -> NonLeafNodeBuilder { NonLeafNodeBuilder { node_type: NodeType::Literal { string }, child_nodes: Vec::new(), diff --git a/pumpkin/src/entity/mod.rs b/pumpkin/src/entity/mod.rs index 68c323a6b..71204dfa7 100644 --- a/pumpkin/src/entity/mod.rs +++ b/pumpkin/src/entity/mod.rs @@ -146,7 +146,7 @@ impl Entity { // This has some vanilla magic let mut x = x; let mut z = z; - while x * x + z * z < 1.0E-5 { + while x.mul_add(x, z * z) < 1.0E-5 { x = (rand::random::() - rand::random::()) * 0.01; z = (rand::random::() - rand::random::()) * 0.01; } diff --git a/pumpkin/src/entity/player.rs b/pumpkin/src/entity/player.rs index dd9a900fe..aee382fe2 100644 --- a/pumpkin/src/entity/player.rs +++ b/pumpkin/src/entity/player.rs @@ -96,9 +96,8 @@ impl Player { entity_id: EntityId, gamemode: GameMode, ) -> Self { - let gameprofile = match client.gameprofile.lock().clone() { - Some(profile) => profile, - None => { + let gameprofile = client.gameprofile.lock().clone().map_or_else( + || { log::error!("No gameprofile?. Impossible"); GameProfile { id: uuid::Uuid::new_v4(), @@ -106,8 +105,9 @@ impl Player { properties: vec![], profile_actions: None, } - } - }; + }, + |profile| profile, + ); let config = client.config.lock().clone().unwrap_or_default(); Self { entity: Entity::new(entity_id, world, EntityType::Player, 1.62), @@ -135,7 +135,7 @@ impl Player { self.entity.world.remove_player(self); } - pub fn entity_id(&self) -> EntityId { + pub const fn entity_id(&self) -> EntityId { self.entity.entity_id } diff --git a/pumpkin/src/main.rs b/pumpkin/src/main.rs index bb709c22c..ab21ef1b3 100644 --- a/pumpkin/src/main.rs +++ b/pumpkin/src/main.rs @@ -1,3 +1,16 @@ +#![deny(clippy::all)] +// #![warn(clippy::pedantic)] +// #![warn(clippy::restriction)] +#![warn(clippy::nursery)] +#![warn(clippy::cargo)] +// expect +#![expect(clippy::cargo_common_metadata)] +#![expect(clippy::multiple_crate_versions)] +#![expect(clippy::while_float)] +#![expect(clippy::significant_drop_in_scrutinee)] +#![expect(clippy::significant_drop_tightening)] +#![expect(clippy::future_not_send)] +#![expect(clippy::single_call_fn)] #![expect(clippy::await_holding_lock)] #[cfg(target_os = "wasi")] diff --git a/pumpkin/src/rcon/mod.rs b/pumpkin/src/rcon/mod.rs index 3aad9732b..252210f8c 100644 --- a/pumpkin/src/rcon/mod.rs +++ b/pumpkin/src/rcon/mod.rs @@ -28,7 +28,7 @@ pub enum RCONError { const SERVER: Token = Token(0); -pub struct RCONServer {} +pub struct RCONServer; impl RCONServer { pub async fn new(config: &RCONConfig, server: Arc) -> Result { @@ -122,7 +122,7 @@ pub struct RCONClient { } impl RCONClient { - pub fn new(connection: TcpStream) -> Self { + pub const fn new(connection: TcpStream) -> Self { Self { connection, logged_in: false, diff --git a/pumpkin/src/rcon/packet.rs b/pumpkin/src/rcon/packet.rs index 7c3edac83..f4cdc7925 100644 --- a/pumpkin/src/rcon/packet.rs +++ b/pumpkin/src/rcon/packet.rs @@ -8,7 +8,7 @@ use thiserror::Error; use tokio::io::AsyncReadExt; /// Client -> Server -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[repr(i32)] pub enum ServerboundPacket { /// Typically, the first packet sent by the client, which is used to authenticate the connection with the server. @@ -19,7 +19,7 @@ pub enum ServerboundPacket { } impl ServerboundPacket { - pub fn from_i32(n: i32) -> Self { + pub const fn from_i32(n: i32) -> Self { match n { 3 => Self::Auth, 2 => Self::ExecCommand, @@ -28,7 +28,7 @@ impl ServerboundPacket { } } -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[repr(i32)] /// Server -> Client pub enum ClientboundPacket { @@ -76,7 +76,7 @@ pub struct Packet { } impl Packet { - pub async fn deserialize(incoming: &mut Vec) -> Result, PacketError> { + pub async fn deserialize(incoming: &mut Vec) -> Result, PacketError> { if incoming.len() < 4 { return Ok(None); } @@ -98,7 +98,7 @@ impl Packet { } incoming.drain(0..len as usize); - let packet = Packet { + let packet = Self { id, ptype: ServerboundPacket::from_i32(ty), body: String::from_utf8(payload).map_err(PacketError::InvalidBody)?, @@ -110,11 +110,11 @@ impl Packet { &self.body } - pub fn get_type(&self) -> ServerboundPacket { + pub const fn get_type(&self) -> ServerboundPacket { self.ptype } - pub fn get_id(&self) -> i32 { + pub const fn get_id(&self) -> i32 { self.id } } diff --git a/pumpkin/src/server/connection_cache.rs b/pumpkin/src/server/connection_cache.rs index 76aa3b076..c87091fc8 100644 --- a/pumpkin/src/server/connection_cache.rs +++ b/pumpkin/src/server/connection_cache.rs @@ -22,9 +22,9 @@ pub struct CachedBranding { } impl CachedBranding { - pub fn new() -> CachedBranding { + pub fn new() -> Self { let cached_server_brand = Self::build_brand(); - CachedBranding { + Self { cached_server_brand, } } @@ -46,7 +46,7 @@ impl CachedStatus { let status_response_json = serde_json::to_string(&status_response) .expect("Failed to parse Status response into JSON"); - CachedStatus { + Self { _status_response: status_response, status_response_json, } diff --git a/pumpkin/src/server/key_store.rs b/pumpkin/src/server/key_store.rs index 54393dccf..12bfb829c 100644 --- a/pumpkin/src/server/key_store.rs +++ b/pumpkin/src/server/key_store.rs @@ -22,7 +22,7 @@ impl KeyStore { &private_key.e().to_bytes_be(), ) .into_boxed_slice(); - KeyStore { + Self { _public_key: public_key, private_key, public_key_der, From b5452245d878210bc6652035a9b61d3f7c4ddb8d Mon Sep 17 00:00:00 2001 From: Snowiiii Date: Fri, 11 Oct 2024 11:43:58 +0200 Subject: [PATCH 54/65] Convert packets matches into their functions --- pumpkin/src/client/mod.rs | 217 ++++++++++++++++++++++---------------- 1 file changed, 127 insertions(+), 90 deletions(-) diff --git a/pumpkin/src/client/mod.rs b/pumpkin/src/client/mod.rs index d88889070..252c5b9e2 100644 --- a/pumpkin/src/client/mod.rs +++ b/pumpkin/src/client/mod.rs @@ -220,99 +220,19 @@ impl Client { server: &Arc, packet: &mut RawPacket, ) -> Result<(), DeserializerError> { - // TODO: handle each packet's Error instead of calling .unwrap() - let bytebuf = &mut packet.bytebuf; match self.connection_state.load() { - pumpkin_protocol::ConnectionState::HandShake => match packet.id.0 { - SHandShake::PACKET_ID => { - self.handle_handshake(server, SHandShake::read(bytebuf)?); - Ok(()) - } - _ => { - log::error!( - "Failed to handle packet id {} while in Handshake state", - packet.id.0 - ); - Ok(()) - } - }, - pumpkin_protocol::ConnectionState::Status => match packet.id.0 { - SStatusRequest::PACKET_ID => { - self.handle_status_request(server, SStatusRequest::read(bytebuf)?); - Ok(()) - } - SStatusPingRequest::PACKET_ID => { - self.handle_ping_request(server, SStatusPingRequest::read(bytebuf)?); - Ok(()) - } - _ => { - log::error!( - "Failed to handle packet id {} while in Status state", - packet.id.0 - ); - Ok(()) - } - }, + pumpkin_protocol::ConnectionState::HandShake => { + self.handle_handshake_packet(server, packet) + } + pumpkin_protocol::ConnectionState::Status => self.handle_status_packet(server, packet), // TODO: Check config if transfer is enabled pumpkin_protocol::ConnectionState::Login - | pumpkin_protocol::ConnectionState::Transfer => match packet.id.0 { - SLoginStart::PACKET_ID => { - self.handle_login_start(server, SLoginStart::read(bytebuf)?); - Ok(()) - } - SEncryptionResponse::PACKET_ID => { - self.handle_encryption_response(server, SEncryptionResponse::read(bytebuf)?) - .await; - Ok(()) - } - SLoginPluginResponse::PACKET_ID => { - self.handle_plugin_response(server, SLoginPluginResponse::read(bytebuf)?); - Ok(()) - } - SLoginAcknowledged::PACKET_ID => { - self.handle_login_acknowledged(server, SLoginAcknowledged::read(bytebuf)?); - Ok(()) - } - _ => { - log::error!( - "Failed to handle packet id {} while in Login state", - packet.id.0 - ); - Ok(()) - } - }, - pumpkin_protocol::ConnectionState::Config => match packet.id.0 { - SClientInformationConfig::PACKET_ID => { - self.handle_client_information_config( - server, - SClientInformationConfig::read(bytebuf)?, - ); - Ok(()) - } - SPluginMessage::PACKET_ID => { - self.handle_plugin_message(server, SPluginMessage::read(bytebuf)?); - Ok(()) - } - SAcknowledgeFinishConfig::PACKET_ID => { - self.handle_config_acknowledged( - server, - SAcknowledgeFinishConfig::read(bytebuf)?, - ) - .await; - Ok(()) - } - SKnownPacks::PACKET_ID => { - self.handle_known_packs(server, SKnownPacks::read(bytebuf)?); - Ok(()) - } - _ => { - log::error!( - "Failed to handle packet id {} while in Config state", - packet.id.0 - ); - Ok(()) - } - }, + | pumpkin_protocol::ConnectionState::Transfer => { + self.handle_login_packet(server, packet).await + } + pumpkin_protocol::ConnectionState::Config => { + self.handle_config_packet(server, packet).await + } _ => { log::error!("Invalid Connection state {:?}", self.connection_state); Ok(()) @@ -320,6 +240,123 @@ impl Client { } } + fn handle_handshake_packet( + &self, + server: &Arc, + packet: &mut RawPacket, + ) -> Result<(), DeserializerError> { + let bytebuf = &mut packet.bytebuf; + match packet.id.0 { + SHandShake::PACKET_ID => { + self.handle_handshake(server, SHandShake::read(bytebuf)?); + Ok(()) + } + _ => { + log::error!( + "Failed to handle packet id {} while in Handshake state", + packet.id.0 + ); + Ok(()) + } + } + } + + fn handle_status_packet( + &self, + server: &Arc, + packet: &mut RawPacket, + ) -> Result<(), DeserializerError> { + let bytebuf = &mut packet.bytebuf; + match packet.id.0 { + SStatusRequest::PACKET_ID => { + self.handle_status_request(server, SStatusRequest::read(bytebuf)?); + Ok(()) + } + SStatusPingRequest::PACKET_ID => { + self.handle_ping_request(server, SStatusPingRequest::read(bytebuf)?); + Ok(()) + } + _ => { + log::error!( + "Failed to handle packet id {} while in Status state", + packet.id.0 + ); + Ok(()) + } + } + } + + async fn handle_login_packet( + &self, + server: &Arc, + packet: &mut RawPacket, + ) -> Result<(), DeserializerError> { + let bytebuf = &mut packet.bytebuf; + match packet.id.0 { + SLoginStart::PACKET_ID => { + self.handle_login_start(server, SLoginStart::read(bytebuf)?); + Ok(()) + } + SEncryptionResponse::PACKET_ID => { + self.handle_encryption_response(server, SEncryptionResponse::read(bytebuf)?) + .await; + Ok(()) + } + SLoginPluginResponse::PACKET_ID => { + self.handle_plugin_response(server, SLoginPluginResponse::read(bytebuf)?); + Ok(()) + } + SLoginAcknowledged::PACKET_ID => { + self.handle_login_acknowledged(server, SLoginAcknowledged::read(bytebuf)?); + Ok(()) + } + _ => { + log::error!( + "Failed to handle packet id {} while in Login state", + packet.id.0 + ); + Ok(()) + } + } + } + + async fn handle_config_packet( + &self, + server: &Arc, + packet: &mut RawPacket, + ) -> Result<(), DeserializerError> { + let bytebuf = &mut packet.bytebuf; + match packet.id.0 { + SClientInformationConfig::PACKET_ID => { + self.handle_client_information_config( + server, + SClientInformationConfig::read(bytebuf)?, + ); + Ok(()) + } + SPluginMessage::PACKET_ID => { + self.handle_plugin_message(server, SPluginMessage::read(bytebuf)?); + Ok(()) + } + SAcknowledgeFinishConfig::PACKET_ID => { + self.handle_config_acknowledged(server, SAcknowledgeFinishConfig::read(bytebuf)?) + .await; + Ok(()) + } + SKnownPacks::PACKET_ID => { + self.handle_known_packs(server, SKnownPacks::read(bytebuf)?); + Ok(()) + } + _ => { + log::error!( + "Failed to handle packet id {} while in Config state", + packet.id.0 + ); + Ok(()) + } + } + } + /// Reads the connection until our buffer of len 4096 is full, then decode /// Close connection when an error occurs or when the Client closed the connection pub async fn poll(&self, event: &Event) { From 4076cda942b028edaf9ec999fd1ce0778aca86bb Mon Sep 17 00:00:00 2001 From: Snowiiii Date: Fri, 11 Oct 2024 12:00:18 +0200 Subject: [PATCH 55/65] Add Clientbound Set Health --- .../src/client/play/c_set_health.rs | 22 +++++++++++++++++++ pumpkin-protocol/src/client/play/mod.rs | 2 ++ pumpkin/src/entity/player.rs | 6 +++-- 3 files changed, 28 insertions(+), 2 deletions(-) create mode 100644 pumpkin-protocol/src/client/play/c_set_health.rs diff --git a/pumpkin-protocol/src/client/play/c_set_health.rs b/pumpkin-protocol/src/client/play/c_set_health.rs new file mode 100644 index 000000000..a598444f7 --- /dev/null +++ b/pumpkin-protocol/src/client/play/c_set_health.rs @@ -0,0 +1,22 @@ +use pumpkin_macros::packet; +use serde::Serialize; + +use crate::VarInt; + +#[derive(Serialize)] +#[packet(0x5D)] +pub struct CSetHealth { + health: f32, + food: VarInt, + food_saturation: f32, +} + +impl CSetHealth { + pub fn new(health: f32, food: VarInt, food_saturation: f32) -> Self { + Self { + health, + food, + food_saturation, + } + } +} diff --git a/pumpkin-protocol/src/client/play/mod.rs b/pumpkin-protocol/src/client/play/mod.rs index ff9dd2461..47838aed6 100644 --- a/pumpkin-protocol/src/client/play/mod.rs +++ b/pumpkin-protocol/src/client/play/mod.rs @@ -28,6 +28,7 @@ mod c_remove_entities; mod c_set_container_content; mod c_set_container_property; mod c_set_container_slot; +mod c_set_health; mod c_set_held_item; mod c_set_title; mod c_spawn_player; @@ -72,6 +73,7 @@ pub use c_remove_entities::*; pub use c_set_container_content::*; pub use c_set_container_property::*; pub use c_set_container_slot::*; +pub use c_set_health::*; pub use c_set_held_item::*; pub use c_set_title::*; pub use c_spawn_player::*; diff --git a/pumpkin/src/entity/player.rs b/pumpkin/src/entity/player.rs index aee382fe2..5850da0d8 100644 --- a/pumpkin/src/entity/player.rs +++ b/pumpkin/src/entity/player.rs @@ -17,8 +17,8 @@ use pumpkin_inventory::player::PlayerInventory; use pumpkin_protocol::{ bytebuf::{packet_id::Packet, DeserializerError}, client::play::{ - CGameEvent, CPlayDisconnect, CPlayerAbilities, CPlayerInfoUpdate, CSyncPlayerPosition, - CSystemChatMessage, GameEvent, PlayerAction, + CGameEvent, CPlayDisconnect, CPlayerAbilities, CPlayerInfoUpdate, CSetHealth, + CSyncPlayerPosition, CSystemChatMessage, GameEvent, PlayerAction, }, server::play::{ SChatCommand, SChatMessage, SClickContainer, SClientInformationPlay, SConfirmTeleport, @@ -232,6 +232,8 @@ impl Player { self.entity.health.store(health); self.food.store(food, std::sync::atomic::Ordering::Relaxed); self.food_saturation.store(food_saturation); + self.client + .send_packet(&CSetHealth::new(health, food.into(), food_saturation)); } pub fn set_gamemode(&self, gamemode: GameMode) { From 9ac4bdbd504e9eaaca052a02a04fa01f9db50491 Mon Sep 17 00:00:00 2001 From: Snowiiii Date: Fri, 11 Oct 2024 12:54:42 +0200 Subject: [PATCH 56/65] Add Living Entity struct --- pumpkin/src/client/container.rs | 1 + pumpkin/src/client/player_packet.rs | 29 ++++++++++-------- pumpkin/src/commands/cmd_kill.rs | 2 +- pumpkin/src/entity/living.rs | 46 +++++++++++++++++++++++++++++ pumpkin/src/entity/mod.rs | 19 ++---------- pumpkin/src/entity/player.rs | 31 +++++++++++-------- pumpkin/src/world/mod.rs | 4 +-- pumpkin/src/world/player_chunker.rs | 4 +-- 8 files changed, 89 insertions(+), 47 deletions(-) create mode 100644 pumpkin/src/entity/living.rs diff --git a/pumpkin/src/client/container.rs b/pumpkin/src/client/container.rs index bb3a01d1a..bd1cf5db0 100644 --- a/pumpkin/src/client/container.rs +++ b/pumpkin/src/client/container.rs @@ -394,6 +394,7 @@ impl Player { // Also refactor out a better method to get individual advanced state ids let players = self + .living_entity .entity .world .current_players diff --git a/pumpkin/src/client/player_packet.rs b/pumpkin/src/client/player_packet.rs index a9a881c0c..10d9e5f8d 100644 --- a/pumpkin/src/client/player_packet.rs +++ b/pumpkin/src/client/player_packet.rs @@ -50,7 +50,9 @@ impl Player { if let Some((id, position)) = awaiting_teleport.as_ref() { if id == &confirm_teleport.teleport_id { // we should set the pos now to that we requested in the teleport packet, Is may fixed issues when the client sended position packets while being teleported - self.entity.set_pos(position.x, position.y, position.z); + self.living_entity + .entity + .set_pos(position.x, position.y, position.z); *awaiting_teleport = None; } else { @@ -76,7 +78,7 @@ impl Player { self.kick(TextComponent::text("Invalid movement")); return; } - let entity = &self.entity; + let entity = &self.living_entity.entity; entity.set_pos( Self::clamp_horizontal(position.x), Self::clamp_vertical(position.feet_y), @@ -135,7 +137,7 @@ impl Player { self.kick(TextComponent::text("Invalid rotation")); return; } - let entity = &self.entity; + let entity = &self.living_entity.entity; entity.set_pos( Self::clamp_horizontal(position_rotation.x), @@ -200,7 +202,7 @@ impl Player { self.kick(TextComponent::text("Invalid rotation")); return; } - let entity = &self.entity; + let entity = &self.living_entity.entity; entity .on_ground .store(rotation.ground, std::sync::atomic::Ordering::Relaxed); @@ -228,7 +230,8 @@ impl Player { } pub fn handle_player_ground(&self, _server: &Arc, ground: SSetPlayerGround) { - self.entity + self.living_entity + .entity .on_ground .store(ground.on_ground, std::sync::atomic::Ordering::Relaxed); } @@ -239,7 +242,7 @@ impl Player { } if let Some(action) = Action::from_i32(command.action.0) { - let entity = &self.entity; + let entity = &self.living_entity.entity; match action { pumpkin_protocol::server::play::Action::StartSneaking => { if !entity.sneaking.load(std::sync::atomic::Ordering::Relaxed) { @@ -289,7 +292,7 @@ impl Player { Hand::Off => Animation::SwingOffhand, }; let id = self.entity_id(); - let world = &self.entity.world; + let world = &self.living_entity.entity.world; world.broadcast_packet_expect( &[self.client.token], &CEntityAnimation::new(id.into(), animation as u8), @@ -313,7 +316,7 @@ impl Player { // TODO: filter message & validation let gameprofile = &self.gameprofile; - let entity = &self.entity; + let entity = &self.living_entity.entity; let world = &entity.world; world.broadcast_packet_all(&CPlayerChatMessage::new( gameprofile.id, @@ -367,7 +370,7 @@ impl Player { pub async fn handle_interact(&self, _: &Arc, interact: SInteract) { let sneaking = interact.sneaking; - let entity = &self.entity; + let entity = &self.living_entity.entity; if entity.sneaking.load(std::sync::atomic::Ordering::Relaxed) != sneaking { entity.set_sneaking(sneaking).await; } @@ -381,7 +384,7 @@ impl Player { let world = &entity.world; let attacked_player = world.get_player_by_entityid(entity_id.0 as EntityId); if let Some(player) = attacked_player { - let victem_entity = &player.entity; + let victem_entity = &player.living_entity.entity; if config.protect_creative && player.gamemode.load() == GameMode::Creative { @@ -447,7 +450,7 @@ impl Player { let location = player_action.location; // Block break & block break sound // TODO: currently this is always dirt replace it - let entity = &self.entity; + let entity = &self.living_entity.entity; let world = &entity.world; world.broadcast_packet_all(&CWorldEvent::new(2001, &location, 11, false)); // AIR @@ -471,7 +474,7 @@ impl Player { } // Block break & block break sound // TODO: currently this is always dirt replace it - let entity = &self.entity; + let entity = &self.living_entity.entity; let world = &entity.world; world.broadcast_packet_all(&CWorldEvent::new(2001, &location, 11, false)); // AIR @@ -518,7 +521,7 @@ impl Player { ) .expect("All item ids are in the global registry"); if let Ok(block_state_id) = BlockState::new(minecraft_id, None) { - let entity = &self.entity; + let entity = &self.living_entity.entity; let world = &entity.world; world.broadcast_packet_all(&CBlockUpdate::new( &location, diff --git a/pumpkin/src/commands/cmd_kill.rs b/pumpkin/src/commands/cmd_kill.rs index 3b0bdf3fa..77c95f40e 100644 --- a/pumpkin/src/commands/cmd_kill.rs +++ b/pumpkin/src/commands/cmd_kill.rs @@ -18,7 +18,7 @@ pub fn init_command_tree<'a>() -> CommandTree<'a> { CommandTree::new(NAMES, DESCRIPTION).with_child( argument(ARG_TARGET, consume_arg_target).execute(&|sender, server, args| { let target = parse_arg_player(sender, server, ARG_TARGET, args)?; - target.entity.kill(); + target.living_entity.kill(); sender.send_message( TextComponent::text("Player has been killed.").color_named(NamedColor::Blue), diff --git a/pumpkin/src/entity/living.rs b/pumpkin/src/entity/living.rs new file mode 100644 index 000000000..a6ae33ede --- /dev/null +++ b/pumpkin/src/entity/living.rs @@ -0,0 +1,46 @@ +use crossbeam::atomic::AtomicCell; +use pumpkin_protocol::client::play::{CEntityStatus, CSetEntityMetadata, Metadata}; + +use super::Entity; + +/// Represents a Living Entity (e.g. Player, Zombie, Enderman...) +pub struct LivingEntity { + pub entity: Entity, + /// The entity's current health level. + pub health: AtomicCell, +} + +impl LivingEntity { + pub const fn new(entity: Entity) -> Self { + Self { + entity, + health: AtomicCell::new(20.0), + } + } + + pub fn set_health(&self, health: f32) { + self.health.store(health); + // tell everyone entities health changed + self.entity + .world + .broadcast_packet_all(&CSetEntityMetadata::new( + self.entity.entity_id.into(), + Metadata::new(9, 3.into(), health), + )); + } + + /// Kills the Entity + /// + /// This is similar to `kill` but Spawn Particles, Animation and plays death sound + pub fn kill(&self) { + // Spawns death smoke particles + self.entity + .world + .broadcast_packet_all(&CEntityStatus::new(self.entity.entity_id, 60)); + // Plays the death sound and death animation + self.entity + .world + .broadcast_packet_all(&CEntityStatus::new(self.entity.entity_id, 3)); + self.entity.remove(); + } +} diff --git a/pumpkin/src/entity/mod.rs b/pumpkin/src/entity/mod.rs index 71204dfa7..9ffcbb94a 100644 --- a/pumpkin/src/entity/mod.rs +++ b/pumpkin/src/entity/mod.rs @@ -8,14 +8,16 @@ use pumpkin_core::math::{ }; use pumpkin_entity::{entity_type::EntityType, pose::EntityPose, EntityId}; use pumpkin_protocol::{ - client::play::{CEntityStatus, CSetEntityMetadata, Metadata}, + client::play::{CSetEntityMetadata, Metadata}, VarInt, }; use crate::world::World; +pub mod living; pub mod player; +/// Represents a not living Entity (e.g. Item, Egg, Snowball...) pub struct Entity { /// A unique identifier for the entity pub entity_id: EntityId, @@ -24,7 +26,6 @@ pub struct Entity { /// The world in which the entity exists. pub world: Arc, /// The entity's current health level. - pub health: AtomicCell, /// The entity's current position in the world pub pos: AtomicCell>, @@ -75,7 +76,6 @@ impl Entity { sneaking: AtomicBool::new(false), world, // TODO: Load this from previous instance - health: AtomicCell::new(20.0), sprinting: AtomicBool::new(false), fall_flying: AtomicBool::new(false), yaw: AtomicCell::new(0.0), @@ -121,19 +121,6 @@ impl Entity { self.pitch.store(pitch); } - /// Kills the Entity - /// - /// This is similar to `kill` but Spawn Particles, Animation and plays death sound - pub fn kill(&self) { - // Spawns death smoke particles - self.world - .broadcast_packet_all(&CEntityStatus::new(self.entity_id, 60)); - // Plays the death sound and death animation - self.world - .broadcast_packet_all(&CEntityStatus::new(self.entity_id, 3)); - self.remove(); - } - /// Removes the Entity from their current World pub fn remove(&self) { self.world.remove_entity(self); diff --git a/pumpkin/src/entity/player.rs b/pumpkin/src/entity/player.rs index 5850da0d8..fc39cdf2b 100644 --- a/pumpkin/src/entity/player.rs +++ b/pumpkin/src/entity/player.rs @@ -38,15 +38,14 @@ use crate::{ world::World, }; -use super::Entity; +use super::{living::LivingEntity, Entity}; /// Represents a Minecraft player entity. /// /// A `Player` is a special type of entity that represents a human player connected to the server. pub struct Player { - /// The underlying entity object that represents the player. - pub entity: Entity, - + /// The underlying living entity object that represents the player. + pub living_entity: LivingEntity, /// The player's game profile information, including their username and UUID. pub gameprofile: GameProfile, /// The client connection associated with the player. @@ -110,7 +109,12 @@ impl Player { ); let config = client.config.lock().clone().unwrap_or_default(); Self { - entity: Entity::new(entity_id, world, EntityType::Player, 1.62), + living_entity: LivingEntity::new(Entity::new( + entity_id, + world, + EntityType::Player, + 1.62, + )), config: Mutex::new(config), gameprofile, client, @@ -132,11 +136,11 @@ impl Player { /// Removes the Player out of the current World pub async fn remove(&self) { - self.entity.world.remove_player(self); + self.living_entity.entity.world.remove_player(self); } pub const fn entity_id(&self) -> EntityId { - self.entity.entity_id + self.living_entity.entity.entity_id } /// Updates the current abilities the Player has @@ -174,7 +178,7 @@ impl Player { .store(0, std::sync::atomic::Ordering::Relaxed); } let teleport_id = i + 1; - let entity = &self.entity; + let entity = &self.living_entity.entity; entity.set_pos(x, y, z); entity.set_rotation(yaw, pitch); *self.awaiting_teleport.lock() = Some((teleport_id.into(), Vector3::new(x, y, z))); @@ -200,8 +204,8 @@ impl Player { pub fn can_interact_with_block_at(&self, pos: &WorldPosition, additional_range: f64) -> bool { let d = self.block_interaction_range() + additional_range; let box_pos = BoundingBox::from_block(pos); - let entity_pos = self.entity.pos.load(); - let standing_eye_height = self.entity.standing_eye_height; + let entity_pos = self.living_entity.entity.pos.load(); + let standing_eye_height = self.living_entity.entity.standing_eye_height; box_pos.squared_magnitude(Vector3 { x: entity_pos.x, y: entity_pos.y + standing_eye_height as f64, @@ -228,8 +232,8 @@ impl Player { self.client.close() } - pub fn update_health(&self, health: f32, food: i32, food_saturation: f32) { - self.entity.health.store(health); + pub fn set_health(&self, health: f32, food: i32, food_saturation: f32) { + self.living_entity.set_health(health); self.food.store(food, std::sync::atomic::Ordering::Relaxed); self.food_saturation.store(food_saturation); self.client @@ -246,7 +250,8 @@ impl Player { self.gamemode.store(gamemode); // So a little story time. I actually made an abilties_from_gamemode function. I looked at vanilla and they always send the abilties from the gamemode. But the funny thing actually is. That the client // does actually use the same method and set the abilties when receiving the CGameEvent gamemode packet. Just Mojang nonsense - self.entity + self.living_entity + .entity .world .broadcast_packet_all(&CPlayerInfoUpdate::new( 0x04, diff --git a/pumpkin/src/world/mod.rs b/pumpkin/src/world/mod.rs index 97672a0ca..58ce26082 100644 --- a/pumpkin/src/world/mod.rs +++ b/pumpkin/src/world/mod.rs @@ -186,7 +186,7 @@ impl World { // spawn players for our client let token = player.client.token; for (_, existing_player) in self.current_players.lock().iter().filter(|c| c.0 != &token) { - let entity = &existing_player.entity; + let entity = &existing_player.living_entity.entity; let pos = entity.pos.load(); let gameprofile = &existing_player.gameprofile; player.client.send_packet(&CSpawnEntity::new( @@ -293,7 +293,7 @@ impl World { &[player.client.token], &CRemovePlayerInfo::new(1.into(), &[uuid]), ); - self.remove_entity(&player.entity); + self.remove_entity(&player.living_entity.entity); } pub fn remove_entity(&self, entity: &Entity) { diff --git a/pumpkin/src/world/player_chunker.rs b/pumpkin/src/world/player_chunker.rs index d6fb893aa..2c6f1c7d9 100644 --- a/pumpkin/src/world/player_chunker.rs +++ b/pumpkin/src/world/player_chunker.rs @@ -20,10 +20,10 @@ fn get_view_distance(player: &Player) -> i8 { } pub async fn player_join(world: &World, player: Arc) { - let new_watched = chunk_section_from_pos(&player.entity.block_pos.load()); + let new_watched = chunk_section_from_pos(&player.living_entity.entity.block_pos.load()); player.watched_section.store(new_watched); let watched_section = new_watched; - let chunk_pos = player.entity.chunk_pos.load(); + let chunk_pos = player.living_entity.entity.chunk_pos.load(); player.client.send_packet(&CCenterChunk { chunk_x: chunk_pos.x.into(), chunk_z: chunk_pos.z.into(), From bcbde2062317d3cbd46982eb708486da4bf50ee8 Mon Sep 17 00:00:00 2001 From: Snowiiii Date: Fri, 11 Oct 2024 16:41:52 +0200 Subject: [PATCH 57/65] Make Logging configurable --- pumpkin-config/src/commands.rs | 8 ++++-- pumpkin-config/src/lib.rs | 3 +++ pumpkin-config/src/logging.rs | 40 +++++++++++++++++++++++++++++ pumpkin-config/src/rcon.rs | 26 +++++++++++++++++++ pumpkin/Cargo.toml | 2 +- pumpkin/src/client/player_packet.rs | 7 +++++ pumpkin/src/main.rs | 38 ++++++++++++++++++++++++--- pumpkin/src/rcon/mod.rs | 32 +++++++++++++++++------ 8 files changed, 141 insertions(+), 15 deletions(-) create mode 100644 pumpkin-config/src/logging.rs diff --git a/pumpkin-config/src/commands.rs b/pumpkin-config/src/commands.rs index 94ee4d8c2..8ced89b47 100644 --- a/pumpkin-config/src/commands.rs +++ b/pumpkin-config/src/commands.rs @@ -4,11 +4,15 @@ use serde::{Deserialize, Serialize}; pub struct CommandsConfig { /// Are commands from the Console accepted ? pub use_console: bool, - // TODO: commands... + /// Should be commands from players be logged in console? + pub log_console: bool, // TODO: commands... } impl Default for CommandsConfig { fn default() -> Self { - Self { use_console: true } + Self { + use_console: true, + log_console: true, + } } } diff --git a/pumpkin-config/src/lib.rs b/pumpkin-config/src/lib.rs index a65caa444..438aa9064 100644 --- a/pumpkin-config/src/lib.rs +++ b/pumpkin-config/src/lib.rs @@ -1,4 +1,5 @@ use log::warn; +use logging::LoggingConfig; use pumpkin_core::{Difficulty, GameMode}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; @@ -10,6 +11,7 @@ use std::{ }; pub mod auth; +pub mod logging; pub mod proxy; pub mod resource_pack; @@ -46,6 +48,7 @@ pub struct AdvancedConfiguration { pub commands: CommandsConfig, pub rcon: RCONConfig, pub pvp: PVPConfig, + pub logging: LoggingConfig, } #[derive(Serialize, Deserialize)] diff --git a/pumpkin-config/src/logging.rs b/pumpkin-config/src/logging.rs new file mode 100644 index 000000000..d6bcc9027 --- /dev/null +++ b/pumpkin-config/src/logging.rs @@ -0,0 +1,40 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Deserialize, Serialize)] +pub struct LoggingConfig { + pub enabled: bool, + pub level: LevelFilter, + pub env: bool, + pub threads: bool, + pub color: bool, + pub timestamp: bool, +} + +impl Default for LoggingConfig { + fn default() -> Self { + Self { + enabled: true, + level: LevelFilter::Info, + env: false, + threads: true, + color: true, + timestamp: true, + } + } +} + +#[derive(Deserialize, Serialize, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] +pub enum LevelFilter { + /// A level lower than all log levels. + Off, + /// Corresponds to the `Error` log level. + Error, + /// Corresponds to the `Warn` log level. + Warn, + /// Corresponds to the `Info` log level. + Info, + /// Corresponds to the `Debug` log level. + Debug, + /// Corresponds to the `Trace` log level. + Trace, +} diff --git a/pumpkin-config/src/rcon.rs b/pumpkin-config/src/rcon.rs index f15fa655e..f7de36b45 100644 --- a/pumpkin-config/src/rcon.rs +++ b/pumpkin-config/src/rcon.rs @@ -13,6 +13,31 @@ pub struct RCONConfig { /// The maximum number of concurrent RCON connections allowed. /// If 0 there is no limit pub max_connections: u32, + /// RCON Logging + pub logging: RCONLogging, +} + +#[derive(Deserialize, Serialize, Clone, Debug)] +pub struct RCONLogging { + /// Whether successful RCON logins should be logged. + pub log_logged_successfully: bool, + /// Whether failed RCON login attempts with incorrect passwords should be logged. + pub log_wrong_password: bool, + /// Whether all RCON commands, regardless of success or failure, should be logged. + pub log_commands: bool, + /// Whether RCON quit commands should be logged. + pub log_quit: bool, +} + +impl Default for RCONLogging { + fn default() -> Self { + Self { + log_logged_successfully: true, + log_wrong_password: true, + log_commands: true, + log_quit: true, + } + } } impl Default for RCONConfig { @@ -22,6 +47,7 @@ impl Default for RCONConfig { address: SocketAddr::new(Ipv4Addr::new(0, 0, 0, 0).into(), 25575), password: "".to_string(), max_connections: 0, + logging: Default::default(), } } } diff --git a/pumpkin/Cargo.toml b/pumpkin/Cargo.toml index 401417a60..3320c4d6c 100644 --- a/pumpkin/Cargo.toml +++ b/pumpkin/Cargo.toml @@ -61,7 +61,7 @@ base64 = "0.22.1" png = "0.17.14" # logging -simple_logger = "5.0.0" +simple_logger = { version = "5.0.0", features = ["threads"] } log.workspace = true # networking diff --git a/pumpkin/src/client/player_packet.rs b/pumpkin/src/client/player_packet.rs index 10d9e5f8d..fd8a31a1a 100644 --- a/pumpkin/src/client/player_packet.rs +++ b/pumpkin/src/client/player_packet.rs @@ -227,6 +227,13 @@ impl Player { pub fn handle_chat_command(&self, server: &Arc, command: SChatCommand) { let dispatcher = server.command_dispatcher.clone(); dispatcher.handle_command(&mut CommandSender::Player(self), server, &command.command); + if ADVANCED_CONFIG.commands.log_console { + log::info!( + "Player ({}): executed command /{}", + self.gameprofile.name, + command.command + ); + } } pub fn handle_player_ground(&self, _server: &Arc, ground: SSetPlayerGround) { diff --git a/pumpkin/src/main.rs b/pumpkin/src/main.rs index ab21ef1b3..62db705bf 100644 --- a/pumpkin/src/main.rs +++ b/pumpkin/src/main.rs @@ -16,6 +16,7 @@ #[cfg(target_os = "wasi")] compile_error!("Compiling for WASI targets is not supported!"); +use log::LevelFilter; use mio::net::TcpListener; use mio::{Events, Interest, Poll, Token}; @@ -38,6 +39,38 @@ pub mod server; pub mod util; pub mod world; +fn init_logger() { + use pumpkin_config::ADVANCED_CONFIG; + if ADVANCED_CONFIG.logging.enabled { + let mut logger = simple_logger::SimpleLogger::new(); + + if !ADVANCED_CONFIG.logging.timestamp { + logger = logger.without_timestamps(); + } + + if ADVANCED_CONFIG.logging.env { + logger = logger.env(); + } + + logger = logger.with_level(convert_logger_filter(ADVANCED_CONFIG.logging.level)); + + logger = logger.with_colors(ADVANCED_CONFIG.logging.color); + logger = logger.with_threads(ADVANCED_CONFIG.logging.threads); + logger.init().unwrap() + } +} + +fn convert_logger_filter(level: pumpkin_config::logging::LevelFilter) -> LevelFilter { + match level { + pumpkin_config::logging::LevelFilter::Off => LevelFilter::Off, + pumpkin_config::logging::LevelFilter::Error => LevelFilter::Error, + pumpkin_config::logging::LevelFilter::Warn => LevelFilter::Warn, + pumpkin_config::logging::LevelFilter::Info => LevelFilter::Info, + pumpkin_config::logging::LevelFilter::Debug => LevelFilter::Debug, + pumpkin_config::logging::LevelFilter::Trace => LevelFilter::Trace, + } +} + fn main() -> io::Result<()> { use std::sync::Arc; @@ -46,10 +79,7 @@ fn main() -> io::Result<()> { use pumpkin_core::text::{color::NamedColor, TextComponent}; use rcon::RCONServer; - simple_logger::SimpleLogger::new() - .with_level(log::LevelFilter::Info) - .init() - .unwrap(); + init_logger(); let rt = tokio::runtime::Builder::new_multi_thread() .enable_all() diff --git a/pumpkin/src/rcon/mod.rs b/pumpkin/src/rcon/mod.rs index 252210f8c..a508f941a 100644 --- a/pumpkin/src/rcon/mod.rs +++ b/pumpkin/src/rcon/mod.rs @@ -1,6 +1,7 @@ use std::{ collections::HashMap, io::{self, Read, Write}, + net::SocketAddr, sync::Arc, }; @@ -9,7 +10,7 @@ use mio::{ Events, Interest, Poll, Token, }; use packet::{ClientboundPacket, Packet, PacketError, ServerboundPacket}; -use pumpkin_config::RCONConfig; +use pumpkin_config::{RCONConfig, ADVANCED_CONFIG}; use thiserror::Error; use crate::server::Server; @@ -71,11 +72,9 @@ impl RCONServer { return Err(e); } }; - log::info!("Accepted connection from: {}", address); if config.max_connections != 0 && connections.len() >= config.max_connections as usize { - log::warn!("Max RCON connections reached"); break; } @@ -87,7 +86,7 @@ impl RCONServer { Interest::READABLE.add(Interest::WRITABLE), ) .unwrap(); - connections.insert(token, RCONClient::new(connection)); + connections.insert(token, RCONClient::new(connection, address)); }, token => { @@ -98,6 +97,13 @@ impl RCONServer { }; if done { if let Some(mut client) = connections.remove(&token) { + let config = &ADVANCED_CONFIG.rcon; + if config.logging.log_quit { + log::info!( + "RCON ({}): Client closed connection", + client.address + ); + } poll.registry().deregister(&mut client.connection)?; } } @@ -116,15 +122,17 @@ impl RCONServer { pub struct RCONClient { connection: TcpStream, + address: SocketAddr, logged_in: bool, incoming: Vec, closed: bool, } impl RCONClient { - pub const fn new(connection: TcpStream) -> Self { + pub const fn new(connection: TcpStream, address: SocketAddr) -> Self { Self { connection, + address, logged_in: false, incoming: Vec::new(), closed: false, @@ -147,7 +155,7 @@ impl RCONClient { } // If we get a close here, we might have a reply, which we still want to write. let _ = self.poll(server, password).await.map_err(|e| { - log::error!("rcon error: {e}"); + log::error!("RCON error: {e}"); self.closed = true; }); } @@ -161,16 +169,21 @@ impl RCONClient { None => return Ok(()), }; + let config = &ADVANCED_CONFIG.rcon; match packet.get_type() { ServerboundPacket::Auth => { let body = packet.get_body(); if !body.is_empty() && packet.get_body() == password { self.send(ClientboundPacket::AuthResponse, packet.get_id(), "".into()) .await?; - log::info!("RCON Client logged in successfully"); + if config.logging.log_logged_successfully { + log::info!("RCON ({}): Client logged in successfully", self.address); + } self.logged_in = true; } else { - log::warn!("RCON Client has tried wrong password"); + if config.logging.log_wrong_password { + log::info!("RCON ({}): Client has tried wrong password", self.address); + } self.send(ClientboundPacket::AuthResponse, -1, "".into()) .await?; self.closed = true; @@ -186,6 +199,9 @@ impl RCONClient { packet.get_body(), ); for line in output { + if config.logging.log_commands { + log::info!("RCON ({}): {}", self.address, line); + } self.send(ClientboundPacket::Output, packet.get_id(), line) .await?; } From e0180ef0cf2a085534f55c598015217b1c168432 Mon Sep 17 00:00:00 2001 From: Snowiiii Date: Fri, 11 Oct 2024 16:47:24 +0200 Subject: [PATCH 58/65] Fix: clippy --- pumpkin/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pumpkin/src/main.rs b/pumpkin/src/main.rs index 62db705bf..976910003 100644 --- a/pumpkin/src/main.rs +++ b/pumpkin/src/main.rs @@ -60,7 +60,7 @@ fn init_logger() { } } -fn convert_logger_filter(level: pumpkin_config::logging::LevelFilter) -> LevelFilter { +const fn convert_logger_filter(level: pumpkin_config::logging::LevelFilter) -> LevelFilter { match level { pumpkin_config::logging::LevelFilter::Off => LevelFilter::Off, pumpkin_config::logging::LevelFilter::Error => LevelFilter::Error, From 5adaf5aa17b96974f243b75ee8ecb12c8239f8a8 Mon Sep 17 00:00:00 2001 From: lukas0008 Date: Fri, 11 Oct 2024 16:59:59 +0200 Subject: [PATCH 59/65] Add default values for config --- Cargo.lock | 12 +++++++ pumpkin-config/Cargo.toml | 1 + pumpkin-config/src/auth.rs | 51 ++++++++++++++++++++--------- pumpkin-config/src/commands.rs | 3 ++ pumpkin-config/src/compression.rs | 7 ++++ pumpkin-config/src/lib.rs | 29 +++++++++++++++- pumpkin-config/src/proxy.rs | 2 ++ pumpkin-config/src/pvp.rs | 7 ++++ pumpkin-config/src/rcon.rs | 12 ++++++- pumpkin-config/src/resource_pack.rs | 1 + 10 files changed, 108 insertions(+), 17 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6ddf14516..73b534e16 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1925,6 +1925,7 @@ dependencies = [ "log", "pumpkin-core", "serde", + "serde-inline-default", "toml", ] @@ -2443,6 +2444,17 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-inline-default" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "484b43bb1114a28d1a574f5682d6079fa4c20e76faaff0cfd048216650e57101" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "serde_bytes" version = "0.11.15" diff --git a/pumpkin-config/Cargo.toml b/pumpkin-config/Cargo.toml index d3ce22e43..5c2a8df09 100644 --- a/pumpkin-config/Cargo.toml +++ b/pumpkin-config/Cargo.toml @@ -9,3 +9,4 @@ serde.workspace = true log.workspace = true toml = "0.8" +serde-inline-default = "0.2.1" diff --git a/pumpkin-config/src/auth.rs b/pumpkin-config/src/auth.rs index c6606cd86..ad9d611b8 100644 --- a/pumpkin-config/src/auth.rs +++ b/pumpkin-config/src/auth.rs @@ -1,50 +1,78 @@ use pumpkin_core::ProfileAction; use serde::{Deserialize, Serialize}; +use serde_inline_default::serde_inline_default; +#[serde_inline_default] #[derive(Deserialize, Serialize)] pub struct AuthenticationConfig { /// Whether to use Mojang authentication. + #[serde_inline_default(true)] pub enabled: bool, /// Prevent proxy connections. + #[serde_inline_default(false)] pub prevent_proxy_connections: bool, /// Player profile handling. + #[serde(default)] pub player_profile: PlayerProfileConfig, /// Texture handling. + #[serde(default)] pub textures: TextureConfig, } +impl Default for AuthenticationConfig { + fn default() -> Self { + Self { + enabled: true, + prevent_proxy_connections: false, + player_profile: Default::default(), + textures: Default::default(), + } + } +} + #[derive(Deserialize, Serialize)] +#[serde(default)] pub struct PlayerProfileConfig { /// Allow players flagged by Mojang (banned, forced name change). pub allow_banned_players: bool, /// Depends on the value above + #[serde(default = "default_allowed_actions")] pub allowed_actions: Vec, } +fn default_allowed_actions() -> Vec { + vec![ + ProfileAction::ForcedNameChange, + ProfileAction::UsingBannedSkin, + ] +} + impl Default for PlayerProfileConfig { fn default() -> Self { Self { allow_banned_players: false, - allowed_actions: vec![ - ProfileAction::ForcedNameChange, - ProfileAction::UsingBannedSkin, - ], + allowed_actions: default_allowed_actions(), } } } +#[serde_inline_default] #[derive(Deserialize, Serialize)] pub struct TextureConfig { /// Whether to use player textures. + #[serde_inline_default(true)] pub enabled: bool, + #[serde_inline_default(vec!["http".into(), "https".into()])] pub allowed_url_schemes: Vec, + #[serde_inline_default(vec![".minecraft.net".into(), ".mojang.com".into()])] pub allowed_url_domains: Vec, /// Specific texture types. + #[serde(default)] pub types: TextureTypes, } @@ -60,13 +88,17 @@ impl Default for TextureConfig { } #[derive(Deserialize, Serialize)] +#[serde_inline_default] pub struct TextureTypes { /// Use player skins. + #[serde_inline_default(true)] pub skin: bool, /// Use player capes. + #[serde_inline_default(true)] pub cape: bool, /// Use player elytras. /// (i didn't know myself that there are custom elytras) + #[serde_inline_default(true)] pub elytra: bool, } @@ -79,14 +111,3 @@ impl Default for TextureTypes { } } } - -impl Default for AuthenticationConfig { - fn default() -> Self { - Self { - enabled: true, - prevent_proxy_connections: false, - player_profile: Default::default(), - textures: Default::default(), - } - } -} diff --git a/pumpkin-config/src/commands.rs b/pumpkin-config/src/commands.rs index 94ee4d8c2..cf20b064c 100644 --- a/pumpkin-config/src/commands.rs +++ b/pumpkin-config/src/commands.rs @@ -1,8 +1,11 @@ use serde::{Deserialize, Serialize}; +use serde_inline_default::serde_inline_default; #[derive(Deserialize, Serialize)] +#[serde_inline_default] pub struct CommandsConfig { /// Are commands from the Console accepted ? + #[serde_inline_default(true)] pub use_console: bool, // TODO: commands... } diff --git a/pumpkin-config/src/compression.rs b/pumpkin-config/src/compression.rs index 9bdd733d4..9b9b6b9d9 100644 --- a/pumpkin-config/src/compression.rs +++ b/pumpkin-config/src/compression.rs @@ -1,22 +1,29 @@ use serde::{Deserialize, Serialize}; +use serde_inline_default::serde_inline_default; +#[serde_inline_default] #[derive(Deserialize, Serialize)] /// Packet compression pub struct CompressionConfig { /// Is compression enabled ? + #[serde_inline_default(true)] pub enabled: bool, #[serde(flatten)] + #[serde(default)] pub compression_info: CompressionInfo, } +#[serde_inline_default] #[derive(Deserialize, Serialize, Clone)] /// We have this in a Seperate struct so we can use it outside of the Config pub struct CompressionInfo { /// The compression threshold used when compression is enabled + #[serde_inline_default(256)] pub threshold: u32, /// A value between 0..9 /// 1 = Optimize for the best speed of encoding. /// 9 = Optimize for the size of data being encoded. + #[serde_inline_default(4)] pub level: u32, } diff --git a/pumpkin-config/src/lib.rs b/pumpkin-config/src/lib.rs index a65caa444..d4ec18479 100644 --- a/pumpkin-config/src/lib.rs +++ b/pumpkin-config/src/lib.rs @@ -2,6 +2,9 @@ use log::warn; use pumpkin_core::{Difficulty, GameMode}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; +// TODO: when https://github.com/rust-lang/rfcs/pull/3681 gets merged, replace serde-inline-default with native syntax +use serde_inline_default::serde_inline_default; + use std::{ fs, net::{Ipv4Addr, SocketAddr}, @@ -39,47 +42,71 @@ pub static BASIC_CONFIG: LazyLock = LazyLock::new(BasicConfi /// Important: The Configuration should match Vanilla by default #[derive(Deserialize, Serialize, Default)] pub struct AdvancedConfiguration { + #[serde(default)] pub proxy: ProxyConfig, + #[serde(default)] pub authentication: AuthenticationConfig, + #[serde(default)] pub packet_compression: CompressionConfig, + #[serde(default)] pub resource_pack: ResourcePackConfig, + #[serde(default)] pub commands: CommandsConfig, + #[serde(default)] pub rcon: RCONConfig, + #[serde(default)] pub pvp: PVPConfig, } +#[serde_inline_default] #[derive(Serialize, Deserialize)] pub struct BasicConfiguration { /// The address to bind the server to. + #[serde(default = "default_server_address")] pub server_address: SocketAddr, /// The seed for world generation. + #[serde(default = "String::new")] pub seed: String, /// The maximum number of players allowed on the server. + #[serde_inline_default(10000)] pub max_players: u32, /// The maximum view distance for players. + #[serde_inline_default(10)] pub view_distance: u8, /// The maximum simulated view distance. + #[serde_inline_default(10)] pub simulation_distance: u8, /// The default game difficulty. + #[serde_inline_default(Difficulty::Normal)] pub default_difficulty: Difficulty, /// Whether the Nether dimension is enabled. + #[serde_inline_default(true)] pub allow_nether: bool, /// Whether the server is in hardcore mode. + #[serde_inline_default(false)] pub hardcore: bool, /// Whether online mode is enabled. Requires valid Minecraft accounts. + #[serde_inline_default(true)] pub online_mode: bool, /// Whether packet encryption is enabled. Required when online mode is enabled. + #[serde_inline_default(true)] pub encryption: bool, /// The server's description displayed on the status screen. + #[serde_inline_default("A Blazing fast Pumpkin Server!".to_string())] pub motd: String, /// The default game mode for players. + #[serde_inline_default(GameMode::Survival)] pub default_gamemode: GameMode, } +fn default_server_address() -> SocketAddr { + SocketAddr::new(Ipv4Addr::new(0, 0, 0, 0).into(), 25565) +} + impl Default for BasicConfiguration { fn default() -> Self { Self { - server_address: SocketAddr::new(Ipv4Addr::new(0, 0, 0, 0).into(), 25565), + server_address: default_server_address(), seed: "".to_string(), max_players: 100000, view_distance: 10, diff --git a/pumpkin-config/src/proxy.rs b/pumpkin-config/src/proxy.rs index dba52f66e..1d1e44336 100644 --- a/pumpkin-config/src/proxy.rs +++ b/pumpkin-config/src/proxy.rs @@ -1,12 +1,14 @@ use serde::{Deserialize, Serialize}; #[derive(Deserialize, Serialize, Default)] +#[serde(default)] pub struct ProxyConfig { pub enabled: bool, pub velocity: VelocityConfig, } #[derive(Deserialize, Serialize)] +#[serde(default)] pub struct VelocityConfig { pub enabled: bool, pub secret: String, diff --git a/pumpkin-config/src/pvp.rs b/pumpkin-config/src/pvp.rs index cfbddc6c4..de5d9c70f 100644 --- a/pumpkin-config/src/pvp.rs +++ b/pumpkin-config/src/pvp.rs @@ -1,16 +1,23 @@ use serde::{Deserialize, Serialize}; +use serde_inline_default::serde_inline_default; +#[serde_inline_default] #[derive(Deserialize, Serialize)] pub struct PVPConfig { /// Is PVP enabled ? + #[serde_inline_default(true)] pub enabled: bool, /// Do we want to have the Red hurt animation & fov bobbing + #[serde_inline_default(true)] pub hurt_animation: bool, /// Should players in creative be protected against PVP + #[serde_inline_default(true)] pub protect_creative: bool, /// Has PVP Knockback? + #[serde_inline_default(true)] pub knockback: bool, /// Should player swing when attacking? + #[serde_inline_default(true)] pub swing: bool, } diff --git a/pumpkin-config/src/rcon.rs b/pumpkin-config/src/rcon.rs index f15fa655e..61d68a8d2 100644 --- a/pumpkin-config/src/rcon.rs +++ b/pumpkin-config/src/rcon.rs @@ -1,25 +1,35 @@ use std::net::{Ipv4Addr, SocketAddr}; use serde::{Deserialize, Serialize}; +use serde_inline_default::serde_inline_default; +#[serde_inline_default] #[derive(Deserialize, Serialize, Clone)] pub struct RCONConfig { /// Is RCON Enabled? + #[serde_inline_default(false)] pub enabled: bool, /// The network address and port where the RCON server will listen for connections. + #[serde(default = "default_rcon_address")] pub address: SocketAddr, /// The password required for RCON authentication. + #[serde(default)] pub password: String, /// The maximum number of concurrent RCON connections allowed. /// If 0 there is no limit + #[serde(default)] pub max_connections: u32, } +fn default_rcon_address() -> SocketAddr { + SocketAddr::new(Ipv4Addr::new(0, 0, 0, 0).into(), 25575) +} + impl Default for RCONConfig { fn default() -> Self { Self { enabled: false, - address: SocketAddr::new(Ipv4Addr::new(0, 0, 0, 0).into(), 25575), + address: default_rcon_address(), password: "".to_string(), max_connections: 0, } diff --git a/pumpkin-config/src/resource_pack.rs b/pumpkin-config/src/resource_pack.rs index 795595ada..f09b89455 100644 --- a/pumpkin-config/src/resource_pack.rs +++ b/pumpkin-config/src/resource_pack.rs @@ -1,6 +1,7 @@ use serde::{Deserialize, Serialize}; #[derive(Deserialize, Serialize)] +#[serde(default)] pub struct ResourcePackConfig { pub enabled: bool, /// The path to the resource pack. From 7db8c0ed97f17d888241b474ad1cdefd68af53be Mon Sep 17 00:00:00 2001 From: lukas0008 Date: Fri, 11 Oct 2024 17:12:35 +0200 Subject: [PATCH 60/65] Add serde defaults for logging --- pumpkin-config/src/commands.rs | 1 + pumpkin-config/src/lib.rs | 8 +------- pumpkin-config/src/logging.rs | 8 ++++++++ pumpkin-config/src/rcon.rs | 5 +++++ 4 files changed, 15 insertions(+), 7 deletions(-) diff --git a/pumpkin-config/src/commands.rs b/pumpkin-config/src/commands.rs index 2e975746d..936360818 100644 --- a/pumpkin-config/src/commands.rs +++ b/pumpkin-config/src/commands.rs @@ -8,6 +8,7 @@ pub struct CommandsConfig { #[serde_inline_default(true)] pub use_console: bool, /// Should be commands from players be logged in console? + #[serde_inline_default(true)] pub log_console: bool, // TODO: commands... } diff --git a/pumpkin-config/src/lib.rs b/pumpkin-config/src/lib.rs index 28e812c54..6062bb259 100644 --- a/pumpkin-config/src/lib.rs +++ b/pumpkin-config/src/lib.rs @@ -43,20 +43,14 @@ pub static BASIC_CONFIG: LazyLock = LazyLock::new(BasicConfi /// This also allows you get some Performance or Resource boosts. /// Important: The Configuration should match Vanilla by default #[derive(Deserialize, Serialize, Default)] +#[serde(default)] pub struct AdvancedConfiguration { - #[serde(default)] pub proxy: ProxyConfig, - #[serde(default)] pub authentication: AuthenticationConfig, - #[serde(default)] pub packet_compression: CompressionConfig, - #[serde(default)] pub resource_pack: ResourcePackConfig, - #[serde(default)] pub commands: CommandsConfig, - #[serde(default)] pub rcon: RCONConfig, - #[serde(default)] pub pvp: PVPConfig, pub logging: LoggingConfig, } diff --git a/pumpkin-config/src/logging.rs b/pumpkin-config/src/logging.rs index d6bcc9027..62991aa8c 100644 --- a/pumpkin-config/src/logging.rs +++ b/pumpkin-config/src/logging.rs @@ -1,12 +1,20 @@ use serde::{Deserialize, Serialize}; +use serde_inline_default::serde_inline_default; +#[serde_inline_default] #[derive(Deserialize, Serialize)] pub struct LoggingConfig { + #[serde_inline_default(true)] pub enabled: bool, + #[serde_inline_default(LevelFilter::Info)] pub level: LevelFilter, + #[serde_inline_default(false)] pub env: bool, + #[serde_inline_default(true)] pub threads: bool, + #[serde_inline_default(true)] pub color: bool, + #[serde_inline_default(true)] pub timestamp: bool, } diff --git a/pumpkin-config/src/rcon.rs b/pumpkin-config/src/rcon.rs index ccbb13436..bbffa53c9 100644 --- a/pumpkin-config/src/rcon.rs +++ b/pumpkin-config/src/rcon.rs @@ -23,15 +23,20 @@ pub struct RCONConfig { pub logging: RCONLogging, } +#[serde_inline_default] #[derive(Deserialize, Serialize, Clone, Debug)] pub struct RCONLogging { /// Whether successful RCON logins should be logged. + #[serde_inline_default(true)] pub log_logged_successfully: bool, /// Whether failed RCON login attempts with incorrect passwords should be logged. + #[serde_inline_default(true)] pub log_wrong_password: bool, /// Whether all RCON commands, regardless of success or failure, should be logged. + #[serde_inline_default(true)] pub log_commands: bool, /// Whether RCON quit commands should be logged. + #[serde_inline_default(true)] pub log_quit: bool, } From d8220bf49b3c94420de76f30356cfd043330d741 Mon Sep 17 00:00:00 2001 From: Snowiiii Date: Sat, 12 Oct 2024 17:39:43 +0200 Subject: [PATCH 61/65] docs: Overhaul --- CONTRIBUTING.md | 5 +- README.md | 49 +- STRUCTURE.md | 17 - docs/.vitepress/config.mts | 38 +- docs/config/advanced.md | 388 ++++ docs/config/basic.md | 117 ++ .../README.md => docs/config/introduction.md | 8 +- docs/developer/authentication.md | 29 + docs/developer/introduction.md | 8 + docs/developer/networking.md | 272 +++ docs/plugins/about.md | 15 - docs/plugins/getting-started-rs.md | 4 - package-lock.json | 1765 +++++++++++++++-- package.json | 4 +- pumpkin-config/src/compression.rs | 2 +- pumpkin-entity/README.md | 0 pumpkin-protocol/README.md | 4 + .../src/server/config/s_plugin_message.rs | 2 +- 18 files changed, 2484 insertions(+), 243 deletions(-) delete mode 100644 STRUCTURE.md create mode 100644 docs/config/advanced.md create mode 100644 docs/config/basic.md rename pumpkin-config/README.md => docs/config/introduction.md (58%) create mode 100644 docs/developer/authentication.md create mode 100644 docs/developer/introduction.md create mode 100644 docs/developer/networking.md delete mode 100644 docs/plugins/about.md delete mode 100644 docs/plugins/getting-started-rs.md delete mode 100644 pumpkin-entity/README.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index aa557628e..05f54afe4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -39,9 +39,8 @@ And in release: cargo run --no-default-features --release ``` -### Project Structure - -Before contributing, it would be helpful to get to know the project structure, for further information, visit [STRUCTURE.md](STRUCTURE.md) +### Docs +The Documentation of Pumpkin can be found at https://snowiiii.github.io/Pumpkin/ ### Additional Information diff --git a/README.md b/README.md index e6d4be5d5..9f9505c62 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ -Pumpkin is a Minecraft server built entirely in Rust, offering a fast, efficient, +[Pumpkin](https://snowiiii.github.io/Pumpkin/) is a Minecraft server built entirely in Rust, offering a fast, efficient, and customizable experience. It prioritizes performance and player enjoyment while adhering to the core mechanics of the game. ![image](https://github.com/user-attachments/assets/7e2e865e-b150-4675-a2d5-b52f9900378e) @@ -73,55 +73,22 @@ and customizable experience. It prioritizes performance and player enjoyment whi Check out our [Github Project](https://github.com/users/Snowiiii/projects/12/views/3) to see current progress ## How to run - -There are currently no release builds, because there was no release :D. - -To get Pumpkin running you first have to clone it: - -```shell -git clone https://github.com/Snowiiii/Pumpkin.git -cd Pumpkin -``` - -You also may have to [install rust](https://www.rust-lang.org/tools/install) when you don't already have. - -You can place a vanilla world into the Pumpkin/ directory when you want. Just name the World to `world` - -Then run: - -> [!NOTE] -> This can take a while. Because we enabled heavy optimizations for release builds -> -> To apply further optimizations specfic to your CPU and use your CPU features. You should set the target-cpu=native -> Rust flag. - -```shell -cargo run --release -``` - -### Docker - -Experimental Docker support is available. -The image is currently not published anywhere, but you can use the following command to build it: - -```shell -docker build . -t pumpkin -``` - -To run it use the following command: - -```shell -docker run --rm -p 25565:25565 -v "./world:/pumpkin/world" pumpkin -``` +See https://snowiiii.github.io/Pumpkin/about/quick-start.html ## Contributions Contributions are welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) +## Docs +The Documentation of Pumpkin can be found at https://snowiiii.github.io/Pumpkin/ + ## Communication Consider joining our [discord](https://discord.gg/wT8XjrjKkf) to stay up-to-date on events, updates, and connect with other members. +## Funding +If you want to fund my and help the project, Check out my [GitHub sponsors](https://github.com/sponsors/Snowiiii) + ## Thanks A big thanks to [wiki.vg](https://wiki.vg/) for providing valuable information used in the development of this project. diff --git a/STRUCTURE.md b/STRUCTURE.md deleted file mode 100644 index 52f2208f5..000000000 --- a/STRUCTURE.md +++ /dev/null @@ -1,17 +0,0 @@ -# Project Structure - -## Overview - -Pumpkin is split into multiple crates, thus having a set project structure between contributors is essential. - -## Pumpkin-Core - -The core crate has some special rules that only apply to it: - -- It may not depend on any other pumpkin crate -- There may not be any files directly under src/, except for the mod.rs file (this is to help with organisation) - -## Other crate rules - -- [`pumpkin-protocol`](/pumpkin-protocol/) - contains definitions for packet types **and** their serialization (be it through serde, or manually implementing `ClientPacket`/`ServerPacket`), only the `pumpkin` crate may depend on this -- `pumpkin-macros` - similarly to `pumpkin-core`, it may not depend on any other pumpkin crate diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index fe7139230..b2cbd9cf1 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -9,26 +9,36 @@ export default defineConfig({ base: "/Pumpkin/", themeConfig: { // https://vitepress.dev/reference/default-theme-config + search: { + provider: "local", + }, sidebar: [ { text: "About", items: [ { text: "Introduction", link: "/about/introduction" }, { text: "Quick Start", link: "/about/quick-start" }, + ], + }, + { + text: "Developers", + items: [ { text: "Contributing", link: "https://github.com/Snowiiii/Pumpkin/blob/master/CONTRIBUTING.md", }, + { text: "Introduction", link: "/developer/introduction" }, + { text: "Networking", link: "/developer/networking" }, + { text: "Authentication", link: "/developer/authentication" }, ], }, + { - text: "Plugins", + text: "Configuration", items: [ - { text: "About Plugins", link: "/plugins/about" }, - { - text: "Getting Started in Rust", - link: "/plugins/getting-started-rs", - }, + { text: "Introduction", link: "/config/introduction" }, + { text: "Basic", link: "/config/basic" }, + { text: "Advanced", link: "/config/advanced" }, ], }, ], @@ -39,6 +49,22 @@ export default defineConfig({ ], logo: "/assets/icon.png", + footer: { + message: "Released under the MIT License.", + copyright: "Copyright © 2024-present Aleksandr Medvedev", + }, + editLink: { + pattern: "https://github.com/Snowiiii/Pumpkin/blob/master/docs/:path", + text: "Edit this page on GitHub", + }, + lastUpdated: { + text: "Updated at", + formatOptions: { + dateStyle: "medium", + timeStyle: "medium", + }, + }, + outline: "deep" }, head: [["link", { rel: "icon", href: "/assets/favicon.ico" }]], }); diff --git a/docs/config/advanced.md b/docs/config/advanced.md new file mode 100644 index 000000000..316d4ca46 --- /dev/null +++ b/docs/config/advanced.md @@ -0,0 +1,388 @@ +### Advanced Configuration + +### Proxy + +`proxy` + +Wether Proxy Configuration is enabled + +```toml +enabled=false +``` + +#### Velocity + +`proxy.velocity` + +Wether [Velocity](https://papermc.io/software/velocity) Proxy is enabled + +> [!IMPORTANT] +> Velocity support is currently WIP + +```toml +enabled=false +``` + +##### Velocity Secret + +This secret is used to ensure that player info forwarded by Velocity comes from your proxy and not from someone pretending to run Velocity + +```toml +secret= +``` + +### Authentication + +`authentication` + +Wether Authentication is enabled + +```toml +enabled=false +``` + +#### Prevent Proxy Connections + +Prevent proxy connections + +```toml +prevent_proxy_connections=false +``` + +#### Player Profile + +`authentication.player_profile` + +##### Allow Banned Players + +Allow players flagged by Mojang (banned, forced name change) + +```toml +allow_banned_players=false +``` + +##### Allowed Actions + +Depends on the value above + +```toml +allowed_actions=["FORCED_NAME_CHANGE", "USING_BANNED_SKIN"] +``` + +```toml +FORCED_NAME_CHANGE +USING_BANNED_SKIN +``` + +#### Textures + +`authentication.textures` + +Whether to filter/validate player textures (e.g. Skins/Capes) + +```toml +enabled=true +``` + +##### Allowed URL Schemes + +Allowed URL Schemes for Textures + +```toml +allowed_url_schemes=["http", "https"] +``` + +##### Allowed URL Domains + +Allowed URL domains for Textures + +```toml +allowed_url_domains=[".minecraft.net", ".mojang.com"] +``` + +#### Texture Types + +`authentication.textures.types` + +##### Skin + +Use player skins + +```toml +skin=true +``` + +##### Cape + +Use player capes + +```toml +cape=true +``` + +##### Elytra + +Use player elytras +(i didn't know myself that there are custom elytras) + +```toml +elytra=true +``` + +### Compression + +`packet_compression` + +Wether Packet Compression is enabled + +```toml +enable=true +``` + +#### Compression Info + +##### Threshold + +The compression threshold used when compression is enabled + +```toml +threshold=256 +``` + +##### Level + +The Compression Level + +> [!IMPORTANT] +> A value between 0..9 +> +> 1 = Optimize for the best speed of encoding. +> +> 9 = Optimize for the size of data being encoded. + +```toml +level=4 +``` + +### Resource Pack + +`resource_pack` + +Wether a Resource Pack is enabled + +```toml +enable=false +``` + +#### Resource Pack URL + +The download URL of the resource pack + +```toml +resource_pack_url= +``` + +#### Resource Pack SHA1 + +The SHA1 hash (40) of the resource pack + +```toml +resource_pack_sha1= +``` + +#### Prompt Message + +Custom prompt Text component, Leave blank for none + +```toml +prompt_message= +``` + +#### Force + +Will force the Player to accept the resource pack + +```toml +force=false +``` + +### Commands + +`commands` + +#### Use Console + +Are commands from the Console accepted + +```toml +use_console=true +``` + +#### Log Console + +Should be commands from players be logged in console + +```toml +log_console=true +``` + +### RCON Config + +`rcon` + +Wether RCON is enabled + +```toml +enable=false +``` + +#### Address + +The network address and port where the RCON server will listen for connections + +```toml +address=false +``` + +#### Password + +The password required for RCON authentication + +```toml +password= +``` + +#### Maximum Connections + +The maximum number of concurrent RCON connections allowed + +If 0 there is no limit + +```toml +max_connections=0 +``` + +#### RCON Logging + +`rcon.logging` + +##### Logged Successfully + +Whether successful RCON logins should be logged + +```toml +log_logged_successfully=true +``` + +##### Wrong Password + +Whether failed RCON login attempts with incorrect passwords should be logged + +```toml +log_wrong_password=true +``` + +##### Commands + +Whether all RCON commands, regardless of success or failure, should be logged + +```toml +log_commands=true +``` + +##### Disconnect + +Whether RCON client quit should be logged + +```toml +log_quit=true +``` + +### PVP + +`pvp` + +Whether PVP is enabled + +```toml +enable=true +``` + +#### Hurt Animation + +Do we want to have the Red hurt animation & fov bobbing + +```toml +hurt_animation=true +``` + +#### Protect Creative + +Should players in creative be protected against PVP + +```toml +protect_creative=true +``` + +#### Knockback + +Has PVP Knockback (Velocity) + +```toml +knockback=true +``` + +#### Swing + +Should player swing when attacking + +```toml +swing=true +``` + +### Logging +`logging` +Whether Logging is enabled + +```toml +enable=true +``` + +#### Level +At which level should be logged +```toml +level=Info +``` +```toml +Off +Error +Warn +Info +Debug +Trace +``` + +#### Env +Enables the user to choose log level by setting `RUST_LOG=` environment variable +```toml +env=false +``` + +#### Threads +Should threads be printed in the message +```toml +threads=true +``` + +#### Color +Should color be enabled for logging messages +```toml +color=true +``` + +#### Timestamp +Should the timestamp be printed in the message + +```toml +timestamp=true +``` \ No newline at end of file diff --git a/docs/config/basic.md b/docs/config/basic.md new file mode 100644 index 000000000..ac8b118fc --- /dev/null +++ b/docs/config/basic.md @@ -0,0 +1,117 @@ +### Basic Configuration + +Representing `configuration.toml` + +### Server Address + +The address to bind the server to + +```toml +server_address=0.0.0.0 +``` + +### Seed + +The seed for world generation + +```toml +seed= +``` + +### Max players + +The maximum number of players allowed on the server + +```toml +max_players=10000 +``` + +### View distance + +The maximum view distance for players + +```toml +view_distance=10 +``` + +### Simulation distance + +The maximum simulation distance for players + +```toml +simulation_distance=10 +``` + +### Default difficulty + +The default game difficulty + +```toml +default_difficulty=Normal +``` + +```toml +Peaceful +Easy +Normal +Hard +``` + +### Allow nether + +Whether the Nether dimension is enabled + +```toml +allow_nether=true +``` + +### Hardcore + +Whether the server is in hardcore mode. + +```toml +hardcore=true +``` + +### Online Mode + +Whether online mode is enabled. Requires valid Minecraft accounts + +```toml +online_mode=true +``` + +### Encryption + +Whether packet encryption is enabled + +> [!IMPORTANT] +> Required when online mode is enabled + +```toml +encryption=true +``` + +### Motd + +The server's description displayed on the status screen. + +```toml +motd=true +``` + +### Default gamemode + +The default game mode for players + +```toml +default_gamemode=Survival +``` + +```toml +Undefined +Survival +Creative +Adventure +Spectator +``` diff --git a/pumpkin-config/README.md b/docs/config/introduction.md similarity index 58% rename from pumpkin-config/README.md rename to docs/config/introduction.md index c8506926b..b008cd628 100644 --- a/pumpkin-config/README.md +++ b/docs/config/introduction.md @@ -1,6 +1,12 @@ -### Pumpkin Configuration +### Configuration Pumpkin offers a robust configuration system that allows users to customize various aspects of the server's behavior without relying on external plugins. This provides flexibility and control over the server's operation. +### Basic / Advanced +Pumpkin's Configuration is split into a basic Configuration made for quick changes and important changes and a more Advanced Configuration + +- `configuration.toml`: simple and can be compared to the vanilla `server.properties`. +- `features.toml`: designed to have all features of pumpkin at one place, making it a large configuration + #### Key Features: - Extensive Customization: Configure server settings, player behavior, world generation, and more. - Performance Optimization: Optimize server performance through configuration tweaks. diff --git a/docs/developer/authentication.md b/docs/developer/authentication.md new file mode 100644 index 000000000..c590d73e7 --- /dev/null +++ b/docs/developer/authentication.md @@ -0,0 +1,29 @@ +### Authentication + +### Why Authentication + +Minecraft is the most Popular game out there, And is is very easy to play it without paying for it. In Fact you don't pay for the Game, You pay for an Minecraft Account. +People who don't bough the Game but play online are using [Cracked Accounts](#cracked-accounts) + +#### Cracked Accounts + +- Don't cost any Money +- Everyone can set their own Nickname +- Have no UUID +- Have no Skin/Cape +- Not Secure + +The Problem is that everyone can name themself how they want, Allowing to Join the Server as a Staff Member for example and having extended permissions, +Cracked accounts are also often used for Botting and [Denial of Service](https://de.wikipedia.org/wiki/Denial_of_Service) Attacks. + +### Cracked Server + +By default the `online_mode` is enabled in the configuration, This enables Authentication disallowing [Cracked Accounts](#cracked-accounts). When you are willing to allow Cracked Accounts, you can dissable `online_mode` +in the `configuration.toml` + +### How Authentication works +To ensure a player has a premium accounts: + +1. A client with a premium account sends a login request to the Mojang session server. +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 . diff --git a/docs/developer/introduction.md b/docs/developer/introduction.md new file mode 100644 index 000000000..a1efdbfcf --- /dev/null +++ b/docs/developer/introduction.md @@ -0,0 +1,8 @@ +### Introduction + +Welcome to the Pumpkin Documentation! + +Whether you're an internal Pumpkin developer or working on a Pumpkin plugin, this documentation is your resource for everything Pumpkin. + +> [!IMPORTANT] +> While Pumpkin currently doesn't have plugin support, this documentation provides valuable insights into the platform's architecture and functionality, which can be helpful for understanding how to create potential future plugins. diff --git a/docs/developer/networking.md b/docs/developer/networking.md new file mode 100644 index 000000000..824c5045d --- /dev/null +++ b/docs/developer/networking.md @@ -0,0 +1,272 @@ +### Networking + +Most of the Networking code in Pumpkin, can be found at [Pumpkin-Protocol](https://github.com/Snowiiii/Pumpkin/tree/master/pumpkin-protocol) + +Serverbound: Client->Server + +Clientbound: Server->Client + +### Structure + +Packets in the Pumpkin protocol are organized by functionality and state. + +`server`: Contains definitions for serverbound packets. + +`client`: Contains definitions for clientbound packets. + +### States + +**Handshake**: Always the first packet being send from the Client. This begins also determins the next state, usally to indicate if the player thans perform a Status Request, Join the Server or wants to be transfered. + +**Status**: Indicates the Client wants to see a Status response (MOTD). + +**Login**: The Login sequence. Indicates the Client wants to join to the Server + +**Config**: A sequence of Configuration packets beining mostly send from the Server to the Client. (Features, Resource Pack, Server Links...) + +**Play**: The final state which indicate the Player is now ready to Join in also used to handle all other Gameplay packets. + +### Minecraft Protocol + +You can find all Minecraft Java packets at https://wiki.vg/Protocol. There you also can see in which [State](#States) they are. +You also can see all the information the Packets has which we can either Write or Read depending if its Serverbound or Clientbound + +### Adding a Clientbound Packet + +1. Adding a Packet is easy. First you have to dereive serde Serialize for packets. + +```rust +#[derive(Serialize)] +``` + +2. Next you have set the packet id using the packet macro + +```rust +#[packet(0x1D)] +``` + +3. Now you can create the Struct. + +> [!IMPORTANT] +> Please start the Packet name with "C" for Clientbound. +> Also please add the State to the packet if its a Packet sended in multiple States, For example there are 3 Disconnect Packets. +> +> - CLoginDisconnect +> - CConfigDisconnect +> - CPlayDisconnect + +Create fields within your packet structure to represent the data that will be sent to the client. + +> [!IMPORTANT] +> Use descriptive field names and appropriate data types. + +Example: + +```rust +pub struct CPlayDisconnect { + reason: TextComponent, + more fields... +} +``` + +4. Also don't forgot to impl a new function for Clientbound Packets so we can actaully send then by putting in the values + +Example: + +```rust +impl CPlayDisconnect { + pub fn new(reason: TextComponent) -> Self { + Self { reason } + } +} +``` + +5. At the End everything should come together, + +```rust +#[derive(Serialize)] +#[packet(0x1D)] +pub struct CPlayDisconnect { + reason: TextComponent, +} + +impl CPlayDisconnect { + pub fn new(reason: TextComponent) -> Self { + Self { reason } + } +} +``` + +6. You can also Serialize the Packet manually, Which can be usefull if the Packet is more complex + +```diff +-#[derive(Serialize)] + ++ impl ClientPacket for CPlayDisconnect { ++ fn write(&self, bytebuf: &mut crate::bytebuf::ByteBuffer) { ++ bytebuf.put_slice(&self.reason.encode()); ++ } +``` + +7. You can now send the Packet. See [Sending Packets](#sending-packets) + +### Adding a Serverbound Packet + +1. Adding a Packet is easy. First you have to dereive serde Deserialize for packets. + +```rust +#[derive(Deserialize)] +``` + +2. Next you have set the packet id using the packet macro + +```rust +#[packet(0x1A)] +``` + +3. Now you can create the Struct. + +> [!IMPORTANT] +> Please start the Packet name with "S" for Serverbound. +> Also please add the State to the packet if its a Packet sended in multiple States. + +Create fields within your packet structure to represent the data that will be sent to the client. + +> [!IMPORTANT] +> Use descriptive field names and appropriate data types. + +Example: + +```rust +pub struct SPlayerPosition { + pub x: f64, + pub feet_y: f64, + pub z: f64, + pub ground: bool, +} +``` + +4. At the End everything should come together, + +```rust +#[derive(Deserialize)] +#[packet(0x1A)] +pub struct SPlayerPosition { + pub x: f64, + pub feet_y: f64, + pub z: f64, + pub ground: bool, +} +``` + +5. You can also Deserialize the Packet manually, Which can be usefull if the Packet is more complex + +```diff +-#[derive(Deserialize)] + ++ impl ServerPacket for SPlayerPosition { ++ fn read(bytebuf: &mut ByteBuffer) -> Result { ++ Ok(Self { ++ x: bytebuf.get_f64()?, ++ feet_y: bytebuf.get_f64()?, ++ z: bytebuf.get_f64()?, ++ ground: bytebuf.get_bool()?, ++ }) ++ } +``` + +6. You can listen for the Packet. See [Receive Packets](#receiving-packets) + +### Client + +Pumpkin has stores Client and Players seperatly, Everything what is not reached the Play State is a Simple Client. Here are the Differences + +**Client** + +- Can only be in Status/Login/Transfer/Config State +- Is not a living entity +- Has small resource consumption + +**Player** + +- Can only be in Play State +- Is a living entity in a world +- Has more data, Consumes more resources + +#### Sending Packets + +Example: + +```rust +// Works only in Status State +client.send_packet(&CStatusResponse::new("{ description: "A Description"}")); +``` + +#### Receiving Packets + +For Clients: +`src/client/mod.rs` + +```diff +// Put the Packet into the right State + fn handle_mystate_packet( + &self, + server: &Arc, + packet: &mut RawPacket, +) -> Result<(), DeserializerError> { + let bytebuf = &mut packet.bytebuf; + match packet.id.0 { + SHandShake::PACKET_ID => { + self.handle_handshake(server, SHandShake::read(bytebuf)?); + Ok(()) + } ++ MyPacket::PACKET_ID => { ++ self.handle_mypacket(server, MyPacket::read(bytebuf)?); ++ Ok(()) ++ } + _ => { + log::error!( + "Failed to handle packet id {} while in ... state", + packet.id.0 + ); + Ok(()) + } + } +} +``` + +For Players: +`src/entity/player.rs` + +```diff +// Players only have Play State + fn handle_play_packet( + &self, + server: &Arc, + packet: &mut RawPacket, +) -> Result<(), DeserializerError> { + let bytebuf = &mut packet.bytebuf; + match packet.id.0 { + SHandShake::PACKET_ID => { + self.handle_handshake(server, SHandShake::read(bytebuf)?); + Ok(()) + } ++ MyPacket::PACKET_ID => { ++ self.handle_mypacket(server, MyPacket::read(bytebuf)?); ++ Ok(()) ++ } + _ => { + log::error!( + "Failed to handle packet id {} while in ... state", + packet.id.0 + ); + Ok(()) + } + } +} +``` + +### Porting + +To port to a new Minecraft version, You can compare difference in Protocol on wiki.vg https://wiki.vg/index.php?title=Protocol&action=history +Also change the `CURRENT_MC_PROTOCOL` in `src/lib.rs` diff --git a/docs/plugins/about.md b/docs/plugins/about.md deleted file mode 100644 index 110fe955a..000000000 --- a/docs/plugins/about.md +++ /dev/null @@ -1,15 +0,0 @@ -# Plugins - -Pumpkin uses [Extism](https://extism.org/) for loading plugins. -This means that you can write your plugins in any language that can compile to Extism WASM. -These languages include: - -- Rust -- JavaScript / TypeScript -- Golang -- C# -- F# -- C -- Haskell -- Zig -- AssemblyScript diff --git a/docs/plugins/getting-started-rs.md b/docs/plugins/getting-started-rs.md deleted file mode 100644 index 5bed83b39..000000000 --- a/docs/plugins/getting-started-rs.md +++ /dev/null @@ -1,4 +0,0 @@ -# Getting Started in Rust - -Rust in one of the supported plugin languages. -This page has not been written yet. diff --git a/package-lock.json b/package-lock.json index 2325a32a8..c858d9cef 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,16 +1,18 @@ { - "name": "Pumpkin", + "name": "pumpkin", "lockfileVersion": 3, "requires": true, "packages": { "": { "devDependencies": { - "vitepress": "^1.3.4", - "vue": "^3.4.38" + "vitepress": "^1.4.0", + "vue": "^3.5.12" } }, "node_modules/@algolia/autocomplete-core": { "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.9.3.tgz", + "integrity": "sha512-009HdfugtGCdC4JdXUbVJClA0q0zh24yyePn+KUGk3rP7j8FEe/m5Yo/z65gn6nP/cM39PxpzqKrL7A6fP6PPw==", "dev": true, "license": "MIT", "dependencies": { @@ -20,6 +22,8 @@ }, "node_modules/@algolia/autocomplete-plugin-algolia-insights": { "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.9.3.tgz", + "integrity": "sha512-a/yTUkcO/Vyy+JffmAnTWbr4/90cLzw+CC3bRbhnULr/EM0fGNvM13oQQ14f2moLMcVDyAx/leczLlAOovhSZg==", "dev": true, "license": "MIT", "dependencies": { @@ -31,6 +35,8 @@ }, "node_modules/@algolia/autocomplete-preset-algolia": { "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.9.3.tgz", + "integrity": "sha512-d4qlt6YmrLMYy95n5TB52wtNDr6EgAIPH81dvvvW8UmuWRgxEtY0NJiPwl/h95JtG2vmRM804M0DSwMCNZlzRA==", "dev": true, "license": "MIT", "dependencies": { @@ -43,6 +49,8 @@ }, "node_modules/@algolia/autocomplete-shared": { "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.9.3.tgz", + "integrity": "sha512-Wnm9E4Ye6Rl6sTTqjoymD+l8DjSTHsHboVRYrKgEt8Q7UHm9nYbqhN/i0fhUYA3OAEH7WA8x3jfpnmJm3rKvaQ==", "dev": true, "license": "MIT", "peerDependencies": { @@ -52,6 +60,8 @@ }, "node_modules/@algolia/cache-browser-local-storage": { "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.24.0.tgz", + "integrity": "sha512-t63W9BnoXVrGy9iYHBgObNXqYXM3tYXCjDSHeNwnsc324r4o5UiVKUiAB4THQ5z9U5hTj6qUvwg/Ez43ZD85ww==", "dev": true, "license": "MIT", "dependencies": { @@ -60,11 +70,15 @@ }, "node_modules/@algolia/cache-common": { "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/cache-common/-/cache-common-4.24.0.tgz", + "integrity": "sha512-emi+v+DmVLpMGhp0V9q9h5CdkURsNmFC+cOS6uK9ndeJm9J4TiqSvPYVu+THUP8P/S08rxf5x2P+p3CfID0Y4g==", "dev": true, "license": "MIT" }, "node_modules/@algolia/cache-in-memory": { "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/cache-in-memory/-/cache-in-memory-4.24.0.tgz", + "integrity": "sha512-gDrt2so19jW26jY3/MkFg5mEypFIPbPoXsQGQWAi6TrCPsNOSEYepBMPlucqWigsmEy/prp5ug2jy/N3PVG/8w==", "dev": true, "license": "MIT", "dependencies": { @@ -73,6 +87,8 @@ }, "node_modules/@algolia/client-account": { "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-account/-/client-account-4.24.0.tgz", + "integrity": "sha512-adcvyJ3KjPZFDybxlqnf+5KgxJtBjwTPTeyG2aOyoJvx0Y8dUQAEOEVOJ/GBxX0WWNbmaSrhDURMhc+QeevDsA==", "dev": true, "license": "MIT", "dependencies": { @@ -81,8 +97,33 @@ "@algolia/transporter": "4.24.0" } }, + "node_modules/@algolia/client-account/node_modules/@algolia/client-common": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.24.0.tgz", + "integrity": "sha512-bc2ROsNL6w6rqpl5jj/UywlIYC21TwSSoFHKl01lYirGMW+9Eek6r02Tocg4gZ8HAw3iBvu6XQiM3BEbmEMoiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/requester-common": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, + "node_modules/@algolia/client-account/node_modules/@algolia/client-search": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.24.0.tgz", + "integrity": "sha512-uRW6EpNapmLAD0mW47OXqTP8eiIx5F6qN9/x/7HHO6owL3N1IXqydGwW5nhDFBrV+ldouro2W1VX3XlcUXEFCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "4.24.0", + "@algolia/requester-common": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, "node_modules/@algolia/client-analytics": { "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-4.24.0.tgz", + "integrity": "sha512-y8jOZt1OjwWU4N2qr8G4AxXAzaa8DBvyHTWlHzX/7Me1LX8OayfgHexqrsL4vSBcoMmVw2XnVW9MhL+Y2ZDJXg==", "dev": true, "license": "MIT", "dependencies": { @@ -92,8 +133,10 @@ "@algolia/transporter": "4.24.0" } }, - "node_modules/@algolia/client-common": { + "node_modules/@algolia/client-analytics/node_modules/@algolia/client-common": { "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.24.0.tgz", + "integrity": "sha512-bc2ROsNL6w6rqpl5jj/UywlIYC21TwSSoFHKl01lYirGMW+9Eek6r02Tocg4gZ8HAw3iBvu6XQiM3BEbmEMoiA==", "dev": true, "license": "MIT", "dependencies": { @@ -101,8 +144,10 @@ "@algolia/transporter": "4.24.0" } }, - "node_modules/@algolia/client-personalization": { + "node_modules/@algolia/client-analytics/node_modules/@algolia/client-search": { "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.24.0.tgz", + "integrity": "sha512-uRW6EpNapmLAD0mW47OXqTP8eiIx5F6qN9/x/7HHO6owL3N1IXqydGwW5nhDFBrV+ldouro2W1VX3XlcUXEFCA==", "dev": true, "license": "MIT", "dependencies": { @@ -111,23 +156,68 @@ "@algolia/transporter": "4.24.0" } }, + "node_modules/@algolia/client-common": { + "version": "5.8.1", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.8.1.tgz", + "integrity": "sha512-MLX/gipPFEhJPCExsxXf9tnt+kLfWCe9JWRp1adcoVySkhzPxpIeSiWaQaOqyy0TYIgIpdeVx/emlBT9Ni8GFw==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-personalization": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-4.24.0.tgz", + "integrity": "sha512-l5FRFm/yngztweU0HdUzz1rC4yoWCFo3IF+dVIVTfEPg906eZg5BOd1k0K6rZx5JzyyoP4LdmOikfkfGsKVE9w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "4.24.0", + "@algolia/requester-common": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, + "node_modules/@algolia/client-personalization/node_modules/@algolia/client-common": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.24.0.tgz", + "integrity": "sha512-bc2ROsNL6w6rqpl5jj/UywlIYC21TwSSoFHKl01lYirGMW+9Eek6r02Tocg4gZ8HAw3iBvu6XQiM3BEbmEMoiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/requester-common": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, "node_modules/@algolia/client-search": { - "version": "4.24.0", + "version": "5.8.1", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.8.1.tgz", + "integrity": "sha512-zy3P4fI28GfzKihUw5+L76pEedQxyLDiMsdDYEWghIz8yAnELDatPNEThyWuUk8fD0PeVoCi1M4tr1iz00fOtQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "@algolia/client-common": "4.24.0", - "@algolia/requester-common": "4.24.0", - "@algolia/transporter": "4.24.0" + "@algolia/client-common": "5.8.1", + "@algolia/requester-browser-xhr": "5.8.1", + "@algolia/requester-fetch": "5.8.1", + "@algolia/requester-node-http": "5.8.1" + }, + "engines": { + "node": ">= 14.0.0" } }, "node_modules/@algolia/logger-common": { "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/logger-common/-/logger-common-4.24.0.tgz", + "integrity": "sha512-LLUNjkahj9KtKYrQhFKCzMx0BY3RnNP4FEtO+sBybCjJ73E8jNdaKJ/Dd8A/VA4imVHP5tADZ8pn5B8Ga/wTMA==", "dev": true, "license": "MIT" }, "node_modules/@algolia/logger-console": { "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/logger-console/-/logger-console-4.24.0.tgz", + "integrity": "sha512-X4C8IoHgHfiUROfoRCV+lzSy+LHMgkoEEU1BbKcsfnV0i0S20zyy0NLww9dwVHUWNfPPxdMU+/wKmLGYf96yTg==", "dev": true, "license": "MIT", "dependencies": { @@ -136,6 +226,8 @@ }, "node_modules/@algolia/recommend": { "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-4.24.0.tgz", + "integrity": "sha512-P9kcgerfVBpfYHDfVZDvvdJv0lEoCvzNlOy2nykyt5bK8TyieYyiD0lguIJdRZZYGre03WIAFf14pgE+V+IBlw==", "dev": true, "license": "MIT", "dependencies": { @@ -152,29 +244,102 @@ "@algolia/transporter": "4.24.0" } }, - "node_modules/@algolia/requester-browser-xhr": { + "node_modules/@algolia/recommend/node_modules/@algolia/client-common": { "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.24.0.tgz", + "integrity": "sha512-bc2ROsNL6w6rqpl5jj/UywlIYC21TwSSoFHKl01lYirGMW+9Eek6r02Tocg4gZ8HAw3iBvu6XQiM3BEbmEMoiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/requester-common": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, + "node_modules/@algolia/recommend/node_modules/@algolia/client-search": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.24.0.tgz", + "integrity": "sha512-uRW6EpNapmLAD0mW47OXqTP8eiIx5F6qN9/x/7HHO6owL3N1IXqydGwW5nhDFBrV+ldouro2W1VX3XlcUXEFCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "4.24.0", + "@algolia/requester-common": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, + "node_modules/@algolia/recommend/node_modules/@algolia/requester-browser-xhr": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.24.0.tgz", + "integrity": "sha512-Z2NxZMb6+nVXSjF13YpjYTdvV3032YTBSGm2vnYvYPA6mMxzM3v5rsCiSspndn9rzIW4Qp1lPHBvuoKJV6jnAA==", "dev": true, "license": "MIT", "dependencies": { "@algolia/requester-common": "4.24.0" } }, + "node_modules/@algolia/recommend/node_modules/@algolia/requester-node-http": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.24.0.tgz", + "integrity": "sha512-JF18yTjNOVYvU/L3UosRcvbPMGT9B+/GQWNWnenIImglzNVGpyzChkXLnrSf6uxwVNO6ESGu6oN8MqcGQcjQJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/requester-common": "4.24.0" + } + }, + "node_modules/@algolia/requester-browser-xhr": { + "version": "5.8.1", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.8.1.tgz", + "integrity": "sha512-x0iULVrx5PocaYBqH+G6jyEsEHf7m5FDiZW7CP8AaJdzdCzoUyx7YH6e6TSCNlkFEjwmn8uj05coN8uljCHXTg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@algolia/client-common": "5.8.1" + }, + "engines": { + "node": ">= 14.0.0" + } + }, "node_modules/@algolia/requester-common": { "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-common/-/requester-common-4.24.0.tgz", + "integrity": "sha512-k3CXJ2OVnvgE3HMwcojpvY6d9kgKMPRxs/kVohrwF5WMr2fnqojnycZkxPoEg+bXm8fi5BBfFmOqgYztRtHsQA==", "dev": true, "license": "MIT" }, - "node_modules/@algolia/requester-node-http": { - "version": "4.24.0", + "node_modules/@algolia/requester-fetch": { + "version": "5.8.1", + "resolved": "https://registry.npmjs.org/@algolia/requester-fetch/-/requester-fetch-5.8.1.tgz", + "integrity": "sha512-SRWGrNsKSLNYIDNlVKVkf4wxsm6h57xI+0b8JPm0wUe0ly0jymAgQU2yW2GDzNuXyiPiS7U1oWwaVGs71IT5Pw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "@algolia/requester-common": "4.24.0" + "@algolia/client-common": "5.8.1" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/requester-node-http": { + "version": "5.8.1", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.8.1.tgz", + "integrity": "sha512-pYylr2gBsV68E88bltaVoJHIc3YNIllVmA12d+jefAcutR9ytQM7iP6dXbCYuRqF4CHF32YvZuwvqNI3J4kowA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@algolia/client-common": "5.8.1" + }, + "engines": { + "node": ">= 14.0.0" } }, "node_modules/@algolia/transporter": { "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/transporter/-/transporter-4.24.0.tgz", + "integrity": "sha512-86nI7w6NzWxd1Zp9q3413dRshDqAzSbsQjhcDhPIatEFiZrL1/TjnHL8S7jVKFePlIMzDsZWXAXwXzcok9c5oA==", "dev": true, "license": "MIT", "dependencies": { @@ -184,7 +349,9 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.24.8", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.7.tgz", + "integrity": "sha512-CbkjYdsJNHFk8uqpEkpCvRs3YRp9tY6FmFY7wLMSYuGYkrdUi7r2lc4/wqsvlHoMznX3WJ9IP8giGPq68T/Y6g==", "dev": true, "license": "MIT", "engines": { @@ -192,7 +359,9 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.24.7", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.7.tgz", + "integrity": "sha512-AM6TzwYqGChO45oiuPqwL2t20/HdMC1rTPAesnBCgPCSF1x3oN9MVUwQV2iyz4xqWrctwK5RNC8LV22kaQCNYg==", "dev": true, "license": "MIT", "engines": { @@ -200,11 +369,13 @@ } }, "node_modules/@babel/parser": { - "version": "7.25.6", + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.8.tgz", + "integrity": "sha512-HcttkxzdPucv3nNFmfOOMfFf64KgdJVqm1KaCm25dPGMLElo9nsLvXeJECQg8UzPuBGLyTSA0ZzqCtDSzKTEoQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.25.6" + "@babel/types": "^7.25.8" }, "bin": { "parser": "bin/babel-parser.js" @@ -214,12 +385,14 @@ } }, "node_modules/@babel/types": { - "version": "7.25.6", + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.8.tgz", + "integrity": "sha512-JWtuCu8VQsMladxVz/P4HzHUGCAwpuqacmowgXFs5XjxIgKuNjnLokQzuVjlTvIzODaDmpjT3oxcC48vyk9EWg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.24.8", - "@babel/helper-validator-identifier": "^7.24.7", + "@babel/helper-string-parser": "^7.25.7", + "@babel/helper-validator-identifier": "^7.25.7", "to-fast-properties": "^2.0.0" }, "engines": { @@ -227,27 +400,33 @@ } }, "node_modules/@docsearch/css": { - "version": "3.6.1", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.6.2.tgz", + "integrity": "sha512-vKNZepO2j7MrYBTZIGXvlUOIR+v9KRf70FApRgovWrj3GTs1EITz/Xb0AOlm1xsQBp16clVZj1SY/qaOJbQtZw==", "dev": true, "license": "MIT" }, "node_modules/@docsearch/js": { - "version": "3.6.1", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/@docsearch/js/-/js-3.6.2.tgz", + "integrity": "sha512-pS4YZF+VzUogYrkblCucQ0Oy2m8Wggk8Kk7lECmZM60hTbaydSIhJTTiCrmoxtBqV8wxORnOqcqqOfbmkkQEcA==", "dev": true, "license": "MIT", "dependencies": { - "@docsearch/react": "3.6.1", + "@docsearch/react": "3.6.2", "preact": "^10.0.0" } }, "node_modules/@docsearch/react": { - "version": "3.6.1", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.6.2.tgz", + "integrity": "sha512-rtZce46OOkVflCQH71IdbXSFK+S8iJZlUF56XBW5rIgx/eG5qoomC7Ag3anZson1bBac/JFQn7XOBfved/IMRA==", "dev": true, "license": "MIT", "dependencies": { "@algolia/autocomplete-core": "1.9.3", "@algolia/autocomplete-preset-algolia": "1.9.3", - "@docsearch/css": "3.6.1", + "@docsearch/css": "3.6.2", "algoliasearch": "^4.19.1" }, "peerDependencies": { @@ -271,8 +450,282 @@ } } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, "node_modules/@esbuild/linux-x64": { "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", "cpu": [ "x64" ], @@ -286,13 +739,273 @@ "node": ">=12" } }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", "dev": true, "license": "MIT" }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.0.tgz", + "integrity": "sha512-Q6HJd7Y6xdB48x8ZNVDOqsbh2uByBhgK8PiQgPhwkIw/HC/YX5Ghq2mQY5sRMZWHb3VsFkWooUVOZHKr7DmDIA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.24.0.tgz", + "integrity": "sha512-ijLnS1qFId8xhKjT81uBHuuJp2lU4x2yxa4ctFPtG+MqEE6+C5f/+X/bStmxapgmwLwiL3ih122xv8kVARNAZA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.24.0.tgz", + "integrity": "sha512-bIv+X9xeSs1XCk6DVvkO+S/z8/2AMt/2lMqdQbMrmVpgFvXlmde9mLcbQpztXm1tajC3raFDqegsH18HQPMYtA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.24.0.tgz", + "integrity": "sha512-X6/nOwoFN7RT2svEQWUsW/5C/fYMBe4fnLK9DQk4SX4mgVBiTA9h64kjUYPvGQ0F/9xwJ5U5UfTbl6BEjaQdBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.24.0.tgz", + "integrity": "sha512-0KXvIJQMOImLCVCz9uvvdPgfyWo93aHHp8ui3FrtOP57svqrF/roSSR5pjqL2hcMp0ljeGlU4q9o/rQaAQ3AYA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.24.0.tgz", + "integrity": "sha512-it2BW6kKFVh8xk/BnHfakEeoLPv8STIISekpoF+nBgWM4d55CZKc7T4Dx1pEbTnYm/xEKMgy1MNtYuoA8RFIWw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.24.0.tgz", + "integrity": "sha512-i0xTLXjqap2eRfulFVlSnM5dEbTVque/3Pi4g2y7cxrs7+a9De42z4XxKLYJ7+OhE3IgxvfQM7vQc43bwTgPwA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.24.0.tgz", + "integrity": "sha512-9E6MKUJhDuDh604Qco5yP/3qn3y7SLXYuiC0Rpr89aMScS2UAmK1wHP2b7KAa1nSjWJc/f/Lc0Wl1L47qjiyQw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.24.0.tgz", + "integrity": "sha512-2XFFPJ2XMEiF5Zi2EBf4h73oR1V/lycirxZxHZNc93SqDN/IWhYYSYj8I9381ikUFXZrz2v7r2tOVk2NBwxrWw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.24.0.tgz", + "integrity": "sha512-M3Dg4hlwuntUCdzU7KjYqbbd+BLq3JMAOhCKdBE3TcMGMZbKkDdJ5ivNdehOssMCIokNHFOsv7DO4rlEOfyKpg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.24.0.tgz", + "integrity": "sha512-mjBaoo4ocxJppTorZVKWFpy1bfFj9FeCMJqzlMQGjpNPY9JwQi7OuS1axzNIk0nMX6jSgy6ZURDZ2w0QW6D56g==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.21.2", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.24.0.tgz", + "integrity": "sha512-ZXFk7M72R0YYFN5q13niV0B7G8/5dcQ9JDp8keJSfr3GoZeXEoMHP/HlvqROA3OMbMdfr19IjCeNAnPUG93b6A==", "cpu": [ "x64" ], @@ -304,7 +1017,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.21.2", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.24.0.tgz", + "integrity": "sha512-w1i+L7kAXZNdYl+vFvzSZy8Y1arS7vMgIy8wusXJzRrPyof5LAb02KGr1PD2EkRcl73kHulIID0M501lN+vobQ==", "cpu": [ "x64" ], @@ -315,35 +1030,125 @@ "linux" ] }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.24.0.tgz", + "integrity": "sha512-VXBrnPWgBpVDCVY6XF3LEW0pOU51KbaHhccHw6AS6vBWIC60eqsH19DAeeObl+g8nKAz04QFdl/Cefta0xQtUQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.24.0.tgz", + "integrity": "sha512-xrNcGDU0OxVcPTH/8n/ShH4UevZxKIO6HJFK0e15XItZP2UcaiLFd5kiX7hJnqCbSztUF8Qot+JWBC/QXRPYWQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.24.0.tgz", + "integrity": "sha512-fbMkAF7fufku0N2dE5TBXcNlg0pt0cJue4xBRE2Qc5Vqikxr4VCgKj/ht6SMdFcOacVA9rqF70APJ8RN/4vMJw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@shikijs/core": { - "version": "1.16.1", + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.22.0.tgz", + "integrity": "sha512-S8sMe4q71TJAW+qG93s5VaiihujRK6rqDFqBnxqvga/3LvqHEnxqBIOPkt//IdXVtHkQWKu4nOQNk0uBGicU7Q==", "dev": true, "license": "MIT", "dependencies": { - "@shikijs/vscode-textmate": "^9.2.0", - "@types/hast": "^3.0.4" + "@shikijs/engine-javascript": "1.22.0", + "@shikijs/engine-oniguruma": "1.22.0", + "@shikijs/types": "1.22.0", + "@shikijs/vscode-textmate": "^9.3.0", + "@types/hast": "^3.0.4", + "hast-util-to-html": "^9.0.3" + } + }, + "node_modules/@shikijs/engine-javascript": { + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-1.22.0.tgz", + "integrity": "sha512-AeEtF4Gcck2dwBqCFUKYfsCq0s+eEbCEbkUuFou53NZ0sTGnJnJ/05KHQFZxpii5HMXbocV9URYVowOP2wH5kw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "1.22.0", + "@shikijs/vscode-textmate": "^9.3.0", + "oniguruma-to-js": "0.4.3" + } + }, + "node_modules/@shikijs/engine-oniguruma": { + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-1.22.0.tgz", + "integrity": "sha512-5iBVjhu/DYs1HB0BKsRRFipRrD7rqjxlWTj4F2Pf+nQSPqc3kcyqFFeZXnBMzDf0HdqaFVvhDRAGiYNvyLP+Mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "1.22.0", + "@shikijs/vscode-textmate": "^9.3.0" } }, "node_modules/@shikijs/transformers": { - "version": "1.16.1", + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/@shikijs/transformers/-/transformers-1.22.0.tgz", + "integrity": "sha512-k7iMOYuGQA62KwAuJOQBgH2IQb5vP8uiB3lMvAMGUgAMMurePOx3Z7oNqJdcpxqZP6I9cc7nc4DNqSKduCxmdg==", "dev": true, "license": "MIT", "dependencies": { - "shiki": "1.16.1" + "shiki": "1.22.0" + } + }, + "node_modules/@shikijs/types": { + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-1.22.0.tgz", + "integrity": "sha512-Fw/Nr7FGFhlQqHfxzZY8Cwtwk5E9nKDUgeLjZgt3UuhcM3yJR9xj3ZGNravZZok8XmEZMiYkSMTPlPkULB8nww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/vscode-textmate": "^9.3.0", + "@types/hast": "^3.0.4" } }, "node_modules/@shikijs/vscode-textmate": { - "version": "9.2.0", + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-9.3.0.tgz", + "integrity": "sha512-jn7/7ky30idSkd/O5yDBfAnVt+JJpepofP/POZ1iMOxK59cOfqIgg/Dj0eFsjOTMw+4ycJN0uhZH/Eb0bs/EUA==", "dev": true, "license": "MIT" }, "node_modules/@types/estree": { - "version": "1.0.5", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", "dev": true, "license": "MIT" }, "node_modules/@types/hast": { "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", "dev": true, "license": "MIT", "dependencies": { @@ -352,11 +1157,15 @@ }, "node_modules/@types/linkify-it": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", "dev": true, "license": "MIT" }, "node_modules/@types/markdown-it": { "version": "14.1.2", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz", + "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", "dev": true, "license": "MIT", "dependencies": { @@ -364,23 +1173,48 @@ "@types/mdurl": "^2" } }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, "node_modules/@types/mdurl": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", "dev": true, "license": "MIT" }, "node_modules/@types/unist": { "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", "dev": true, "license": "MIT" }, "node_modules/@types/web-bluetooth": { "version": "0.0.20", + "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz", + "integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==", "dev": true, "license": "MIT" }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true, + "license": "ISC" + }, "node_modules/@vitejs/plugin-vue": { - "version": "5.1.3", + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.1.4.tgz", + "integrity": "sha512-N2XSI2n3sQqp5w7Y/AN/L2XDjBIRGqXko+eDp42sydYSBeJuSm5a1sLf8zakmo8u7tA8NmBgoDLA1HeOESjp9A==", "dev": true, "license": "MIT", "engines": { @@ -392,65 +1226,77 @@ } }, "node_modules/@vue/compiler-core": { - "version": "3.4.38", + "version": "3.5.12", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.12.tgz", + "integrity": "sha512-ISyBTRMmMYagUxhcpyEH0hpXRd/KqDU4ymofPgl2XAkY9ZhQ+h0ovEZJIiPop13UmR/54oA2cgMDjgroRelaEw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.24.7", - "@vue/shared": "3.4.38", + "@babel/parser": "^7.25.3", + "@vue/shared": "3.5.12", "entities": "^4.5.0", "estree-walker": "^2.0.2", "source-map-js": "^1.2.0" } }, "node_modules/@vue/compiler-dom": { - "version": "3.4.38", + "version": "3.5.12", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.12.tgz", + "integrity": "sha512-9G6PbJ03uwxLHKQ3P42cMTi85lDRvGLB2rSGOiQqtXELat6uI4n8cNz9yjfVHRPIu+MsK6TE418Giruvgptckg==", "dev": true, "license": "MIT", "dependencies": { - "@vue/compiler-core": "3.4.38", - "@vue/shared": "3.4.38" + "@vue/compiler-core": "3.5.12", + "@vue/shared": "3.5.12" } }, "node_modules/@vue/compiler-sfc": { - "version": "3.4.38", + "version": "3.5.12", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.12.tgz", + "integrity": "sha512-2k973OGo2JuAa5+ZlekuQJtitI5CgLMOwgl94BzMCsKZCX/xiqzJYzapl4opFogKHqwJk34vfsaKpfEhd1k5nw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.24.7", - "@vue/compiler-core": "3.4.38", - "@vue/compiler-dom": "3.4.38", - "@vue/compiler-ssr": "3.4.38", - "@vue/shared": "3.4.38", + "@babel/parser": "^7.25.3", + "@vue/compiler-core": "3.5.12", + "@vue/compiler-dom": "3.5.12", + "@vue/compiler-ssr": "3.5.12", + "@vue/shared": "3.5.12", "estree-walker": "^2.0.2", - "magic-string": "^0.30.10", - "postcss": "^8.4.40", + "magic-string": "^0.30.11", + "postcss": "^8.4.47", "source-map-js": "^1.2.0" } }, "node_modules/@vue/compiler-ssr": { - "version": "3.4.38", + "version": "3.5.12", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.12.tgz", + "integrity": "sha512-eLwc7v6bfGBSM7wZOGPmRavSWzNFF6+PdRhE+VFJhNCgHiF8AM7ccoqcv5kBXA2eWUfigD7byekvf/JsOfKvPA==", "dev": true, "license": "MIT", "dependencies": { - "@vue/compiler-dom": "3.4.38", - "@vue/shared": "3.4.38" + "@vue/compiler-dom": "3.5.12", + "@vue/shared": "3.5.12" } }, "node_modules/@vue/devtools-api": { - "version": "7.3.9", + "version": "7.4.6", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.4.6.tgz", + "integrity": "sha512-XipBV5k0/IfTr0sNBDTg7OBUCp51cYMMXyPxLXJZ4K/wmUeMqt8cVdr2ZZGOFq+si/jTyCYnNxeKoyev5DOUUA==", "dev": true, "license": "MIT", "dependencies": { - "@vue/devtools-kit": "^7.3.9" + "@vue/devtools-kit": "^7.4.6" } }, "node_modules/@vue/devtools-kit": { - "version": "7.3.9", + "version": "7.4.6", + "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.4.6.tgz", + "integrity": "sha512-NbYBwPWgEic1AOd9bWExz9weBzFdjiIfov0yRn4DrRfR+EQJCI9dn4I0XS7IxYGdkmUJi8mFW42LLk18WsGqew==", "dev": true, "license": "MIT", "dependencies": { - "@vue/devtools-shared": "^7.3.9", + "@vue/devtools-shared": "^7.4.6", "birpc": "^0.2.17", "hookable": "^5.5.3", "mitt": "^3.0.1", @@ -460,7 +1306,9 @@ } }, "node_modules/@vue/devtools-shared": { - "version": "7.3.9", + "version": "7.4.6", + "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.4.6.tgz", + "integrity": "sha512-rPeSBzElnHYMB05Cc056BQiJpgocQjY8XVulgni+O9a9Gr9tNXgPteSzFFD+fT/iWMxNuUgGKs9CuW5DZewfIg==", "dev": true, "license": "MIT", "dependencies": { @@ -468,71 +1316,112 @@ } }, "node_modules/@vue/reactivity": { - "version": "3.4.38", + "version": "3.5.12", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.12.tgz", + "integrity": "sha512-UzaN3Da7xnJXdz4Okb/BGbAaomRHc3RdoWqTzlvd9+WBR5m3J39J1fGcHes7U3za0ruYn/iYy/a1euhMEHvTAg==", "dev": true, "license": "MIT", "dependencies": { - "@vue/shared": "3.4.38" + "@vue/shared": "3.5.12" } }, "node_modules/@vue/runtime-core": { - "version": "3.4.38", + "version": "3.5.12", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.12.tgz", + "integrity": "sha512-hrMUYV6tpocr3TL3Ad8DqxOdpDe4zuQY4HPY3X/VRh+L2myQO8MFXPAMarIOSGNu0bFAjh1yBkMPXZBqCk62Uw==", "dev": true, "license": "MIT", "dependencies": { - "@vue/reactivity": "3.4.38", - "@vue/shared": "3.4.38" + "@vue/reactivity": "3.5.12", + "@vue/shared": "3.5.12" } }, "node_modules/@vue/runtime-dom": { - "version": "3.4.38", + "version": "3.5.12", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.12.tgz", + "integrity": "sha512-q8VFxR9A2MRfBr6/55Q3umyoN7ya836FzRXajPB6/Vvuv0zOPL+qltd9rIMzG/DbRLAIlREmnLsplEF/kotXKA==", "dev": true, "license": "MIT", "dependencies": { - "@vue/reactivity": "3.4.38", - "@vue/runtime-core": "3.4.38", - "@vue/shared": "3.4.38", + "@vue/reactivity": "3.5.12", + "@vue/runtime-core": "3.5.12", + "@vue/shared": "3.5.12", "csstype": "^3.1.3" } }, "node_modules/@vue/server-renderer": { - "version": "3.4.38", + "version": "3.5.12", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.12.tgz", + "integrity": "sha512-I3QoeDDeEPZm8yR28JtY+rk880Oqmj43hreIBVTicisFTx/Dl7JpG72g/X7YF8hnQD3IFhkky5i2bPonwrTVPg==", "dev": true, "license": "MIT", "dependencies": { - "@vue/compiler-ssr": "3.4.38", - "@vue/shared": "3.4.38" + "@vue/compiler-ssr": "3.5.12", + "@vue/shared": "3.5.12" }, "peerDependencies": { - "vue": "3.4.38" + "vue": "3.5.12" } }, "node_modules/@vue/shared": { - "version": "3.4.38", + "version": "3.5.12", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.12.tgz", + "integrity": "sha512-L2RPSAwUFbgZH20etwrXyVyCBu9OxRSi8T/38QsvnkJyvq2LufW2lDCOzm7t/U9C1mkhJGWYfCuFBCmIuNivrg==", "dev": true, "license": "MIT" }, "node_modules/@vueuse/core": { - "version": "11.0.3", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-11.1.0.tgz", + "integrity": "sha512-P6dk79QYA6sKQnghrUz/1tHi0n9mrb/iO1WTMk/ElLmTyNqgDeSZ3wcDf6fRBGzRJbeG1dxzEOvLENMjr+E3fg==", "dev": true, "license": "MIT", "dependencies": { "@types/web-bluetooth": "^0.0.20", - "@vueuse/metadata": "11.0.3", - "@vueuse/shared": "11.0.3", + "@vueuse/metadata": "11.1.0", + "@vueuse/shared": "11.1.0", "vue-demi": ">=0.14.10" }, "funding": { "url": "https://github.com/sponsors/antfu" } }, + "node_modules/@vueuse/core/node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, "node_modules/@vueuse/integrations": { - "version": "11.0.3", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/@vueuse/integrations/-/integrations-11.1.0.tgz", + "integrity": "sha512-O2ZgrAGPy0qAjpoI2YR3egNgyEqwG85fxfwmA9BshRIGjV4G6yu6CfOPpMHAOoCD+UfsIl7Vb1bXJ6ifrHYDDA==", "dev": true, "license": "MIT", "dependencies": { - "@vueuse/core": "11.0.3", - "@vueuse/shared": "11.0.3", + "@vueuse/core": "11.1.0", + "@vueuse/shared": "11.1.0", "vue-demi": ">=0.14.10" }, "funding": { @@ -591,8 +1480,37 @@ } } }, + "node_modules/@vueuse/integrations/node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, "node_modules/@vueuse/metadata": { - "version": "11.0.3", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-11.1.0.tgz", + "integrity": "sha512-l9Q502TBTaPYGanl1G+hPgd3QX5s4CGnpXriVBR5fEZ/goI6fvDaVmIl3Td8oKFurOxTmbXvBPSsgrd6eu6HYg==", "dev": true, "license": "MIT", "funding": { @@ -600,7 +1518,9 @@ } }, "node_modules/@vueuse/shared": { - "version": "11.0.3", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-11.1.0.tgz", + "integrity": "sha512-YUtIpY122q7osj+zsNMFAfMTubGz0sn5QzE5gPzAIiCmtt2ha3uQUY1+JPyL4gRCTsLPX82Y9brNbo/aqlA91w==", "dev": true, "license": "MIT", "dependencies": { @@ -610,8 +1530,37 @@ "url": "https://github.com/sponsors/antfu" } }, + "node_modules/@vueuse/shared/node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, "node_modules/algoliasearch": { "version": "4.24.0", + "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-4.24.0.tgz", + "integrity": "sha512-bf0QV/9jVejssFBmz2HQLxUadxk574t4iwjCKp5E7NBzwKkrDEhKPISIIjAU/p6K5qDx3qoeh4+26zWN1jmw3g==", "dev": true, "license": "MIT", "dependencies": { @@ -632,16 +1581,107 @@ "@algolia/transporter": "4.24.0" } }, + "node_modules/algoliasearch/node_modules/@algolia/client-common": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.24.0.tgz", + "integrity": "sha512-bc2ROsNL6w6rqpl5jj/UywlIYC21TwSSoFHKl01lYirGMW+9Eek6r02Tocg4gZ8HAw3iBvu6XQiM3BEbmEMoiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/requester-common": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, + "node_modules/algoliasearch/node_modules/@algolia/client-search": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.24.0.tgz", + "integrity": "sha512-uRW6EpNapmLAD0mW47OXqTP8eiIx5F6qN9/x/7HHO6owL3N1IXqydGwW5nhDFBrV+ldouro2W1VX3XlcUXEFCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "4.24.0", + "@algolia/requester-common": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, + "node_modules/algoliasearch/node_modules/@algolia/requester-browser-xhr": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.24.0.tgz", + "integrity": "sha512-Z2NxZMb6+nVXSjF13YpjYTdvV3032YTBSGm2vnYvYPA6mMxzM3v5rsCiSspndn9rzIW4Qp1lPHBvuoKJV6jnAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/requester-common": "4.24.0" + } + }, + "node_modules/algoliasearch/node_modules/@algolia/requester-node-http": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.24.0.tgz", + "integrity": "sha512-JF18yTjNOVYvU/L3UosRcvbPMGT9B+/GQWNWnenIImglzNVGpyzChkXLnrSf6uxwVNO6ESGu6oN8MqcGQcjQJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/requester-common": "4.24.0" + } + }, "node_modules/birpc": { - "version": "0.2.17", + "version": "0.2.19", + "resolved": "https://registry.npmjs.org/birpc/-/birpc-0.2.19.tgz", + "integrity": "sha512-5WeXXAvTmitV1RqJFppT5QtUiz2p1mRSYU000Jkft5ZUCLJIk4uQriYNO50HknxKwM6jd8utNc66K1qGIwwWBQ==", "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/antfu" } }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/copy-anything": { "version": "3.0.5", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-3.0.5.tgz", + "integrity": "sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==", "dev": true, "license": "MIT", "dependencies": { @@ -656,11 +1696,39 @@ }, "node_modules/csstype": { "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "dev": true, "license": "MIT" }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/entities": { "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", "dev": true, "license": "BSD-2-Clause", "engines": { @@ -672,6 +1740,8 @@ }, "node_modules/esbuild": { "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -709,24 +1779,96 @@ }, "node_modules/estree-walker": { "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", "dev": true, "license": "MIT" }, "node_modules/focus-trap": { - "version": "7.5.4", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.6.0.tgz", + "integrity": "sha512-1td0l3pMkWJLFipobUcGaf+5DTY4PLDDrcqoSaKP8ediO/CoWCCYk/fT/Y2A4e6TNB+Sh6clRJCjOPPnKoNHnQ==", "dev": true, "license": "MIT", "dependencies": { "tabbable": "^6.2.0" } }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/hast-util-to-html": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.3.tgz", + "integrity": "sha512-M17uBDzMJ9RPCqLMO92gNNUDuBSq10a25SDBI08iCCxmorf4Yy6sYHK57n9WAbRAAaU+DuR4W6GN9K4DFZesYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-whitespace": "^3.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "stringify-entities": "^4.0.0", + "zwitch": "^2.0.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/hookable": { "version": "5.5.3", + "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", + "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", "dev": true, "license": "MIT" }, + "node_modules/html-void-elements": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", + "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-what": { "version": "4.1.16", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.16.tgz", + "integrity": "sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==", "dev": true, "license": "MIT", "engines": { @@ -737,7 +1879,9 @@ } }, "node_modules/magic-string": { - "version": "0.30.11", + "version": "0.30.12", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.12.tgz", + "integrity": "sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw==", "dev": true, "license": "MIT", "dependencies": { @@ -746,21 +1890,145 @@ }, "node_modules/mark.js": { "version": "8.11.1", + "resolved": "https://registry.npmjs.org/mark.js/-/mark.js-8.11.1.tgz", + "integrity": "sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==", "dev": true, "license": "MIT" }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz", + "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", + "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.0.tgz", + "integrity": "sha512-pS+ROfCXAGLWCOc8egcBvT0kf27GoWMqtdarNfDcjb6YLuV5cM3ioG45Ys2qOVqeqSbjaKg72vU+Wby3eddPsA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.0.tgz", + "integrity": "sha512-WhYv5UEcZrbAtlsnPuChHUAsu/iBPOVaEVsntLBIdpibO0ddy8OzavZz3iL2xVvBZOpolujSliP65Kq0/7KIYw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", + "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.0.tgz", + "integrity": "sha512-oNh6S2WMHWRZrmutsRmDDfkzKtxF+bc2VxLC9dvtrDIRFln627VsFP6fLMgTryGDljgLPjkrzQSDcPrjPyDJ5w==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, "node_modules/minisearch": { "version": "7.1.0", + "resolved": "https://registry.npmjs.org/minisearch/-/minisearch-7.1.0.tgz", + "integrity": "sha512-tv7c/uefWdEhcu6hvrfTihflgeEi2tN6VV7HJnCjK6VxM75QQJh4t9FwJCsA2EsRS8LCnu3W87CuGPWMocOLCA==", "dev": true, "license": "MIT" }, "node_modules/mitt": { "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", "dev": true, "license": "MIT" }, "node_modules/nanoid": { "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", "dev": true, "funding": [ { @@ -776,18 +2044,37 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/oniguruma-to-js": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/oniguruma-to-js/-/oniguruma-to-js-0.4.3.tgz", + "integrity": "sha512-X0jWUcAlxORhOqqBREgPMgnshB7ZGYszBNspP+tS9hPD3l13CdaXcHbgImoHUHlrvGx/7AvFEkTRhAGYh+jzjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "regex": "^4.3.2" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/perfect-debounce": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", "dev": true, "license": "MIT" }, "node_modules/picocolors": { - "version": "1.0.1", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", + "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", "dev": true, "license": "ISC" }, "node_modules/postcss": { - "version": "8.4.44", + "version": "8.4.47", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", + "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", "dev": true, "funding": [ { @@ -806,15 +2093,17 @@ "license": "MIT", "dependencies": { "nanoid": "^3.3.7", - "picocolors": "^1.0.1", - "source-map-js": "^1.2.0" + "picocolors": "^1.1.0", + "source-map-js": "^1.2.1" }, "engines": { "node": "^10 || ^12 || >=14" } }, "node_modules/preact": { - "version": "10.23.2", + "version": "10.24.2", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.24.2.tgz", + "integrity": "sha512-1cSoF0aCC8uaARATfrlz4VCBqE8LwZwRfLgkxJOQwAlQt6ayTmi0D9OF7nXid1POI5SZidFuG9CnlXbDfLqY/Q==", "dev": true, "license": "MIT", "funding": { @@ -822,17 +2111,39 @@ "url": "https://opencollective.com/preact" } }, + "node_modules/property-information": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz", + "integrity": "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/regex": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/regex/-/regex-4.3.3.tgz", + "integrity": "sha512-r/AadFO7owAq1QJVeZ/nq9jNS1vyZt+6t1p/E59B56Rn2GCya+gr1KSyOzNL/er+r+B7phv5jG2xU2Nz1YkmJg==", + "dev": true, + "license": "MIT" + }, "node_modules/rfdc": { "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", "dev": true, "license": "MIT" }, "node_modules/rollup": { - "version": "4.21.2", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.24.0.tgz", + "integrity": "sha512-DOmrlGSXNk1DM0ljiQA+i+o0rSLhtii1je5wgk60j49d1jHT5YYttBv1iWOnYSTG+fZZESUOSNiAl89SIet+Cg==", "dev": true, "license": "MIT", "dependencies": { - "@types/estree": "1.0.5" + "@types/estree": "1.0.6" }, "bin": { "rollup": "dist/bin/rollup" @@ -842,59 +2153,98 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.21.2", - "@rollup/rollup-android-arm64": "4.21.2", - "@rollup/rollup-darwin-arm64": "4.21.2", - "@rollup/rollup-darwin-x64": "4.21.2", - "@rollup/rollup-linux-arm-gnueabihf": "4.21.2", - "@rollup/rollup-linux-arm-musleabihf": "4.21.2", - "@rollup/rollup-linux-arm64-gnu": "4.21.2", - "@rollup/rollup-linux-arm64-musl": "4.21.2", - "@rollup/rollup-linux-powerpc64le-gnu": "4.21.2", - "@rollup/rollup-linux-riscv64-gnu": "4.21.2", - "@rollup/rollup-linux-s390x-gnu": "4.21.2", - "@rollup/rollup-linux-x64-gnu": "4.21.2", - "@rollup/rollup-linux-x64-musl": "4.21.2", - "@rollup/rollup-win32-arm64-msvc": "4.21.2", - "@rollup/rollup-win32-ia32-msvc": "4.21.2", - "@rollup/rollup-win32-x64-msvc": "4.21.2", + "@rollup/rollup-android-arm-eabi": "4.24.0", + "@rollup/rollup-android-arm64": "4.24.0", + "@rollup/rollup-darwin-arm64": "4.24.0", + "@rollup/rollup-darwin-x64": "4.24.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.24.0", + "@rollup/rollup-linux-arm-musleabihf": "4.24.0", + "@rollup/rollup-linux-arm64-gnu": "4.24.0", + "@rollup/rollup-linux-arm64-musl": "4.24.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.24.0", + "@rollup/rollup-linux-riscv64-gnu": "4.24.0", + "@rollup/rollup-linux-s390x-gnu": "4.24.0", + "@rollup/rollup-linux-x64-gnu": "4.24.0", + "@rollup/rollup-linux-x64-musl": "4.24.0", + "@rollup/rollup-win32-arm64-msvc": "4.24.0", + "@rollup/rollup-win32-ia32-msvc": "4.24.0", + "@rollup/rollup-win32-x64-msvc": "4.24.0", "fsevents": "~2.3.2" } }, "node_modules/search-insights": { - "version": "2.17.0", + "version": "2.17.2", + "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.17.2.tgz", + "integrity": "sha512-zFNpOpUO+tY2D85KrxJ+aqwnIfdEGi06UH2+xEb+Bp9Mwznmauqc9djbnBibJO5mpfUPPa8st6Sx65+vbeO45g==", "dev": true, "license": "MIT", "peer": true }, "node_modules/shiki": { - "version": "1.16.1", + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-1.22.0.tgz", + "integrity": "sha512-/t5LlhNs+UOKQCYBtl5ZsH/Vclz73GIqT2yQsCBygr8L/ppTdmpL4w3kPLoZJbMKVWtoG77Ue1feOjZfDxvMkw==", "dev": true, "license": "MIT", "dependencies": { - "@shikijs/core": "1.16.1", - "@shikijs/vscode-textmate": "^9.2.0", + "@shikijs/core": "1.22.0", + "@shikijs/engine-javascript": "1.22.0", + "@shikijs/engine-oniguruma": "1.22.0", + "@shikijs/types": "1.22.0", + "@shikijs/vscode-textmate": "^9.3.0", "@types/hast": "^3.0.4" } }, "node_modules/source-map-js": { - "version": "1.2.0", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/speakingurl": { "version": "14.0.1", + "resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz", + "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==", "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "dev": true, + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/superjson": { "version": "2.2.1", + "resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.1.tgz", + "integrity": "sha512-8iGv75BYOa0xRJHK5vRLEjE2H/i4lulTjzpUXic3Eg8akftYjkmQDa8JARQ42rlczXyFR3IeRoeFCc7RxHsYZA==", "dev": true, "license": "MIT", "dependencies": { @@ -906,24 +2256,144 @@ }, "node_modules/tabbable": { "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", + "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==", "dev": true, "license": "MIT" }, "node_modules/to-fast-properties": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", "dev": true, "license": "MIT", "engines": { "node": ">=4" } }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/unist-util-is": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", + "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", + "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz", + "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/vite": { - "version": "5.4.2", + "version": "5.4.8", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.8.tgz", + "integrity": "sha512-FqrItQ4DT1NC4zCUqMB4c4AZORMKIa0m8/URVCZ77OZ/QSNeJ54bU1vrFADbDsuwfIPcgknRkmqakQcgnL4GiQ==", "dev": true, "license": "MIT", "dependencies": { "esbuild": "^0.21.3", - "postcss": "^8.4.41", + "postcss": "^8.4.43", "rollup": "^4.20.0" }, "bin": { @@ -976,26 +2446,29 @@ } }, "node_modules/vitepress": { - "version": "1.3.4", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/vitepress/-/vitepress-1.4.0.tgz", + "integrity": "sha512-JXCv4EsKTDyAFb6C/UjZr7nsGAzZ6mafVk2rx7rG5o8N+B/4QstIk+iEOe/9dKoU6V624UIC6g1pZ+K63rxhlw==", "dev": true, "license": "MIT", "dependencies": { - "@docsearch/css": "^3.6.1", - "@docsearch/js": "^3.6.1", - "@shikijs/core": "^1.13.0", - "@shikijs/transformers": "^1.13.0", + "@docsearch/css": "^3.6.2", + "@docsearch/js": "^3.6.2", + "@shikijs/core": "^1.22.0", + "@shikijs/transformers": "^1.22.0", + "@shikijs/types": "^1.22.0", "@types/markdown-it": "^14.1.2", - "@vitejs/plugin-vue": "^5.1.2", - "@vue/devtools-api": "^7.3.8", - "@vue/shared": "^3.4.38", - "@vueuse/core": "^11.0.0", - "@vueuse/integrations": "^11.0.0", - "focus-trap": "^7.5.4", + "@vitejs/plugin-vue": "^5.1.4", + "@vue/devtools-api": "^7.4.6", + "@vue/shared": "^3.5.11", + "@vueuse/core": "^11.1.0", + "@vueuse/integrations": "^11.1.0", + "focus-trap": "^7.6.0", "mark.js": "8.11.1", "minisearch": "^7.1.0", - "shiki": "^1.13.0", - "vite": "^5.4.1", - "vue": "^3.4.38" + "shiki": "^1.22.0", + "vite": "^5.4.8", + "vue": "^3.5.11" }, "bin": { "vitepress": "bin/vitepress.js" @@ -1014,15 +2487,17 @@ } }, "node_modules/vue": { - "version": "3.4.38", + "version": "3.5.12", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.12.tgz", + "integrity": "sha512-CLVZtXtn2ItBIi/zHZ0Sg1Xkb7+PU32bJJ8Bmy7ts3jxXTcbfsEfBivFYYWz1Hur+lalqGAh65Coin0r+HRUfg==", "dev": true, "license": "MIT", "dependencies": { - "@vue/compiler-dom": "3.4.38", - "@vue/compiler-sfc": "3.4.38", - "@vue/runtime-dom": "3.4.38", - "@vue/server-renderer": "3.4.38", - "@vue/shared": "3.4.38" + "@vue/compiler-dom": "3.5.12", + "@vue/compiler-sfc": "3.5.12", + "@vue/runtime-dom": "3.5.12", + "@vue/server-renderer": "3.5.12", + "@vue/shared": "3.5.12" }, "peerDependencies": { "typescript": "*" @@ -1033,29 +2508,15 @@ } } }, - "node_modules/vue-demi": { - "version": "0.14.10", + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", "dev": true, - "hasInstallScript": true, "license": "MIT", - "bin": { - "vue-demi-fix": "bin/vue-demi-fix.js", - "vue-demi-switch": "bin/vue-demi-switch.js" - }, - "engines": { - "node": ">=12" - }, "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "@vue/composition-api": "^1.0.0-rc.1", - "vue": "^3.0.0-0 || ^2.6.0" - }, - "peerDependenciesMeta": { - "@vue/composition-api": { - "optional": true - } + "type": "github", + "url": "https://github.com/sponsors/wooorm" } } } diff --git a/package.json b/package.json index 29967d6b8..8578e6172 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "docs:preview": "vitepress preview docs" }, "devDependencies": { - "vitepress": "^1.3.4", - "vue": "^3.4.38" + "vitepress": "^1.4.0", + "vue": "^3.5.12" } } \ No newline at end of file diff --git a/pumpkin-config/src/compression.rs b/pumpkin-config/src/compression.rs index 9b9b6b9d9..74eb1c1bc 100644 --- a/pumpkin-config/src/compression.rs +++ b/pumpkin-config/src/compression.rs @@ -5,7 +5,7 @@ use serde_inline_default::serde_inline_default; #[derive(Deserialize, Serialize)] /// Packet compression pub struct CompressionConfig { - /// Is compression enabled ? + /// Wether compression is enabled #[serde_inline_default(true)] pub enabled: bool, #[serde(flatten)] diff --git a/pumpkin-entity/README.md b/pumpkin-entity/README.md deleted file mode 100644 index e69de29bb..000000000 diff --git a/pumpkin-protocol/README.md b/pumpkin-protocol/README.md index 9eec8c892..e7b469872 100644 --- a/pumpkin-protocol/README.md +++ b/pumpkin-protocol/README.md @@ -1,6 +1,10 @@ ### Pumpkin Protocol Contains all Serverbound(Client->Server) and Clientbound(Server->Client) Packets. +### Features +- [x] ZLib Compression +- [x] AES/CFB8 Encryiption + Packets in the Pumpkin protocol are organized by functionality and state. `server`: Contains definitions for serverbound packets. diff --git a/pumpkin-protocol/src/server/config/s_plugin_message.rs b/pumpkin-protocol/src/server/config/s_plugin_message.rs index 19e06c853..9150bf296 100644 --- a/pumpkin-protocol/src/server/config/s_plugin_message.rs +++ b/pumpkin-protocol/src/server/config/s_plugin_message.rs @@ -14,7 +14,7 @@ pub struct SPluginMessage { impl ServerPacket for SPluginMessage { fn read(bytebuf: &mut ByteBuffer) -> Result { Ok(Self { - channel: bytebuf.get_string().unwrap(), + channel: bytebuf.get_string()?, data: bytebuf.get_slice().to_vec(), }) } From 7505b09a54aedf5eedbfd68b828362fce06e08aa Mon Sep 17 00:00:00 2001 From: David Rush <43554173+dbwrush@users.noreply.github.com> Date: Sat, 12 Oct 2024 10:47:38 -0500 Subject: [PATCH 62/65] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9f9505c62..af83e5208 100644 --- a/README.md +++ b/README.md @@ -87,7 +87,7 @@ The Documentation of Pumpkin can be found at https://snowiiii.github.io/Pumpkin/ Consider joining our [discord](https://discord.gg/wT8XjrjKkf) to stay up-to-date on events, updates, and connect with other members. ## Funding -If you want to fund my and help the project, Check out my [GitHub sponsors](https://github.com/sponsors/Snowiiii) +If you want to fund me and help the project, Check out my [GitHub sponsors](https://github.com/sponsors/Snowiiii) ## Thanks From 47f149d154ba3ef98b2eae6101454733af331f2f Mon Sep 17 00:00:00 2001 From: Snowiiii Date: Sun, 13 Oct 2024 12:15:16 +0200 Subject: [PATCH 63/65] Support custom authentication servers --- README.md | 3 +- docs/about/introduction.md | 3 +- docs/config/advanced.md | 41 ++++++++++++++++- docs/developer/authentication.md | 68 ++++++++++++++++++++++++++-- docs/index.md | 6 +-- pumpkin-config/src/auth.rs | 6 +++ pumpkin/src/client/authentication.rs | 14 ++++-- 7 files changed, 129 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index af83e5208..2c23edd84 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,8 @@ and customizable experience. It prioritizes performance and player enjoyment whi ## What Pumpkin will not -- Provide compatibility with Vanilla or Bukkit servers (including configs and plugins). +- Be a drop-in replacement for vanilla or other servers +- Be compatible with plugins or mods for other servers - Function as a framework for building a server from scratch. > [!IMPORTANT] diff --git a/docs/about/introduction.md b/docs/about/introduction.md index ffb6f8b28..42ed30c2a 100644 --- a/docs/about/introduction.md +++ b/docs/about/introduction.md @@ -15,7 +15,8 @@ and customizable experience. It prioritizes performance and player enjoyment whi ## What Pumpkin will not -- Provide compatibility with Vanilla or Bukkit servers (including configs and plugins). +- Be a drop-in replacement for vanilla or other servers +- Be compatible with plugins or mods for other servers - Function as a framework for building a server from scratch. > [!IMPORTANT] diff --git a/docs/config/advanced.md b/docs/config/advanced.md index 316d4ca46..60fd99047 100644 --- a/docs/config/advanced.md +++ b/docs/config/advanced.md @@ -41,6 +41,19 @@ Wether Authentication is enabled enabled=false ``` +#### Authentication URL + +The Authentication URL being used + +> [!IMPORTANT] +> {username} | The Username from the requested player +> +> {server_hash} | The SHA1 Encrypted hash + +```toml +auth_url="https://sessionserver.mojang.com/session/minecraft/hasJoined?username={username}&serverId={server_hash}" +``` + #### Prevent Proxy Connections Prevent proxy connections @@ -49,6 +62,21 @@ Prevent proxy connections prevent_proxy_connections=false ``` +#### Prevent Proxy Connections URL + +The Authentication URL being used + +> [!IMPORTANT] +> {username} | The Username from the requested player +> +> {server_hash} | The SHA1 Encrypted hash +> +> {ip} | The IP of the requested Player + +```toml +prevent_proxy_connection_auth_url = "https://sessionserver.mojang.com/session/minecraft/hasJoined?username={username}&serverId={server_hash}&ip={ip}" +``` + #### Player Profile `authentication.player_profile` @@ -341,6 +369,7 @@ swing=true ``` ### Logging + `logging` Whether Logging is enabled @@ -349,10 +378,13 @@ enable=true ``` #### Level + At which level should be logged + ```toml level=Info ``` + ```toml Off Error @@ -363,26 +395,33 @@ Trace ``` #### Env + Enables the user to choose log level by setting `RUST_LOG=` environment variable + ```toml env=false ``` #### Threads + Should threads be printed in the message + ```toml threads=true ``` #### Color + Should color be enabled for logging messages + ```toml color=true ``` #### Timestamp + Should the timestamp be printed in the message ```toml timestamp=true -``` \ No newline at end of file +``` diff --git a/docs/developer/authentication.md b/docs/developer/authentication.md index c590d73e7..9d9accb31 100644 --- a/docs/developer/authentication.md +++ b/docs/developer/authentication.md @@ -21,9 +21,71 @@ Cracked accounts are also often used for Botting and [Denial of Service](https:/ By default the `online_mode` is enabled in the configuration, This enables Authentication disallowing [Cracked Accounts](#cracked-accounts). When you are willing to allow Cracked Accounts, you can dissable `online_mode` in the `configuration.toml` -### How Authentication works +### How Mojang Authentication works + To ensure a player has a premium accounts: 1. A client with a premium account sends a login request to the Mojang session server. -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 . +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. +4. If the request was successfull, It will give use more information about the Player (e.g. UUID, Name, Skin/Cape...) + +### Custom Authentication Server + +Pumpkin does support custom Authentication servers, You can replace the Authentication URL in `features.toml`. + +Pumpkin Authentication works like this (Mojang/Custom): + +1. GET Request > Authentication + +2. Status Code 200 > Successfull + +3. Successfull > Parse JSON Game Profile + +#### Game Profile + +```rust +id: UUID +``` + +```rust +name: String +``` + +```rust +properties: Array +``` + +> [!IMPORTANT] +> Optional, Only present when actions are taken + +```rust +profile_actions: Array +``` + +##### Property + +```rust +name: String +``` + +> [!IMPORTANT] +> base 64 + +```rust +- value: String +``` + +> [!IMPORTANT] +> Optional, base 64 + +```rust +- signature: String +``` + +##### Profile Action + +```rust +FORCED_NAME_CHANGE +USING_BANNED_SKIN +``` diff --git a/docs/index.md b/docs/index.md index 8a34490e0..2359e0c8d 100644 --- a/docs/index.md +++ b/docs/index.md @@ -11,11 +11,11 @@ hero: text: Quick Start link: /about/quick-start - theme: alt - text: Documentation - link: /about/introduction + text: Configuration + link: /config/introduction - theme: alt text: For developers - link: /plugins/about + link: /developer/introduction features: - title: Written in Rust diff --git a/pumpkin-config/src/auth.rs b/pumpkin-config/src/auth.rs index ad9d611b8..09b77b8b2 100644 --- a/pumpkin-config/src/auth.rs +++ b/pumpkin-config/src/auth.rs @@ -9,10 +9,14 @@ pub struct AuthenticationConfig { #[serde_inline_default(true)] pub enabled: bool, + pub auth_url: String, + /// Prevent proxy connections. #[serde_inline_default(false)] pub prevent_proxy_connections: bool, + pub prevent_proxy_connection_auth_url: String, + /// Player profile handling. #[serde(default)] pub player_profile: PlayerProfileConfig, @@ -29,6 +33,8 @@ impl Default for AuthenticationConfig { prevent_proxy_connections: false, player_profile: Default::default(), textures: Default::default(), + auth_url: "https://sessionserver.mojang.com/session/minecraft/hasJoined?username={username}&serverId={server_hash}".to_string(), + prevent_proxy_connection_auth_url: "https://sessionserver.mojang.com/session/minecraft/hasJoined?username={username}&serverId={server_hash}&ip={ip}".to_string(), } } } diff --git a/pumpkin/src/client/authentication.rs b/pumpkin/src/client/authentication.rs index 062c01b43..195dfa2ae 100644 --- a/pumpkin/src/client/authentication.rs +++ b/pumpkin/src/client/authentication.rs @@ -60,9 +60,18 @@ pub async fn authenticate( assert!(ADVANCED_CONFIG.authentication.enabled); assert!(server.auth_client.is_some()); let address = if ADVANCED_CONFIG.authentication.prevent_proxy_connections { - format!("https://sessionserver.mojang.com/session/minecraft/hasJoined?username={username}&serverId={server_hash}&ip={ip}") + ADVANCED_CONFIG + .authentication + .auth_url + .replace("{username}", username) + .replace("{server_hash}", server_hash) + .replace("{}", &ip.to_string()) } else { - format!("https://sessionserver.mojang.com/session/minecraft/hasJoined?username={username}&serverId={server_hash}") + ADVANCED_CONFIG + .authentication + .auth_url + .replace("{username}", username) + .replace("{server_hash}", server_hash) }; let auth_client = server .auth_client @@ -107,7 +116,6 @@ pub fn is_texture_url_valid(url: Url, config: &TextureConfig) -> Result<(), Text return Err(TextureError::DisallowedUrlScheme(scheme.to_string())); } let domain = url.domain().unwrap_or(""); - dbg!(domain); if !config .allowed_url_domains .iter() From 033648bd94f4d936b53485ce32893381cc35fe19 Mon Sep 17 00:00:00 2001 From: Snowiiii Date: Sun, 13 Oct 2024 13:05:59 +0200 Subject: [PATCH 64/65] docs: add troubleshooting --- docs/.vitepress/config.mts | 6 +++ docs/config/introduction.md | 9 +++-- docs/developer/authentication.md | 56 +++++++++------------------ docs/developer/introduction.md | 2 +- docs/troubleshooting/common_issues.md | 33 ++++++++++++++++ package-lock.json | 12 +++--- package.json | 2 +- 7 files changed, 72 insertions(+), 48 deletions(-) create mode 100644 docs/troubleshooting/common_issues.md diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index b2cbd9cf1..be407364b 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -41,6 +41,12 @@ export default defineConfig({ { text: "Advanced", link: "/config/advanced" }, ], }, + { + text: "Troubleshooting", + items: [ + { text: "Common Issues", link: "/troubleshooting/common_issues.md" }, + ], + }, ], socialLinks: [ diff --git a/docs/config/introduction.md b/docs/config/introduction.md index b008cd628..b7cd31e68 100644 --- a/docs/config/introduction.md +++ b/docs/config/introduction.md @@ -1,13 +1,16 @@ ### Configuration + Pumpkin offers a robust configuration system that allows users to customize various aspects of the server's behavior without relying on external plugins. This provides flexibility and control over the server's operation. ### Basic / Advanced + Pumpkin's Configuration is split into a basic Configuration made for quick changes and important changes and a more Advanced Configuration - `configuration.toml`: simple and can be compared to the vanilla `server.properties`. - `features.toml`: designed to have all features of pumpkin at one place, making it a large configuration #### Key Features: - - Extensive Customization: Configure server settings, player behavior, world generation, and more. - - Performance Optimization: Optimize server performance through configuration tweaks. - - Plugin-Free Customization: Achieve desired changes without the need for additional plugins. + +- Extensive Customization: Configure server settings, player behavior, world generation, and more. +- Performance Optimization: Optimize server performance through configuration tweaks. +- Plugin-Free Customization: Achieve desired changes without the need for additional plugins. diff --git a/docs/developer/authentication.md b/docs/developer/authentication.md index 9d9accb31..acbc8b1aa 100644 --- a/docs/developer/authentication.md +++ b/docs/developer/authentication.md @@ -34,58 +34,40 @@ To ensure a player has a premium accounts: Pumpkin does support custom Authentication servers, You can replace the Authentication URL in `features.toml`. -Pumpkin Authentication works like this (Mojang/Custom): +#### How Pumpkin Authentication Works -1. GET Request > Authentication +1. **GET Request:** Pumpkin sends a GET request to the specified authentication URL. -2. Status Code 200 > Successfull +2. **Status Code 200:** If the authentication is successful, the server responds with a status code of 200. -3. Successfull > Parse JSON Game Profile +3. **Parse JSON Game Profile:** Pumpkin parses the JSON game profile returned in the response. #### Game Profile ```rust -id: UUID -``` - -```rust -name: String -``` - -```rust -properties: Array -``` - -> [!IMPORTANT] -> Optional, Only present when actions are taken - -```rust -profile_actions: Array +struct GameProfile { + id: UUID, + name: String, + properties: Vec, + profile_actions: Option>, // Optional, Only present when actions are applied +} ``` ##### Property ```rust -name: String -``` - -> [!IMPORTANT] -> base 64 - -```rust -- value: String -``` - -> [!IMPORTANT] -> Optional, base 64 - -```rust -- signature: String +struct Property { + name: String, + value: String, // Base64 encoded + signature: Option, // Optional, Base64 encoded +} ``` ##### Profile Action ```rust -FORCED_NAME_CHANGE -USING_BANNED_SKIN +enum ProfileAction { + FORCED_NAME_CHANGE, + USING_BANNED_SKIN, +} ``` diff --git a/docs/developer/introduction.md b/docs/developer/introduction.md index a1efdbfcf..c1670a622 100644 --- a/docs/developer/introduction.md +++ b/docs/developer/introduction.md @@ -5,4 +5,4 @@ Welcome to the Pumpkin Documentation! Whether you're an internal Pumpkin developer or working on a Pumpkin plugin, this documentation is your resource for everything Pumpkin. > [!IMPORTANT] -> While Pumpkin currently doesn't have plugin support, this documentation provides valuable insights into the platform's architecture and functionality, which can be helpful for understanding how to create potential future plugins. +> While Pumpkin currently doesn't have plugin support yet, this documentation provides valuable insights into the platform's architecture and functionality, which can be helpful for understanding how to create potential future plugins. diff --git a/docs/troubleshooting/common_issues.md b/docs/troubleshooting/common_issues.md new file mode 100644 index 000000000..881cae1da --- /dev/null +++ b/docs/troubleshooting/common_issues.md @@ -0,0 +1,33 @@ +### Common Issues + +1. ### Broken Chunk Lighting + + See [#93](https://github.com/Snowiiii/Pumpkin/issues/93) + + **Issue:** Broken chunk lighting in your Minecraft server. + + **Cause:** The server is currently not calculating lighting for chunks, we working on that. + + **Temporary Fix:** Use a full-bright resource pack. This will temporarily resolve the issue by making all chunks appear brightly lit. You can find many full-bright resource packs online. + +2. ### I can place blocks inside me + + See [#49](https://github.com/Snowiiii/Pumpkin/issues/49) + + **Issue:** Players are able to place block in them. + + **Cause:** The server is currently not calculating hitboxes for blocks, we working on that. + +3. ### Server is unresponsive + + **Issue:** You have to wait before reconnect or can't do basic things while chunks are loading. + + **Cause:** The server has currently blocking issues, we working on that. + +4. ### Failed to verify username + + **Issue:** Some players reported having issues loggin into the Server, Having "Failed to verify username" error. + + **Cause:** This has to do with Authentication, Usally with the prevent proxy connections setting. + + **Fix:** Disable `prevent_proxy_connections` in `features.toml` diff --git a/package-lock.json b/package-lock.json index c858d9cef..df845bdb2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,7 +5,7 @@ "packages": { "": { "devDependencies": { - "vitepress": "^1.4.0", + "vitepress": "^1.4.1", "vue": "^3.5.12" } }, @@ -2446,9 +2446,9 @@ } }, "node_modules/vitepress": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/vitepress/-/vitepress-1.4.0.tgz", - "integrity": "sha512-JXCv4EsKTDyAFb6C/UjZr7nsGAzZ6mafVk2rx7rG5o8N+B/4QstIk+iEOe/9dKoU6V624UIC6g1pZ+K63rxhlw==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/vitepress/-/vitepress-1.4.1.tgz", + "integrity": "sha512-C2rQ7PMlDVqgsaHOa0uJtgGGWaGv74QMaGL62lxKbtFkYtosJB5HAfZ8+pEbfzzvLemYaYwaiQdFLBlexK2sFw==", "dev": true, "license": "MIT", "dependencies": { @@ -2460,7 +2460,7 @@ "@types/markdown-it": "^14.1.2", "@vitejs/plugin-vue": "^5.1.4", "@vue/devtools-api": "^7.4.6", - "@vue/shared": "^3.5.11", + "@vue/shared": "^3.5.12", "@vueuse/core": "^11.1.0", "@vueuse/integrations": "^11.1.0", "focus-trap": "^7.6.0", @@ -2468,7 +2468,7 @@ "minisearch": "^7.1.0", "shiki": "^1.22.0", "vite": "^5.4.8", - "vue": "^3.5.11" + "vue": "^3.5.12" }, "bin": { "vitepress": "bin/vitepress.js" diff --git a/package.json b/package.json index 8578e6172..677311e75 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "docs:preview": "vitepress preview docs" }, "devDependencies": { - "vitepress": "^1.4.0", + "vitepress": "^1.4.1", "vue": "^3.5.12" } } \ No newline at end of file From 533357ceaaefbf78ccb579d5d61a9c35ecf4515f Mon Sep 17 00:00:00 2001 From: Snowiiii Date: Sun, 13 Oct 2024 15:17:09 +0200 Subject: [PATCH 65/65] Fix: kick in player state when client fails --- pumpkin/src/client/mod.rs | 8 +++++++- pumpkin/src/entity/player.rs | 3 +-- pumpkin/src/main.rs | 3 +-- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/pumpkin/src/client/mod.rs b/pumpkin/src/client/mod.rs index 252c5b9e2..2fcd008a9 100644 --- a/pumpkin/src/client/mod.rs +++ b/pumpkin/src/client/mod.rs @@ -17,9 +17,10 @@ use crossbeam::atomic::AtomicCell; use mio::{event::Event, net::TcpStream, Token}; use parking_lot::Mutex; use pumpkin_config::compression::CompressionInfo; +use pumpkin_core::text::TextComponent; use pumpkin_protocol::{ bytebuf::{packet_id::Packet, DeserializerError}, - client::{config::CConfigDisconnect, login::CLoginDisconnect}, + client::{config::CConfigDisconnect, login::CLoginDisconnect, play::CPlayDisconnect}, packet_decoder::PacketDecoder, packet_encoder::PacketEncoder, server::{ @@ -416,6 +417,11 @@ impl Client { self.try_send_packet(&CConfigDisconnect::new(reason)) .unwrap_or_else(|_| self.close()); } + // This way players get kicked when players using client functions (e.g. poll, send_packet) + ConnectionState::Play => { + self.try_send_packet(&CPlayDisconnect::new(&TextComponent::text(reason))) + .unwrap_or_else(|_| self.close()); + } _ => { log::warn!("Can't kick in {:?} State", self.connection_state) } diff --git a/pumpkin/src/entity/player.rs b/pumpkin/src/entity/player.rs index fc39cdf2b..496cedfe0 100644 --- a/pumpkin/src/entity/player.rs +++ b/pumpkin/src/entity/player.rs @@ -26,7 +26,7 @@ use pumpkin_protocol::{ SPlayerPositionRotation, SPlayerRotation, SSetCreativeSlot, SSetHeldItem, SSetPlayerGround, SSwingArm, SUseItem, SUseItemOn, }, - ConnectionState, RawPacket, ServerPacket, VarInt, + RawPacket, ServerPacket, VarInt, }; use pumpkin_protocol::server::play::{SCloseContainer, SKeepAlive}; @@ -215,7 +215,6 @@ impl Player { /// Kicks the Client with a reason depending on the connection state pub fn kick(&self, reason: TextComponent) { - assert!(self.client.connection_state.load() == ConnectionState::Play); assert!(!self .client .closed diff --git a/pumpkin/src/main.rs b/pumpkin/src/main.rs index 976910003..8cf3c0153 100644 --- a/pumpkin/src/main.rs +++ b/pumpkin/src/main.rs @@ -234,7 +234,7 @@ fn main() -> io::Result<()> { } clients.insert(token, client); }, - + // Maybe received an event for a TCP connection. token => { // Poll Players if let Some(player) = players.get_mut(&token) { @@ -256,7 +256,6 @@ fn main() -> io::Result<()> { }; // Poll current Clients (non players) - // Maybe received an event for a TCP connection. let (done, make_player) = if let Some(client) = clients.get_mut(&token) { client.poll(event).await; let closed = client.closed.load(std::sync::atomic::Ordering::Relaxed);