Commands: Fix Bug, Refactor Arguments, Add /tp (#227)

* fix command dispatch, add/refactor arguments & add tp command

* implement teleport facing location/facing entity

* implement teleport facing location/facing entity

* implement arg_player/arg_entity and refactor commands accordingly

* implement ~ coordinate notation

* refactor arg_position_3d

* major command refactor

* add documentation

* add default names to more argument types

* add argument names to arg_bounded_nums

* change command argument api

---------

Co-authored-by: user622628252416 <nnew8715@gmail.com>
This commit is contained in:
user622628252416
2024-11-03 23:15:49 +01:00
committed by GitHub
parent 8db277f63e
commit 87aa5ec493
31 changed files with 1492 additions and 427 deletions

View File

@@ -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<String, Option<String>> {
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<Arc<crate::entity::player::Player>, 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())))
}
}
}

View File

@@ -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<String, Option<String>> {
let Some(arg) = args.pop() else {
return Err(None);
};
// TODO implement ~ ^ notations
let value = arg.parse::<f64>().map_err(|err| Some(err.to_string()))?;
Ok(value.to_string())
}
}
pub fn parse_arg_position(
arg_name: &str,
consumed_args: &ConsumedArgs<'_>,
) -> Result<f64, InvalidTreeError> {
let s = consumed_args
.get(arg_name)
.ok_or(InvalidTreeError::InvalidConsumptionError(None))?;
let value = s
.parse::<f64>()
.map_err(|err| InvalidTreeError::InvalidConsumptionError(Some(err.to_string())))?;
Ok(value)
}

View File

@@ -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<String, Option<String>> {
args.pop().ok_or(None).map(ToString::to_string)
}
}

View File

@@ -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<T: ArgNum> {
min_inclusive: Option<T>,
max_inclusive: Option<T>,
name: Option<&'static str>,
}
#[async_trait]
impl<T: ArgNum> ArgumentConsumer for BoundedNumArgumentConsumer<T> {
async fn consume<'a>(
&self,
_src: &CommandSender<'a>,
_server: &'a Server,
args: &mut RawArgs<'a>,
) -> Option<Arg<'a>> {
let x = args.pop()?.parse::<T>().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<T> {
type Data = T;
fn find_arg(args: &super::ConsumedArgs, name: &str) -> Result<Self::Data, InvalidTreeError> {
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<T: ArgNum> BoundedNumArgumentConsumer<T> {
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<Self>;
}
impl ArgNum for f64 {
fn to_arg<'a>(self) -> Arg<'a> {
Arg::F64(self)
}
fn from_arg(arg: &Arg<'_>) -> Option<Self> {
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<Self> {
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<Self> {
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<Self> {
match arg {
Arg::U32(x) => Some(*x),
_ => None,
}
}
}
impl<T: ArgNum> DefaultNameArgConsumer for BoundedNumArgumentConsumer<T> {
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
}
}

View File

@@ -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<Arg<'a>> {
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<Self::Data, InvalidTreeError> {
match args.get(name) {
Some(Arg::CommandTree(tree)) => Ok(tree),
_ => Err(InvalidTreeError::InvalidConsumptionError(Some(
name.to_string(),
))),
}
}
}

View File

@@ -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<Arg<'a>> {
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<Player>];
fn find_arg(
args: &'a super::ConsumedArgs,
name: &'a str,
) -> Result<Self::Data, InvalidTreeError> {
match args.get(name) {
Some(Arg::Entities(data)) => Ok(data),
_ => Err(InvalidTreeError::InvalidConsumptionError(Some(
name.to_string(),
))),
}
}
}

View File

@@ -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<Arg<'a>> {
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<Player>;
fn find_arg(
args: &'a super::ConsumedArgs,
name: &'a str,
) -> Result<Self::Data, InvalidTreeError> {
match args.get(name) {
Some(Arg::Entity(data)) => Ok(data.clone()),
_ => Err(InvalidTreeError::InvalidConsumptionError(Some(
name.to_string(),
))),
}
}
}

View File

@@ -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<Arg<'a>> {
let s = args.pop()?;
if let Ok(id) = s.parse::<u8>() {
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<Self::Data, InvalidTreeError> {
match args.get(name) {
Some(Arg::GameMode(data)) => Ok(*data),
_ => Err(InvalidTreeError::InvalidConsumptionError(Some(
name.to_string(),
))),
}
}
}

View File

@@ -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<Arg<'a>> {
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<Self::Data, InvalidTreeError> {
match args.get(name) {
Some(Arg::Msg(data)) => Ok(data),
_ => Err(InvalidTreeError::InvalidConsumptionError(Some(
name.to_string(),
))),
}
}
}

View File

@@ -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<Arg<'a>> {
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<Player>];
fn find_arg(
args: &'a super::ConsumedArgs,
name: &'a str,
) -> Result<Self::Data, InvalidTreeError> {
match args.get(name) {
Some(Arg::Players(data)) => Ok(data),
_ => Err(InvalidTreeError::InvalidConsumptionError(Some(
name.to_string(),
))),
}
}
}

View File

@@ -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<Arg<'a>> {
let x = args.pop()?.parse::<f64>().ok()?;
let z = args.pop()?.parse::<f64>().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<f64>;
fn find_arg(
args: &'a super::ConsumedArgs,
name: &'a str,
) -> Result<Self::Data, InvalidTreeError> {
match args.get(name) {
Some(Arg::Pos2D(data)) => Ok(*data),
_ => Err(InvalidTreeError::InvalidConsumptionError(Some(
name.to_string(),
))),
}
}
}

View File

