From 2ae6b08c4752bbfd2f31ee35e735b982cd66d6ca Mon Sep 17 00:00:00 2001 From: Commandcracker <49335821+Commandcracker@users.noreply.github.com> Date: Wed, 25 Dec 2024 13:32:21 +0100 Subject: [PATCH 1/5] Upgrade Alpine to 3.21 (see CVE-2024-9143) (#409) --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 57db48cf1..760bc6351 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM rust:1-alpine3.20 AS builder +FROM rust:1-alpine3.21 AS builder ARG GIT_VERSION=Docker ENV GIT_VERSION=$GIT_VERSION ENV RUSTFLAGS="-C target-feature=-crt-static -C target-cpu=native" @@ -16,7 +16,7 @@ RUN --mount=type=cache,sharing=private,target=/pumpkin/target \ # strip debug symbols from binary RUN strip pumpkin.release -FROM alpine:3.20 +FROM alpine:3.21 # Identifying information for registries like ghcr.io LABEL org.opencontainers.image.source=https://github.com/Snowiiii/Pumpkin From 991ac37eaeb30d6c66a653c43fc4571141b924e3 Mon Sep 17 00:00:00 2001 From: DataModel <183248792+DataM0del@users.noreply.github.com> Date: Wed, 25 Dec 2024 07:59:44 -0500 Subject: [PATCH 2/5] fix(pumpkin-inventory/src/drag_handler.rs): divide by 0 (#393) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(pumpkin-inventory/src/drag_handler.rs): divide by 0 Please work Also, commit made on mobile using Built-in GitHub editor 🔥 * fix(pumpkin-inventory/src/drag_handler.rs): syntax errors --- pumpkin-inventory/src/drag_handler.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pumpkin-inventory/src/drag_handler.rs b/pumpkin-inventory/src/drag_handler.rs index fefcd6059..4a6ea4388 100644 --- a/pumpkin-inventory/src/drag_handler.rs +++ b/pumpkin-inventory/src/drag_handler.rs @@ -120,8 +120,12 @@ impl DragHandler { let changing_slots = drag.possibly_changing_slots(&slots_cloned, carried_item.item_id); let amount_of_slots = changing_slots.clone().count(); - let (amount_per_slot, remainder) = - (carried_item.item_count as usize).div_rem_euclid(&amount_of_slots); + let (amount_per_slot, remainder) = if amount_of_slots == 0 { + // TODO: please work lol + (1, 0) + } else { + (carried_item.item_count as usize).div_rem_euclid(&amount_of_slots) + }; let mut item_in_each_slot = *carried_item; item_in_each_slot.item_count = amount_per_slot as u8; changing_slots.for_each(|slot| *slots[slot] = Some(item_in_each_slot)); From 0e9b1af76004cccbfce5cb8fd5c40c3d830a01e9 Mon Sep 17 00:00:00 2001 From: kralverde <80051564+kralverde@users.noreply.github.com> Date: Wed, 25 Dec 2024 08:43:26 -0500 Subject: [PATCH 3/5] make command dispatcher writable (#405) --- pumpkin/src/command/args/arg_command.rs | 10 ++++---- pumpkin/src/command/args/mod.rs | 2 +- pumpkin/src/command/client_cmd_suggestions.rs | 8 ++++--- pumpkin/src/command/commands/cmd_help.rs | 4 ++-- pumpkin/src/command/dispatcher.rs | 24 ++++++++++++++----- pumpkin/src/command/mod.rs | 4 ++-- pumpkin/src/command/tree.rs | 5 ++-- pumpkin/src/main.rs | 2 +- pumpkin/src/net/packet/play.rs | 8 +++---- pumpkin/src/net/rcon/mod.rs | 2 +- pumpkin/src/server/mod.rs | 4 ++-- pumpkin/src/world/mod.rs | 4 +--- 12 files changed, 44 insertions(+), 33 deletions(-) diff --git a/pumpkin/src/command/args/arg_command.rs b/pumpkin/src/command/args/arg_command.rs index 939559872..9055fde10 100644 --- a/pumpkin/src/command/args/arg_command.rs +++ b/pumpkin/src/command/args/arg_command.rs @@ -37,10 +37,10 @@ impl ArgumentConsumer for CommandTreeArgumentConsumer { ) -> Option> { let s = args.pop()?; - let dispatcher = &server.command_dispatcher; - return dispatcher + let dispatcher = server.command_dispatcher.read().await; + dispatcher .get_tree(s) - .map_or_else(|_| None, |tree| Some(Arg::CommandTree(tree))); + .map_or_else(|_| None, |tree| Some(Arg::CommandTree(tree))) } async fn suggest<'a>( @@ -53,8 +53,8 @@ impl ArgumentConsumer for CommandTreeArgumentConsumer { return Ok(None); }; - let suggestions = server - .command_dispatcher + let dispatcher = server.command_dispatcher.read().await; + let suggestions = dispatcher .commands .keys() .filter(|suggestion| suggestion.starts_with(input)) diff --git a/pumpkin/src/command/args/mod.rs b/pumpkin/src/command/args/mod.rs index f27d850a5..8342386bd 100644 --- a/pumpkin/src/command/args/mod.rs +++ b/pumpkin/src/command/args/mod.rs @@ -83,7 +83,7 @@ pub(crate) enum Arg<'a> { Pos2D(Vector2), Rotation(f32, f32), GameMode(GameMode), - CommandTree(&'a CommandTree<'a>), + CommandTree(CommandTree<'a>), Item(&'a str), ResourceLocation(&'a str), Block(&'a str), diff --git a/pumpkin/src/command/client_cmd_suggestions.rs b/pumpkin/src/command/client_cmd_suggestions.rs index a1bec5da8..7c8c7b2fb 100644 --- a/pumpkin/src/command/client_cmd_suggestions.rs +++ b/pumpkin/src/command/client_cmd_suggestions.rs @@ -1,6 +1,7 @@ use std::sync::Arc; use pumpkin_protocol::client::play::{CCommands, ProtoNode, ProtoNodeType}; +use tokio::sync::RwLock; use crate::entity::player::Player; @@ -11,11 +12,12 @@ use super::{ pub async fn send_c_commands_packet<'a>( player: &Arc, - dispatcher: &'a CommandDispatcher<'a>, + dispatcher: &RwLock>, ) { let cmd_src = super::CommandSender::Player(player.clone()); let mut first_level = Vec::new(); + let dispatcher = dispatcher.read().await; for key in dispatcher.commands.keys() { let Ok(tree) = dispatcher.get_tree(key) else { continue; @@ -72,8 +74,8 @@ impl<'a> ProtoNodeBuilder<'a> { fn nodes_to_proto_node_builders<'a>( cmd_src: &super::CommandSender, - nodes: &'a [Node<'a>], - children: &'a [usize], + nodes: &[Node<'a>], + children: &[usize], ) -> (bool, Vec>) { let mut child_nodes = Vec::new(); let mut is_executable = false; diff --git a/pumpkin/src/command/commands/cmd_help.rs b/pumpkin/src/command/commands/cmd_help.rs index 8c714dcbc..e382febf1 100644 --- a/pumpkin/src/command/commands/cmd_help.rs +++ b/pumpkin/src/command/commands/cmd_help.rs @@ -121,8 +121,8 @@ impl CommandExecutor for BaseHelpExecutor { } }; - let mut commands: Vec<&CommandTree> = server - .command_dispatcher + let dispatcher = server.command_dispatcher.read().await; + let mut commands: Vec<&CommandTree> = dispatcher .commands .values() .filter_map(|cmd| match cmd { diff --git a/pumpkin/src/command/dispatcher.rs b/pumpkin/src/command/dispatcher.rs index e63a67ac2..b2dc27ae1 100644 --- a/pumpkin/src/command/dispatcher.rs +++ b/pumpkin/src/command/dispatcher.rs @@ -102,7 +102,7 @@ impl<'a> CommandDispatcher<'a> { // try paths and collect the nodes that fail // todo: make this more fine-grained for path in tree.iter_paths() { - match Self::try_find_suggestions_on_path(src, server, &path, tree, &mut raw_args, cmd) + match Self::try_find_suggestions_on_path(src, server, &path, &tree, &mut raw_args, cmd) .await { Err(InvalidConsumption(s)) => { @@ -151,7 +151,7 @@ impl<'a> CommandDispatcher<'a> { // try paths until fitting path is found for path in tree.iter_paths() { - if Self::try_is_fitting_path(src, server, &path, tree, &mut raw_args.clone()).await? { + if Self::try_is_fitting_path(src, server, &path, &tree, &mut raw_args.clone()).await? { return Ok(()); } } @@ -160,22 +160,22 @@ impl<'a> CommandDispatcher<'a> { ))) } - pub(crate) fn get_tree(&'a self, key: &str) -> Result<&'a CommandTree<'a>, CommandError> { + pub(crate) fn get_tree(&self, key: &str) -> Result, CommandError> { let command = self .commands .get(key) .ok_or(GeneralCommandIssue("Command not found".to_string()))?; match command { - Command::Tree(tree) => Ok(tree), + Command::Tree(tree) => Ok(tree.clone()), Command::Alias(target) => { - let Some(Command::Tree(tree)) = &self.commands.get(target) else { + let Some(Command::Tree(tree)) = self.commands.get(target) else { log::error!("Error while parsing command alias \"{key}\": pointing to \"{target}\" which is not a valid tree"); return Err(GeneralCommandIssue( "Internal Error (See logs for details)".into(), )); }; - Ok(tree) + Ok(tree.clone()) } } } @@ -282,3 +282,15 @@ impl<'a> CommandDispatcher<'a> { self.commands.insert(primary_name, Command::Tree(tree)); } } + +#[cfg(test)] +mod test { + use crate::command::{default_dispatcher, tree::CommandTree}; + + #[test] + fn test_dynamic_command() { + let mut dispatcher = default_dispatcher(); + let tree = CommandTree::new(["test"], "test_desc"); + dispatcher.register(tree); + } +} diff --git a/pumpkin/src/command/mod.rs b/pumpkin/src/command/mod.rs index c3f1b67d3..9ab330381 100644 --- a/pumpkin/src/command/mod.rs +++ b/pumpkin/src/command/mod.rs @@ -107,7 +107,7 @@ impl<'a> CommandSender<'a> { } #[must_use] -pub fn default_dispatcher<'a>() -> Arc> { +pub fn default_dispatcher<'a>() -> CommandDispatcher<'a> { let mut dispatcher = CommandDispatcher::default(); dispatcher.register(cmd_pumpkin::init_command_tree()); @@ -129,7 +129,7 @@ pub fn default_dispatcher<'a>() -> Arc> { dispatcher.register(cmd_transfer::init_command_tree()); dispatcher.register(cmd_fill::init_command_tree()); - Arc::new(dispatcher) + dispatcher } #[async_trait] diff --git a/pumpkin/src/command/tree.rs b/pumpkin/src/command/tree.rs index 06d9c0e58..9ea18e350 100644 --- a/pumpkin/src/command/tree.rs +++ b/pumpkin/src/command/tree.rs @@ -5,12 +5,13 @@ use std::{collections::VecDeque, fmt::Debug}; /// see [`crate::commands::tree_builder::argument`] pub type RawArgs<'a> = Vec<&'a str>; -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Node<'a> { pub(crate) children: Vec, pub(crate) node_type: NodeType<'a>, } +#[derive(Clone)] pub enum NodeType<'a> { ExecuteLeaf { executor: &'a dyn CommandExecutor, @@ -50,7 +51,7 @@ pub enum Command<'a> { Alias(&'a str), } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct CommandTree<'a> { pub(crate) nodes: Vec>, pub(crate) children: Vec, diff --git a/pumpkin/src/main.rs b/pumpkin/src/main.rs index 232f30a16..5009e96f3 100644 --- a/pumpkin/src/main.rs +++ b/pumpkin/src/main.rs @@ -306,7 +306,7 @@ fn setup_console(server: Arc) { .expect("Failed to read console line"); if !out.is_empty() { - let dispatcher = server.command_dispatcher.clone(); + let dispatcher = server.command_dispatcher.read().await; dispatcher .handle_command(&mut command::CommandSender::Console, &server, &out) .await; diff --git a/pumpkin/src/net/packet/play.rs b/pumpkin/src/net/packet/play.rs index e30626781..b674f4dfb 100644 --- a/pumpkin/src/net/packet/play.rs +++ b/pumpkin/src/net/packet/play.rs @@ -286,7 +286,7 @@ impl Player { server: &Arc, command: SChatCommand, ) { - let dispatcher = server.command_dispatcher.clone(); + let dispatcher = server.command_dispatcher.read().await; dispatcher .handle_command( &mut CommandSender::Player(self.clone()), @@ -877,10 +877,8 @@ impl Player { return; }; - let suggestions = server - .command_dispatcher - .find_suggestions(&mut src, server, cmd) - .await; + let dispatcher = server.command_dispatcher.read().await; + let suggestions = dispatcher.find_suggestions(&mut src, server, cmd).await; let response = CCommandSuggestions::new( packet.id, diff --git a/pumpkin/src/net/rcon/mod.rs b/pumpkin/src/net/rcon/mod.rs index 96adbc968..1f36d827b 100644 --- a/pumpkin/src/net/rcon/mod.rs +++ b/pumpkin/src/net/rcon/mod.rs @@ -104,7 +104,7 @@ impl RCONClient { ServerboundPacket::ExecCommand => { if self.logged_in { let output = tokio::sync::Mutex::new(Vec::new()); - let dispatcher = server.command_dispatcher.clone(); + let dispatcher = server.command_dispatcher.read().await; dispatcher .handle_command( &mut crate::command::CommandSender::Rcon(&output), diff --git a/pumpkin/src/server/mod.rs b/pumpkin/src/server/mod.rs index c672eb2c8..cb5c57b13 100644 --- a/pumpkin/src/server/mod.rs +++ b/pumpkin/src/server/mod.rs @@ -47,7 +47,7 @@ pub struct Server { /// Saves server branding information. server_branding: CachedBranding, /// Saves and Dispatches commands to appropriate handlers. - pub command_dispatcher: Arc>, + pub command_dispatcher: RwLock>, /// Saves and calls blocks blocks pub block_manager: Arc, /// Manages multiple worlds within the server. @@ -79,7 +79,7 @@ impl Server { }); // First register default command, after that plugins can put in their own - let command_dispatcher = default_dispatcher(); + let command_dispatcher = RwLock::new(default_dispatcher()); let world = World::load( Dimension::OverWorld.into_level( diff --git a/pumpkin/src/world/mod.rs b/pumpkin/src/world/mod.rs index 02c13b10f..52d6b8c3c 100644 --- a/pumpkin/src/world/mod.rs +++ b/pumpkin/src/world/mod.rs @@ -229,7 +229,6 @@ impl World { player: Arc, server: &Server, ) { - let command_dispatcher = &server.command_dispatcher; let dimensions: Vec = server.dimensions.iter().map(DimensionType::name).collect(); @@ -270,8 +269,7 @@ impl World { .await; // permissions, i. e. the commands a player may use player.send_permission_lvl_update().await; - client_cmd_suggestions::send_c_commands_packet(&player, command_dispatcher).await; - + client_cmd_suggestions::send_c_commands_packet(&player, &server.command_dispatcher).await; // teleport let mut position = Vector3::new(10.0, 120.0, 10.0); let yaw = 10.0; From e8deff519ec75aefdc18eda4321841da13e9aa16 Mon Sep 17 00:00:00 2001 From: Tiiita <96084154+Tiiita@users.noreply.github.com> Date: Wed, 25 Dec 2024 16:53:39 +0100 Subject: [PATCH 4/5] Add nearby players collision check when trying to place blocks (#293) * Try to update boundingbox when changin pos * Remove unused imports * Add nearby player check when placing blocks * Add seach for nearby players when placing block * Reformat * Fix conflict * fix: conflicts and fix clippy --------- Co-authored-by: Alexander Medvedev --- pumpkin/src/net/packet/play.rs | 11 ++++-- pumpkin/src/world/mod.rs | 62 ++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 3 deletions(-) diff --git a/pumpkin/src/net/packet/play.rs b/pumpkin/src/net/packet/play.rs index b674f4dfb..ec01ee04e 100644 --- a/pumpkin/src/net/packet/play.rs +++ b/pumpkin/src/net/packet/play.rs @@ -764,9 +764,14 @@ impl Player { } let block_bounding_box = BoundingBox::from_block(&world_pos); - let bounding_box = entity.bounding_box.load(); - //TODO: Make this check for every entity in that position - if !bounding_box.intersects(&block_bounding_box) { + let mut intersects = false; + for player in world.get_nearby_players(entity.pos.load(), 20).await { + let bounding_box = player.1.living_entity.entity.bounding_box.load(); + if bounding_box.intersects(&block_bounding_box) { + intersects = true; + } + } + if !intersects { world .set_block_state(world_pos, block.default_state_id) .await; diff --git a/pumpkin/src/world/mod.rs b/pumpkin/src/world/mod.rs index 52d6b8c3c..92239719b 100644 --- a/pumpkin/src/world/mod.rs +++ b/pumpkin/src/world/mod.rs @@ -630,6 +630,68 @@ impl World { return self.current_players.lock().await.get(&id).cloned(); } + /// Gets a list of players who's location equals the given position in the world. + /// + /// It iterates through the players in the world and checks their location. If the player's location matches the + /// given position it will add this to a Vec which it later returns. If no + /// player was found in that position it will just return an empty Vec. + /// + /// # Arguments + /// + /// * `position`: The position the function will check. + pub async fn get_players_by_pos( + &self, + position: WorldPosition, + ) -> HashMap> { + self.current_players + .lock() + .await + .iter() + .filter_map(|(uuid, player)| { + let player_block_pos = player.living_entity.entity.block_pos.load().0; + (position.0.x == player_block_pos.x + && position.0.y == player_block_pos.y + && position.0.z == player_block_pos.z) + .then(|| (*uuid, Arc::clone(player))) + }) + .collect::>>() + } + + /// Gets the nearby players around a given world position + /// It "creates" a sphere and checks if whether players are inside + /// and returns a hashmap where the uuid is the key and the player + /// object the value. + /// + /// # Arguments + /// * `pos`: The middlepoint of the sphere + /// * `radius`: The radius of the sphere. The higher the radius + /// the more area will be checked, in every direction. + pub async fn get_nearby_players( + &self, + pos: Vector3, + radius: u16, + ) -> HashMap> { + let radius_squared = (f64::from(radius)).powi(2); + + let mut found_players = HashMap::new(); + for player in self.current_players.lock().await.iter() { + let player_pos = player.1.living_entity.entity.pos.load(); + + let diff = Vector3::new( + player_pos.x - pos.x, + player_pos.y - pos.y, + player_pos.z - pos.z, + ); + + let distance_squared = diff.x.powi(2) + diff.y.powi(2) + diff.z.powi(2); + if distance_squared <= radius_squared { + found_players.insert(*player.0, player.1.clone()); + } + } + + found_players + } + /// Adds a player to the world and broadcasts a join message if enabled. /// /// This function takes a player's UUID and an `Arc` reference. From febbfd89e6c647f11612721c3f8a1b4cc7fad9cc Mon Sep 17 00:00:00 2001 From: Alexander Medvedev Date: Thu, 26 Dec 2024 00:04:43 +0100 Subject: [PATCH 5/5] Remove `target-cpu=native` from Docker This can cause illegal instructions when using the public docker image build on the CI --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 760bc6351..7fc544257 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ FROM rust:1-alpine3.21 AS builder ARG GIT_VERSION=Docker ENV GIT_VERSION=$GIT_VERSION -ENV RUSTFLAGS="-C target-feature=-crt-static -C target-cpu=native" +ENV RUSTFLAGS="-C target-feature=-crt-static" RUN apk add --no-cache musl-dev WORKDIR /pumpkin