diff --git a/pumpkin/src/command/arg_player.rs b/pumpkin/src/command/arg_player.rs deleted file mode 100644 index bc0a7b652..000000000 --- a/pumpkin/src/command/arg_player.rs +++ /dev/null @@ -1,78 +0,0 @@ -use std::sync::Arc; - -use async_trait::async_trait; - -use crate::command::dispatcher::InvalidTreeError; -use crate::command::dispatcher::InvalidTreeError::InvalidConsumptionError; -use crate::command::tree::{ConsumedArgs, RawArgs}; -use crate::command::CommandSender; -use crate::server::Server; - -use super::tree::ArgumentConsumer; - -/// TODO: Seperate base functionality of these two methods into single method - -/// todo: implement (so far only own name + @s/@p is implemented) -pub(crate) struct PlayerArgumentConsumer {} - -#[async_trait] -impl ArgumentConsumer for PlayerArgumentConsumer { - async fn consume<'a>( - &self, - src: &CommandSender<'a>, - server: &Server, - args: &mut RawArgs<'a>, - ) -> Result> { - if let Some(arg) = args.pop() { - match arg { - "@s" => { - if src.is_player() { - return Ok(arg.into()); - } - return Err(Some("You are not a Player".into())); - } - "@p" if src.is_player() => return Ok(arg.into()), - "@r" => todo!(), // todo: implement random player target selector - "@a" | "@e" => todo!(), // todo: implement all players target selector - name => { - // todo: implement any other player than sender - for world in &server.worlds { - if world.get_player_by_name(name).await.is_some() { - return Ok(name.into()); - } - } - return Err(Some(format!("Player not found: {arg}"))); - } - } - } - Err(None) - } -} - -/// todo: implement (so far only own name + @s/@p is implemented) -pub async fn parse_arg_player<'a>( - src: &mut CommandSender<'a>, - server: &Server, - arg_name: &str, - consumed_args: &ConsumedArgs<'a>, -) -> Result, InvalidTreeError> { - let s = consumed_args - .get(arg_name) - .ok_or(InvalidConsumptionError(None))? - .as_str(); - - match s { - "@s" if src.is_player() => Ok(src.as_player().unwrap()), - "@p" => todo!(), - "@r" => todo!(), // todo: implement random player target selector - "@a" | "@e" => todo!(), // todo: implement all players target selector - name => { - for world in &server.worlds { - if let Some(player) = world.get_player_by_name(name).await { - return Ok(player); - } - } - Err(InvalidConsumptionError(Some(s.into()))) - } - } -} diff --git a/pumpkin/src/command/arg_position.rs b/pumpkin/src/command/arg_position.rs deleted file mode 100644 index 89b9a4af2..000000000 --- a/pumpkin/src/command/arg_position.rs +++ /dev/null @@ -1,45 +0,0 @@ -use async_trait::async_trait; - -use crate::command::dispatcher::InvalidTreeError; -use crate::command::tree::{ConsumedArgs, RawArgs}; -use crate::command::CommandSender; -use crate::server::Server; - -use super::tree::ArgumentConsumer; - -/// TODO: Seperate base functionality of these two methods into single method - -/// todo: implement (so far only own name + @s/@p is implemented) -pub(crate) struct PositionArgumentConsumer; - -#[async_trait] -impl ArgumentConsumer for PositionArgumentConsumer { - async fn consume<'a>( - &self, - _src: &CommandSender<'a>, - _server: &Server, - args: &mut RawArgs<'a>, - ) -> Result> { - let Some(arg) = args.pop() else { - return Err(None); - }; - - // TODO implement ~ ^ notations - let value = arg.parse::().map_err(|err| Some(err.to_string()))?; - Ok(value.to_string()) - } -} - -pub fn parse_arg_position( - arg_name: &str, - consumed_args: &ConsumedArgs<'_>, -) -> Result { - let s = consumed_args - .get(arg_name) - .ok_or(InvalidTreeError::InvalidConsumptionError(None))?; - - let value = s - .parse::() - .map_err(|err| InvalidTreeError::InvalidConsumptionError(Some(err.to_string())))?; - Ok(value) -} diff --git a/pumpkin/src/command/arg_simple.rs b/pumpkin/src/command/arg_simple.rs deleted file mode 100644 index 2a5d38ad9..000000000 --- a/pumpkin/src/command/arg_simple.rs +++ /dev/null @@ -1,23 +0,0 @@ -use async_trait::async_trait; - -use crate::server::Server; - -use super::{ - tree::{ArgumentConsumer, RawArgs}, - CommandSender, -}; - -/// Should never be a permanent solution -pub(crate) struct SimpleArgConsumer; - -#[async_trait] -impl ArgumentConsumer for SimpleArgConsumer { - async fn consume<'a>( - &self, - _sender: &CommandSender<'a>, - _server: &Server, - args: &mut RawArgs<'a>, - ) -> Result> { - args.pop().ok_or(None).map(ToString::to_string) - } -} diff --git a/pumpkin/src/command/args/arg_bounded_num.rs b/pumpkin/src/command/args/arg_bounded_num.rs new file mode 100644 index 000000000..97c23ccb7 --- /dev/null +++ b/pumpkin/src/command/args/arg_bounded_num.rs @@ -0,0 +1,157 @@ +use core::f64; +use std::str::FromStr; + +use async_trait::async_trait; + +use crate::command::dispatcher::InvalidTreeError; +use crate::command::tree::RawArgs; +use crate::command::CommandSender; +use crate::server::Server; + +use super::super::args::ArgumentConsumer; +use super::{Arg, DefaultNameArgConsumer, FindArg}; + +/// Consumes a single generic num, but only if it's in bounds. +pub(crate) struct BoundedNumArgumentConsumer { + min_inclusive: Option, + max_inclusive: Option, + name: Option<&'static str>, +} + +#[async_trait] +impl ArgumentConsumer for BoundedNumArgumentConsumer { + async fn consume<'a>( + &self, + _src: &CommandSender<'a>, + _server: &'a Server, + args: &mut RawArgs<'a>, + ) -> Option> { + let x = args.pop()?.parse::().ok()?; + + if let Some(max) = self.max_inclusive { + if x > max { + return None; + } + } + + if let Some(min) = self.min_inclusive { + if x < min { + return None; + } + } + + Some(x.to_arg()) + } +} + +impl<'a, T: ArgNum> FindArg<'a> for BoundedNumArgumentConsumer { + type Data = T; + + fn find_arg(args: &super::ConsumedArgs, name: &str) -> Result { + match args.get(name) { + Some(arg) => match T::from_arg(arg) { + Some(x) => Ok(x), + _ => Err(InvalidTreeError::InvalidConsumptionError(Some( + name.to_string(), + ))), + }, + _ => Err(InvalidTreeError::InvalidConsumptionError(Some( + name.to_string(), + ))), + } + } +} + +impl BoundedNumArgumentConsumer { + pub(crate) const fn new() -> Self { + Self { + min_inclusive: None, + max_inclusive: None, + name: None, + } + } + + pub(crate) const fn min(mut self, min_inclusive: T) -> Self { + self.min_inclusive = Some(min_inclusive); + self + } + + #[allow(unused)] + pub(crate) const fn max(mut self, max_inclusive: T) -> Self { + self.max_inclusive = Some(max_inclusive); + self + } + + pub(crate) const fn name(mut self, name: &'static str) -> Self { + self.name = Some(name); + self + } +} + +pub(crate) trait ArgNum: PartialOrd + Copy + Send + Sync + FromStr { + fn to_arg<'a>(self) -> Arg<'a>; + fn from_arg(arg: &Arg<'_>) -> Option; +} + +impl ArgNum for f64 { + fn to_arg<'a>(self) -> Arg<'a> { + Arg::F64(self) + } + + fn from_arg(arg: &Arg<'_>) -> Option { + match arg { + Arg::F64(x) => Some(*x), + _ => None, + } + } +} + +impl ArgNum for f32 { + fn to_arg<'a>(self) -> Arg<'a> { + Arg::F32(self) + } + + fn from_arg(arg: &Arg<'_>) -> Option { + match arg { + Arg::F32(x) => Some(*x), + _ => None, + } + } +} + +impl ArgNum for i32 { + fn to_arg<'a>(self) -> Arg<'a> { + Arg::I32(self) + } + + fn from_arg(arg: &Arg<'_>) -> Option { + match arg { + Arg::I32(x) => Some(*x), + _ => None, + } + } +} + +impl ArgNum for u32 { + fn to_arg<'a>(self) -> Arg<'a> { + Arg::U32(self) + } + + fn from_arg(arg: &Arg<'_>) -> Option { + match arg { + Arg::U32(x) => Some(*x), + _ => None, + } + } +} + +impl DefaultNameArgConsumer for BoundedNumArgumentConsumer { + fn default_name(&self) -> &'static str { + // setting a single default name for all BoundedNumArgumentConsumer variants is probably a bad idea since it would lead to confusion + self.name.expect("Only use *_default variants of methods with a BoundedNumArgumentConsumer that has a name.") + } + + fn get_argument_consumer(&self) -> &dyn ArgumentConsumer { + self + } +} diff --git a/pumpkin/src/command/args/arg_command.rs b/pumpkin/src/command/args/arg_command.rs new file mode 100644 index 000000000..6c1a1fd61 --- /dev/null +++ b/pumpkin/src/command/args/arg_command.rs @@ -0,0 +1,58 @@ +use async_trait::async_trait; + +use crate::{ + command::{ + dispatcher::InvalidTreeError, + tree::{CommandTree, RawArgs}, + CommandSender, + }, + server::Server, +}; + +use super::{Arg, ArgumentConsumer, DefaultNameArgConsumer, FindArg}; + +pub(crate) struct CommandTreeArgumentConsumer; + +#[async_trait] +impl ArgumentConsumer for CommandTreeArgumentConsumer { + async fn consume<'a>( + &self, + _sender: &CommandSender<'a>, + server: &'a Server, + args: &mut RawArgs<'a>, + ) -> Option> { + let s = args.pop()?; + + let dispatcher = &server.command_dispatcher; + return match dispatcher.get_tree(s) { + Ok(tree) => Some(Arg::CommandTree(tree)), + Err(_) => None, + }; + } +} + +impl DefaultNameArgConsumer for CommandTreeArgumentConsumer { + fn default_name(&self) -> &'static str { + "cmd" + } + + fn get_argument_consumer(&self) -> &dyn ArgumentConsumer { + &CommandTreeArgumentConsumer + } +} + +impl<'a> FindArg<'a> for CommandTreeArgumentConsumer { + type Data = &'a CommandTree<'a>; + + fn find_arg( + args: &'a super::ConsumedArgs, + name: &'a str, + ) -> Result { + match args.get(name) { + Some(Arg::CommandTree(tree)) => Ok(tree), + _ => Err(InvalidTreeError::InvalidConsumptionError(Some( + name.to_string(), + ))), + } + } +} diff --git a/pumpkin/src/command/args/arg_entities.rs b/pumpkin/src/command/args/arg_entities.rs new file mode 100644 index 000000000..1483eaca6 --- /dev/null +++ b/pumpkin/src/command/args/arg_entities.rs @@ -0,0 +1,59 @@ +use std::sync::Arc; + +use async_trait::async_trait; + +use crate::command::dispatcher::InvalidTreeError; +use crate::command::tree::RawArgs; +use crate::command::CommandSender; +use crate::entity::player::Player; +use crate::server::Server; + +use super::super::args::ArgumentConsumer; +use super::arg_player::PlayersArgumentConsumer; +use super::{Arg, DefaultNameArgConsumer, FindArg}; + +/// todo: implement (currently just calls [`super::arg_player::PlayerArgumentConsumer`]) +/// +/// For selecting zero, one or multiple entities, eg. using @s, a player name, @a or @e +pub(crate) struct EntitiesArgumentConsumer; + +#[async_trait] +impl ArgumentConsumer for EntitiesArgumentConsumer { + async fn consume<'a>( + &self, + src: &CommandSender<'a>, + server: &'a Server, + args: &mut RawArgs<'a>, + ) -> Option> { + match PlayersArgumentConsumer.consume(src, server, args).await { + Some(Arg::Players(p)) => Some(Arg::Entities(p)), + _ => None, + } + } +} + +impl DefaultNameArgConsumer for EntitiesArgumentConsumer { + fn default_name(&self) -> &'static str { + "targets" + } + + fn get_argument_consumer(&self) -> &dyn ArgumentConsumer { + &EntitiesArgumentConsumer + } +} + +impl<'a> FindArg<'a> for EntitiesArgumentConsumer { + type Data = &'a [Arc]; + + fn find_arg( + args: &'a super::ConsumedArgs, + name: &'a str, + ) -> Result { + match args.get(name) { + Some(Arg::Entities(data)) => Ok(data), + _ => Err(InvalidTreeError::InvalidConsumptionError(Some( + name.to_string(), + ))), + } + } +} diff --git a/pumpkin/src/command/args/arg_entity.rs b/pumpkin/src/command/args/arg_entity.rs new file mode 100644 index 000000000..cbcc933e7 --- /dev/null +++ b/pumpkin/src/command/args/arg_entity.rs @@ -0,0 +1,80 @@ +use std::sync::Arc; + +use async_trait::async_trait; + +use crate::command::dispatcher::InvalidTreeError; +use crate::command::tree::RawArgs; +use crate::command::CommandSender; +use crate::entity::player::Player; +use crate::server::Server; + +use super::super::args::ArgumentConsumer; +use super::{Arg, DefaultNameArgConsumer, FindArg}; + +/// todo: implement for entitites that aren't players +/// +/// For selecting a single entity, eg. using @s, a player name or entity uuid. +/// +/// Use [`super::arg_entities::EntitiesArgumentConsumer`] when there may be multiple targets. +pub(crate) struct EntityArgumentConsumer; + +#[async_trait] +impl ArgumentConsumer for EntityArgumentConsumer { + async fn consume<'a>( + &self, + src: &CommandSender<'a>, + server: &'a Server, + args: &mut RawArgs<'a>, + ) -> Option> { + let s = args.pop()?; + + let entity = match s { + // @s is always valid when sender is a player + "@s" => match src { + CommandSender::Player(p) => Some(p.clone()), + _ => None, + }, + // @n/@p are always valid when sender is a player + #[allow(clippy::match_same_arms)] // todo: implement for non-players + "@n" | "@p" => match src { + CommandSender::Player(p) => Some(p.clone()), + // todo: implement for non-players: how should this behave when sender is console/rcon? + _ => None, + }, + // @r is valid when there is at least one player + "@r" => server.get_random_player().await, + // @a/@e/@r are not valid because we're looking for a single entity + "@a" | "@e" => None, + // player name is only valid if player is online + name => server.get_player_by_name(name).await, + }; + + entity.map(Arg::Entity) + } +} + +impl DefaultNameArgConsumer for EntityArgumentConsumer { + fn default_name(&self) -> &'static str { + "target" + } + + fn get_argument_consumer(&self) -> &dyn ArgumentConsumer { + &EntityArgumentConsumer + } +} + +impl<'a> FindArg<'a> for EntityArgumentConsumer { + type Data = Arc; + + fn find_arg( + args: &'a super::ConsumedArgs, + name: &'a str, + ) -> Result { + match args.get(name) { + Some(Arg::Entity(data)) => Ok(data.clone()), + _ => Err(InvalidTreeError::InvalidConsumptionError(Some( + name.to_string(), + ))), + } + } +} diff --git a/pumpkin/src/command/args/arg_gamemode.rs b/pumpkin/src/command/args/arg_gamemode.rs new file mode 100644 index 000000000..54f957786 --- /dev/null +++ b/pumpkin/src/command/args/arg_gamemode.rs @@ -0,0 +1,64 @@ +use std::str::FromStr; + +use async_trait::async_trait; +use num_traits::FromPrimitive; +use pumpkin_core::GameMode; + +use crate::{ + command::{dispatcher::InvalidTreeError, tree::RawArgs, CommandSender}, + server::Server, +}; + +use super::{Arg, ArgumentConsumer, DefaultNameArgConsumer, FindArg}; + +pub(crate) struct GamemodeArgumentConsumer; + +#[async_trait] +impl ArgumentConsumer for GamemodeArgumentConsumer { + async fn consume<'a>( + &self, + _sender: &CommandSender<'a>, + _server: &'a Server, + args: &mut RawArgs<'a>, + ) -> Option> { + let s = args.pop()?; + + if let Ok(id) = s.parse::() { + match GameMode::from_u8(id) { + None | Some(GameMode::Undefined) => {} + Some(gamemode) => return Some(Arg::GameMode(gamemode)), + }; + }; + + match GameMode::from_str(s) { + Err(_) | Ok(GameMode::Undefined) => None, + Ok(gamemode) => Some(Arg::GameMode(gamemode)), + } + } +} + +impl DefaultNameArgConsumer for GamemodeArgumentConsumer { + fn default_name(&self) -> &'static str { + "gamemode" + } + + fn get_argument_consumer(&self) -> &dyn ArgumentConsumer { + &GamemodeArgumentConsumer + } +} + +impl<'a> FindArg<'a> for GamemodeArgumentConsumer { + type Data = GameMode; + + fn find_arg( + args: &'a super::ConsumedArgs, + name: &'a str, + ) -> Result { + match args.get(name) { + Some(Arg::GameMode(data)) => Ok(*data), + _ => Err(InvalidTreeError::InvalidConsumptionError(Some( + name.to_string(), + ))), + } + } +} diff --git a/pumpkin/src/command/args/arg_message.rs b/pumpkin/src/command/args/arg_message.rs new file mode 100644 index 000000000..48d79e7b9 --- /dev/null +++ b/pumpkin/src/command/args/arg_message.rs @@ -0,0 +1,59 @@ +use async_trait::async_trait; + +use crate::{command::dispatcher::InvalidTreeError, server::Server}; + +use super::{ + super::{ + args::{ArgumentConsumer, RawArgs}, + CommandSender, + }, + Arg, DefaultNameArgConsumer, FindArg, +}; + +/// Consumes all remaining words/args. Does not consume if there is no word. +pub(crate) struct MsgArgConsumer; + +#[async_trait] +impl ArgumentConsumer for MsgArgConsumer { + async fn consume<'a>( + &self, + _sender: &CommandSender<'a>, + _server: &'a Server, + args: &mut RawArgs<'a>, + ) -> Option> { + let mut msg = args.pop()?.to_string(); + + while let Some(word) = args.pop() { + msg.push(' '); + msg.push_str(word); + } + + Some(Arg::Msg(msg)) + } +} + +impl DefaultNameArgConsumer for MsgArgConsumer { + fn default_name(&self) -> &'static str { + "msg" + } + + fn get_argument_consumer(&self) -> &dyn ArgumentConsumer { + &MsgArgConsumer + } +} + +impl<'a> FindArg<'a> for MsgArgConsumer { + type Data = &'a str; + + fn find_arg( + args: &'a super::ConsumedArgs, + name: &'a str, + ) -> Result { + match args.get(name) { + Some(Arg::Msg(data)) => Ok(data), + _ => Err(InvalidTreeError::InvalidConsumptionError(Some( + name.to_string(), + ))), + } + } +} diff --git a/pumpkin/src/command/args/arg_player.rs b/pumpkin/src/command/args/arg_player.rs new file mode 100644 index 000000000..f10ed5e58 --- /dev/null +++ b/pumpkin/src/command/args/arg_player.rs @@ -0,0 +1,76 @@ +use std::sync::Arc; + +use async_trait::async_trait; + +use crate::command::dispatcher::InvalidTreeError; +use crate::command::tree::RawArgs; +use crate::command::CommandSender; +use crate::entity::player::Player; +use crate::server::Server; + +use super::super::args::ArgumentConsumer; +use super::{Arg, DefaultNameArgConsumer, FindArg}; + +/// Select zero, one or multiple players +pub(crate) struct PlayersArgumentConsumer; + +#[async_trait] +impl ArgumentConsumer for PlayersArgumentConsumer { + async fn consume<'a>( + &self, + src: &CommandSender<'a>, + server: &'a Server, + args: &mut RawArgs<'a>, + ) -> Option> { + let players = match args.pop()? { + "@s" => match src { + CommandSender::Player(p) => Some(vec![p.clone()]), + _ => None, + }, + #[allow(clippy::match_same_arms)] + // todo: implement for non-players and remove this line + "@n" | "@p" => match src { + CommandSender::Player(p) => Some(vec![p.clone()]), + // todo: implement for non-players: how should this behave when sender is console/rcon? + _ => None, + }, + "@r" => { + if let Some(p) = server.get_random_player().await { + Some(vec![p.clone()]) + } else { + Some(vec![]) + } + } + "@a" | "@e" => Some(server.get_all_players().await), + name => server.get_player_by_name(name).await.map(|p| vec![p]), + }; + + players.map(Arg::Players) + } +} + +impl DefaultNameArgConsumer for PlayersArgumentConsumer { + fn default_name(&self) -> &'static str { + "player" + } + + fn get_argument_consumer(&self) -> &dyn ArgumentConsumer { + &PlayersArgumentConsumer + } +} + +impl<'a> FindArg<'a> for PlayersArgumentConsumer { + type Data = &'a [Arc]; + + fn find_arg( + args: &'a super::ConsumedArgs, + name: &'a str, + ) -> Result { + match args.get(name) { + Some(Arg::Players(data)) => Ok(data), + _ => Err(InvalidTreeError::InvalidConsumptionError(Some( + name.to_string(), + ))), + } + } +} diff --git a/pumpkin/src/command/args/arg_position_2d.rs b/pumpkin/src/command/args/arg_position_2d.rs new file mode 100644 index 000000000..7a239119e --- /dev/null +++ b/pumpkin/src/command/args/arg_position_2d.rs @@ -0,0 +1,56 @@ +use async_trait::async_trait; +use pumpkin_core::math::vector2::Vector2; + +use crate::command::dispatcher::InvalidTreeError; +use crate::command::tree::RawArgs; +use crate::command::CommandSender; +use crate::server::Server; + +use super::super::args::ArgumentConsumer; +use super::{Arg, DefaultNameArgConsumer, FindArg}; + +/// x and z coordinates only +/// +/// todo: implememnt ~ ^ notations +pub(crate) struct Position2DArgumentConsumer; + +#[async_trait] +impl ArgumentConsumer for Position2DArgumentConsumer { + async fn consume<'a>( + &self, + _src: &CommandSender<'a>, + _server: &'a Server, + args: &mut RawArgs<'a>, + ) -> Option> { + let x = args.pop()?.parse::().ok()?; + let z = args.pop()?.parse::().ok()?; + + Some(Arg::Pos2D(Vector2::new(x, z))) + } +} + +impl DefaultNameArgConsumer for Position2DArgumentConsumer { + fn default_name(&self) -> &'static str { + "pos2d" + } + + fn get_argument_consumer(&self) -> &dyn ArgumentConsumer { + &Position2DArgumentConsumer + } +} + +impl<'a> FindArg<'a> for Position2DArgumentConsumer { + type Data = Vector2; + + fn find_arg( + args: &'a super::ConsumedArgs, + name: &'a str, + ) -> Result { + match args.get(name) { + Some(Arg::Pos2D(data)) => Ok(*data), + _ => Err(InvalidTreeError::InvalidConsumptionError(Some( + name.to_string(), + ))), + } + } +} diff --git a/pumpkin/src/command/args/arg_position_3d.rs b/pumpkin/src/command/args/arg_position_3d.rs new file mode 100644 index 000000000..451b4049c --- /dev/null +++ b/pumpkin/src/command/args/arg_position_3d.rs @@ -0,0 +1,104 @@ +use std::num::ParseFloatError; + +use async_trait::async_trait; +use pumpkin_core::math::vector3::Vector3; + +use crate::command::dispatcher::InvalidTreeError; +use crate::command::tree::RawArgs; +use crate::command::CommandSender; +use crate::server::Server; + +use super::super::args::ArgumentConsumer; +use super::{Arg, DefaultNameArgConsumer, FindArg}; + +/// x, y and z coordinates +pub(crate) struct Position3DArgumentConsumer; + +#[async_trait] +impl ArgumentConsumer for Position3DArgumentConsumer { + async fn consume<'a>( + &self, + src: &CommandSender<'a>, + _server: &'a Server, + args: &mut RawArgs<'a>, + ) -> Option> { + let pos = Position3D::try_new(args.pop(), args.pop(), args.pop())?; + + let vec3 = pos.try_get_values(src.position())?; + + Some(Arg::Pos3D(vec3)) + } +} + +struct Position3D(Coordinate, Coordinate, Coordinate); + +impl Position3D { + fn try_new(x: Option<&str>, y: Option<&str>, z: Option<&str>) -> Option { + Some(Self( + x?.try_into().ok()?, + y?.try_into().ok()?, + z?.try_into().ok()?, + )) + } + + fn try_get_values(self, origin: Option>) -> Option> { + Some(Vector3::new( + self.0.value(origin.map(|o| o.x))?, + self.1.value(origin.map(|o| o.y))?, + self.2.value(origin.map(|o| o.z))?, + )) + } +} + +impl DefaultNameArgConsumer for Position3DArgumentConsumer { + fn default_name(&self) -> &'static str { + "pos" + } + + fn get_argument_consumer(&self) -> &dyn ArgumentConsumer { + &Position3DArgumentConsumer + } +} + +enum Coordinate { + Absolute(f64), + Relative(f64), +} + +impl TryFrom<&str> for Coordinate { + type Error = ParseFloatError; + + fn try_from(s: &str) -> Result { + if let Some(s) = s.strip_prefix('~') { + let offset = if s.is_empty() { 0.0 } else { s.parse()? }; + Ok(Self::Relative(offset)) + } else { + Ok(Self::Absolute(s.parse()?)) + } + } +} + +impl Coordinate { + fn value(self, origin: Option) -> Option { + match self { + Self::Absolute(v) => Some(v), + Self::Relative(offset) => Some(origin? + offset), + } + } +} + +impl<'a> FindArg<'a> for Position3DArgumentConsumer { + type Data = Vector3; + + fn find_arg( + args: &'a super::ConsumedArgs, + name: &'a str, + ) -> Result { + match args.get(name) { + Some(Arg::Pos3D(data)) => Ok(*data), + _ => Err(InvalidTreeError::InvalidConsumptionError(Some( + name.to_string(), + ))), + } + } +} diff --git a/pumpkin/src/command/args/arg_rotation.rs b/pumpkin/src/command/args/arg_rotation.rs new file mode 100644 index 000000000..7ae4402fd --- /dev/null +++ b/pumpkin/src/command/args/arg_rotation.rs @@ -0,0 +1,62 @@ +use async_trait::async_trait; + +use crate::command::dispatcher::InvalidTreeError; +use crate::command::tree::RawArgs; +use crate::command::CommandSender; +use crate::server::Server; + +use super::super::args::ArgumentConsumer; +use super::{Arg, DefaultNameArgConsumer, FindArg}; + +/// yaw and pitch +pub(crate) struct RotationArgumentConsumer; + +#[async_trait] +impl ArgumentConsumer for RotationArgumentConsumer { + async fn consume<'a>( + &self, + _src: &CommandSender<'a>, + _server: &'a Server, + args: &mut RawArgs<'a>, + ) -> Option> { + let mut yaw = args.pop()?.parse::().ok()?; + let mut pitch = args.pop()?.parse::().ok()?; + + yaw %= 360.0; + if yaw >= 180.0 { + yaw -= 360.0; + }; + pitch %= 360.0; + if pitch >= 180.0 { + pitch -= 360.0; + }; + + Some(Arg::Rotation(yaw, pitch)) + } +} + +impl DefaultNameArgConsumer for RotationArgumentConsumer { + fn default_name(&self) -> &'static str { + "rotation" + } + + fn get_argument_consumer(&self) -> &dyn ArgumentConsumer { + &RotationArgumentConsumer + } +} + +impl<'a> FindArg<'a> for RotationArgumentConsumer { + type Data = (f32, f32); + + fn find_arg( + args: &'a super::ConsumedArgs, + name: &'a str, + ) -> Result { + match args.get(name) { + Some(Arg::Rotation(yaw, pitch)) => Ok((*yaw, *pitch)), + _ => Err(InvalidTreeError::InvalidConsumptionError(Some( + name.to_string(), + ))), + } + } +} diff --git a/pumpkin/src/command/args/arg_simple.rs b/pumpkin/src/command/args/arg_simple.rs new file mode 100644 index 000000000..5691d0cbd --- /dev/null +++ b/pumpkin/src/command/args/arg_simple.rs @@ -0,0 +1,43 @@ +use async_trait::async_trait; + +use crate::{command::dispatcher::InvalidTreeError, server::Server}; + +use super::{ + super::{ + args::{ArgumentConsumer, RawArgs}, + CommandSender, + }, + Arg, FindArg, +}; + +/// Should never be a permanent solution +#[allow(unused)] +pub(crate) struct SimpleArgConsumer; + +#[async_trait] +impl ArgumentConsumer for SimpleArgConsumer { + async fn consume<'a>( + &self, + _sender: &CommandSender<'a>, + _server: &'a Server, + args: &mut RawArgs<'a>, + ) -> Option> { + Some(Arg::Simple(args.pop()?.to_string())) + } +} + +impl<'a> FindArg<'a> for SimpleArgConsumer { + type Data = &'a str; + + fn find_arg( + args: &'a super::ConsumedArgs, + name: &'a str, + ) -> Result { + match args.get(name) { + Some(Arg::Simple(data)) => Ok(data), + _ => Err(InvalidTreeError::InvalidConsumptionError(Some( + name.to_string(), + ))), + } + } +} diff --git a/pumpkin/src/command/args/mod.rs b/pumpkin/src/command/args/mod.rs new file mode 100644 index 000000000..877dc8bca --- /dev/null +++ b/pumpkin/src/command/args/mod.rs @@ -0,0 +1,95 @@ +use std::{collections::HashMap, hash::Hash, sync::Arc}; + +use async_trait::async_trait; +use pumpkin_core::{ + math::{vector2::Vector2, vector3::Vector3}, + GameMode, +}; + +use crate::{entity::player::Player, server::Server}; + +use super::{ + dispatcher::InvalidTreeError, + tree::{CommandTree, RawArgs}, + CommandSender, +}; + +pub(crate) mod arg_bounded_num; +pub(crate) mod arg_command; +pub(crate) mod arg_entities; +pub(crate) mod arg_entity; +pub(crate) mod arg_gamemode; +pub(crate) mod arg_message; +pub(crate) mod arg_player; +pub(crate) mod arg_position_2d; +pub(crate) mod arg_position_3d; +pub(crate) mod arg_rotation; +pub(crate) mod arg_simple; + +/// see [`crate::commands::tree_builder::argument`] +/// Provide value or an Optional error message, If no Error message provided the default will be used +#[async_trait] +pub(crate) trait ArgumentConsumer: Sync { + async fn consume<'a>( + &self, + sender: &CommandSender<'a>, + server: &'a Server, + args: &mut RawArgs<'a>, + ) -> Option>; +} + +pub(crate) trait DefaultNameArgConsumer: ArgumentConsumer { + fn default_name(&self) -> &'static str; + + /// needed because trait upcasting is not stable + fn get_argument_consumer(&self) -> &dyn ArgumentConsumer; +} + +#[derive(Clone)] +pub(crate) enum Arg<'a> { + Entities(Vec>), + Entity(Arc), + Players(Vec>), + Pos3D(Vector3), + Pos2D(Vector2), + Rotation(f32, f32), + GameMode(GameMode), + CommandTree(&'a CommandTree<'a>), + Msg(String), + F64(f64), + F32(f32), + I32(i32), + #[allow(unused)] + U32(u32), + #[allow(unused)] + Simple(String), +} + +/// see [`crate::commands::tree_builder::argument`] and [`CommandTree::execute`]/[`crate::commands::tree_builder::NonLeafNodeBuilder::execute`] +pub(crate) type ConsumedArgs<'a> = HashMap<&'a str, Arg<'a>>; + +pub(crate) trait GetCloned { + fn get_cloned(&self, key: &K) -> Option; +} + +impl GetCloned for HashMap { + fn get_cloned(&self, key: &K) -> Option { + self.get(key).cloned() + } +} + +pub(crate) trait FindArg<'a> { + type Data; + + fn find_arg(args: &'a ConsumedArgs, name: &'a str) -> Result; +} + +pub(crate) trait FindArgDefaultName<'a, T> { + fn find_arg_default_name(&self, args: &'a ConsumedArgs) -> Result; +} + +impl<'a, T, C: FindArg<'a, Data = T> + DefaultNameArgConsumer> FindArgDefaultName<'a, T> for C { + fn find_arg_default_name(&self, args: &'a ConsumedArgs) -> Result { + C::find_arg(args, self.default_name()) + } +} diff --git a/pumpkin/src/command/commands/cmd_echest.rs b/pumpkin/src/command/commands/cmd_echest.rs index 04ac05acb..20c9d7c8d 100644 --- a/pumpkin/src/command/commands/cmd_echest.rs +++ b/pumpkin/src/command/commands/cmd_echest.rs @@ -2,7 +2,7 @@ use async_trait::async_trait; use pumpkin_inventory::OpenContainer; use crate::command::{ - tree::CommandTree, tree::ConsumedArgs, CommandExecutor, CommandSender, InvalidTreeError, + args::ConsumedArgs, tree::CommandTree, CommandExecutor, CommandSender, InvalidTreeError, }; const NAMES: [&str; 2] = ["echest", "enderchest"]; @@ -10,7 +10,7 @@ const NAMES: [&str; 2] = ["echest", "enderchest"]; const DESCRIPTION: &str = "Show your personal enderchest (this command is used for testing container behaviour)"; -struct EchestExecutor {} +struct EchestExecutor; #[async_trait] impl CommandExecutor for EchestExecutor { @@ -42,5 +42,5 @@ impl CommandExecutor for EchestExecutor { } pub fn init_command_tree<'a>() -> CommandTree<'a> { - CommandTree::new(NAMES, DESCRIPTION).execute(&EchestExecutor {}) + CommandTree::new(NAMES, DESCRIPTION).execute(&EchestExecutor) } diff --git a/pumpkin/src/command/commands/cmd_gamemode.rs b/pumpkin/src/command/commands/cmd_gamemode.rs index 3825ec044..af19ac11c 100644 --- a/pumpkin/src/command/commands/cmd_gamemode.rs +++ b/pumpkin/src/command/commands/cmd_gamemode.rs @@ -1,18 +1,18 @@ -use std::str::FromStr; - use async_trait::async_trait; -use num_traits::FromPrimitive; -use pumpkin_core::GameMode; + +use crate::command::args::arg_gamemode::GamemodeArgumentConsumer; +use crate::command::args::GetCloned; use crate::TextComponent; -use crate::command::arg_player::{parse_arg_player, PlayerArgumentConsumer}; +use crate::command::args::arg_player::PlayersArgumentConsumer; +use crate::command::args::{Arg, ConsumedArgs}; use crate::command::dispatcher::InvalidTreeError; use crate::command::dispatcher::InvalidTreeError::{ InvalidConsumptionError, InvalidRequirementError, }; -use crate::command::tree::{ArgumentConsumer, CommandTree, ConsumedArgs, RawArgs}; +use crate::command::tree::CommandTree; use crate::command::tree_builder::{argument, require}; use crate::command::CommandSender::Player; use crate::command::{CommandExecutor, CommandSender}; @@ -25,54 +25,7 @@ const DESCRIPTION: &str = "Change a player's gamemode."; const ARG_GAMEMODE: &str = "gamemode"; const ARG_TARGET: &str = "target"; -struct GamemodeArgumentConsumer {} - -#[async_trait] -impl ArgumentConsumer for GamemodeArgumentConsumer { - async fn consume<'a>( - &self, - _sender: &CommandSender<'a>, - _server: &Server, - args: &mut RawArgs<'a>, - ) -> Result> { - if let Some(arg) = args.pop() { - if let Ok(id) = arg.parse::() { - match GameMode::from_u8(id) { - None | Some(GameMode::Undefined) => {} - Some(_) => return Ok(arg.into()), - }; - }; - - match GameMode::from_str(arg) { - Err(_) | Ok(GameMode::Undefined) => { - return Err(Some(format!("Gamemode not found: {arg}"))) - } - Ok(_) => return Ok(arg.into()), - } - } - Err(None) - } -} - -pub fn parse_arg_gamemode(consumed_args: &ConsumedArgs) -> Result { - let s = consumed_args - .get(ARG_GAMEMODE) - .ok_or(InvalidConsumptionError(None))?; - - if let Ok(id) = s.parse::() { - match GameMode::from_u8(id) { - None | Some(GameMode::Undefined) => {} - Some(gamemode) => return Ok(gamemode), - }; - }; - - match GameMode::from_str(s) { - Err(_) | Ok(GameMode::Undefined) => Err(InvalidConsumptionError(Some(s.into()))), - Ok(gamemode) => Ok(gamemode), - } -} - -struct GamemodeTargetSelf {} +struct GamemodeTargetSelf; #[async_trait] impl CommandExecutor for GamemodeTargetSelf { @@ -82,7 +35,9 @@ impl CommandExecutor for GamemodeTargetSelf { _server: &Server, args: &ConsumedArgs<'a>, ) -> Result<(), InvalidTreeError> { - let gamemode = parse_arg_gamemode(args)?; + let Some(Arg::GameMode(gamemode)) = args.get_cloned(&ARG_GAMEMODE) else { + return Err(InvalidConsumptionError(Some(ARG_GAMEMODE.into()))); + }; if let Player(target) = sender { if target.gamemode.load() == gamemode { @@ -106,34 +61,46 @@ impl CommandExecutor for GamemodeTargetSelf { } } -struct GamemodeTargetPlayer {} +struct GamemodeTargetPlayer; #[async_trait] impl CommandExecutor for GamemodeTargetPlayer { async fn execute<'a>( &self, sender: &mut CommandSender<'a>, - server: &Server, + _server: &Server, args: &ConsumedArgs<'a>, ) -> Result<(), InvalidTreeError> { - let gamemode = parse_arg_gamemode(args)?; - let target = parse_arg_player(sender, server, ARG_TARGET, args).await?; - - if target.gamemode.load() == gamemode { - sender - .send_message(TextComponent::text(&format!( - "{} is already in {:?} gamemode", - target.gameprofile.name, gamemode - ))) - .await; - } else { - target.set_gamemode(gamemode).await; - sender - .send_message(TextComponent::text(&format!( - "{}'s Game mode was set to {:?}", - target.gameprofile.name, gamemode - ))) - .await; + let Some(Arg::GameMode(gamemode)) = args.get_cloned(&ARG_GAMEMODE) else { + return Err(InvalidConsumptionError(Some(ARG_GAMEMODE.into()))); + }; + let Some(Arg::Players(targets)) = args.get(ARG_TARGET) else { + return Err(InvalidConsumptionError(Some(ARG_TARGET.into()))); + }; + + let target_count = targets.len(); + + for target in targets { + if target.gamemode.load() == gamemode { + if target_count == 1 { + sender + .send_message(TextComponent::text(&format!( + "{} is already in {:?} gamemode", + target.gameprofile.name, gamemode + ))) + .await; + } + } else { + target.set_gamemode(gamemode).await; + if target_count == 1 { + sender + .send_message(TextComponent::text(&format!( + "{}'s Game mode was set to {:?}", + target.gameprofile.name, gamemode + ))) + .await; + } + } } Ok(()) @@ -144,11 +111,10 @@ impl CommandExecutor for GamemodeTargetPlayer { 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, &GamemodeArgumentConsumer {}) - .with_child(require(&|sender| sender.is_player()).execute(&GamemodeTargetSelf {})) + argument(ARG_GAMEMODE, &GamemodeArgumentConsumer) + .with_child(require(&|sender| sender.is_player()).execute(&GamemodeTargetSelf)) .with_child( - argument(ARG_TARGET, &PlayerArgumentConsumer {}) - .execute(&GamemodeTargetPlayer {}), + argument(ARG_TARGET, &PlayersArgumentConsumer).execute(&GamemodeTargetPlayer), ), ), ) diff --git a/pumpkin/src/command/commands/cmd_help.rs b/pumpkin/src/command/commands/cmd_help.rs index c7a518565..9f304806b 100644 --- a/pumpkin/src/command/commands/cmd_help.rs +++ b/pumpkin/src/command/commands/cmd_help.rs @@ -1,9 +1,11 @@ use async_trait::async_trait; use pumpkin_core::text::TextComponent; +use crate::command::args::arg_command::CommandTreeArgumentConsumer; +use crate::command::args::{Arg, ConsumedArgs}; +use crate::command::dispatcher::InvalidTreeError; use crate::command::dispatcher::InvalidTreeError::InvalidConsumptionError; -use crate::command::dispatcher::{CommandDispatcher, InvalidTreeError}; -use crate::command::tree::{ArgumentConsumer, Command, CommandTree, ConsumedArgs, RawArgs}; +use crate::command::tree::{Command, CommandTree}; use crate::command::tree_builder::argument; use crate::command::{CommandExecutor, CommandSender}; use crate::server::Server; @@ -14,48 +16,19 @@ const DESCRIPTION: &str = "Print a help message."; const ARG_COMMAND: &str = "command"; -struct CommandArgumentConsumer {} +struct CommandHelpExecutor; #[async_trait] -impl ArgumentConsumer for CommandArgumentConsumer { - async fn consume<'a>( - &self, - _sender: &CommandSender<'a>, - _server: &Server, - _args: &mut RawArgs<'a>, - ) -> Result> { - //let s = args.pop()?; - - // dispatcher.get_tree(s).ok().map(|tree| tree.names[0].into()) - // TODO: Implement this - Err(None) - } -} - -fn parse_arg_command<'a>( - consumed_args: &'a ConsumedArgs, - dispatcher: &'a CommandDispatcher, -) -> Result<&'a CommandTree<'a>, InvalidTreeError> { - let command_name = consumed_args - .get(ARG_COMMAND) - .ok_or(InvalidConsumptionError(None))?; - - dispatcher - .get_tree(command_name) - .map_err(|_| InvalidConsumptionError(Some(command_name.into()))) -} - -struct BaseHelpExecutor {} - -#[async_trait] -impl CommandExecutor for BaseHelpExecutor { +impl CommandExecutor for CommandHelpExecutor { async fn execute<'a>( &self, sender: &mut CommandSender<'a>, - server: &Server, + _server: &Server, args: &ConsumedArgs<'a>, ) -> Result<(), InvalidTreeError> { - let tree = parse_arg_command(args, &server.command_dispatcher)?; + let Some(Arg::CommandTree(tree)) = args.get(&ARG_COMMAND) else { + return Err(InvalidConsumptionError(Some(ARG_COMMAND.into()))); + }; sender .send_message(TextComponent::text(&format!( @@ -70,10 +43,10 @@ impl CommandExecutor for BaseHelpExecutor { } } -struct CommandHelpExecutor {} +struct BaseHelpExecutor; #[async_trait] -impl CommandExecutor for CommandHelpExecutor { +impl CommandExecutor for BaseHelpExecutor { async fn execute<'a>( &self, sender: &mut CommandSender<'a>, @@ -105,7 +78,7 @@ impl CommandExecutor for CommandHelpExecutor { pub fn init_command_tree<'a>() -> CommandTree<'a> { CommandTree::new(NAMES, DESCRIPTION) .with_child( - argument(ARG_COMMAND, &CommandArgumentConsumer {}).execute(&BaseHelpExecutor {}), + argument(ARG_COMMAND, &CommandTreeArgumentConsumer).execute(&CommandHelpExecutor), ) - .execute(&CommandHelpExecutor {}) + .execute(&BaseHelpExecutor) } diff --git a/pumpkin/src/command/commands/cmd_kick.rs b/pumpkin/src/command/commands/cmd_kick.rs index 041a1af75..33cb696a5 100644 --- a/pumpkin/src/command/commands/cmd_kick.rs +++ b/pumpkin/src/command/commands/cmd_kick.rs @@ -2,37 +2,48 @@ use async_trait::async_trait; use pumpkin_core::text::color::NamedColor; use pumpkin_core::text::TextComponent; -use crate::command::arg_player::{parse_arg_player, PlayerArgumentConsumer}; +use crate::command::args::arg_player::PlayersArgumentConsumer; +use crate::command::args::{Arg, ConsumedArgs}; use crate::command::tree::CommandTree; use crate::command::tree_builder::argument; use crate::command::InvalidTreeError; -use crate::command::{tree::ConsumedArgs, CommandExecutor, CommandSender}; +use crate::command::{CommandExecutor, CommandSender}; +use InvalidTreeError::InvalidConsumptionError; const NAMES: [&str; 1] = ["kick"]; const DESCRIPTION: &str = "Kicks the target player from the server."; const ARG_TARGET: &str = "target"; -struct KickExecutor {} +struct KickExecutor; #[async_trait] impl CommandExecutor for KickExecutor { async fn execute<'a>( &self, sender: &mut CommandSender<'a>, - server: &crate::server::Server, + _server: &crate::server::Server, args: &ConsumedArgs<'a>, ) -> Result<(), InvalidTreeError> { - let target = parse_arg_player(sender, server, ARG_TARGET, args).await?; - target - .kick(TextComponent::text("Kicked by an operator")) - .await; + let Some(Arg::Players(targets)) = args.get(&ARG_TARGET) else { + return Err(InvalidConsumptionError(Some(ARG_TARGET.into()))); + }; - sender - .send_message( - TextComponent::text("Player has been kicked.").color_named(NamedColor::Blue), - ) - .await; + let target_count = targets.len(); + + for target in targets { + target + .kick(TextComponent::text("Kicked by an operator")) + .await; + } + + let msg = if target_count == 1 { + TextComponent::text("Player has been kicked.") + } else { + TextComponent::text_string(format!("{target_count} players have been kicked.")) + }; + + sender.send_message(msg.color_named(NamedColor::Blue)).await; Ok(()) } @@ -40,5 +51,5 @@ impl CommandExecutor for KickExecutor { pub fn init_command_tree<'a>() -> CommandTree<'a> { CommandTree::new(NAMES, DESCRIPTION) - .with_child(argument(ARG_TARGET, &PlayerArgumentConsumer {}).execute(&KickExecutor {})) + .with_child(argument(ARG_TARGET, &PlayersArgumentConsumer).execute(&KickExecutor)) } diff --git a/pumpkin/src/command/commands/cmd_kill.rs b/pumpkin/src/command/commands/cmd_kill.rs index f0141ce72..4f077392f 100644 --- a/pumpkin/src/command/commands/cmd_kill.rs +++ b/pumpkin/src/command/commands/cmd_kill.rs @@ -2,41 +2,73 @@ use async_trait::async_trait; use pumpkin_core::text::color::NamedColor; use pumpkin_core::text::TextComponent; -use crate::command::arg_player::{parse_arg_player, PlayerArgumentConsumer}; +use crate::command::args::arg_entities::EntitiesArgumentConsumer; +use crate::command::args::{Arg, ConsumedArgs}; use crate::command::tree::CommandTree; -use crate::command::tree_builder::argument; -use crate::command::{tree::ConsumedArgs, CommandExecutor, CommandSender, InvalidTreeError}; +use crate::command::tree_builder::{argument, require}; +use crate::command::{CommandExecutor, CommandSender, InvalidTreeError}; +use InvalidTreeError::InvalidConsumptionError; const NAMES: [&str; 1] = ["kill"]; -const DESCRIPTION: &str = "Kills a target player."; +const DESCRIPTION: &str = "Kills all target entities."; const ARG_TARGET: &str = "target"; -struct KillExecutor {} +struct KillExecutor; #[async_trait] impl CommandExecutor for KillExecutor { async fn execute<'a>( &self, sender: &mut CommandSender<'a>, - server: &crate::server::Server, + _server: &crate::server::Server, args: &ConsumedArgs<'a>, ) -> Result<(), InvalidTreeError> { - // TODO parse entities not only players - let target = parse_arg_player(sender, server, ARG_TARGET, args).await?; - target.living_entity.kill().await; + let Some(Arg::Entities(targets)) = args.get(&ARG_TARGET) else { + return Err(InvalidConsumptionError(Some(ARG_TARGET.into()))); + }; + + let target_count = targets.len(); + + for target in targets { + target.living_entity.kill().await; + } + + let msg = if target_count == 1 { + TextComponent::text("Enitity has been killed.") + } else { + TextComponent::text_string(format!("{target_count} entities have been killed.")) + }; + + sender.send_message(msg.color_named(NamedColor::Blue)).await; + + Ok(()) + } +} - sender - .send_message( - TextComponent::text("Player has been killed.").color_named(NamedColor::Blue), - ) - .await; +struct KillSelfExecutor; + +#[async_trait] +impl CommandExecutor for KillSelfExecutor { + async fn execute<'a>( + &self, + sender: &mut CommandSender<'a>, + _server: &crate::server::Server, + _args: &ConsumedArgs<'a>, + ) -> Result<(), InvalidTreeError> { + let target = sender + .as_player() + .ok_or(InvalidTreeError::InvalidRequirementError)?; + + target.living_entity.kill().await; Ok(()) } } +#[allow(clippy::redundant_closure_for_method_calls)] // causes lifetime issues pub fn init_command_tree<'a>() -> CommandTree<'a> { CommandTree::new(NAMES, DESCRIPTION) - .with_child(argument(ARG_TARGET, &PlayerArgumentConsumer {}).execute(&KillExecutor {})) + .with_child(argument(ARG_TARGET, &EntitiesArgumentConsumer).execute(&KillExecutor)) + .with_child(require(&|sender| sender.is_player()).execute(&KillSelfExecutor)) } diff --git a/pumpkin/src/command/commands/cmd_pumpkin.rs b/pumpkin/src/command/commands/cmd_pumpkin.rs index 524ad93be..eae8b2959 100644 --- a/pumpkin/src/command/commands/cmd_pumpkin.rs +++ b/pumpkin/src/command/commands/cmd_pumpkin.rs @@ -4,7 +4,7 @@ use pumpkin_protocol::CURRENT_MC_PROTOCOL; use crate::{ command::{ - tree::CommandTree, tree::ConsumedArgs, CommandExecutor, CommandSender, InvalidTreeError, + args::ConsumedArgs, tree::CommandTree, CommandExecutor, CommandSender, InvalidTreeError, }, server::CURRENT_MC_VERSION, }; @@ -13,7 +13,7 @@ const NAMES: [&str; 1] = ["pumpkin"]; const DESCRIPTION: &str = "Display information about Pumpkin."; -struct PumpkinExecutor {} +struct PumpkinExecutor; #[async_trait] impl CommandExecutor for PumpkinExecutor { @@ -35,5 +35,5 @@ impl CommandExecutor for PumpkinExecutor { } pub fn init_command_tree<'a>() -> CommandTree<'a> { - CommandTree::new(NAMES, DESCRIPTION).execute(&PumpkinExecutor {}) + CommandTree::new(NAMES, DESCRIPTION).execute(&PumpkinExecutor) } diff --git a/pumpkin/src/command/commands/cmd_say.rs b/pumpkin/src/command/commands/cmd_say.rs index 3c1e38e50..4245623b8 100644 --- a/pumpkin/src/command/commands/cmd_say.rs +++ b/pumpkin/src/command/commands/cmd_say.rs @@ -3,11 +3,12 @@ use pumpkin_core::text::TextComponent; use pumpkin_protocol::client::play::CSystemChatMessage; use crate::command::{ - arg_simple::SimpleArgConsumer, - tree::{CommandTree, ConsumedArgs}, + args::{arg_message::MsgArgConsumer, Arg, ConsumedArgs}, + tree::CommandTree, tree_builder::{argument, require}, CommandExecutor, CommandSender, InvalidTreeError, }; +use InvalidTreeError::InvalidConsumptionError; const NAMES: [&str; 1] = ["say"]; @@ -15,7 +16,7 @@ const DESCRIPTION: &str = "Broadcast a message to all Players."; const ARG_MESSAGE: &str = "message"; -struct SayExecutor {} +struct SayExecutor; #[async_trait] impl CommandExecutor for SayExecutor { @@ -31,9 +32,13 @@ impl CommandExecutor for SayExecutor { CommandSender::Player(player) => &player.gameprofile.name, }; + let Some(Arg::Msg(msg)) = args.get(ARG_MESSAGE) else { + return Err(InvalidConsumptionError(Some(ARG_MESSAGE.into()))); + }; + server .broadcast_packet_all(&CSystemChatMessage::new( - &TextComponent::text(&format!("[{}] {}", sender, args.get("message").unwrap())), + &TextComponent::text(&format!("[{sender}] {msg}")), false, )) .await; @@ -44,6 +49,6 @@ impl CommandExecutor for SayExecutor { pub fn init_command_tree<'a>() -> CommandTree<'a> { CommandTree::new(NAMES, DESCRIPTION).with_child( require(&|sender| sender.permission_lvl() >= 2) - .with_child(argument(ARG_MESSAGE, &SimpleArgConsumer {}).execute(&SayExecutor {})), + .with_child(argument(ARG_MESSAGE, &MsgArgConsumer).execute(&SayExecutor)), ) } diff --git a/pumpkin/src/command/commands/cmd_stop.rs b/pumpkin/src/command/commands/cmd_stop.rs index 7689a75f4..5b65508bb 100644 --- a/pumpkin/src/command/commands/cmd_stop.rs +++ b/pumpkin/src/command/commands/cmd_stop.rs @@ -2,15 +2,16 @@ use async_trait::async_trait; use pumpkin_core::text::color::NamedColor; use pumpkin_core::text::TextComponent; +use crate::command::args::ConsumedArgs; use crate::command::tree::CommandTree; use crate::command::tree_builder::require; -use crate::command::{tree::ConsumedArgs, CommandExecutor, CommandSender, InvalidTreeError}; +use crate::command::{CommandExecutor, CommandSender, InvalidTreeError}; const NAMES: [&str; 1] = ["stop"]; const DESCRIPTION: &str = "Stop the server."; -struct StopExecutor {} +struct StopExecutor; #[async_trait] impl CommandExecutor for StopExecutor { @@ -31,5 +32,5 @@ impl CommandExecutor for StopExecutor { pub fn init_command_tree<'a>() -> CommandTree<'a> { CommandTree::new(NAMES, DESCRIPTION) - .with_child(require(&|sender| sender.permission_lvl() >= 4).execute(&StopExecutor {})) + .with_child(require(&|sender| sender.permission_lvl() >= 4).execute(&StopExecutor)) } diff --git a/pumpkin/src/command/commands/cmd_teleport.rs b/pumpkin/src/command/commands/cmd_teleport.rs new file mode 100644 index 000000000..9570c2c36 --- /dev/null +++ b/pumpkin/src/command/commands/cmd_teleport.rs @@ -0,0 +1,277 @@ +use async_trait::async_trait; +use pumpkin_core::math::vector3::Vector3; +use pumpkin_core::text::TextComponent; + +use crate::command::args::arg_entities::EntitiesArgumentConsumer; +use crate::command::args::arg_entity::EntityArgumentConsumer; +use crate::command::args::arg_position_3d::Position3DArgumentConsumer; +use crate::command::args::arg_rotation::RotationArgumentConsumer; +use crate::command::args::ConsumedArgs; +use crate::command::args::FindArg; +use crate::command::tree::CommandTree; +use crate::command::tree_builder::{argument, literal, require}; +use crate::command::InvalidTreeError; +use crate::command::{CommandExecutor, CommandSender}; + +const NAMES: [&str; 2] = ["teleport", "tp"]; +const DESCRIPTION: &str = "Teleports entities, including players."; // todo + +/// position +const ARG_LOCATION: &str = "location"; + +/// single entity +const ARG_DESTINATION: &str = "destination"; + +/// multiple entities +const ARG_TARGETS: &str = "targets"; + +/// rotation: yaw/pitch +const ARG_ROTATION: &str = "rotation"; + +/// single entity +const ARG_FACING_ENTITY: &str = "facingEntity"; + +/// position +const ARG_FACING_LOCATION: &str = "facingLocation"; + +fn yaw_pitch_facing_position( + looking_from: &Vector3, + looking_towards: &Vector3, +) -> (f32, f32) { + let direction_vector = (looking_towards.sub(looking_from)).normalize(); + + let yaw_radians = -direction_vector.x.atan2(direction_vector.z); + let pitch_radians = (-direction_vector.y).asin(); + + let yaw_degrees = yaw_radians * 180.0 / std::f64::consts::PI; + let pitch_degrees = pitch_radians * 180.0 / std::f64::consts::PI; + + (yaw_degrees as f32, pitch_degrees as f32) +} + +struct TpEntitiesToEntityExecutor; + +#[async_trait] +impl CommandExecutor for TpEntitiesToEntityExecutor { + async fn execute<'a>( + &self, + _sender: &mut CommandSender<'a>, + _server: &crate::server::Server, + args: &ConsumedArgs<'a>, + ) -> Result<(), InvalidTreeError> { + let targets = EntitiesArgumentConsumer::find_arg(args, ARG_TARGETS)?; + + let destination = EntityArgumentConsumer::find_arg(args, ARG_DESTINATION)?; + let pos = destination.living_entity.entity.pos.load(); + + for target in targets { + let yaw = target.living_entity.entity.yaw.load(); + let pitch = target.living_entity.entity.pitch.load(); + target.teleport(pos, yaw, pitch).await; + } + + Ok(()) + } +} + +struct TpEntitiesToPosFacingPosExecutor; + +#[async_trait] +impl CommandExecutor for TpEntitiesToPosFacingPosExecutor { + async fn execute<'a>( + &self, + _sender: &mut CommandSender<'a>, + _server: &crate::server::Server, + args: &ConsumedArgs<'a>, + ) -> Result<(), InvalidTreeError> { + let targets = EntitiesArgumentConsumer::find_arg(args, ARG_TARGETS)?; + + let pos = Position3DArgumentConsumer::find_arg(args, ARG_LOCATION)?; + + let facing_pos = Position3DArgumentConsumer::find_arg(args, ARG_FACING_LOCATION)?; + let (yaw, pitch) = yaw_pitch_facing_position(&pos, &facing_pos); + + for target in targets { + target.teleport(pos, yaw, pitch).await; + } + + Ok(()) + } +} + +struct TpEntitiesToPosFacingEntityExecutor; + +#[async_trait] +impl CommandExecutor for TpEntitiesToPosFacingEntityExecutor { + async fn execute<'a>( + &self, + _sender: &mut CommandSender<'a>, + _server: &crate::server::Server, + args: &ConsumedArgs<'a>, + ) -> Result<(), InvalidTreeError> { + let targets = EntitiesArgumentConsumer::find_arg(args, ARG_TARGETS)?; + + let pos = Position3DArgumentConsumer::find_arg(args, ARG_LOCATION)?; + + let facing_entity = &EntityArgumentConsumer::find_arg(args, ARG_FACING_ENTITY)? + .living_entity + .entity; + let (yaw, pitch) = yaw_pitch_facing_position(&pos, &facing_entity.pos.load()); + + for target in targets { + target.teleport(pos, yaw, pitch).await; + } + + Ok(()) + } +} + +struct TpEntitiesToPosWithRotationExecutor; + +#[async_trait] +impl CommandExecutor for TpEntitiesToPosWithRotationExecutor { + async fn execute<'a>( + &self, + _sender: &mut CommandSender<'a>, + _server: &crate::server::Server, + args: &ConsumedArgs<'a>, + ) -> Result<(), InvalidTreeError> { + let targets = EntitiesArgumentConsumer::find_arg(args, ARG_TARGETS)?; + + let pos = Position3DArgumentConsumer::find_arg(args, ARG_LOCATION)?; + + let (yaw, pitch) = RotationArgumentConsumer::find_arg(args, ARG_ROTATION)?; + + for target in targets { + target.teleport(pos, yaw, pitch).await; + } + + Ok(()) + } +} + +struct TpEntitiesToPosExecutor; + +#[async_trait] +impl CommandExecutor for TpEntitiesToPosExecutor { + async fn execute<'a>( + &self, + _sender: &mut CommandSender<'a>, + _server: &crate::server::Server, + args: &ConsumedArgs<'a>, + ) -> Result<(), InvalidTreeError> { + let targets = EntitiesArgumentConsumer::find_arg(args, ARG_TARGETS)?; + + let pos = Position3DArgumentConsumer::find_arg(args, ARG_LOCATION)?; + + for target in targets { + let yaw = target.living_entity.entity.yaw.load(); + let pitch = target.living_entity.entity.pitch.load(); + target.teleport(pos, yaw, pitch).await; + } + + Ok(()) + } +} + +struct TpSelfToEntityExecutor; + +#[async_trait] +impl CommandExecutor for TpSelfToEntityExecutor { + async fn execute<'a>( + &self, + sender: &mut CommandSender<'a>, + _server: &crate::server::Server, + args: &ConsumedArgs<'a>, + ) -> Result<(), InvalidTreeError> { + let destination = EntityArgumentConsumer::find_arg(args, ARG_DESTINATION)?; + let pos = destination.living_entity.entity.pos.load(); + + match sender { + CommandSender::Player(player) => { + let yaw = player.living_entity.entity.yaw.load(); + let pitch = player.living_entity.entity.pitch.load(); + player.teleport(pos, yaw, pitch).await; + } + _ => { + sender + .send_message(TextComponent::text( + "Only players may execute this command.", + )) + .await; + } + }; + + Ok(()) + } +} + +struct TpSelfToPosExecutor; + +#[async_trait] +impl CommandExecutor for TpSelfToPosExecutor { + async fn execute<'a>( + &self, + sender: &mut CommandSender<'a>, + _server: &crate::server::Server, + args: &ConsumedArgs<'a>, + ) -> Result<(), InvalidTreeError> { + match sender { + CommandSender::Player(player) => { + let pos = Position3DArgumentConsumer::find_arg(args, ARG_LOCATION)?; + let yaw = player.living_entity.entity.yaw.load(); + let pitch = player.living_entity.entity.pitch.load(); + player.teleport(pos, yaw, pitch).await; + } + _ => { + sender + .send_message(TextComponent::text( + "Only players may execute this command.", + )) + .await; + } + }; + + Ok(()) + } +} + +pub fn init_command_tree<'a>() -> CommandTree<'a> { + CommandTree::new(NAMES, DESCRIPTION).with_child( + require(&|sender| sender.permission_lvl() >= 2) + .with_child( + argument(ARG_TARGETS, &EntitiesArgumentConsumer) + .with_child( + argument(ARG_LOCATION, &Position3DArgumentConsumer) + .execute(&TpEntitiesToPosExecutor) + .with_child( + argument(ARG_ROTATION, &RotationArgumentConsumer) + .execute(&TpEntitiesToPosWithRotationExecutor), + ) + .with_child( + literal("facing") + .with_child( + literal("entity").with_child( + argument(ARG_FACING_ENTITY, &EntityArgumentConsumer) + .execute(&TpEntitiesToPosFacingEntityExecutor), + ), + ) + .with_child( + argument(ARG_FACING_LOCATION, &Position3DArgumentConsumer) + .execute(&TpEntitiesToPosFacingPosExecutor), + ), + ), + ) + .with_child( + argument(ARG_DESTINATION, &EntityArgumentConsumer) + .execute(&TpEntitiesToEntityExecutor), + ), + ) + .with_child( + argument(ARG_LOCATION, &Position3DArgumentConsumer).execute(&TpSelfToPosExecutor), + ) + .with_child( + argument(ARG_DESTINATION, &EntityArgumentConsumer).execute(&TpSelfToEntityExecutor), + ), + ) +} diff --git a/pumpkin/src/command/commands/cmd_worldborder.rs b/pumpkin/src/command/commands/cmd_worldborder.rs index 457785466..f3a1fd319 100644 --- a/pumpkin/src/command/commands/cmd_worldborder.rs +++ b/pumpkin/src/command/commands/cmd_worldborder.rs @@ -1,15 +1,20 @@ use async_trait::async_trait; -use pumpkin_core::text::{ - color::{Color, NamedColor}, - TextComponent, +use pumpkin_core::{ + math::vector2::Vector2, + text::{ + color::{Color, NamedColor}, + TextComponent, + }, }; use crate::{ command::{ - arg_position::{parse_arg_position, PositionArgumentConsumer}, - arg_simple::SimpleArgConsumer, - tree::{CommandTree, ConsumedArgs}, - tree_builder::{argument, literal}, + args::{ + arg_bounded_num::BoundedNumArgumentConsumer, + arg_position_2d::Position2DArgumentConsumer, ConsumedArgs, FindArgDefaultName, + }, + tree::CommandTree, + tree_builder::{argument_default_name, literal}, CommandExecutor, CommandSender, InvalidTreeError, }, server::Server, @@ -61,11 +66,7 @@ impl CommandExecutor for WorldborderSetExecutor { .expect("There should always be atleast one world"); let mut border = world.worldborder.lock().await; - let distance = args - .get("distance") - .ok_or(InvalidTreeError::InvalidConsumptionError(None))? - .parse::() - .map_err(|err| InvalidTreeError::InvalidConsumptionError(Some(err.to_string())))?; + let distance = DISTANCE_CONSUMER.find_arg_default_name(args)?; if (distance - border.new_diameter).abs() < f64::EPSILON { sender @@ -103,16 +104,8 @@ impl CommandExecutor for WorldborderSetTimeExecutor { .expect("There should always be atleast one world"); let mut border = world.worldborder.lock().await; - let distance = args - .get("distance") - .ok_or(InvalidTreeError::InvalidConsumptionError(None))? - .parse::() - .map_err(|err| InvalidTreeError::InvalidConsumptionError(Some(err.to_string())))?; - let time = args - .get("time") - .ok_or(InvalidTreeError::InvalidConsumptionError(None))? - .parse::() - .map_err(|err| InvalidTreeError::InvalidConsumptionError(Some(err.to_string())))?; + let distance = DISTANCE_CONSUMER.find_arg_default_name(args)?; + let time = TIME_CONSUMER.find_arg_default_name(args)?; match distance.total_cmp(&border.new_diameter) { std::cmp::Ordering::Equal => { @@ -161,11 +154,7 @@ impl CommandExecutor for WorldborderAddExecutor { .expect("There should always be atleast one world"); let mut border = world.worldborder.lock().await; - let distance = args - .get("distance") - .ok_or(InvalidTreeError::InvalidConsumptionError(None))? - .parse::() - .map_err(|err| InvalidTreeError::InvalidConsumptionError(Some(err.to_string())))?; + let distance = DISTANCE_CONSUMER.find_arg_default_name(args)?; if distance == 0.0 { sender @@ -205,16 +194,8 @@ impl CommandExecutor for WorldborderAddTimeExecutor { .expect("There should always be atleast one world"); let mut border = world.worldborder.lock().await; - let distance = args - .get("distance") - .ok_or(InvalidTreeError::InvalidConsumptionError(None))? - .parse::() - .map_err(|err| InvalidTreeError::InvalidConsumptionError(Some(err.to_string())))?; - let time = args - .get("time") - .ok_or(InvalidTreeError::InvalidConsumptionError(None))? - .parse::() - .map_err(|err| InvalidTreeError::InvalidConsumptionError(Some(err.to_string())))?; + let distance = DISTANCE_CONSUMER.find_arg_default_name(args)?; + let time = TIME_CONSUMER.find_arg_default_name(args)?; let distance = distance + border.new_diameter; @@ -265,8 +246,7 @@ impl CommandExecutor for WorldborderCenterExecutor { .expect("There should always be atleast one world"); let mut border = world.worldborder.lock().await; - let x = parse_arg_position("x", args)?; - let z = parse_arg_position("z", args)?; + let Vector2 { x, z } = Position2DArgumentConsumer.find_arg_default_name(args)?; sender .send_message(TextComponent::text(&format!( @@ -294,11 +274,7 @@ impl CommandExecutor for WorldborderDamageAmountExecutor { .expect("There should always be atleast one world"); let mut border = world.worldborder.lock().await; - let damage_per_block = args - .get("damage_per_block") - .ok_or(InvalidTreeError::InvalidConsumptionError(None))? - .parse::() - .map_err(|err| InvalidTreeError::InvalidConsumptionError(Some(err.to_string())))?; + let damage_per_block = DAMAGE_PER_BLOCK_CONSUMER.find_arg_default_name(args)?; if (damage_per_block - border.damage_per_block).abs() < f32::EPSILON { sender @@ -338,11 +314,7 @@ impl CommandExecutor for WorldborderDamageBufferExecutor { .expect("There should always be atleast one world"); let mut border = world.worldborder.lock().await; - let buffer = args - .get("distance") - .ok_or(InvalidTreeError::InvalidConsumptionError(None))? - .parse::() - .map_err(|err| InvalidTreeError::InvalidConsumptionError(Some(err.to_string())))?; + let buffer = DAMAGE_BUFFER_CONSUMER.find_arg_default_name(args)?; if (buffer - border.buffer).abs() < f32::EPSILON { sender @@ -382,11 +354,7 @@ impl CommandExecutor for WorldborderWarningDistanceExecutor { .expect("There should always be atleast one world"); let mut border = world.worldborder.lock().await; - let distance = args - .get("distance") - .ok_or(InvalidTreeError::InvalidConsumptionError(None))? - .parse::() - .map_err(|err| InvalidTreeError::InvalidConsumptionError(Some(err.to_string())))?; + let distance = WARNING_DISTANCE_CONSUMER.find_arg_default_name(args)?; if distance == border.warning_blocks { sender @@ -426,11 +394,7 @@ impl CommandExecutor for WorldborderWarningTimeExecutor { .expect("There should always be atleast one world"); let mut border = world.worldborder.lock().await; - let time = args - .get("time") - .ok_or(InvalidTreeError::InvalidConsumptionError(None))? - .parse::() - .map_err(|err| InvalidTreeError::InvalidConsumptionError(Some(err.to_string())))?; + let time = TIME_CONSUMER.find_arg_default_name(args)?; if time == border.warning_time { sender @@ -454,33 +418,48 @@ impl CommandExecutor for WorldborderWarningTimeExecutor { } } +static DISTANCE_CONSUMER: BoundedNumArgumentConsumer = + BoundedNumArgumentConsumer::new().min(0.0).name("distance"); + +static TIME_CONSUMER: BoundedNumArgumentConsumer = + BoundedNumArgumentConsumer::new().min(0).name("time"); + +static DAMAGE_PER_BLOCK_CONSUMER: BoundedNumArgumentConsumer = + BoundedNumArgumentConsumer::new() + .min(0.0) + .name("damage_per_block"); + +static DAMAGE_BUFFER_CONSUMER: BoundedNumArgumentConsumer = + BoundedNumArgumentConsumer::new().min(0.0).name("buffer"); + +static WARNING_DISTANCE_CONSUMER: BoundedNumArgumentConsumer = + BoundedNumArgumentConsumer::new().min(0).name("distance"); + pub fn init_command_tree<'a>() -> CommandTree<'a> { CommandTree::new(NAMES, DESCRIPTION) .with_child( literal("add").with_child( - argument("distance", &SimpleArgConsumer) + argument_default_name(&DISTANCE_CONSUMER) .execute(&WorldborderAddExecutor) .with_child( - argument("time", &SimpleArgConsumer).execute(&WorldborderAddTimeExecutor), + argument_default_name(&TIME_CONSUMER).execute(&WorldborderAddTimeExecutor), ), ), ) .with_child(literal("center").with_child( - argument("x", &PositionArgumentConsumer).with_child( - argument("z", &PositionArgumentConsumer).execute(&WorldborderCenterExecutor), - ), + argument_default_name(&Position2DArgumentConsumer).execute(&WorldborderCenterExecutor), )) .with_child( literal("damage") .with_child( literal("amount").with_child( - argument("damage_per_block", &SimpleArgConsumer) + argument_default_name(&DAMAGE_PER_BLOCK_CONSUMER) .execute(&WorldborderDamageAmountExecutor), ), ) .with_child( literal("buffer").with_child( - argument("distance", &SimpleArgConsumer) + argument_default_name(&DAMAGE_BUFFER_CONSUMER) .execute(&WorldborderDamageBufferExecutor), ), ), @@ -488,10 +467,10 @@ pub fn init_command_tree<'a>() -> CommandTree<'a> { .with_child(literal("get").execute(&WorldborderGetExecutor)) .with_child( literal("set").with_child( - argument("distance", &SimpleArgConsumer) + argument_default_name(&DISTANCE_CONSUMER) .execute(&WorldborderSetExecutor) .with_child( - argument("time", &SimpleArgConsumer).execute(&WorldborderSetTimeExecutor), + argument_default_name(&TIME_CONSUMER).execute(&WorldborderSetTimeExecutor), ), ), ) @@ -499,12 +478,12 @@ pub fn init_command_tree<'a>() -> CommandTree<'a> { literal("warning") .with_child( literal("distance").with_child( - argument("distance", &SimpleArgConsumer) + argument_default_name(&WARNING_DISTANCE_CONSUMER) .execute(&WorldborderWarningDistanceExecutor), ), ) .with_child(literal("time").with_child( - argument("time", &SimpleArgConsumer).execute(&WorldborderWarningTimeExecutor), + argument_default_name(&TIME_CONSUMER).execute(&WorldborderWarningTimeExecutor), )), ) } diff --git a/pumpkin/src/command/commands/mod.rs b/pumpkin/src/command/commands/mod.rs index a19959aba..38fe51669 100644 --- a/pumpkin/src/command/commands/mod.rs +++ b/pumpkin/src/command/commands/mod.rs @@ -6,4 +6,5 @@ pub mod cmd_kill; pub mod cmd_pumpkin; pub mod cmd_say; pub mod cmd_stop; +pub mod cmd_teleport; pub mod cmd_worldborder; diff --git a/pumpkin/src/command/dispatcher.rs b/pumpkin/src/command/dispatcher.rs index 6526d5daf..6dca7f475 100644 --- a/pumpkin/src/command/dispatcher.rs +++ b/pumpkin/src/command/dispatcher.rs @@ -3,11 +3,13 @@ use pumpkin_core::text::TextComponent; use crate::command::dispatcher::InvalidTreeError::{ InvalidConsumptionError, InvalidRequirementError, }; -use crate::command::tree::{Command, CommandTree, ConsumedArgs, NodeType, RawArgs}; +use crate::command::tree::{Command, CommandTree, NodeType, RawArgs}; use crate::command::CommandSender; use crate::server::Server; use std::collections::HashMap; +use super::args::ConsumedArgs; + #[derive(Debug)] pub(crate) enum InvalidTreeError { /// This error means that there was an error while parsing a previously consumed argument. @@ -29,7 +31,7 @@ impl<'a> CommandDispatcher<'a> { pub async fn handle_command( &'a self, sender: &mut CommandSender<'a>, - server: &Server, + server: &'a Server, cmd: &'a str, ) { if let Err(err) = self.dispatch(sender, server, cmd).await { @@ -46,7 +48,7 @@ impl<'a> CommandDispatcher<'a> { pub(crate) async fn dispatch( &'a self, src: &mut CommandSender<'a>, - server: &Server, + server: &'a Server, cmd: &'a str, ) -> Result<(), String> { // Other languages dont use the ascii whitespace @@ -67,15 +69,11 @@ impl<'a> CommandDispatcher<'a> { println!("Error while parsing command \"{cmd}\": a requirement that was expected was not met."); return Err("Internal Error (See logs for details)".into()); } - Ok(is_fitting_path) => match is_fitting_path { - Ok(()) => return Ok(()), - Err(error) => { - // Custom error message or not ? - if let Some(error) = error { - return Err(error); - } + Ok(is_fitting_path) => { + if is_fitting_path { + return Ok(()); } - }, + } } } Err(format!("Invalid Syntax. Usage: {tree}")) @@ -98,11 +96,11 @@ impl<'a> CommandDispatcher<'a> { async fn try_is_fitting_path( src: &mut CommandSender<'a>, - server: &Server, + server: &'a Server, path: &[usize], tree: &CommandTree<'a>, mut raw_args: RawArgs<'a>, - ) -> Result>, InvalidTreeError> { + ) -> Result { let mut parsed_args: ConsumedArgs = HashMap::new(); for node in path.iter().map(|&i| &tree.nodes[i]) { @@ -110,35 +108,33 @@ impl<'a> CommandDispatcher<'a> { NodeType::ExecuteLeaf { executor } => { return if raw_args.is_empty() { executor.execute(src, server, &parsed_args).await?; - Ok(Ok(())) + Ok(true) } else { - Ok(Err(None)) + Ok(false) }; } NodeType::Literal { string, .. } => { if raw_args.pop() != Some(string) { - return Ok(Err(None)); + return Ok(false); } } NodeType::Argument { consumer, name, .. } => { match consumer.consume(src, server, &mut raw_args).await { - Ok(consumed) => { + Some(consumed) => { parsed_args.insert(name, consumed); } - Err(err) => { - return Ok(Err(err)); - } + None => return Ok(false), } } NodeType::Require { predicate, .. } => { if !predicate(src) { - return Ok(Err(None)); + return Ok(false); } } } } - Ok(Err(None)) + Ok(false) } /// Register a command with the dispatcher. diff --git a/pumpkin/src/command/mod.rs b/pumpkin/src/command/mod.rs index 573f628b0..bc6a63614 100644 --- a/pumpkin/src/command/mod.rs +++ b/pumpkin/src/command/mod.rs @@ -1,22 +1,20 @@ use std::sync::Arc; +use args::ConsumedArgs; use async_trait::async_trait; use commands::{ cmd_echest, cmd_gamemode, cmd_help, cmd_kick, cmd_kill, cmd_pumpkin, cmd_say, cmd_stop, - cmd_worldborder, + cmd_teleport, cmd_worldborder, }; use dispatcher::InvalidTreeError; +use pumpkin_core::math::vector3::Vector3; use pumpkin_core::text::TextComponent; -use tree::ConsumedArgs; use crate::command::dispatcher::CommandDispatcher; use crate::entity::player::Player; use crate::server::Server; -mod arg_player; -mod arg_position; -mod arg_simple; - +pub mod args; mod commands; pub mod dispatcher; mod tree; @@ -60,6 +58,14 @@ impl<'a> CommandSender<'a> { pub const fn permission_lvl(&self) -> i32 { 4 } + + #[must_use] + pub fn position(&self) -> Option> { + match self { + CommandSender::Console | CommandSender::Rcon(..) => None, + CommandSender::Player(p) => Some(p.living_entity.entity.pos.load()), + } + } } #[must_use] @@ -75,6 +81,7 @@ pub fn default_dispatcher<'a>() -> Arc> { dispatcher.register(cmd_kill::init_command_tree()); dispatcher.register(cmd_kick::init_command_tree()); dispatcher.register(cmd_worldborder::init_command_tree()); + dispatcher.register(cmd_teleport::init_command_tree()); Arc::new(dispatcher) } diff --git a/pumpkin/src/command/tree.rs b/pumpkin/src/command/tree.rs index 7b0d45a56..5db18fa39 100644 --- a/pumpkin/src/command/tree.rs +++ b/pumpkin/src/command/tree.rs @@ -1,27 +1,11 @@ -use async_trait::async_trait; - -use super::CommandExecutor; -use crate::{command::CommandSender, server::Server}; -use std::collections::{HashMap, VecDeque}; +use super::{args::ArgumentConsumer, CommandExecutor}; +use crate::command::CommandSender; +use std::{collections::VecDeque, fmt::Debug}; /// see [`crate::commands::tree_builder::argument`] pub type RawArgs<'a> = Vec<&'a str>; -/// see [`crate::commands::tree_builder::argument`] and [`CommandTree::execute`]/[`crate::commands::tree_builder::NonLeafNodeBuilder::execute`] -pub type ConsumedArgs<'a> = HashMap<&'a str, String>; - -/// see [`crate::commands::tree_builder::argument`] -/// Provide value or an Optional error message, If no Error message provided the default will be used -#[async_trait] -pub(crate) trait ArgumentConsumer: Sync { - async fn consume<'a>( - &self, - sender: &CommandSender<'a>, - server: &Server, - args: &mut RawArgs<'a>, - ) -> Result>; -} - +#[derive(Debug)] pub struct Node<'a> { pub(crate) children: Vec, pub(crate) node_type: NodeType<'a>, @@ -43,11 +27,30 @@ pub enum NodeType<'a> { }, } +impl Debug for NodeType<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::ExecuteLeaf { .. } => f + .debug_struct("ExecuteLeaf") + .field("executor", &"..") + .finish(), + Self::Literal { string } => f.debug_struct("Literal").field("string", string).finish(), + Self::Argument { name, .. } => f + .debug_struct("Argument") + .field("name", name) + .field("consumer", &"..") + .finish(), + Self::Require { .. } => f.debug_struct("Require").field("predicate", &"..").finish(), + } + } +} + pub enum Command<'a> { Tree(CommandTree<'a>), Alias(&'a str), } +#[derive(Debug)] pub struct CommandTree<'a> { pub(crate) nodes: Vec>, pub(crate) children: Vec, diff --git a/pumpkin/src/command/tree_builder.rs b/pumpkin/src/command/tree_builder.rs index b6f99b533..e750ea0bb 100644 --- a/pumpkin/src/command/tree_builder.rs +++ b/pumpkin/src/command/tree_builder.rs @@ -1,6 +1,8 @@ -use crate::command::tree::{ArgumentConsumer, CommandTree, Node, NodeType}; +use crate::command::args::ArgumentConsumer; +use crate::command::tree::{CommandTree, Node, NodeType}; use crate::command::CommandSender; +use super::args::DefaultNameArgConsumer; use super::CommandExecutor; impl<'a> CommandTree<'a> { @@ -147,6 +149,18 @@ pub fn argument<'a>(name: &'a str, consumer: &'a dyn ArgumentConsumer) -> NonLea } } +/// same as [`crate::command::tree_builder::argument`], but uses default arg name of consumer +pub fn argument_default_name(consumer: &dyn DefaultNameArgConsumer) -> NonLeafNodeBuilder<'_> { + NonLeafNodeBuilder { + node_type: NodeType::Argument { + name: consumer.default_name(), + consumer: consumer.get_argument_consumer(), + }, + child_nodes: Vec::new(), + leaf_nodes: Vec::new(), + } +} + /// ```predicate``` should return ```false``` if requirement for reaching following [Node]s is not /// met. pub fn require(predicate: &(dyn Fn(&CommandSender) -> bool + Sync)) -> NonLeafNodeBuilder { diff --git a/pumpkin/src/server/mod.rs b/pumpkin/src/server/mod.rs index eb6ff064b..231ea4714 100644 --- a/pumpkin/src/server/mod.rs +++ b/pumpkin/src/server/mod.rs @@ -9,6 +9,7 @@ use pumpkin_protocol::client::login::CEncryptionRequest; use pumpkin_protocol::{client::config::CPluginMessage, ClientPacket}; use pumpkin_registry::Registry; use pumpkin_world::dimension::Dimension; +use rand::prelude::SliceRandom; use std::collections::HashMap; use std::{ sync::{ @@ -200,6 +201,26 @@ impl Server { None } + /// Returns all players from all worlds. + pub async fn get_all_players(&self) -> Vec> { + let mut players = Vec::>::new(); + + for world in &self.worlds { + for (_, player) in world.current_players.lock().await.iter() { + players.push(player.clone()); + } + } + + players + } + + /// Returns a random player from any of the worlds or None if all worlds are empty. + pub async fn get_random_player(&self) -> Option> { + let players = self.get_all_players().await; + + players.choose(&mut rand::thread_rng()).map(Arc::<_>::clone) + } + /// Searches for a player by their UUID across all worlds. /// /// This function iterates through each world managed by the server and attempts to find a player with the specified UUID. @@ -236,6 +257,18 @@ impl Server { count } + /// Similar to [`Server::get_player_count`] >= n, but may be more efficient since it stops it's iteration through all worlds as soon as n players were found. + pub async fn has_n_players(&self, n: usize) -> bool { + let mut count = 0; + for world in &self.worlds { + count += world.current_players.lock().await.len(); + if count >= n { + return true; + } + } + false + } + /// Generates a new entity id /// This should be global pub fn new_entity_id(&self) -> EntityId {