@@ -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<Arg<'a>> {
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<Self> {
Some(Self(
x?.try_into().ok()?,
y?.try_into().ok()?,
z?.try_into().ok()?,
))
}
fn try_get_values(self, origin: Option<Vector3<f64>>) -> Option<Vector3<f64>> {
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<Self, Self::Error> {
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<f64>) -> Option<f64> {
match self {
Self::Absolute(v) => Some(v),
Self::Relative(offset) => Some(origin? + offset),
}
}
}
impl<'a> FindArg<'a> for Position3DArgumentConsumer {
type Data = Vector3<f64>;
fn find_arg(
args: &'a super::ConsumedArgs,
name: &'a str,
) -> Result<Self::Data, InvalidTreeError> {
match args.get(name) {
Some(Arg::Pos3D(data)) => Ok(*data),
_ => Err(InvalidTreeError::InvalidConsumptionError(Some(
name.to_string(),
))),
}
}
}

View File

@@ -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<Arg<'a>> {
let mut yaw = args.pop()?.parse::<f32>().ok()?;
let mut pitch = args.pop()?.parse::<f32>().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<Self::Data, InvalidTreeError> {
match args.get(name) {
Some(Arg::Rotation(yaw, pitch)) => Ok((*yaw, *pitch)),
_ => Err(InvalidTreeError::InvalidConsumptionError(Some(
name.to_string(),
))),
}
}
}

View File

@@ -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<Arg<'a>> {
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<Self::Data, InvalidTreeError> {
match args.get(name) {
Some(Arg::Simple(data)) => Ok(data),
_ => Err(InvalidTreeError::InvalidConsumptionError(Some(
name.to_string(),
))),
}
}
}

View File

@@ -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<Arg<'a>>;
}
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<Arc<Player>>),
Entity(Arc<Player>),
Players(Vec<Arc<Player>>),
Pos3D(Vector3<f64>),
Pos2D(Vector2<f64>),
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<K, V: Clone> {
fn get_cloned(&self, key: &K) -> Option<V>;
}
impl<K: Eq + Hash, V: Clone> GetCloned<K, V> for HashMap<K, V> {
fn get_cloned(&self, key: &K) -> Option<V> {
self.get(key).cloned()
}
}
pub(crate) trait FindArg<'a> {
type Data;
fn find_arg(args: &'a ConsumedArgs, name: &'a str) -> Result<Self::Data, InvalidTreeError>;
}
pub(crate) trait FindArgDefaultName<'a, T> {
fn find_arg_default_name(&self, args: &'a ConsumedArgs) -> Result<T, InvalidTreeError>;
}
impl<'a, T, C: FindArg<'a, Data = T> + DefaultNameArgConsumer> FindArgDefaultName<'a, T> for C {
fn find_arg_default_name(&self, args: &'a ConsumedArgs) -> Result<T, InvalidTreeError> {
C::find_arg(args, self.default_name())
}
}

View File

@@ -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)
}

View File

@@ -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<String, Option<String>> {
if let Some(arg) = args.pop() {
if let Ok(id) = arg.parse::<u8>() {
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<GameMode, InvalidTreeError> {
let s = consumed_args
.get(ARG_GAMEMODE)
.ok_or(InvalidConsumptionError(None))?;
if let Ok(id) = s.parse::<u8>() {
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?;
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())));
};
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 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),
),
),
)

View File

@@ -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<String, Option<String>> {
//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)
}

View File

@@ -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))
}

View File

@@ -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())));
};
sender
.send_message(
TextComponent::text("Player has been killed.").color_named(NamedColor::Blue),
)
.await;
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(())
}
}
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))
}

View File

@@ -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)
}

View File

@@ -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)),
)
}

View File

@@ -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))
}

View File

@@ -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<f64>,
looking_towards: &Vector3<f64>,
) -> (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),
),
)
}

View File

@@ -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::<f64>()
.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::<f64>()
.map_err(|err| InvalidTreeError::InvalidConsumptionError(Some(err.to_string())))?;
let time = args
.get("time")
.ok_or(InvalidTreeError::InvalidConsumptionError(None))?
.parse::<i32>()
.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::<f64>()
.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::<f64>()
.map_err(|err| InvalidTreeError::InvalidConsumptionError(Some(err.to_string())))?;
let time = args
.get("time")
.ok_or(InvalidTreeError::InvalidConsumptionError(None))?
.parse::<i32>()
.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::<f32>()
.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::<f32>()
.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::<i32>()
.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::<i32>()
.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<f64> =
BoundedNumArgumentConsumer::new().min(0.0).name("distance");
static TIME_CONSUMER: BoundedNumArgumentConsumer<i32> =
BoundedNumArgumentConsumer::new().min(0).name("time");
static DAMAGE_PER_BLOCK_CONSUMER: BoundedNumArgumentConsumer<f32> =
BoundedNumArgumentConsumer::new()
.min(0.0)
.name("damage_per_block");
static DAMAGE_BUFFER_CONSUMER: BoundedNumArgumentConsumer<f32> =
BoundedNumArgumentConsumer::new().min(0.0).name("buffer");
static WARNING_DISTANCE_CONSUMER: BoundedNumArgumentConsumer<i32> =
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),
)),
)
}

View File

@@ -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;

View File

@@ -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<Result<(), Option<String>>, InvalidTreeError> {
) -> Result<bool, InvalidTreeError> {
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.

View File

@@ -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<Vector3<f64>> {
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<CommandDispatcher<'a>> {
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)
}

View File

@@ -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<String, Option<String>>;
}
#[derive(Debug)]
pub struct Node<'a> {
pub(crate) children: Vec<usize>,
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<Node<'a>>,
pub(crate) children: Vec<usize>,

View File

@@ -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 {

View File

@@ -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<Arc<Player>> {
let mut players = Vec::<Arc<Player>>::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<Arc<Player>> {
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 {