From c8df113842ba699ec6f24b70a4aecddb40c48149 Mon Sep 17 00:00:00 2001 From: van-sprundel Date: Fri, 18 Oct 2024 20:42:49 +0200 Subject: [PATCH 01/15] Add say command --- pumpkin/src/commands/cmd_say.rs | 34 +++++++++++++++++++++++++++++++++ pumpkin/src/commands/mod.rs | 2 ++ pumpkin/src/server/mod.rs | 8 ++++++++ pumpkin/src/world/mod.rs | 9 ++++++++- 4 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 pumpkin/src/commands/cmd_say.rs diff --git a/pumpkin/src/commands/cmd_say.rs b/pumpkin/src/commands/cmd_say.rs new file mode 100644 index 000000000..b6f8bbd22 --- /dev/null +++ b/pumpkin/src/commands/cmd_say.rs @@ -0,0 +1,34 @@ +use crate::commands::tree::CommandTree; +use crate::commands::tree::RawArgs; +use crate::commands::tree_builder::argument; +use crate::commands::CommandSender; +use pumpkin_core::text::{color::NamedColor, TextComponent}; + +const NAMES: [&str; 1] = ["say"]; +const DESCRIPTION: &str = "Sends a message to all players."; + +const ARG_CONTENT: &str = "content"; + +pub fn consume_arg_content(_src: &CommandSender, args: &mut RawArgs) -> Option { + args.pop().map_or(None, |v| Some(v.to_string())) +} + +pub fn init_command_tree<'a>() -> CommandTree<'a> { + CommandTree::new(NAMES, DESCRIPTION).with_child( + argument(ARG_CONTENT, consume_arg_content).execute(&|sender, server, args| { + if let Some(content) = args.get("content") { + let message = &format!("[Server]: {content}"); + + server + .broadcast_message(&TextComponent::text(message).color_named(NamedColor::Blue)); + } else { + sender.send_message( + TextComponent::text("Please provide a message: /say [content]") + .color_named(NamedColor::Red), + ); + } + + Ok(()) + }), + ) +} diff --git a/pumpkin/src/commands/mod.rs b/pumpkin/src/commands/mod.rs index ef6e1b230..a2e96d84d 100644 --- a/pumpkin/src/commands/mod.rs +++ b/pumpkin/src/commands/mod.rs @@ -12,6 +12,7 @@ mod cmd_help; mod cmd_kick; mod cmd_kill; mod cmd_pumpkin; +mod cmd_say; mod cmd_stop; pub mod dispatcher; mod tree; @@ -77,6 +78,7 @@ pub fn default_dispatcher<'a>() -> CommandDispatcher<'a> { dispatcher.register(cmd_echest::init_command_tree()); dispatcher.register(cmd_kill::init_command_tree()); dispatcher.register(cmd_kick::init_command_tree()); + dispatcher.register(cmd_say::init_command_tree()); dispatcher } diff --git a/pumpkin/src/server/mod.rs b/pumpkin/src/server/mod.rs index 6deee69e2..344d69a1b 100644 --- a/pumpkin/src/server/mod.rs +++ b/pumpkin/src/server/mod.rs @@ -2,6 +2,7 @@ use connection_cache::{CachedBranding, CachedStatus}; use key_store::KeyStore; use parking_lot::{Mutex, RwLock}; use pumpkin_config::BASIC_CONFIG; +use pumpkin_core::text::TextComponent; use pumpkin_core::GameMode; use pumpkin_entity::EntityId; use pumpkin_inventory::drag_handler::DragHandler; @@ -127,6 +128,13 @@ impl Server { } } + /// Sends a message to all players in every world + pub fn broadcast_message(&self, content: &TextComponent) { + for world in &self.worlds { + world.broadcast_message(content); + } + } + /// Searches every world for a player by name pub fn get_player_by_name(&self, name: &str) -> Option> { for world in self.worlds.iter() { diff --git a/pumpkin/src/world/mod.rs b/pumpkin/src/world/mod.rs index 78e9f1c31..73fbf0231 100644 --- a/pumpkin/src/world/mod.rs +++ b/pumpkin/src/world/mod.rs @@ -9,7 +9,7 @@ use crate::{ use num_traits::ToPrimitive; use parking_lot::Mutex; use pumpkin_config::BasicConfiguration; -use pumpkin_core::math::vector2::Vector2; +use pumpkin_core::{math::vector2::Vector2, text::TextComponent}; use pumpkin_entity::{entity_type::EntityType, EntityId}; use pumpkin_protocol::{ client::play::{ @@ -259,6 +259,13 @@ impl World { dbg!("DONE CHUNKS", inst.elapsed()); } + /// Sends a message to all players + pub fn broadcast_message(&self, content: &TextComponent) { + for player in self.current_players.lock().values() { + player.send_system_message(content.clone()); + } + } + /// Gets a Player by entity id pub fn get_player_by_entityid(&self, id: EntityId) -> Option> { for player in self.current_players.lock().values() { From 118da0d1df529198d312a6c7c742c4604044aaf9 Mon Sep 17 00:00:00 2001 From: van-sprundel Date: Fri, 18 Oct 2024 22:07:19 +0200 Subject: [PATCH 02/15] Use map instead of map_or --- pumpkin/src/commands/cmd_say.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pumpkin/src/commands/cmd_say.rs b/pumpkin/src/commands/cmd_say.rs index b6f8bbd22..b8887f1a5 100644 --- a/pumpkin/src/commands/cmd_say.rs +++ b/pumpkin/src/commands/cmd_say.rs @@ -10,7 +10,7 @@ const DESCRIPTION: &str = "Sends a message to all players."; const ARG_CONTENT: &str = "content"; pub fn consume_arg_content(_src: &CommandSender, args: &mut RawArgs) -> Option { - args.pop().map_or(None, |v| Some(v.to_string())) + args.pop().map(|v| v.to_string()) } pub fn init_command_tree<'a>() -> CommandTree<'a> { From b3561a55247a16aa5a4aed7aaba0a8b15c4bb916 Mon Sep 17 00:00:00 2001 From: van-sprundel Date: Fri, 18 Oct 2024 22:32:38 +0200 Subject: [PATCH 03/15] Update impl --- pumpkin/src/commands/cmd_say.rs | 9 +++++---- pumpkin/src/server/mod.rs | 4 ++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/pumpkin/src/commands/cmd_say.rs b/pumpkin/src/commands/cmd_say.rs index b8887f1a5..cb7c04c9a 100644 --- a/pumpkin/src/commands/cmd_say.rs +++ b/pumpkin/src/commands/cmd_say.rs @@ -17,13 +17,14 @@ pub fn init_command_tree<'a>() -> CommandTree<'a> { CommandTree::new(NAMES, DESCRIPTION).with_child( argument(ARG_CONTENT, consume_arg_content).execute(&|sender, server, args| { if let Some(content) = args.get("content") { - let message = &format!("[Server]: {content}"); + let message = &format!("[Console]: {content}"); + let message = TextComponent::text(message).color_named(NamedColor::Blue); - server - .broadcast_message(&TextComponent::text(message).color_named(NamedColor::Blue)); + server.broadcast_message(message.clone()); + sender.send_message(message); } else { sender.send_message( - TextComponent::text("Please provide a message: /say [content]") + TextComponent::text("Please provide a message: say [content]") .color_named(NamedColor::Red), ); } diff --git a/pumpkin/src/server/mod.rs b/pumpkin/src/server/mod.rs index 344d69a1b..07002b2f3 100644 --- a/pumpkin/src/server/mod.rs +++ b/pumpkin/src/server/mod.rs @@ -129,9 +129,9 @@ impl Server { } /// Sends a message to all players in every world - pub fn broadcast_message(&self, content: &TextComponent) { + pub fn broadcast_message(&self, content: TextComponent) { for world in &self.worlds { - world.broadcast_message(content); + world.broadcast_message(&content); } } From ca376b7ffc31b1d00d8c20792a795095508d2a39 Mon Sep 17 00:00:00 2001 From: van-sprundel Date: Fri, 18 Oct 2024 22:08:52 +0200 Subject: [PATCH 04/15] Add target selector logic --- pumpkin/Cargo.toml | 4 +- pumpkin/src/commands/cmd_say.rs | 82 +++++++++++++++++++++++++++++++++ pumpkin/src/server/mod.rs | 30 ++++++++++++ pumpkin/src/world/mod.rs | 9 ++++ 4 files changed, 124 insertions(+), 1 deletion(-) diff --git a/pumpkin/Cargo.toml b/pumpkin/Cargo.toml index 8e737ab63..00eebfa4c 100644 --- a/pumpkin/Cargo.toml +++ b/pumpkin/Cargo.toml @@ -24,6 +24,8 @@ bytes = "1.7" rand = "0.8.5" +regex = "1.11.0" + num-traits = "0.2" num-derive = "0.4" num-bigint = "0.4" @@ -53,7 +55,7 @@ thiserror = "1.0" # icon loading base64 = "0.22.1" -png = "0.17.14" +png = "0.17.14" # logging simple_logger = { version = "5.0.0", features = ["threads"] } diff --git a/pumpkin/src/commands/cmd_say.rs b/pumpkin/src/commands/cmd_say.rs index cb7c04c9a..2c25312f8 100644 --- a/pumpkin/src/commands/cmd_say.rs +++ b/pumpkin/src/commands/cmd_say.rs @@ -2,7 +2,10 @@ use crate::commands::tree::CommandTree; use crate::commands::tree::RawArgs; use crate::commands::tree_builder::argument; use crate::commands::CommandSender; +use crate::server::Server; +use pumpkin_core::math::vector3::Vector3; use pumpkin_core::text::{color::NamedColor, TextComponent}; +use regex::Regex; const NAMES: [&str; 1] = ["say"]; const DESCRIPTION: &str = "Sends a message to all players."; @@ -17,6 +20,7 @@ pub fn init_command_tree<'a>() -> CommandTree<'a> { CommandTree::new(NAMES, DESCRIPTION).with_child( argument(ARG_CONTENT, consume_arg_content).execute(&|sender, server, args| { if let Some(content) = args.get("content") { + let content = parse_selectors(content, sender, server); let message = &format!("[Console]: {content}"); let message = TextComponent::text(message).color_named(NamedColor::Blue); @@ -33,3 +37,81 @@ pub fn init_command_tree<'a>() -> CommandTree<'a> { }), ) } + +fn parse_selectors(content: &str, sender: &CommandSender, server: &Server) -> String { + let mut final_message = String::new(); + let mut current_pos = 0; + + // regex to match + // (@p, @a, @r, @e, @s, @here) + let selector_pattern = Regex::new(r"@[parehs]\b").unwrap(); + + for selector_match in selector_pattern.find_iter(content) { + let selector = selector_match.as_str(); + let start = selector_match.start(); + + // before selector + final_message.push_str(&content[current_pos..start]); + + let result = match selector { + "@p" => { + let position = match sender { + CommandSender::Player(p) => p.last_position.load(), + _ => Vector3::new(0., 0., 0.), + }; + vec![server + .get_nearest_player(&position) + .map_or_else(|| String::from("nobody"), |p| p.gameprofile.name.clone())] + } + "@r" => { + let online_players: Vec = server + .get_online_players() + .map(|p| p.gameprofile.name.clone()) + .collect(); + + if online_players.is_empty() { + vec![String::from("nobody")] + } else { + vec![online_players[rand::random::() % online_players.len()].clone()] + } + } + "@s" => match sender { + CommandSender::Player(p) => vec![p.gameprofile.name.clone()], + _ => vec![String::from("console")], + }, + "@a" => server + .get_online_players() + .map(|p| p.gameprofile.name.clone()) + .collect::>(), + "@e" => vec![], // TODO + "@here" => server + .get_online_players() + .map(|p| p.gameprofile.name.clone()) + .collect::>(), + _ => continue, + }; + + // formatted player names + final_message.push_str(&format_player_names(&result)); + + current_pos = selector_match.end(); + } + + final_message.push_str(&content[current_pos..]); + + final_message +} + +// Helper function to format player names according to spec +// see https://minecraft.fandom.com/wiki/Commands/say +fn format_player_names(names: &[String]) -> String { + match names.len() { + 0 => String::new(), + 1 => names[0].clone(), + 2 => format!("{} and {}", names[0], names[1]), + _ => { + let (last, rest) = names.split_last().unwrap(); + format!("{}, and {}", rest.join(", "), last) + } + } +} diff --git a/pumpkin/src/server/mod.rs b/pumpkin/src/server/mod.rs index 07002b2f3..43606122d 100644 --- a/pumpkin/src/server/mod.rs +++ b/pumpkin/src/server/mod.rs @@ -2,6 +2,7 @@ use connection_cache::{CachedBranding, CachedStatus}; use key_store::KeyStore; use parking_lot::{Mutex, RwLock}; use pumpkin_config::BASIC_CONFIG; +use pumpkin_core::math::vector3::Vector3; use pumpkin_core::text::TextComponent; use pumpkin_core::GameMode; use pumpkin_entity::EntityId; @@ -135,6 +136,35 @@ impl Server { } } + /// Get all online players + pub fn get_online_players(&self) -> impl Iterator> + '_ { + self.worlds.iter().flat_map(|world| world.get_players()) + } + + /// Gets the nearest player from the world position + pub fn get_nearest_player(&self, target: &Vector3) -> Option> { + // TODO respect which world the player is in + let world = self.worlds.first()?; + + fn distance(p1: &Vector3, p2: &Vector3) -> f64 { + let dx = p1.x - p2.x; + let dy = p1.y - p2.y; + let dz = p1.z - p2.z; + dz.mul_add(dz, dx.mul_add(dx, dy * dy)).sqrt() + } + + world + .current_players + .lock() + .values() + .min_by(|a, b| { + let dist_a = distance(&a.last_position.load(), target); + let dist_b = distance(&b.last_position.load(), target); + dist_a.partial_cmp(&dist_b).unwrap() + }) + .cloned() + } + /// Searches every world for a player by name pub fn get_player_by_name(&self, name: &str) -> Option> { for world in self.worlds.iter() { diff --git a/pumpkin/src/world/mod.rs b/pumpkin/src/world/mod.rs index 73fbf0231..173d5c234 100644 --- a/pumpkin/src/world/mod.rs +++ b/pumpkin/src/world/mod.rs @@ -266,6 +266,15 @@ impl World { } } + /// Gets all players + pub fn get_players(&self) -> Vec> { + self.current_players + .lock() + .values() + .cloned() + .collect::>() + } + /// Gets a Player by entity id pub fn get_player_by_entityid(&self, id: EntityId) -> Option> { for player in self.current_players.lock().values() { From fa4f1bbd3bdbbd9bb1c2b21d076ec63c7ffb7731 Mon Sep 17 00:00:00 2001 From: van-sprundel Date: Sat, 19 Oct 2024 00:05:21 +0200 Subject: [PATCH 05/15] Use tokens instead, remove regex dependency --- pumpkin/Cargo.toml | 2 -- pumpkin/src/commands/cmd_say.rs | 36 ++++++++++++++------------------- 2 files changed, 15 insertions(+), 23 deletions(-) diff --git a/pumpkin/Cargo.toml b/pumpkin/Cargo.toml index 00eebfa4c..89091407b 100644 --- a/pumpkin/Cargo.toml +++ b/pumpkin/Cargo.toml @@ -24,8 +24,6 @@ bytes = "1.7" rand = "0.8.5" -regex = "1.11.0" - num-traits = "0.2" num-derive = "0.4" num-bigint = "0.4" diff --git a/pumpkin/src/commands/cmd_say.rs b/pumpkin/src/commands/cmd_say.rs index 2c25312f8..a0fa2f33a 100644 --- a/pumpkin/src/commands/cmd_say.rs +++ b/pumpkin/src/commands/cmd_say.rs @@ -5,7 +5,6 @@ use crate::commands::CommandSender; use crate::server::Server; use pumpkin_core::math::vector3::Vector3; use pumpkin_core::text::{color::NamedColor, TextComponent}; -use regex::Regex; const NAMES: [&str; 1] = ["say"]; const DESCRIPTION: &str = "Sends a message to all players."; @@ -13,7 +12,14 @@ const DESCRIPTION: &str = "Sends a message to all players."; const ARG_CONTENT: &str = "content"; pub fn consume_arg_content(_src: &CommandSender, args: &mut RawArgs) -> Option { - args.pop().map(|v| v.to_string()) + let mut all_args: Vec = args.drain(..).map(|v| v.to_string()).collect(); + + if all_args.is_empty() { + None + } else { + all_args.reverse(); + Some(all_args.join(" ")) + } } pub fn init_command_tree<'a>() -> CommandTree<'a> { @@ -40,20 +46,12 @@ pub fn init_command_tree<'a>() -> CommandTree<'a> { fn parse_selectors(content: &str, sender: &CommandSender, server: &Server) -> String { let mut final_message = String::new(); - let mut current_pos = 0; - - // regex to match - // (@p, @a, @r, @e, @s, @here) - let selector_pattern = Regex::new(r"@[parehs]\b").unwrap(); - for selector_match in selector_pattern.find_iter(content) { - let selector = selector_match.as_str(); - let start = selector_match.start(); + let tokens: Vec<&str> = content.split_whitespace().collect(); - // before selector - final_message.push_str(&content[current_pos..start]); - - let result = match selector { + for token in tokens { + // TODO impl @e + let result = match token { "@p" => { let position = match sender { CommandSender::Player(p) => p.last_position.load(), @@ -83,23 +81,19 @@ fn parse_selectors(content: &str, sender: &CommandSender, server: &Server) -> St .get_online_players() .map(|p| p.gameprofile.name.clone()) .collect::>(), - "@e" => vec![], // TODO "@here" => server .get_online_players() .map(|p| p.gameprofile.name.clone()) .collect::>(), - _ => continue, + _ => vec![token.to_string()], }; // formatted player names final_message.push_str(&format_player_names(&result)); - - current_pos = selector_match.end(); + final_message.push(' '); } - final_message.push_str(&content[current_pos..]); - - final_message + final_message.trim_end().to_string() } // Helper function to format player names according to spec From 9b624db496d324710c9f2f8fea4334be141ad0c0 Mon Sep 17 00:00:00 2001 From: van-sprundel Date: Sat, 19 Oct 2024 00:11:24 +0200 Subject: [PATCH 06/15] Moved distance function to core --- pumpkin-core/src/math/distance.rs | 9 +++++++++ pumpkin-core/src/math/mod.rs | 1 + pumpkin/src/server/mod.rs | 8 +------- 3 files changed, 11 insertions(+), 7 deletions(-) create mode 100644 pumpkin-core/src/math/distance.rs diff --git a/pumpkin-core/src/math/distance.rs b/pumpkin-core/src/math/distance.rs new file mode 100644 index 000000000..2a38d1c8f --- /dev/null +++ b/pumpkin-core/src/math/distance.rs @@ -0,0 +1,9 @@ +use super::vector3::Vector3; + +pub fn distance(p1: &Vector3, p2: &Vector3) -> f64 { + let dx = p1.x - p2.x; + let dy = p1.y - p2.y; + let dz = p1.z - p2.z; + + dz.mul_add(dz, dx.mul_add(dx, dy * dy)).sqrt() +} diff --git a/pumpkin-core/src/math/mod.rs b/pumpkin-core/src/math/mod.rs index e1f7149f0..78a4bb5b2 100644 --- a/pumpkin-core/src/math/mod.rs +++ b/pumpkin-core/src/math/mod.rs @@ -3,6 +3,7 @@ pub mod position; pub mod vector2; pub mod vector3; pub mod voxel_shape; +pub mod distance; pub fn wrap_degrees(var: f32) -> f32 { let mut var1 = var % 360.0; diff --git a/pumpkin/src/server/mod.rs b/pumpkin/src/server/mod.rs index 43606122d..7153b1ad3 100644 --- a/pumpkin/src/server/mod.rs +++ b/pumpkin/src/server/mod.rs @@ -2,6 +2,7 @@ use connection_cache::{CachedBranding, CachedStatus}; use key_store::KeyStore; use parking_lot::{Mutex, RwLock}; use pumpkin_config::BASIC_CONFIG; +use pumpkin_core::math::distance::distance; use pumpkin_core::math::vector3::Vector3; use pumpkin_core::text::TextComponent; use pumpkin_core::GameMode; @@ -146,13 +147,6 @@ impl Server { // TODO respect which world the player is in let world = self.worlds.first()?; - fn distance(p1: &Vector3, p2: &Vector3) -> f64 { - let dx = p1.x - p2.x; - let dy = p1.y - p2.y; - let dz = p1.z - p2.z; - dz.mul_add(dz, dx.mul_add(dx, dy * dy)).sqrt() - } - world .current_players .lock() From 36aba13757479b3c1304ef9fa0023caecc722355 Mon Sep 17 00:00:00 2001 From: van-sprundel Date: Sat, 19 Oct 2024 11:41:33 +0200 Subject: [PATCH 07/15] Use reference for broadcast_message --- pumpkin/src/commands/cmd_say.rs | 2 +- pumpkin/src/server/mod.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pumpkin/src/commands/cmd_say.rs b/pumpkin/src/commands/cmd_say.rs index a0fa2f33a..ea4705b0a 100644 --- a/pumpkin/src/commands/cmd_say.rs +++ b/pumpkin/src/commands/cmd_say.rs @@ -30,7 +30,7 @@ pub fn init_command_tree<'a>() -> CommandTree<'a> { let message = &format!("[Console]: {content}"); let message = TextComponent::text(message).color_named(NamedColor::Blue); - server.broadcast_message(message.clone()); + server.broadcast_message(&message); sender.send_message(message); } else { sender.send_message( diff --git a/pumpkin/src/server/mod.rs b/pumpkin/src/server/mod.rs index 7153b1ad3..7e56eadb9 100644 --- a/pumpkin/src/server/mod.rs +++ b/pumpkin/src/server/mod.rs @@ -131,9 +131,9 @@ impl Server { } /// Sends a message to all players in every world - pub fn broadcast_message(&self, content: TextComponent) { + pub fn broadcast_message(&self, content: &TextComponent) { for world in &self.worlds { - world.broadcast_message(&content); + world.broadcast_message(content); } } From b2415afb2b30b08a6ab0e002e91e5e8ff2218ca2 Mon Sep 17 00:00:00 2001 From: van-sprundel Date: Sat, 19 Oct 2024 11:41:45 +0200 Subject: [PATCH 08/15] Cargo fmt --- pumpkin-core/src/math/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pumpkin-core/src/math/mod.rs b/pumpkin-core/src/math/mod.rs index 78a4bb5b2..d3c37e384 100644 --- a/pumpkin-core/src/math/mod.rs +++ b/pumpkin-core/src/math/mod.rs @@ -1,9 +1,9 @@ pub mod boundingbox; +pub mod distance; pub mod position; pub mod vector2; pub mod vector3; pub mod voxel_shape; -pub mod distance; pub fn wrap_degrees(var: f32) -> f32 { let mut var1 = var % 360.0; From 3b03ebeb3ad49703fb1b956f36f706a1d0b5c9b6 Mon Sep 17 00:00:00 2001 From: van-sprundel Date: Sat, 19 Oct 2024 11:56:38 +0200 Subject: [PATCH 09/15] Only allow player for get_nearest_player --- pumpkin/src/commands/cmd_say.rs | 23 ++++++++++++++--------- pumpkin/src/server/mod.rs | 17 +++++++++-------- 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/pumpkin/src/commands/cmd_say.rs b/pumpkin/src/commands/cmd_say.rs index ea4705b0a..3a086a23c 100644 --- a/pumpkin/src/commands/cmd_say.rs +++ b/pumpkin/src/commands/cmd_say.rs @@ -3,7 +3,6 @@ use crate::commands::tree::RawArgs; use crate::commands::tree_builder::argument; use crate::commands::CommandSender; use crate::server::Server; -use pumpkin_core::math::vector3::Vector3; use pumpkin_core::text::{color::NamedColor, TextComponent}; const NAMES: [&str; 1] = ["say"]; @@ -53,13 +52,15 @@ fn parse_selectors(content: &str, sender: &CommandSender, server: &Server) -> St // TODO impl @e let result = match token { "@p" => { - let position = match sender { - CommandSender::Player(p) => p.last_position.load(), - _ => Vector3::new(0., 0., 0.), - }; - vec![server - .get_nearest_player(&position) - .map_or_else(|| String::from("nobody"), |p| p.gameprofile.name.clone())] + if let CommandSender::Player(player) = sender { + vec![server + .get_nearest_player(player) + .map_or_else(|| String::from("nobody"), |p| p.gameprofile.name.clone())] + } else { + final_message.push_str(token); + final_message.push(' '); + continue; + } } "@r" => { let online_players: Vec = server @@ -85,7 +86,11 @@ fn parse_selectors(content: &str, sender: &CommandSender, server: &Server) -> St .get_online_players() .map(|p| p.gameprofile.name.clone()) .collect::>(), - _ => vec![token.to_string()], + _ => { + final_message.push_str(token); + final_message.push(' '); + continue; + } }; // formatted player names diff --git a/pumpkin/src/server/mod.rs b/pumpkin/src/server/mod.rs index 7e56eadb9..cad1beeb0 100644 --- a/pumpkin/src/server/mod.rs +++ b/pumpkin/src/server/mod.rs @@ -3,7 +3,6 @@ use key_store::KeyStore; use parking_lot::{Mutex, RwLock}; use pumpkin_config::BASIC_CONFIG; use pumpkin_core::math::distance::distance; -use pumpkin_core::math::vector3::Vector3; use pumpkin_core::text::TextComponent; use pumpkin_core::GameMode; use pumpkin_entity::EntityId; @@ -142,18 +141,20 @@ impl Server { self.worlds.iter().flat_map(|world| world.get_players()) } - /// Gets the nearest player from the world position - pub fn get_nearest_player(&self, target: &Vector3) -> Option> { - // TODO respect which world the player is in - let world = self.worlds.first()?; + /// Gets the nearest player from another player + pub fn get_nearest_player(&self, player: &Player) -> Option> { + let target = player.last_position.load(); - world + player + .living_entity + .entity + .world .current_players .lock() .values() .min_by(|a, b| { - let dist_a = distance(&a.last_position.load(), target); - let dist_b = distance(&b.last_position.load(), target); + let dist_a = distance(&a.last_position.load(), &target); + let dist_b = distance(&b.last_position.load(), &target); dist_a.partial_cmp(&dist_b).unwrap() }) .cloned() From 1b3e040770898d0629aa9ad4450dcb85f7020791 Mon Sep 17 00:00:00 2001 From: van-sprundel Date: Sat, 19 Oct 2024 12:13:37 +0200 Subject: [PATCH 10/15] Move to get_player_names to avoid arc cloning, updated func --- pumpkin/src/commands/cmd_say.rs | 85 +++++++++++++++------------------ pumpkin/src/server/mod.rs | 10 ++-- pumpkin/src/world/mod.rs | 4 +- 3 files changed, 46 insertions(+), 53 deletions(-) diff --git a/pumpkin/src/commands/cmd_say.rs b/pumpkin/src/commands/cmd_say.rs index 3a086a23c..46f6ac53a 100644 --- a/pumpkin/src/commands/cmd_say.rs +++ b/pumpkin/src/commands/cmd_say.rs @@ -49,58 +49,51 @@ fn parse_selectors(content: &str, sender: &CommandSender, server: &Server) -> St let tokens: Vec<&str> = content.split_whitespace().collect(); for token in tokens { - // TODO impl @e - let result = match token { - "@p" => { - if let CommandSender::Player(player) = sender { - vec![server - .get_nearest_player(player) - .map_or_else(|| String::from("nobody"), |p| p.gameprofile.name.clone())] - } else { - final_message.push_str(token); - final_message.push(' '); - continue; - } - } - "@r" => { - let online_players: Vec = server - .get_online_players() - .map(|p| p.gameprofile.name.clone()) - .collect(); - - if online_players.is_empty() { - vec![String::from("nobody")] - } else { - vec![online_players[rand::random::() % online_players.len()].clone()] - } - } - "@s" => match sender { - CommandSender::Player(p) => vec![p.gameprofile.name.clone()], - _ => vec![String::from("console")], - }, - "@a" => server - .get_online_players() - .map(|p| p.gameprofile.name.clone()) - .collect::>(), - "@here" => server - .get_online_players() - .map(|p| p.gameprofile.name.clone()) - .collect::>(), - _ => { - final_message.push_str(token); - final_message.push(' '); - continue; - } - }; - - // formatted player names - final_message.push_str(&format_player_names(&result)); + let parsed_token = parse_token(token, sender, server); + final_message.push_str(&parsed_token); final_message.push(' '); } final_message.trim_end().to_string() } +fn parse_token<'a>(token: &'a str, sender: &'a CommandSender, server: &'a Server) -> String { + let result = match token { + "@p" => { + if let CommandSender::Player(player) = sender { + server + .get_nearest_player_name(player) + .map_or_else(Vec::new, |player_name| vec![player_name]) + } else { + return token.to_string(); + } + } + "@r" => { + let online_player_names: Vec = server.get_online_player_names(); + + if online_player_names.is_empty() { + vec![String::from("nobody")] + } else { + vec![ + online_player_names[rand::random::() % online_player_names.len()] + .clone(), + ] + } + } + "@s" => match sender { + CommandSender::Player(p) => vec![p.gameprofile.name.clone()], + _ => vec![String::from("console")], + }, + "@a" => server.get_online_player_names(), + "@here" => server.get_online_player_names(), + _ => { + return token.to_string(); + } + }; + + format_player_names(&result) +} + // Helper function to format player names according to spec // see https://minecraft.fandom.com/wiki/Commands/say fn format_player_names(names: &[String]) -> String { diff --git a/pumpkin/src/server/mod.rs b/pumpkin/src/server/mod.rs index cad1beeb0..aa898fc5a 100644 --- a/pumpkin/src/server/mod.rs +++ b/pumpkin/src/server/mod.rs @@ -136,13 +136,13 @@ impl Server { } } - /// Get all online players - pub fn get_online_players(&self) -> impl Iterator> + '_ { - self.worlds.iter().flat_map(|world| world.get_players()) + /// Get all online player names + pub fn get_online_player_names(&self) -> Vec { + self.worlds.iter().flat_map(|world| world.get_player_names()).collect::>() } /// Gets the nearest player from another player - pub fn get_nearest_player(&self, player: &Player) -> Option> { + pub fn get_nearest_player_name(&self, player: &Player) -> Option { let target = player.last_position.load(); player @@ -157,7 +157,7 @@ impl Server { let dist_b = distance(&b.last_position.load(), &target); dist_a.partial_cmp(&dist_b).unwrap() }) - .cloned() + .map(|p| p.gameprofile.name.clone()) } /// Searches every world for a player by name diff --git a/pumpkin/src/world/mod.rs b/pumpkin/src/world/mod.rs index 173d5c234..2e925349d 100644 --- a/pumpkin/src/world/mod.rs +++ b/pumpkin/src/world/mod.rs @@ -267,11 +267,11 @@ impl World { } /// Gets all players - pub fn get_players(&self) -> Vec> { + pub fn get_player_names(&self) -> Vec { self.current_players .lock() .values() - .cloned() + .map(|p| p.gameprofile.name.clone()) .collect::>() } From 7ac0d12d094fa09c12e317aaa25b0f73763d7382 Mon Sep 17 00:00:00 2001 From: van-sprundel Date: Sat, 19 Oct 2024 13:29:48 +0200 Subject: [PATCH 11/15] cargo fmt --- pumpkin/src/server/mod.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pumpkin/src/server/mod.rs b/pumpkin/src/server/mod.rs index aa898fc5a..beecb6e90 100644 --- a/pumpkin/src/server/mod.rs +++ b/pumpkin/src/server/mod.rs @@ -138,7 +138,10 @@ impl Server { /// Get all online player names pub fn get_online_player_names(&self) -> Vec { - self.worlds.iter().flat_map(|world| world.get_player_names()).collect::>() + self.worlds + .iter() + .flat_map(|world| world.get_player_names()) + .collect::>() } /// Gets the nearest player from another player From 07542b1e1eec47781530089e6b67c1e40a004ab5 Mon Sep 17 00:00:00 2001 From: van-sprundel Date: Sat, 19 Oct 2024 13:54:31 +0200 Subject: [PATCH 12/15] Move get_nearest_player_name down to world struct, move func to cmd module This function is specific to the command, we don't want to check any other world since a target specifier only checks the world the player is in --- pumpkin/src/commands/cmd_say.rs | 15 +++++++++++++-- pumpkin/src/server/mod.rs | 20 -------------------- pumpkin/src/world/mod.rs | 17 ++++++++++++++++- 3 files changed, 29 insertions(+), 23 deletions(-) diff --git a/pumpkin/src/commands/cmd_say.rs b/pumpkin/src/commands/cmd_say.rs index 46f6ac53a..4b8075693 100644 --- a/pumpkin/src/commands/cmd_say.rs +++ b/pumpkin/src/commands/cmd_say.rs @@ -2,6 +2,7 @@ use crate::commands::tree::CommandTree; use crate::commands::tree::RawArgs; use crate::commands::tree_builder::argument; use crate::commands::CommandSender; +use crate::entity::player::Player; use crate::server::Server; use pumpkin_core::text::{color::NamedColor, TextComponent}; @@ -61,8 +62,7 @@ fn parse_token<'a>(token: &'a str, sender: &'a CommandSender, server: &'a Server let result = match token { "@p" => { if let CommandSender::Player(player) = sender { - server - .get_nearest_player_name(player) + get_nearest_player_name(player) .map_or_else(Vec::new, |player_name| vec![player_name]) } else { return token.to_string(); @@ -94,6 +94,17 @@ fn parse_token<'a>(token: &'a str, sender: &'a CommandSender, server: &'a Server format_player_names(&result) } +// Gets the nearest player name in the same world +fn get_nearest_player_name(player: &Player) -> Option { + let target = player.last_position.load(); + + player + .living_entity + .entity + .world + .get_nearest_player_name(&target) +} + // Helper function to format player names according to spec // see https://minecraft.fandom.com/wiki/Commands/say fn format_player_names(names: &[String]) -> String { diff --git a/pumpkin/src/server/mod.rs b/pumpkin/src/server/mod.rs index beecb6e90..1851409ec 100644 --- a/pumpkin/src/server/mod.rs +++ b/pumpkin/src/server/mod.rs @@ -2,7 +2,6 @@ use connection_cache::{CachedBranding, CachedStatus}; use key_store::KeyStore; use parking_lot::{Mutex, RwLock}; use pumpkin_config::BASIC_CONFIG; -use pumpkin_core::math::distance::distance; use pumpkin_core::text::TextComponent; use pumpkin_core::GameMode; use pumpkin_entity::EntityId; @@ -144,25 +143,6 @@ impl Server { .collect::>() } - /// Gets the nearest player from another player - pub fn get_nearest_player_name(&self, player: &Player) -> Option { - let target = player.last_position.load(); - - player - .living_entity - .entity - .world - .current_players - .lock() - .values() - .min_by(|a, b| { - let dist_a = distance(&a.last_position.load(), &target); - let dist_b = distance(&b.last_position.load(), &target); - dist_a.partial_cmp(&dist_b).unwrap() - }) - .map(|p| p.gameprofile.name.clone()) - } - /// Searches every world for a player by name pub fn get_player_by_name(&self, name: &str) -> Option> { for world in self.worlds.iter() { diff --git a/pumpkin/src/world/mod.rs b/pumpkin/src/world/mod.rs index 2e925349d..ce5f5662a 100644 --- a/pumpkin/src/world/mod.rs +++ b/pumpkin/src/world/mod.rs @@ -9,7 +9,10 @@ use crate::{ use num_traits::ToPrimitive; use parking_lot::Mutex; use pumpkin_config::BasicConfiguration; -use pumpkin_core::{math::vector2::Vector2, text::TextComponent}; +use pumpkin_core::{ + math::{distance::distance, vector2::Vector2, vector3::Vector3}, + text::TextComponent, +}; use pumpkin_entity::{entity_type::EntityType, EntityId}; use pumpkin_protocol::{ client::play::{ @@ -275,6 +278,18 @@ impl World { .collect::>() } + pub fn get_nearest_player_name(&self, target: &Vector3) -> Option { + self.current_players + .lock() + .values() + .min_by(|a, b| { + let dist_a = distance(&a.last_position.load(), target); + let dist_b = distance(&b.last_position.load(), target); + dist_a.partial_cmp(&dist_b).unwrap() + }) + .map(|p| p.gameprofile.name.clone()) + } + /// Gets a Player by entity id pub fn get_player_by_entityid(&self, id: EntityId) -> Option> { for player in self.current_players.lock().values() { From d80a481b7c8dace5b84d00b63070ba31a46b0e69 Mon Sep 17 00:00:00 2001 From: van-sprundel Date: Sat, 19 Oct 2024 14:06:01 +0200 Subject: [PATCH 13/15] Only send console message when not a player --- pumpkin/src/commands/cmd_say.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pumpkin/src/commands/cmd_say.rs b/pumpkin/src/commands/cmd_say.rs index 4b8075693..4337649db 100644 --- a/pumpkin/src/commands/cmd_say.rs +++ b/pumpkin/src/commands/cmd_say.rs @@ -31,7 +31,10 @@ pub fn init_command_tree<'a>() -> CommandTree<'a> { let message = TextComponent::text(message).color_named(NamedColor::Blue); server.broadcast_message(&message); - sender.send_message(message); + + if !sender.is_player() { + sender.send_message(message); + } } else { sender.send_message( TextComponent::text("Please provide a message: say [content]") From 7b1b427b92e55d8676e1244bd896ec588ccac9ff Mon Sep 17 00:00:00 2001 From: van-sprundel Date: Sat, 19 Oct 2024 14:20:47 +0200 Subject: [PATCH 14/15] Put broadcast_message iter in closure --- pumpkin/src/world/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pumpkin/src/world/mod.rs b/pumpkin/src/world/mod.rs index ce5f5662a..a9fb9eaf6 100644 --- a/pumpkin/src/world/mod.rs +++ b/pumpkin/src/world/mod.rs @@ -264,9 +264,9 @@ impl World { /// Sends a message to all players pub fn broadcast_message(&self, content: &TextComponent) { - for player in self.current_players.lock().values() { + self.current_players.lock().values().for_each(|player| { player.send_system_message(content.clone()); - } + }); } /// Gets all players From 2d249c5fa0a4a04aaf0a91496ddadeadea645a2c Mon Sep 17 00:00:00 2001 From: van-sprundel Date: Sat, 19 Oct 2024 14:22:32 +0200 Subject: [PATCH 15/15] cargo fmt --- pumpkin/src/server/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pumpkin/src/server/mod.rs b/pumpkin/src/server/mod.rs index 1851409ec..5fb8e5737 100644 --- a/pumpkin/src/server/mod.rs +++ b/pumpkin/src/server/mod.rs @@ -130,9 +130,9 @@ impl Server { /// Sends a message to all players in every world pub fn broadcast_message(&self, content: &TextComponent) { - for world in &self.worlds { - world.broadcast_message(content); - } + self.worlds + .iter() + .for_each(|w| w.broadcast_message(content)); } /// Get all online player names