remove async_trait from Commands

This decreased compile time by 30%, Plugins also are not required anymore to use async_trait when adding custom Commands
This commit is contained in:
Alexander Medvedev 2025-12-03 18:25:58 +01:00
parent 644023d89f
commit 71392b4a38
86 changed files with 4617 additions and 4575 deletions

58
Cargo.lock generated
View File

@ -493,9 +493,9 @@ checksum = "0dabb6555f92fb9ee4140454eb5dcd14c7960e1225c6d1a6cc361f032947713e"
[[package]] [[package]]
name = "convert_case" name = "convert_case"
version = "0.7.1" version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb402b8d4c85569410425650ce3eddc7d698ed96d39a73f941b08fb63082f1e7" checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9"
dependencies = [ dependencies = [
"unicode-segmentation", "unicode-segmentation",
] ]
@ -812,22 +812,23 @@ dependencies = [
[[package]] [[package]]
name = "derive_more" name = "derive_more"
version = "2.0.1" version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" checksum = "10b768e943bed7bf2cab53df09f4bc34bfd217cdb57d971e769874c9a6710618"
dependencies = [ dependencies = [
"derive_more-impl", "derive_more-impl",
] ]
[[package]] [[package]]
name = "derive_more-impl" name = "derive_more-impl"
version = "2.0.1" version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" checksum = "6d286bfdaf75e988b4a78e013ecd79c581e06399ab53fbacd2d916c2f904f30b"
dependencies = [ dependencies = [
"convert_case", "convert_case",
"proc-macro2", "proc-macro2",
"quote", "quote",
"rustc_version",
"syn", "syn",
] ]
@ -1373,9 +1374,9 @@ dependencies = [
[[package]] [[package]]
name = "hyper-util" name = "hyper-util"
version = "0.1.18" version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52e9a2a24dc5c6821e71a7030e1e14b7b632acac55c40e9d2e082c621261bb56" checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f"
dependencies = [ dependencies = [
"bytes", "bytes",
"futures-channel", "futures-channel",
@ -1587,9 +1588,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.177" version = "0.2.178"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091"
[[package]] [[package]]
name = "libloading" name = "libloading"
@ -1636,9 +1637,9 @@ dependencies = [
[[package]] [[package]]
name = "log" name = "log"
version = "0.4.28" version = "0.4.29"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
[[package]] [[package]]
name = "lru" name = "lru"
@ -2132,9 +2133,9 @@ dependencies = [
[[package]] [[package]]
name = "prost" name = "prost"
version = "0.14.2" version = "0.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "101fec8d036f8d9d4a1e8ebf90d566d1d798f3b1aa379d2576a54a0d9acea5bd" checksum = "7231bd9b3d3d33c86b58adbac74b5ec0ad9f496b19d22801d773636feaa95f3d"
dependencies = [ dependencies = [
"bytes", "bytes",
"prost-derive", "prost-derive",
@ -2142,9 +2143,9 @@ dependencies = [
[[package]] [[package]]
name = "prost-derive" name = "prost-derive"
version = "0.14.2" version = "0.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2d93e596a829ebe00afa41c3a056e6308d6b8a4c7d869edf184e2c91b1ba564" checksum = "9120690fafc389a67ba3803df527d0ec9cbbc9cc45e4cc20b332996dfb672425"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"itertools 0.14.0", "itertools 0.14.0",
@ -2155,9 +2156,9 @@ dependencies = [
[[package]] [[package]]
name = "prost-types" name = "prost-types"
version = "0.14.2" version = "0.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f5d7b7346e150de32340ae3390b8b3ffa37ad93ec31fb5dad86afe817619e4e7" checksum = "b9b4db3d6da204ed77bb26ba83b6122a73aeb2e87e25fbf7ad2e84c4ccbf8f72"
dependencies = [ dependencies = [
"prost", "prost",
] ]
@ -2555,6 +2556,15 @@ version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
[[package]]
name = "rustc_version"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
dependencies = [
"semver",
]
[[package]] [[package]]
name = "rustix" name = "rustix"
version = "1.1.2" version = "1.1.2"
@ -2668,6 +2678,12 @@ dependencies = [
"zeroize", "zeroize",
] ]
[[package]]
name = "semver"
version = "1.0.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.228" version = "1.0.228"
@ -3441,14 +3457,14 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]] [[package]]
name = "uuid" name = "uuid"
version = "1.18.1" version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a"
dependencies = [ dependencies = [
"getrandom 0.3.4", "getrandom 0.3.4",
"js-sys", "js-sys",
"md-5", "md-5",
"serde", "serde_core",
"wasm-bindgen", "wasm-bindgen",
] ]

View File

@ -43,7 +43,7 @@ futures = { version = "0.3", default-features = false, features = ["executor"] }
rayon = "1.11" rayon = "1.11"
crossbeam = "0.8" crossbeam = "0.8"
uuid = { version = "1.18", features = ["serde", "v3", "v4"] } uuid = { version = "1.19", features = ["serde", "v3", "v4"] }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"

View File

@ -1,9 +1,9 @@
use async_trait::async_trait;
use pumpkin_data::Block; use pumpkin_data::Block;
use pumpkin_data::tag::{RegistryKey, get_tag_ids}; use pumpkin_data::tag::{RegistryKey, get_tag_ids};
use pumpkin_protocol::java::client::play::{ArgumentType, CommandSuggestion, SuggestionProviders}; use pumpkin_protocol::java::client::play::{ArgumentType, SuggestionProviders};
use pumpkin_util::text::TextComponent; use pumpkin_util::text::TextComponent;
use crate::command::args::ConsumeResult;
use crate::{command::dispatcher::CommandError, server::Server}; use crate::{command::dispatcher::CommandError, server::Server};
use super::{ use super::{
@ -26,25 +26,18 @@ impl GetClientSideArgParser for BlockArgumentConsumer {
} }
} }
#[async_trait]
impl ArgumentConsumer for BlockArgumentConsumer { impl ArgumentConsumer for BlockArgumentConsumer {
async fn consume<'a>( fn consume<'a>(
&'a self, &'a self,
_sender: &CommandSender, _sender: &'a CommandSender,
_server: &'a Server, _server: &'a Server,
args: &mut RawArgs<'a>, args: &mut RawArgs<'a>,
) -> Option<Arg<'a>> { ) -> ConsumeResult<'a> {
let s = args.pop()?; let block = args.pop();
Some(Arg::Block(s)) match block {
} Some(s) => Box::pin(async move { Some(Arg::Block(s)) }),
None => Box::pin(async move { None }),
async fn suggest<'a>( }
&'a self,
_sender: &CommandSender,
_server: &'a Server,
_input: &'a str,
) -> Result<Option<Vec<CommandSuggestion>>, CommandError> {
Ok(None)
} }
} }
@ -101,25 +94,18 @@ impl GetClientSideArgParser for BlockPredicateArgumentConsumer {
} }
} }
#[async_trait]
impl ArgumentConsumer for BlockPredicateArgumentConsumer { impl ArgumentConsumer for BlockPredicateArgumentConsumer {
async fn consume<'a>( fn consume<'a>(
&'a self, &'a self,
_sender: &CommandSender, _sender: &'a CommandSender,
_server: &'a Server, _server: &'a Server,
args: &mut RawArgs<'a>, args: &mut RawArgs<'a>,
) -> Option<Arg<'a>> { ) -> ConsumeResult<'a> {
let s = args.pop()?; let block = args.pop();
Some(Arg::BlockPredicate(s)) match block {
} Some(s) => Box::pin(async move { Some(Arg::BlockPredicate(s)) }),
None => Box::pin(async move { None }),
async fn suggest<'a>( }
&'a self,
_sender: &CommandSender,
_server: &'a Server,
_input: &'a str,
) -> Result<Option<Vec<CommandSuggestion>>, CommandError> {
Ok(None)
} }
} }

View File

@ -1,10 +1,9 @@
use crate::command::CommandSender; use crate::command::CommandSender;
use crate::command::args::{Arg, ArgumentConsumer, FindArg, GetClientSideArgParser}; use crate::command::args::{Arg, ArgumentConsumer, ConsumeResult, FindArg, GetClientSideArgParser};
use crate::command::dispatcher::CommandError; use crate::command::dispatcher::CommandError;
use crate::command::tree::RawArgs; use crate::command::tree::RawArgs;
use crate::server::Server; use crate::server::Server;
use async_trait::async_trait; use pumpkin_protocol::java::client::play::{ArgumentType, SuggestionProviders};
use pumpkin_protocol::java::client::play::{ArgumentType, CommandSuggestion, SuggestionProviders};
pub struct BoolArgConsumer; pub struct BoolArgConsumer;
@ -18,30 +17,25 @@ impl GetClientSideArgParser for BoolArgConsumer {
} }
} }
#[async_trait]
impl ArgumentConsumer for BoolArgConsumer { impl ArgumentConsumer for BoolArgConsumer {
async fn consume<'a>( fn consume<'a, 'b>(
&'a self, &'a self,
_sender: &CommandSender, _sender: &'a CommandSender,
_server: &'a Server, _server: &'a Server,
args: &mut RawArgs<'a>, args: &'b mut RawArgs<'a>,
) -> Option<Arg<'a>> { ) -> ConsumeResult<'a> {
let s = args.pop()?; let s_opt: Option<&'a str> = args.pop();
match s { let result: Option<Arg<'a>> = s_opt.map_or_else(
"false" => Some(Arg::Bool(false)), || None,
"true" => Some(Arg::Bool(true)), |s| match s {
_ => None, "false" => Some(Arg::Bool(false)),
} "true" => Some(Arg::Bool(true)),
} _ => None,
},
);
async fn suggest<'a>( Box::pin(async move { result })
&'a self,
_sender: &CommandSender,
_server: &'a Server,
_input: &'a str,
) -> Result<Option<Vec<CommandSuggestion>>, CommandError> {
Ok(None)
} }
} }

View File

@ -1,12 +1,12 @@
use crate::command::CommandSender; use crate::command::CommandSender;
use crate::command::args::{ use crate::command::args::{
Arg, ArgumentConsumer, DefaultNameArgConsumer, FindArg, GetClientSideArgParser, Arg, ArgumentConsumer, ConsumeResult, DefaultNameArgConsumer, FindArg, GetClientSideArgParser,
SuggestResult,
}; };
use crate::command::dispatcher::CommandError; use crate::command::dispatcher::CommandError;
use crate::command::tree::RawArgs; use crate::command::tree::RawArgs;
use crate::server::Server; use crate::server::Server;
use crate::world::bossbar::BossbarColor; use crate::world::bossbar::BossbarColor;
use async_trait::async_trait;
use pumpkin_protocol::java::client::play::{ArgumentType, CommandSuggestion, SuggestionProviders}; use pumpkin_protocol::java::client::play::{ArgumentType, CommandSuggestion, SuggestionProviders};
pub struct BossbarColorArgumentConsumer; pub struct BossbarColorArgumentConsumer;
@ -22,42 +22,50 @@ impl GetClientSideArgParser for BossbarColorArgumentConsumer {
} }
} }
#[async_trait]
impl ArgumentConsumer for BossbarColorArgumentConsumer { impl ArgumentConsumer for BossbarColorArgumentConsumer {
async fn consume<'a>( fn consume<'a, 'b>(
&'a self, &'a self,
_sender: &CommandSender, _sender: &'a CommandSender,
_server: &'a Server, _server: &'a Server,
args: &mut RawArgs<'a>, args: &'b mut RawArgs<'a>,
) -> Option<Arg<'a>> { ) -> ConsumeResult<'a> {
let s = args.pop()?; let s_opt: Option<&'a str> = args.pop();
let color = match s { let result: Option<Arg<'a>> = s_opt.map_or_else(
"blue" => Some(BossbarColor::Blue), || None,
"green" => Some(BossbarColor::Green), |s| {
"pink" => Some(BossbarColor::Pink), let color = match s {
"purple" => Some(BossbarColor::Purple), "blue" => Some(BossbarColor::Blue),
"red" => Some(BossbarColor::Red), "green" => Some(BossbarColor::Green),
"white" => Some(BossbarColor::White), "pink" => Some(BossbarColor::Pink),
"yellow" => Some(BossbarColor::Yellow), "purple" => Some(BossbarColor::Purple),
_ => None, "red" => Some(BossbarColor::Red),
}; "white" => Some(BossbarColor::White),
"yellow" => Some(BossbarColor::Yellow),
_ => None,
};
color.map(Arg::BossbarColor) color.map(Arg::BossbarColor)
},
);
Box::pin(async move { result })
} }
async fn suggest<'a>( fn suggest<'a>(
&'a self, &'a self,
_sender: &CommandSender, _sender: &CommandSender,
_server: &'a Server, _server: &'a Server,
_input: &'a str, _input: &'a str,
) -> Result<Option<Vec<CommandSuggestion>>, CommandError> { ) -> SuggestResult<'a> {
let colors = ["blue", "green", "pink", "purple", "red", "white", "yellow"]; Box::pin(async move {
let suggestions: Vec<CommandSuggestion> = colors let colors = ["blue", "green", "pink", "purple", "red", "white", "yellow"];
.iter() let suggestions: Vec<CommandSuggestion> = colors
.map(|color| CommandSuggestion::new((*color).to_string(), None)) .iter()
.collect(); .map(|color| CommandSuggestion::new((*color).to_string(), None))
Ok(Some(suggestions)) .collect();
Ok(Some(suggestions))
})
} }
} }

View File

@ -1,12 +1,12 @@
use crate::command::CommandSender; use crate::command::CommandSender;
use crate::command::args::{ use crate::command::args::{
Arg, ArgumentConsumer, DefaultNameArgConsumer, FindArg, GetClientSideArgParser, Arg, ArgumentConsumer, ConsumeResult, DefaultNameArgConsumer, FindArg, GetClientSideArgParser,
SuggestResult,
}; };
use crate::command::dispatcher::CommandError; use crate::command::dispatcher::CommandError;
use crate::command::tree::RawArgs; use crate::command::tree::RawArgs;
use crate::server::Server; use crate::server::Server;
use crate::world::bossbar::BossbarDivisions; use crate::world::bossbar::BossbarDivisions;
use async_trait::async_trait;
use pumpkin_protocol::java::client::play::{ArgumentType, CommandSuggestion, SuggestionProviders}; use pumpkin_protocol::java::client::play::{ArgumentType, CommandSuggestion, SuggestionProviders};
pub struct BossbarStyleArgumentConsumer; pub struct BossbarStyleArgumentConsumer;
@ -22,46 +22,54 @@ impl GetClientSideArgParser for BossbarStyleArgumentConsumer {
} }
} }
#[async_trait]
impl ArgumentConsumer for BossbarStyleArgumentConsumer { impl ArgumentConsumer for BossbarStyleArgumentConsumer {
async fn consume<'a>( fn consume<'a, 'b>(
&'a self, &'a self,
_sender: &CommandSender, _sender: &'a CommandSender,
_server: &'a Server, _server: &'a Server,
args: &mut RawArgs<'a>, args: &'b mut RawArgs<'a>,
) -> Option<Arg<'a>> { ) -> ConsumeResult<'a> {
let s = args.pop()?; let s_opt: Option<&'a str> = args.pop();
let style = match s { let result: Option<Arg<'a>> = s_opt.map_or_else(
"notched_10" => Some(BossbarDivisions::Notches10), || None,
"notched_12" => Some(BossbarDivisions::Notches12), |s| {
"notched_20" => Some(BossbarDivisions::Notches20), let style = match s {
"notched_6" => Some(BossbarDivisions::Notches6), "notched_10" => Some(BossbarDivisions::Notches10),
"progress" => Some(BossbarDivisions::NoDivision), "notched_12" => Some(BossbarDivisions::Notches12),
_ => None, "notched_20" => Some(BossbarDivisions::Notches20),
}; "notched_6" => Some(BossbarDivisions::Notches6),
"progress" => Some(BossbarDivisions::NoDivision),
_ => None,
};
style.map(Arg::BossbarStyle) style.map(Arg::BossbarStyle)
},
);
Box::pin(async move { result })
} }
async fn suggest<'a>( fn suggest<'a>(
&'a self, &'a self,
_sender: &CommandSender, _sender: &CommandSender,
_server: &'a Server, _server: &'a Server,
_input: &'a str, _input: &'a str,
) -> Result<Option<Vec<CommandSuggestion>>, CommandError> { ) -> SuggestResult<'a> {
let styles = [ Box::pin(async move {
"notched_10", let styles = [
"notched_12", "notched_10",
"notched_20", "notched_12",
"notched_6", "notched_20",
"progress", "notched_6",
]; "progress",
let suggestions: Vec<CommandSuggestion> = styles ];
.iter() let suggestions: Vec<CommandSuggestion> = styles
.map(|style| CommandSuggestion::new((*style).to_string(), None)) .iter()
.collect(); .map(|style| CommandSuggestion::new((*style).to_string(), None))
Ok(Some(suggestions)) .collect();
Ok(Some(suggestions))
})
} }
} }

View File

@ -2,11 +2,11 @@ use core::f64;
use std::fmt::{Display, Formatter}; use std::fmt::{Display, Formatter};
use std::str::FromStr; use std::str::FromStr;
use async_trait::async_trait; use pumpkin_protocol::java::client::play::ArgumentType;
use pumpkin_protocol::java::client::play::{ArgumentType, CommandSuggestion};
use pumpkin_util::text::TextComponent; use pumpkin_util::text::TextComponent;
use crate::command::CommandSender; use crate::command::CommandSender;
use crate::command::args::ConsumeResult;
use crate::command::dispatcher::CommandError; use crate::command::dispatcher::CommandError;
use crate::command::tree::RawArgs; use crate::command::tree::RawArgs;
use crate::server::Server; use crate::server::Server;
@ -21,47 +21,44 @@ pub struct BoundedNumArgumentConsumer<T: ToFromNumber> {
name: Option<&'static str>, name: Option<&'static str>,
} }
#[async_trait]
impl<T: ToFromNumber> ArgumentConsumer for BoundedNumArgumentConsumer<T> impl<T: ToFromNumber> ArgumentConsumer for BoundedNumArgumentConsumer<T>
where where
Self: GetClientSideArgParser, Self: GetClientSideArgParser,
{ {
async fn consume<'a>( fn consume<'a, 'b>(
&'a self, &'a self,
_src: &CommandSender, _sender: &'a CommandSender,
_server: &'a Server, _server: &'a Server,
args: &mut RawArgs<'a>, args: &'b mut RawArgs<'a>,
) -> Option<Arg<'a>> { ) -> ConsumeResult<'a> {
let x = args.pop()?.parse::<T>().ok()?; // 1. Perform the synchronous mutable operation (pop) outside the Future.
let s_opt: Option<&'a str> = args.pop();
if let Some(max) = self.max_inclusive // 2. Process the entire argument and check bounds synchronously.
&& x > max let result: Option<Arg<'a>> = s_opt
{ // Replace args.pop()?.parse::<T>().ok()?
return Some(Arg::Num(Err(NotInBounds::UpperBound( .and_then(|s| s.parse::<T>().ok())
x.to_number(), .map(|x| {
max.to_number(), // Check Upper Bound (max_inclusive)
)))); if let Some(max) = self.max_inclusive
} && x > max
{
return Arg::Num(Err(NotInBounds::UpperBound(x.to_number(), max.to_number())));
}
if let Some(min) = self.min_inclusive // Check Lower Bound (min_inclusive)
&& x < min if let Some(min) = self.min_inclusive
{ && x < min
return Some(Arg::Num(Err(NotInBounds::LowerBound( {
x.to_number(), return Arg::Num(Err(NotInBounds::LowerBound(x.to_number(), min.to_number())));
min.to_number(), }
))));
}
Some(Arg::Num(Ok(x.to_number()))) // Success case
} Arg::Num(Ok(x.to_number()))
});
async fn suggest<'a>( // 3. Return a Future that immediately resolves to the calculated result.
&'a self, Box::pin(async move { result })
_sender: &CommandSender,
_server: &'a Server,
_input: &'a str,
) -> Result<Option<Vec<CommandSuggestion>>, CommandError> {
Ok(None)
} }
} }

View File

@ -1,4 +1,3 @@
use async_trait::async_trait;
use pumpkin_protocol::java::client::play::{ use pumpkin_protocol::java::client::play::{
ArgumentType, CommandSuggestion, StringProtoArgBehavior, SuggestionProviders, ArgumentType, CommandSuggestion, StringProtoArgBehavior, SuggestionProviders,
}; };
@ -6,7 +5,7 @@ use pumpkin_protocol::java::client::play::{
use crate::{ use crate::{
command::{ command::{
CommandSender, CommandSender,
args::SplitSingleWhitespaceIncludingEmptyParts, args::{ConsumeResult, SplitSingleWhitespaceIncludingEmptyParts, SuggestResult},
dispatcher::CommandError, dispatcher::CommandError,
tree::{CommandTree, RawArgs}, tree::{CommandTree, RawArgs},
}, },
@ -27,40 +26,49 @@ impl GetClientSideArgParser for CommandTreeArgumentConsumer {
} }
} }
#[async_trait]
impl ArgumentConsumer for CommandTreeArgumentConsumer { impl ArgumentConsumer for CommandTreeArgumentConsumer {
async fn consume<'a>( fn consume<'a, 'b>(
&'a self, &'a self,
_sender: &CommandSender, _sender: &'a CommandSender,
server: &'a Server, server: &'a Server,
args: &mut RawArgs<'a>, args: &'b mut RawArgs<'a>,
) -> Option<Arg<'a>> { ) -> ConsumeResult<'a> {
let s = args.pop()?; let s_opt: Option<&'a str> = args.pop();
let dispatcher = server.command_dispatcher.read().await; let Some(s) = s_opt else {
dispatcher return Box::pin(async move { None });
.get_tree(s) };
.map_or_else(|_| None, |tree| Some(Arg::CommandTree(tree.clone())))
Box::pin(async move {
let dispatcher = server.command_dispatcher.read().await;
dispatcher
.get_tree(s)
.ok()
.map(|tree| Arg::CommandTree(tree.clone()))
})
} }
async fn suggest<'a>( fn suggest<'a>(
&'a self, &'a self,
_sender: &CommandSender, _sender: &CommandSender,
server: &'a Server, server: &'a Server,
input: &'a str, input: &'a str,
) -> Result<Option<Vec<CommandSuggestion>>, CommandError> { ) -> SuggestResult<'a> {
let Some(input) = input.split_single_whitespace_including_empty_parts().last() else { Box::pin(async move {
return Ok(None); let Some(input) = input.split_single_whitespace_including_empty_parts().last() else {
}; return Ok(None);
};
let dispatcher = server.command_dispatcher.read().await; let dispatcher = server.command_dispatcher.read().await;
let suggestions = dispatcher let suggestions = dispatcher
.commands .commands
.keys() .keys()
.filter(|suggestion| suggestion.starts_with(input)) .filter(|suggestion| suggestion.starts_with(input))
.map(|suggestion| CommandSuggestion::new(suggestion.clone(), None)) .map(|suggestion| CommandSuggestion::new(suggestion.clone(), None))
.collect(); .collect();
Ok(Some(suggestions)) Ok(Some(suggestions))
})
} }
} }

View File

@ -1,11 +1,15 @@
use std::str::FromStr; use std::str::FromStr;
use async_trait::async_trait;
use pumpkin_protocol::java::client::play::{ArgumentType, CommandSuggestion, SuggestionProviders}; use pumpkin_protocol::java::client::play::{ArgumentType, CommandSuggestion, SuggestionProviders};
use pumpkin_util::Difficulty; use pumpkin_util::Difficulty;
use crate::{ use crate::{
command::{CommandSender, dispatcher::CommandError, tree::RawArgs}, command::{
CommandSender,
args::{ConsumeResult, SuggestResult},
dispatcher::CommandError,
tree::RawArgs,
},
server::Server, server::Server,
}; };
@ -25,32 +29,35 @@ impl GetClientSideArgParser for DifficultyArgumentConsumer {
} }
} }
#[async_trait]
impl ArgumentConsumer for DifficultyArgumentConsumer { impl ArgumentConsumer for DifficultyArgumentConsumer {
async fn consume<'a>( fn consume<'a>(
&'a self, &'a self,
_sender: &CommandSender, _sender: &CommandSender,
_server: &'a Server, _server: &'a Server,
args: &mut RawArgs<'a>, args: &mut RawArgs<'a>,
) -> Option<Arg<'a>> { ) -> ConsumeResult<'a> {
let s = args.pop()?; let s_opt: Option<&'a str> = args.pop();
Difficulty::from_str(s) let result: Option<Arg<'a>> =
.map_or_else(|_| None, |difficulty| Some(Arg::Difficulty(difficulty))) s_opt.and_then(|s| Difficulty::from_str(s).map(Arg::Difficulty).ok());
Box::pin(async move { result })
} }
async fn suggest<'a>( fn suggest<'a>(
&'a self, &'a self,
_sender: &CommandSender, _sender: &CommandSender,
_server: &'a Server, _server: &'a Server,
_input: &'a str, _input: &'a str,
) -> Result<Option<Vec<CommandSuggestion>>, CommandError> { ) -> SuggestResult<'a> {
let difficulties = ["easy", "normal", "hard", "peaceful"]; Box::pin(async move {
let suggestions: Vec<CommandSuggestion> = difficulties let difficulties = ["easy", "normal", "hard", "peaceful"];
.iter() let suggestions: Vec<CommandSuggestion> = difficulties
.map(|difficulty| CommandSuggestion::new((*difficulty).to_string(), None)) .iter()
.collect(); .map(|difficulty| CommandSuggestion::new((*difficulty).to_string(), None))
Ok(Some(suggestions)) .collect();
Ok(Some(suggestions))
})
} }
} }

View File

@ -2,14 +2,14 @@ use std::str::FromStr;
use std::sync::Arc; use std::sync::Arc;
use crate::command::CommandSender; use crate::command::CommandSender;
use crate::command::args::ConsumeResult;
use crate::command::dispatcher::CommandError; use crate::command::dispatcher::CommandError;
use crate::command::tree::RawArgs; use crate::command::tree::RawArgs;
use crate::entity::EntityBase; use crate::entity::EntityBase;
use crate::server::Server; use crate::server::Server;
use async_trait::async_trait;
use pumpkin_data::entity::EntityType; use pumpkin_data::entity::EntityType;
use pumpkin_nbt::compound::NbtCompound; use pumpkin_nbt::compound::NbtCompound;
use pumpkin_protocol::java::client::play::{ArgumentType, CommandSuggestion, SuggestionProviders}; use pumpkin_protocol::java::client::play::{ArgumentType, SuggestionProviders};
use pumpkin_util::GameMode; use pumpkin_util::GameMode;
use uuid::Uuid; use uuid::Uuid;
@ -250,35 +250,34 @@ impl GetClientSideArgParser for EntitiesArgumentConsumer {
} }
} }
#[async_trait]
impl ArgumentConsumer for EntitiesArgumentConsumer { impl ArgumentConsumer for EntitiesArgumentConsumer {
async fn consume<'a>( fn consume<'a, 'b>(
&'a self, &'a self,
src: &CommandSender, sender: &'a CommandSender,
server: &'a Server, server: &'a Server,
args: &mut RawArgs<'a>, args: &'b mut RawArgs<'a>,
) -> Option<Arg<'a>> { ) -> ConsumeResult<'a> {
let s = args.pop()?; let s_opt: Option<&'a str> = args.pop();
let Some(s) = s_opt else {
return Box::pin(async move { None });
};
let entity_selector = match s.parse::<TargetSelector>() { let entity_selector = match s.parse::<TargetSelector>() {
Ok(selector) => selector, Ok(selector) => selector,
Err(e) => { Err(e) => {
log::debug!("Failed to parse target selector '{s}': {e}"); log::debug!("Failed to parse target selector '{s}': {e}");
return None; return Box::pin(async move { None }); // Return a Future resolving to None
} }
}; };
// todo: command context
let entities = server.select_entities(&entity_selector, Some(src)).await;
Some(Arg::Entities(entities)) Box::pin(async move {
} // todo: command context
// This is the required asynchronous operation.
let entities = server.select_entities(&entity_selector, Some(sender)).await;
async fn suggest<'a>( Some(Arg::Entities(entities))
&'a self, })
_sender: &CommandSender,
_server: &'a Server,
_input: &'a str,
) -> Result<Option<Vec<CommandSuggestion>>, CommandError> {
Ok(None)
} }
} }

View File

@ -1,13 +1,13 @@
use std::sync::Arc; use std::sync::Arc;
use crate::command::CommandSender; use crate::command::CommandSender;
use crate::command::args::ConsumeResult;
use crate::command::args::entities::TargetSelector; use crate::command::args::entities::TargetSelector;
use crate::command::dispatcher::CommandError; use crate::command::dispatcher::CommandError;
use crate::command::tree::RawArgs; use crate::command::tree::RawArgs;
use crate::entity::EntityBase; use crate::entity::EntityBase;
use crate::server::Server; use crate::server::Server;
use async_trait::async_trait; use pumpkin_protocol::java::client::play::{ArgumentType, SuggestionProviders};
use pumpkin_protocol::java::client::play::{ArgumentType, CommandSuggestion, SuggestionProviders};
use super::super::args::ArgumentConsumer; use super::super::args::ArgumentConsumer;
use super::{Arg, DefaultNameArgConsumer, FindArg, GetClientSideArgParser}; use super::{Arg, DefaultNameArgConsumer, FindArg, GetClientSideArgParser};
@ -32,41 +32,38 @@ impl GetClientSideArgParser for EntityArgumentConsumer {
} }
} }
#[async_trait]
impl ArgumentConsumer for EntityArgumentConsumer { impl ArgumentConsumer for EntityArgumentConsumer {
async fn consume<'a>( fn consume<'a, 'b>(
&'a self, &'a self,
src: &CommandSender, sender: &'a CommandSender,
server: &'a Server, server: &'a Server,
args: &mut RawArgs<'a>, args: &'b mut RawArgs<'a>,
) -> Option<Arg<'a>> { ) -> ConsumeResult<'a> {
let s = args.pop()?; let s_opt: Option<&'a str> = args.pop();
let Some(s) = s_opt else {
return Box::pin(async move { None });
};
let entity_selector = match s.parse::<TargetSelector>() { let entity_selector = match s.parse::<TargetSelector>() {
Ok(selector) => selector, Ok(selector) => selector,
Err(e) => { Err(e) => {
log::debug!("Failed to parse target selector '{s}': {e}"); log::debug!("Failed to parse target selector '{s}': {e}");
return None; return Box::pin(async move { None });
} }
}; };
if entity_selector.get_limit() > 1 { if entity_selector.get_limit() > 1 {
log::debug!("Target selector '{s}' has limit > 1, expected single entity"); log::debug!("Target selector '{s}' has limit > 1, expected single entity");
return None; return Box::pin(async move { None });
} }
// todo: command context
let entities = server.select_entities(&entity_selector, Some(src)).await;
// Take first Box::pin(async move {
entities.into_iter().next().map(Arg::Entity) // todo: command context
} let entities = server.select_entities(&entity_selector, Some(sender)).await;
async fn suggest<'a>( entities.into_iter().next().map(Arg::Entity)
&'a self, })
_sender: &CommandSender,
_server: &'a Server,
_input: &'a str,
) -> Result<Option<Vec<CommandSuggestion>>, CommandError> {
Ok(None)
} }
} }

View File

@ -1,11 +1,10 @@
use std::str::FromStr; use std::str::FromStr;
use async_trait::async_trait; use pumpkin_protocol::java::client::play::{ArgumentType, SuggestionProviders};
use pumpkin_protocol::java::client::play::{ArgumentType, CommandSuggestion, SuggestionProviders};
use pumpkin_util::GameMode; use pumpkin_util::GameMode;
use crate::{ use crate::{
command::{CommandSender, dispatcher::CommandError, tree::RawArgs}, command::{CommandSender, args::ConsumeResult, dispatcher::CommandError, tree::RawArgs},
server::Server, server::Server,
}; };
@ -23,32 +22,26 @@ impl GetClientSideArgParser for GamemodeArgumentConsumer {
} }
} }
#[async_trait]
impl ArgumentConsumer for GamemodeArgumentConsumer { impl ArgumentConsumer for GamemodeArgumentConsumer {
async fn consume<'a>( fn consume<'a, 'b>(
&'a self, &'a self,
_sender: &CommandSender, _sender: &'a CommandSender,
_server: &'a Server, _server: &'a Server,
args: &mut RawArgs<'a>, args: &'b mut RawArgs<'a>,
) -> Option<Arg<'a>> { ) -> ConsumeResult<'a> {
let s = args.pop()?; let s_opt: Option<&'a str> = args.pop();
if let Ok(id) = s.parse::<i8>() let result: Option<Arg<'a>> = s_opt.and_then(|s| {
&& let Ok(gamemode) = GameMode::try_from(id) if let Ok(id) = s.parse::<i8>()
{ && let Ok(gamemode) = GameMode::try_from(id)
return Some(Arg::GameMode(gamemode)); {
} return Some(Arg::GameMode(gamemode));
}
GameMode::from_str(s).map_or_else(|_| None, |gamemode| Some(Arg::GameMode(gamemode))) GameMode::from_str(s).map(Arg::GameMode).ok() // Convert Result to Option<T>
} });
async fn suggest<'a>( Box::pin(async move { result })
&'a self,
_sender: &CommandSender,
_server: &'a Server,
_input: &'a str,
) -> Result<Option<Vec<CommandSuggestion>>, CommandError> {
Ok(None)
} }
} }

View File

@ -1,9 +1,11 @@
use async_trait::async_trait;
use pumpkin_protocol::java::client::play::{ use pumpkin_protocol::java::client::play::{
ArgumentType, CommandSuggestion, StringProtoArgBehavior, SuggestionProviders, ArgumentType, StringProtoArgBehavior, SuggestionProviders,
}; };
use crate::{command::dispatcher::CommandError, server::Server}; use crate::{
command::{args::ConsumeResult, dispatcher::CommandError},
server::Server,
};
use super::{ use super::{
super::{ super::{
@ -26,31 +28,26 @@ impl GetClientSideArgParser for MsgArgConsumer {
} }
} }
#[async_trait]
impl ArgumentConsumer for MsgArgConsumer { impl ArgumentConsumer for MsgArgConsumer {
async fn consume<'a>( fn consume<'a>(
&'a self, &'a self,
_sender: &CommandSender, _sender: &'a CommandSender,
_server: &'a Server, _server: &'a Server,
args: &mut RawArgs<'a>, args: &mut RawArgs<'a>,
) -> Option<Arg<'a>> { ) -> ConsumeResult<'a> {
let mut msg = args.pop()?.to_string(); let first_word_opt = args.pop();
let mut msg = match first_word_opt {
Some(word) => word.to_string(),
None => return Box::pin(async move { None }),
};
while let Some(word) = args.pop() { while let Some(word) = args.pop() {
msg.push(' '); msg.push(' ');
msg.push_str(word); msg.push_str(word);
} }
Some(Arg::Msg(msg)) Box::pin(async move { Some(Arg::Msg(msg)) })
}
async fn suggest<'a>(
&'a self,
_sender: &CommandSender,
_server: &'a Server,
_input: &'a str,
) -> Result<Option<Vec<CommandSuggestion>>, CommandError> {
Ok(None)
} }
} }

View File

@ -1,6 +1,6 @@
use std::pin::Pin;
use std::{collections::HashMap, hash::Hash, sync::Arc}; use std::{collections::HashMap, hash::Hash, sync::Arc};
use async_trait::async_trait;
use bounded_num::{NotInBounds, Number}; use bounded_num::{NotInBounds, Number};
use pumpkin_data::Enchantment; use pumpkin_data::Enchantment;
use pumpkin_data::damage::DamageType; use pumpkin_data::damage::DamageType;
@ -51,24 +51,30 @@ pub mod textcomponent;
pub mod time; pub mod time;
/// see [`crate::commands::tree::builder::argument`] /// see [`crate::commands::tree::builder::argument`]
#[async_trait] pub type ConsumeResult<'a> = Pin<Box<dyn Future<Output = Option<Arg<'a>>> + Send + 'a>>;
pub trait ArgumentConsumer: Sync + GetClientSideArgParser {
async fn consume<'a>( pub type SuggestResult<'a> =
Pin<Box<dyn Future<Output = Result<Option<Vec<CommandSuggestion>>, CommandError>> + Send + 'a>>;
pub trait ArgumentConsumer: Sync + Send + GetClientSideArgParser {
fn consume<'a>(
&'a self, &'a self,
sender: &CommandSender, sender: &'a CommandSender,
server: &'a Server, server: &'a Server,
args: &mut RawArgs<'a>, args: &mut RawArgs<'a>,
) -> Option<Arg>; ) -> ConsumeResult<'a>;
/// Used for tab completion (but only if argument suggestion type is "`minecraft:ask_server`"!). /// Used for tab completion (but only if argument suggestion type is "`minecraft:ask_server`"!).
/// ///
/// NOTE: This is called after this consumer's [`ArgumentConsumer::consume`] method returned None, so if args is used here, make sure [`ArgumentConsumer::consume`] never returns None after mutating args. /// NOTE: This is called after this consumer's [`ArgumentConsumer::consume`] method returned None, so if args is used here, make sure [`ArgumentConsumer::consume`] never returns None after mutating args.
async fn suggest<'a>( fn suggest<'a>(
&'a self, &'a self,
sender: &CommandSender, _sender: &CommandSender,
server: &'a Server, _server: &'a Server,
input: &'a str, _input: &'a str,
) -> Result<Option<Vec<CommandSuggestion>>, CommandError>; ) -> SuggestResult<'a> {
Box::pin(async move { Ok(None) })
}
} }
pub trait GetClientSideArgParser { pub trait GetClientSideArgParser {

View File

@ -1,9 +1,9 @@
use std::sync::Arc; use std::sync::Arc;
use async_trait::async_trait; use pumpkin_protocol::java::client::play::{ArgumentType, SuggestionProviders};
use pumpkin_protocol::java::client::play::{ArgumentType, CommandSuggestion, SuggestionProviders};
use crate::command::CommandSender; use crate::command::CommandSender;
use crate::command::args::ConsumeResult;
use crate::command::dispatcher::CommandError; use crate::command::dispatcher::CommandError;
use crate::command::tree::RawArgs; use crate::command::tree::RawArgs;
use crate::entity::player::Player; use crate::entity::player::Player;
@ -28,45 +28,49 @@ impl GetClientSideArgParser for PlayersArgumentConsumer {
} }
} }
#[async_trait]
impl ArgumentConsumer for PlayersArgumentConsumer { impl ArgumentConsumer for PlayersArgumentConsumer {
async fn consume<'a>( fn consume<'a, 'b>(
&'a self, &'a self,
src: &CommandSender, sender: &'a CommandSender,
server: &'a Server, server: &'a Server,
args: &mut RawArgs<'a>, args: &'b mut RawArgs<'a>,
) -> Option<Arg<'a>> { ) -> ConsumeResult<'a> {
let s = args.pop()?; let s_opt: Option<&'a str> = args.pop();
let players = match s { let Some(s) = s_opt else {
"@s" => match src { return Box::pin(async move { None });
};
let sync_result: Option<Vec<Arc<Player>>> = match s {
"@s" => match sender {
CommandSender::Player(p) => Some(vec![p.clone()]), CommandSender::Player(p) => Some(vec![p.clone()]),
_ => None, _ => None,
}, },
#[allow(clippy::match_same_arms)] #[allow(clippy::match_same_arms)]
// todo: implement for non-players and remove this line // todo: implement for non-players and remove this line
"@n" | "@p" => match src { "@n" | "@p" => match sender {
CommandSender::Player(p) => Some(vec![p.clone()]), CommandSender::Player(p) => Some(vec![p.clone()]),
// todo: implement for non-players: how should this behave when sender is console/rcon?
_ => None, _ => None,
}, },
"@r" => { _ => None,
(server.get_random_player().await).map_or_else(|| Some(vec![]), |p| Some(vec![p]))
}
"@a" | "@e" => Some(server.get_all_players().await),
name => server.get_player_by_name(name).await.map(|p| vec![p]),
}; };
players.map(Arg::Players) if let Some(players) = sync_result {
} return Box::pin(async move { Some(Arg::Players(players)) });
}
async fn suggest<'a>( Box::pin(async move {
&'a self, let players = match s {
_sender: &CommandSender, "@r" => server
_server: &'a Server, .get_random_player()
_input: &'a str, .await
) -> Result<Option<Vec<CommandSuggestion>>, CommandError> { .map_or_else(|| Some(vec![]), |p| Some(vec![p])),
Ok(None) "@a" | "@e" => Some(server.get_all_players().await),
name => server.get_player_by_name(name).await.map(|p| vec![p]),
};
players.map(Arg::Players)
})
} }
} }

View File

@ -1,9 +1,9 @@
use async_trait::async_trait; use pumpkin_protocol::java::client::play::{ArgumentType, SuggestionProviders};
use pumpkin_protocol::java::client::play::{ArgumentType, CommandSuggestion, SuggestionProviders};
use pumpkin_util::math::vector2::Vector2; use pumpkin_util::math::vector2::Vector2;
use pumpkin_util::math::vector3::Vector3; use pumpkin_util::math::vector3::Vector3;
use crate::command::CommandSender; use crate::command::CommandSender;
use crate::command::args::ConsumeResult;
use crate::command::dispatcher::CommandError; use crate::command::dispatcher::CommandError;
use crate::command::tree::RawArgs; use crate::command::tree::RawArgs;
use crate::server::Server; use crate::server::Server;
@ -27,28 +27,25 @@ impl GetClientSideArgParser for Position2DArgumentConsumer {
} }
} }
#[async_trait]
impl ArgumentConsumer for Position2DArgumentConsumer { impl ArgumentConsumer for Position2DArgumentConsumer {
async fn consume<'a>( fn consume<'a, 'b>(
&'a self, &'a self,
src: &CommandSender, sender: &'a CommandSender,
_server: &'a Server, _server: &'a Server,
args: &mut RawArgs<'a>, args: &'b mut RawArgs<'a>,
) -> Option<Arg<'a>> { ) -> ConsumeResult<'a> {
let pos = MaybeRelativePosition2D::try_new(args.pop()?, args.pop()?)?; let x_str_opt = args.pop();
let z_str_opt = args.pop();
let vec2 = pos.try_to_absolute(src.position())?; let (Some(x_str), Some(z_str)) = (x_str_opt, z_str_opt) else {
return Box::pin(async move { None });
};
Some(Arg::Pos2D(vec2)) let result: Option<Arg<'a>> = MaybeRelativePosition2D::try_new(x_str, z_str)
} .and_then(|pos| pos.try_to_absolute(sender.position()))
.map(Arg::Pos2D);
async fn suggest<'a>( Box::pin(async move { result })
&'a self,
_sender: &CommandSender,
_server: &'a Server,
_input: &'a str,
) -> Result<Option<Vec<CommandSuggestion>>, CommandError> {
Ok(None)
} }
} }

View File

@ -1,8 +1,8 @@
use async_trait::async_trait; use pumpkin_protocol::java::client::play::{ArgumentType, SuggestionProviders};
use pumpkin_protocol::java::client::play::{ArgumentType, CommandSuggestion, SuggestionProviders};
use pumpkin_util::math::vector3::Vector3; use pumpkin_util::math::vector3::Vector3;
use crate::command::CommandSender; use crate::command::CommandSender;
use crate::command::args::ConsumeResult;
use crate::command::dispatcher::CommandError; use crate::command::dispatcher::CommandError;
use crate::command::tree::RawArgs; use crate::command::tree::RawArgs;
use crate::server::Server; use crate::server::Server;
@ -24,28 +24,26 @@ impl GetClientSideArgParser for Position3DArgumentConsumer {
} }
} }
#[async_trait]
impl ArgumentConsumer for Position3DArgumentConsumer { impl ArgumentConsumer for Position3DArgumentConsumer {
async fn consume<'a>( fn consume<'a, 'b>(
&'a self, &'a self,
src: &CommandSender, sender: &'a CommandSender,
_server: &'a Server, _server: &'a Server,
args: &mut RawArgs<'a>, args: &'b mut RawArgs<'a>,
) -> Option<Arg<'a>> { ) -> ConsumeResult<'a> {
let pos = MaybeRelativePosition3D::try_new(args.pop()?, args.pop()?, args.pop()?)?; let x_str_opt = args.pop();
let y_str_opt = args.pop();
let z_str_opt = args.pop();
let vec3 = pos.try_to_absolute(src.position())?; let (Some(x_str), Some(y_str), Some(z_str)) = (x_str_opt, y_str_opt, z_str_opt) else {
return Box::pin(async move { None });
};
Some(Arg::Pos3D(vec3)) let result: Option<Arg<'a>> = MaybeRelativePosition3D::try_new(x_str, y_str, z_str)
} .and_then(|pos| pos.try_to_absolute(sender.position()))
.map(Arg::Pos3D);
async fn suggest<'a>( Box::pin(async move { result })
&'a self,
_sender: &CommandSender,
_server: &'a Server,
_input: &'a str,
) -> Result<Option<Vec<CommandSuggestion>>, CommandError> {
Ok(None)
} }
} }

View File

@ -1,9 +1,9 @@
use async_trait::async_trait; use pumpkin_protocol::java::client::play::{ArgumentType, SuggestionProviders};
use pumpkin_protocol::java::client::play::{ArgumentType, CommandSuggestion, SuggestionProviders};
use pumpkin_util::math::position::BlockPos; use pumpkin_util::math::position::BlockPos;
use pumpkin_util::math::vector3::Vector3; use pumpkin_util::math::vector3::Vector3;
use crate::command::CommandSender; use crate::command::CommandSender;
use crate::command::args::ConsumeResult;
use crate::command::dispatcher::CommandError; use crate::command::dispatcher::CommandError;
use crate::command::tree::RawArgs; use crate::command::tree::RawArgs;
use crate::server::Server; use crate::server::Server;
@ -25,28 +25,26 @@ impl GetClientSideArgParser for BlockPosArgumentConsumer {
} }
} }
#[async_trait]
impl ArgumentConsumer for BlockPosArgumentConsumer { impl ArgumentConsumer for BlockPosArgumentConsumer {
async fn consume<'a>( fn consume<'a, 'b>(
&'a self, &'a self,
src: &CommandSender, sender: &'a CommandSender,
_server: &'a Server, _server: &'a Server,
args: &mut RawArgs<'a>, args: &'b mut RawArgs<'a>,
) -> Option<Arg<'a>> { ) -> ConsumeResult<'a> {
let pos = MaybeRelativeBlockPos::try_new(args.pop()?, args.pop()?, args.pop()?)?; let x_str_opt = args.pop();
let y_str_opt = args.pop();
let z_str_opt = args.pop();
let vec3 = pos.try_to_absolute(src.position())?; let (Some(x_str), Some(y_str), Some(z_str)) = (x_str_opt, y_str_opt, z_str_opt) else {
return Box::pin(async move { None });
};
Some(Arg::BlockPos(vec3)) let result: Option<Arg<'a>> = MaybeRelativeBlockPos::try_new(x_str, y_str, z_str)
} .and_then(|pos| pos.try_to_absolute(sender.position()))
.map(Arg::BlockPos);
async fn suggest<'a>( Box::pin(async move { result })
&'a self,
_sender: &CommandSender,
_server: &'a Server,
_input: &'a str,
) -> Result<Option<Vec<CommandSuggestion>>, CommandError> {
Ok(None)
} }
} }

View File

@ -1,11 +1,10 @@
use async_trait::async_trait;
use pumpkin_data::damage::DamageType; use pumpkin_data::damage::DamageType;
use pumpkin_protocol::java::client::play::{ArgumentType, CommandSuggestion, SuggestionProviders}; use pumpkin_protocol::java::client::play::{ArgumentType, SuggestionProviders};
use crate::command::{ use crate::command::{
CommandSender, CommandSender,
args::{ args::{
Arg, ArgumentConsumer, ConsumedArgs, DefaultNameArgConsumer, FindArg, Arg, ArgumentConsumer, ConsumeResult, ConsumedArgs, DefaultNameArgConsumer, FindArg,
GetClientSideArgParser, GetClientSideArgParser,
}, },
dispatcher::CommandError, dispatcher::CommandError,
@ -27,30 +26,24 @@ impl GetClientSideArgParser for DamageTypeArgumentConsumer {
} }
} }
#[async_trait]
impl ArgumentConsumer for DamageTypeArgumentConsumer { impl ArgumentConsumer for DamageTypeArgumentConsumer {
async fn consume<'a>( fn consume<'a, 'b>(
&'a self, &'a self,
_sender: &CommandSender, _sender: &'a CommandSender,
_server: &'a Server, _server: &'a Server,
args: &mut RawArgs<'a>, args: &'b mut RawArgs<'a>,
) -> Option<Arg<'a>> { ) -> ConsumeResult<'a> {
let name = args.pop()?; let name_opt: Option<&'a str> = args.pop();
let name = name.strip_prefix("minecraft:").unwrap_or(name);
// Create a static damage type first let result: Option<Arg<'a>> = name_opt.map_or_else(
let damage_type = DamageType::from_name(name)?; || None,
// Find matching static damage type from values array |name| {
Some(Arg::DamageType(damage_type)) let name = name.strip_prefix("minecraft:").unwrap_or(name);
} DamageType::from_name(name).map(Arg::DamageType)
},
);
async fn suggest<'a>( Box::pin(async move { result })
&'a self,
_sender: &CommandSender,
_server: &'a Server,
_input: &'a str,
) -> Result<Option<Vec<CommandSuggestion>>, CommandError> {
Ok(None)
} }
} }

View File

@ -1,11 +1,11 @@
use async_trait::async_trait; use async_trait::async_trait;
use pumpkin_data::effect::StatusEffect; use pumpkin_data::effect::StatusEffect;
use pumpkin_protocol::java::client::play::{ArgumentType, CommandSuggestion, SuggestionProviders}; use pumpkin_protocol::java::client::play::{ArgumentType, SuggestionProviders};
use crate::command::{ use crate::command::{
CommandSender, CommandSender,
args::{ args::{
Arg, ArgumentConsumer, ConsumedArgs, DefaultNameArgConsumer, FindArg, Arg, ArgumentConsumer, ConsumeResult, ConsumedArgs, DefaultNameArgConsumer, FindArg,
GetClientSideArgParser, GetClientSideArgParser,
}, },
dispatcher::CommandError, dispatcher::CommandError,
@ -29,27 +29,22 @@ impl GetClientSideArgParser for EffectTypeArgumentConsumer {
#[async_trait] #[async_trait]
impl ArgumentConsumer for EffectTypeArgumentConsumer { impl ArgumentConsumer for EffectTypeArgumentConsumer {
async fn consume<'a>( fn consume<'a, 'b>(
&'a self, &'a self,
_sender: &CommandSender, _sender: &'a CommandSender,
_server: &'a Server, _server: &'a Server,
args: &mut RawArgs<'a>, args: &'b mut RawArgs<'a>,
) -> Option<Arg<'a>> { ) -> ConsumeResult<'a> {
let name = args.pop()?; let status_effect: Option<&'a str> = args.pop();
// Create a static damage type first match status_effect {
let damage_type = StatusEffect::from_name(name.strip_prefix("minecraft:").unwrap_or(name))?; Some(name) => Box::pin(async move {
// Find matching static damage type from values array let status_effect =
Some(Arg::Effect(damage_type)) StatusEffect::from_name(name.strip_prefix("minecraft:").unwrap_or(name))?;
} Some(Arg::Effect(status_effect))
}),
async fn suggest<'a>( None => Box::pin(async move { None }),
&'a self, }
_sender: &CommandSender,
_server: &'a Server,
_input: &'a str,
) -> Result<Option<Vec<CommandSuggestion>>, CommandError> {
Ok(None)
} }
} }

View File

@ -1,11 +1,11 @@
use async_trait::async_trait; use async_trait::async_trait;
use pumpkin_data::Enchantment; use pumpkin_data::Enchantment;
use pumpkin_protocol::java::client::play::{ArgumentType, CommandSuggestion, SuggestionProviders}; use pumpkin_protocol::java::client::play::{ArgumentType, SuggestionProviders};
use crate::command::{ use crate::command::{
CommandSender, CommandSender,
args::{ args::{
Arg, ArgumentConsumer, ConsumedArgs, DefaultNameArgConsumer, FindArg, Arg, ArgumentConsumer, ConsumeResult, ConsumedArgs, DefaultNameArgConsumer, FindArg,
GetClientSideArgParser, GetClientSideArgParser,
}, },
dispatcher::CommandError, dispatcher::CommandError,
@ -29,27 +29,19 @@ impl GetClientSideArgParser for EnchantmentArgumentConsumer {
#[async_trait] #[async_trait]
impl ArgumentConsumer for EnchantmentArgumentConsumer { impl ArgumentConsumer for EnchantmentArgumentConsumer {
async fn consume<'a>( fn consume<'a, 'b>(
&'a self, &'a self,
_sender: &CommandSender, _sender: &'a CommandSender,
_server: &'a Server, _server: &'a Server,
args: &mut RawArgs<'a>, args: &'b mut RawArgs<'a>,
) -> Option<Arg<'a>> { ) -> ConsumeResult<'a> {
let name = args.pop()?; let name_opt: Option<&'a str> = args.pop();
// Create a static damage type first let result: Option<Arg<'a>> = name_opt.map_or_else(
let enchantment = Enchantment::from_name(name)?; || None,
// Find matching static damage type from values array |name| Enchantment::from_name(name).map(Arg::Enchantment),
Some(Arg::Enchantment(enchantment)) );
} Box::pin(async move { result })
async fn suggest<'a>(
&'a self,
_sender: &CommandSender,
_server: &'a Server,
_input: &'a str,
) -> Result<Option<Vec<CommandSuggestion>>, CommandError> {
Ok(None)
} }
} }

View File

@ -1,12 +1,12 @@
use async_trait::async_trait; use async_trait::async_trait;
use pumpkin_data::item::Item; use pumpkin_data::item::Item;
use pumpkin_protocol::java::client::play::{ArgumentType, CommandSuggestion, SuggestionProviders}; use pumpkin_protocol::java::client::play::{ArgumentType, SuggestionProviders};
use pumpkin_util::text::TextComponent; use pumpkin_util::text::TextComponent;
use crate::command::{ use crate::command::{
CommandSender, CommandSender,
args::{ args::{
Arg, ArgumentConsumer, ConsumedArgs, DefaultNameArgConsumer, FindArg, Arg, ArgumentConsumer, ConsumeResult, ConsumedArgs, DefaultNameArgConsumer, FindArg,
GetClientSideArgParser, GetClientSideArgParser,
}, },
dispatcher::CommandError, dispatcher::CommandError,
@ -28,23 +28,17 @@ impl GetClientSideArgParser for ItemArgumentConsumer {
#[async_trait] #[async_trait]
impl ArgumentConsumer for ItemArgumentConsumer { impl ArgumentConsumer for ItemArgumentConsumer {
async fn consume<'a>( fn consume<'a>(
&'a self, &'a self,
_sender: &CommandSender, _sender: &'a CommandSender,
_server: &'a Server, _server: &'a Server,
args: &mut RawArgs<'a>, args: &mut RawArgs<'a>,
) -> Option<Arg<'a>> { ) -> ConsumeResult<'a> {
// todo: get an actual item let item = args.pop();
Some(Arg::Item(args.pop()?)) match item {
} Some(s) => Box::pin(async move { Some(Arg::Item(s)) }),
None => Box::pin(async move { None }),
async fn suggest<'a>( }
&'a self,
_sender: &CommandSender,
_server: &'a Server,
_input: &'a str,
) -> Result<Option<Vec<CommandSuggestion>>, CommandError> {
Ok(None)
} }
} }

View File

@ -1,11 +1,11 @@
use async_trait::async_trait; use async_trait::async_trait;
use pumpkin_data::particle::Particle; use pumpkin_data::particle::Particle;
use pumpkin_protocol::java::client::play::{ArgumentType, CommandSuggestion, SuggestionProviders}; use pumpkin_protocol::java::client::play::{ArgumentType, SuggestionProviders};
use crate::command::{ use crate::command::{
CommandSender, CommandSender,
args::{ args::{
Arg, ArgumentConsumer, ConsumedArgs, DefaultNameArgConsumer, FindArg, Arg, ArgumentConsumer, ConsumeResult, ConsumedArgs, DefaultNameArgConsumer, FindArg,
GetClientSideArgParser, GetClientSideArgParser,
}, },
dispatcher::CommandError, dispatcher::CommandError,
@ -29,27 +29,23 @@ impl GetClientSideArgParser for ParticleArgumentConsumer {
#[async_trait] #[async_trait]
impl ArgumentConsumer for ParticleArgumentConsumer { impl ArgumentConsumer for ParticleArgumentConsumer {
async fn consume<'a>( fn consume<'a, 'b>(
&'a self, &'a self,
_sender: &CommandSender, _sender: &'a CommandSender,
_server: &'a Server, _server: &'a Server,
args: &mut RawArgs<'a>, args: &'b mut RawArgs<'a>,
) -> Option<Arg<'a>> { ) -> ConsumeResult<'a> {
let name = args.pop()?; let name_opt: Option<&'a str> = args.pop();
// Create a static damage type first let result: Option<Arg<'a>> = name_opt.map_or_else(
let particle = Particle::from_name(name.strip_prefix("minecraft:").unwrap_or(name))?; || None,
// Find matching static damage type from values array |name| {
Some(Arg::Particle(particle)) Particle::from_name(name.strip_prefix("minecraft:").unwrap_or(name))
} .map(Arg::Particle)
},
);
async fn suggest<'a>( Box::pin(async move { result })
&'a self,
_sender: &CommandSender,
_server: &'a Server,
_input: &'a str,
) -> Result<Option<Vec<CommandSuggestion>>, CommandError> {
Ok(None)
} }
} }

View File

@ -1,12 +1,11 @@
use crate::command::CommandSender; use crate::command::CommandSender;
use crate::command::args::{ use crate::command::args::{
Arg, ArgumentConsumer, DefaultNameArgConsumer, FindArg, GetClientSideArgParser, Arg, ArgumentConsumer, ConsumeResult, DefaultNameArgConsumer, FindArg, GetClientSideArgParser,
}; };
use crate::command::dispatcher::CommandError; use crate::command::dispatcher::CommandError;
use crate::command::tree::RawArgs; use crate::command::tree::RawArgs;
use crate::server::Server; use crate::server::Server;
use async_trait::async_trait; use pumpkin_protocol::java::client::play::{ArgumentType, SuggestionProviders};
use pumpkin_protocol::java::client::play::{ArgumentType, CommandSuggestion, SuggestionProviders};
pub struct ResourceLocationArgumentConsumer { pub struct ResourceLocationArgumentConsumer {
autocomplete: bool, autocomplete: bool,
@ -22,39 +21,43 @@ impl GetClientSideArgParser for ResourceLocationArgumentConsumer {
} }
} }
#[async_trait]
impl ArgumentConsumer for ResourceLocationArgumentConsumer { impl ArgumentConsumer for ResourceLocationArgumentConsumer {
async fn consume<'a>( fn consume<'a, 'b>(
&'a self, &'a self,
_sender: &CommandSender, _sender: &'a CommandSender,
_server: &'a Server, _server: &'a Server,
args: &mut RawArgs<'a>, args: &'b mut RawArgs<'a>,
) -> Option<Arg<'a>> { ) -> ConsumeResult<'a> {
Some(Arg::ResourceLocation(args.pop()?))
}
async fn suggest<'a>(
&'a self,
_sender: &CommandSender,
_server: &'a Server,
_input: &'a str,
) -> Result<Option<Vec<CommandSuggestion>>, CommandError> {
if !self.autocomplete { if !self.autocomplete {
return Ok(None); return Box::pin(async move { None });
} }
// TODO let s_opt: Option<&'a str> = args.pop();
// let suggestions = server Box::pin(async move { s_opt.map(Arg::ResourceLocation) })
// .bossbars
// .lock()
// .await
// .custom_bossbars
// .keys()
// .map(|suggestion| CommandSuggestion::new(suggestion, None))
// .collect();
Ok(None)
} }
// async fn suggest<'a>(
// &'a self,
// _sender: &CommandSender,
// _server: &'a Server,
// _input: &'a str,
// ) -> Result<Option<Vec<CommandSuggestion>>, CommandError> {
// if !self.autocomplete {
// return Ok(None);
// }
// // TODO
// // let suggestions = server
// // .bossbars
// // .lock()
// // .await
// // .custom_bossbars
// // .keys()
// // .map(|suggestion| CommandSuggestion::new(suggestion, None))
// // .collect();
// Ok(None)
// }
} }
impl DefaultNameArgConsumer for ResourceLocationArgumentConsumer { impl DefaultNameArgConsumer for ResourceLocationArgumentConsumer {

View File

@ -1,7 +1,7 @@
use async_trait::async_trait; use pumpkin_protocol::java::client::play::{ArgumentType, SuggestionProviders};
use pumpkin_protocol::java::client::play::{ArgumentType, CommandSuggestion, SuggestionProviders};
use crate::command::CommandSender; use crate::command::CommandSender;
use crate::command::args::ConsumeResult;
use crate::command::dispatcher::CommandError; use crate::command::dispatcher::CommandError;
use crate::command::tree::RawArgs; use crate::command::tree::RawArgs;
use crate::server::Server; use crate::server::Server;
@ -22,39 +22,36 @@ impl GetClientSideArgParser for RotationArgumentConsumer {
} }
} }
#[async_trait]
impl ArgumentConsumer for RotationArgumentConsumer { impl ArgumentConsumer for RotationArgumentConsumer {
async fn consume<'a>( fn consume<'a, 'b>(
&'a self, &'a self,
_src: &CommandSender, _sender: &'a CommandSender,
_server: &'a Server, _server: &'a Server,
args: &mut RawArgs<'a>, args: &'b mut RawArgs<'a>,
) -> Option<Arg<'a>> { ) -> ConsumeResult<'a> {
let yaw = args.pop()?; let yaw_str_opt = args.pop();
let pitch = args.pop()?; let pitch_str_opt = args.pop();
let mut yaw = yaw.parse::<f32>().ok()?; let (Some(yaw_str), Some(pitch_str)) = (yaw_str_opt, pitch_str_opt) else {
let mut pitch = pitch.parse::<f32>().ok()?; return Box::pin(async move { None });
};
yaw %= 360.0; let result: Option<Arg<'a>> = yaw_str.parse::<f32>().ok().and_then(|mut yaw| {
if yaw >= 180.0 { pitch_str.parse::<f32>().ok().map(|mut pitch| {
yaw -= 360.0; yaw %= 360.0;
} if yaw >= 180.0 {
pitch %= 360.0; yaw -= 360.0;
if pitch >= 180.0 { }
pitch -= 360.0; pitch %= 360.0;
} if pitch >= 180.0 {
pitch -= 360.0;
}
Some(Arg::Rotation(yaw, pitch)) Arg::Rotation(yaw, pitch)
} })
});
async fn suggest<'a>( Box::pin(async move { result })
&'a self,
_sender: &CommandSender,
_server: &'a Server,
_input: &'a str,
) -> Result<Option<Vec<CommandSuggestion>>, CommandError> {
Ok(None)
} }
} }

View File

@ -1,9 +1,11 @@
use async_trait::async_trait;
use pumpkin_protocol::java::client::play::{ use pumpkin_protocol::java::client::play::{
ArgumentType, CommandSuggestion, StringProtoArgBehavior, SuggestionProviders, ArgumentType, StringProtoArgBehavior, SuggestionProviders,
}; };
use crate::{command::dispatcher::CommandError, server::Server}; use crate::{
command::{args::ConsumeResult, dispatcher::CommandError},
server::Server,
};
use super::{ use super::{
super::{ super::{
@ -27,24 +29,16 @@ impl GetClientSideArgParser for SimpleArgConsumer {
} }
} }
#[async_trait]
impl ArgumentConsumer for SimpleArgConsumer { impl ArgumentConsumer for SimpleArgConsumer {
async fn consume<'a>( fn consume<'a, 'b>(
&'a self, &'a self,
_sender: &CommandSender, _sender: &'a CommandSender,
_server: &'a Server, _server: &'a Server,
args: &mut RawArgs<'a>, args: &'b mut RawArgs<'a>,
) -> Option<Arg<'a>> { ) -> ConsumeResult<'a> {
Some(Arg::Simple(args.pop()?)) let s_opt: Option<&'a str> = args.pop();
}
async fn suggest<'a>( Box::pin(async move { s_opt.map(Arg::Simple) })
&'a self,
_sender: &CommandSender,
_server: &'a Server,
_input: &'a str,
) -> Result<Option<Vec<CommandSuggestion>>, CommandError> {
Ok(None)
} }
} }

View File

@ -1,9 +1,11 @@
use async_trait::async_trait;
use pumpkin_data::sound::Sound; use pumpkin_data::sound::Sound;
use pumpkin_protocol::java::client::play::{ArgumentType, CommandSuggestion, SuggestionProviders}; use pumpkin_protocol::java::client::play::{ArgumentType, SuggestionProviders};
use pumpkin_util::text::TextComponent; use pumpkin_util::text::TextComponent;
use crate::{command::dispatcher::CommandError, server::Server}; use crate::{
command::{args::ConsumeResult, dispatcher::CommandError},
server::Server,
};
use super::{ use super::{
super::{ super::{
@ -25,25 +27,16 @@ impl GetClientSideArgParser for SoundArgumentConsumer {
} }
} }
#[async_trait]
impl ArgumentConsumer for SoundArgumentConsumer { impl ArgumentConsumer for SoundArgumentConsumer {
async fn consume<'a>( fn consume<'a, 'b>(
&'a self, &'a self,
_sender: &CommandSender, _sender: &'a CommandSender,
_server: &'a Server, _server: &'a Server,
args: &mut RawArgs<'a>, args: &'b mut RawArgs<'a>,
) -> Option<Arg<'a>> { ) -> ConsumeResult<'a> {
let s = args.pop()?; let s_opt: Option<&'a str> = args.pop();
Some(Arg::Block(s))
}
async fn suggest<'a>( Box::pin(async move { s_opt.map(Arg::Block) })
&'a self,
_sender: &CommandSender,
_server: &'a Server,
_input: &'a str,
) -> Result<Option<Vec<CommandSuggestion>>, CommandError> {
Ok(None)
} }
} }

View File

@ -1,11 +1,11 @@
use crate::command::CommandSender; use crate::command::CommandSender;
use crate::command::args::{ use crate::command::args::{
Arg, ArgumentConsumer, DefaultNameArgConsumer, FindArg, GetClientSideArgParser, Arg, ArgumentConsumer, ConsumeResult, DefaultNameArgConsumer, FindArg, GetClientSideArgParser,
SuggestResult,
}; };
use crate::command::dispatcher::CommandError; use crate::command::dispatcher::CommandError;
use crate::command::tree::RawArgs; use crate::command::tree::RawArgs;
use crate::server::Server; use crate::server::Server;
use async_trait::async_trait;
use pumpkin_data::sound::SoundCategory; use pumpkin_data::sound::SoundCategory;
use pumpkin_protocol::java::client::play::{ArgumentType, CommandSuggestion, SuggestionProviders}; use pumpkin_protocol::java::client::play::{ArgumentType, CommandSuggestion, SuggestionProviders};
@ -24,51 +24,54 @@ impl GetClientSideArgParser for SoundCategoryArgumentConsumer {
} }
} }
#[async_trait]
impl ArgumentConsumer for SoundCategoryArgumentConsumer { impl ArgumentConsumer for SoundCategoryArgumentConsumer {
async fn consume<'a>( fn consume<'a, 'b>(
&'a self, &'a self,
_sender: &CommandSender, _sender: &'a CommandSender,
_server: &'a Server, _server: &'a Server,
args: &mut RawArgs<'a>, args: &'b mut RawArgs<'a>,
) -> Option<Arg<'a>> { ) -> ConsumeResult<'a> {
let s = args.pop()?; let s_opt: Option<&'a str> = args.pop();
// Convert string input to SoundCategory enum let result: Option<Arg<'a>> = s_opt.and_then(|s| {
// Uses lowercase to make the command case-insensitive let category = match s.to_lowercase().as_str() {
let category = match s.to_lowercase().as_str() { "master" => Some(SoundCategory::Master),
"master" => Some(SoundCategory::Master), // Default category, affects all sounds "music" => Some(SoundCategory::Music),
"music" => Some(SoundCategory::Music), // Background music // i don't use SoundCategory::from_name because its is record and not records :c
// i don't use SoundCategory::from_name because its is record and not records :c "record" => Some(SoundCategory::Records),
"record" => Some(SoundCategory::Records), // Music discs "weather" => Some(SoundCategory::Weather),
"weather" => Some(SoundCategory::Weather), // Rain, thunder "block" => Some(SoundCategory::Blocks),
"block" => Some(SoundCategory::Blocks), // Block sounds "hostile" => Some(SoundCategory::Hostile),
"hostile" => Some(SoundCategory::Hostile), // Hostile mob sounds "neutral" => Some(SoundCategory::Neutral),
"neutral" => Some(SoundCategory::Neutral), // Neutral mob sounds "player" => Some(SoundCategory::Players),
"player" => Some(SoundCategory::Players), // Player sounds "ambient" => Some(SoundCategory::Ambient),
"ambient" => Some(SoundCategory::Ambient), // Ambient environment "voice" => Some(SoundCategory::Voice),
"voice" => Some(SoundCategory::Voice), // Voice/speech _ => None,
_ => None, };
};
category.map(Arg::SoundCategory) // Simplified by removing redundant closure category.map(Arg::SoundCategory)
});
Box::pin(async move { result })
} }
async fn suggest<'a>( fn suggest<'a>(
&'a self, &'a self,
_sender: &CommandSender, _sender: &CommandSender,
_server: &'a Server, _server: &'a Server,
_input: &'a str, _input: &'a str,
) -> Result<Option<Vec<CommandSuggestion>>, CommandError> { ) -> SuggestResult<'a> {
let categories = [ Box::pin(async move {
"master", "music", "record", "weather", "block", "hostile", "neutral", "player", let categories = [
"ambient", "voice", "master", "music", "record", "weather", "block", "hostile", "neutral", "player",
]; "ambient", "voice",
let suggestions: Vec<CommandSuggestion> = categories ];
.iter() let suggestions: Vec<CommandSuggestion> = categories
.map(|cat| CommandSuggestion::new((*cat).to_string(), None)) .iter()
.collect(); .map(|cat| CommandSuggestion::new((*cat).to_string(), None))
Ok(Some(suggestions)) .collect();
Ok(Some(suggestions))
})
} }
} }

View File

@ -1,9 +1,12 @@
use async_trait::async_trait; use async_trait::async_trait;
use pumpkin_data::entity::EntityType; use pumpkin_data::entity::EntityType;
use pumpkin_protocol::java::client::play::{ArgumentType, CommandSuggestion, SuggestionProviders}; use pumpkin_protocol::java::client::play::{ArgumentType, SuggestionProviders};
use pumpkin_util::text::TextComponent; use pumpkin_util::text::TextComponent;
use crate::{command::dispatcher::CommandError, server::Server}; use crate::{
command::{args::ConsumeResult, dispatcher::CommandError},
server::Server,
};
use super::{ use super::{
super::{ super::{
@ -27,23 +30,15 @@ impl GetClientSideArgParser for SummonableEntitiesArgumentConsumer {
#[async_trait] #[async_trait]
impl ArgumentConsumer for SummonableEntitiesArgumentConsumer { impl ArgumentConsumer for SummonableEntitiesArgumentConsumer {
async fn consume<'a>( fn consume<'a, 'b>(
&'a self, &'a self,
_sender: &CommandSender, _sender: &'a CommandSender,
_server: &'a Server, _server: &'a Server,
args: &mut RawArgs<'a>, args: &'b mut RawArgs<'a>,
) -> Option<Arg<'a>> { ) -> ConsumeResult<'a> {
let s = args.pop()?; let s_opt: Option<&'a str> = args.pop();
Some(Arg::Block(s))
}
async fn suggest<'a>( Box::pin(async move { s_opt.map(Arg::Block) })
&'a self,
_sender: &CommandSender,
_server: &'a Server,
_input: &'a str,
) -> Result<Option<Vec<CommandSuggestion>>, CommandError> {
Ok(None)
} }
} }

View File

@ -1,10 +1,9 @@
use crate::command::CommandSender; use crate::command::CommandSender;
use crate::command::args::{Arg, ArgumentConsumer, FindArg, GetClientSideArgParser}; use crate::command::args::{Arg, ArgumentConsumer, ConsumeResult, FindArg, GetClientSideArgParser};
use crate::command::dispatcher::CommandError; use crate::command::dispatcher::CommandError;
use crate::command::tree::RawArgs; use crate::command::tree::RawArgs;
use crate::server::Server; use crate::server::Server;
use async_trait::async_trait; use pumpkin_protocol::java::client::play::{ArgumentType, SuggestionProviders};
use pumpkin_protocol::java::client::play::{ArgumentType, CommandSuggestion, SuggestionProviders};
use pumpkin_util::text::TextComponent; use pumpkin_util::text::TextComponent;
pub struct TextComponentArgConsumer; pub struct TextComponentArgConsumer;
@ -19,36 +18,32 @@ impl GetClientSideArgParser for TextComponentArgConsumer {
} }
} }
#[async_trait]
impl ArgumentConsumer for TextComponentArgConsumer { impl ArgumentConsumer for TextComponentArgConsumer {
async fn consume<'a>( fn consume<'a, 'b>(
&'a self, &'a self,
_sender: &CommandSender, _sender: &'a CommandSender,
_server: &'a Server, _server: &'a Server,
args: &mut RawArgs<'a>, args: &'b mut RawArgs<'a>,
) -> Option<Arg<'a>> { ) -> ConsumeResult<'a> {
let s = args.pop()?; let s_opt: Option<&'a str> = args.pop();
let text_component = parse_text_component(s); let Some(s) = s_opt else {
return Box::pin(async move { None });
let Some(text_component) = text_component else {
if s.starts_with('"') && s.ends_with('"') {
let s = s.replace('"', "");
return Some(Arg::TextComponent(TextComponent::text(s)));
}
return None;
}; };
Some(Arg::TextComponent(text_component)) let text_component_opt = parse_text_component(s);
}
async fn suggest<'a>( let final_arg: Option<Arg<'a>> = text_component_opt.map_or_else(
&'a self, || {
_sender: &CommandSender, (s.starts_with('"') && s.ends_with('"')).then(|| {
_server: &'a Server, let s_owned = s.replace('"', "");
_input: &'a str, Arg::TextComponent(TextComponent::text(s_owned))
) -> Result<Option<Vec<CommandSuggestion>>, CommandError> { })
Ok(None) },
|text_component| Some(Arg::TextComponent(text_component)),
);
Box::pin(async move { final_arg })
} }
} }

View File

@ -1,9 +1,11 @@
use async_trait::async_trait; use pumpkin_protocol::java::client::play::{ArgumentType, SuggestionProviders};
use pumpkin_protocol::java::client::play::{ArgumentType, CommandSuggestion, SuggestionProviders};
use crate::command::{ use crate::command::{
CommandSender, CommandSender,
args::{Arg, ArgumentConsumer, DefaultNameArgConsumer, FindArg, GetClientSideArgParser}, args::{
Arg, ArgumentConsumer, ConsumeResult, DefaultNameArgConsumer, FindArg,
GetClientSideArgParser,
},
dispatcher::CommandError, dispatcher::CommandError,
tree::RawArgs, tree::RawArgs,
}; };
@ -21,47 +23,39 @@ impl GetClientSideArgParser for TimeArgumentConsumer {
} }
} }
#[async_trait]
impl ArgumentConsumer for TimeArgumentConsumer { impl ArgumentConsumer for TimeArgumentConsumer {
async fn consume<'a>( fn consume<'a, 'b>(
&'a self, &'a self,
_sender: &CommandSender, _sender: &'a CommandSender,
_server: &'a Server, _server: &'a Server,
args: &mut RawArgs<'a>, args: &'b mut RawArgs<'a>,
) -> Option<Arg<'a>> { ) -> ConsumeResult<'a> {
let s = args.pop()?; let s_opt: Option<&'a str> = args.pop();
// Parse number and unit let result: Option<Arg<'a>> = s_opt.and_then(|s| {
let (num_str, unit) = s let (num_str, unit) = s
.find(|c: char| c.is_alphabetic()) .find(|c: char| c.is_alphabetic())
.map_or((s, "t"), |pos| (&s[..pos], &s[pos..])); .map_or((s, "t"), |pos| (&s[..pos], &s[pos..]));
let number = num_str.parse::<f32>().ok()?; let number = num_str.parse::<f32>().ok()?; // Replaces .ok()?
if number < 0.0 {
return None;
}
// Convert to ticks based on unit if number < 0.0 {
let ticks = match unit { return None;
"d" => number * 24000.0, }
"s" => number * 20.0,
"t" => number,
_ => return None,
};
// Round to nearest integer let ticks = match unit {
let ticks = ticks.round() as i32; "d" => number * 24000.0,
"s" => number * 20.0,
"t" => number,
_ => return None,
};
Some(Arg::Time(ticks)) let ticks = ticks.round() as i32;
}
async fn suggest<'a>( Some(Arg::Time(ticks))
&'a self, });
_sender: &CommandSender,
_server: &'a Server, Box::pin(async move { result })
_input: &'a str,
) -> Result<Option<Vec<CommandSuggestion>>, CommandError> {
Ok(None)
} }
} }

View File

@ -1,3 +1,4 @@
use crate::command::CommandResult;
use crate::entity::EntityBase; use crate::entity::EntityBase;
use crate::{ use crate::{
command::{ command::{
@ -13,7 +14,6 @@ use crate::{
net::DisconnectReason, net::DisconnectReason,
}; };
use CommandError::InvalidConsumption; use CommandError::InvalidConsumption;
use async_trait::async_trait;
use pumpkin_util::text::TextComponent; use pumpkin_util::text::TextComponent;
const NAMES: [&str; 1] = ["ban"]; const NAMES: [&str; 1] = ["ban"];
@ -24,43 +24,45 @@ const ARG_REASON: &str = "reason";
struct NoReasonExecutor; struct NoReasonExecutor;
#[async_trait]
impl CommandExecutor for NoReasonExecutor { impl CommandExecutor for NoReasonExecutor {
async fn execute<'a>( fn execute<'a>(
&self, &'a self,
sender: &mut CommandSender, sender: &'a CommandSender,
_server: &crate::server::Server, _server: &'a crate::server::Server,
args: &ConsumedArgs<'a>, args: &'a ConsumedArgs<'a>,
) -> Result<(), CommandError> { ) -> CommandResult<'a> {
let Some(Arg::Players(targets)) = args.get(&ARG_TARGET) else { Box::pin(async move {
return Err(InvalidConsumption(Some(ARG_TARGET.into()))); let Some(Arg::Players(targets)) = args.get(&ARG_TARGET) else {
}; return Err(InvalidConsumption(Some(ARG_TARGET.into())));
};
ban_player(sender, &targets[0], None).await; ban_player(sender, &targets[0], None).await;
Ok(()) Ok(())
})
} }
} }
struct ReasonExecutor; struct ReasonExecutor;
#[async_trait]
impl CommandExecutor for ReasonExecutor { impl CommandExecutor for ReasonExecutor {
async fn execute<'a>( fn execute<'a>(
&self, &'a self,
sender: &mut CommandSender, sender: &'a CommandSender,
_server: &crate::server::Server, _server: &'a crate::server::Server,
args: &ConsumedArgs<'a>, args: &'a ConsumedArgs<'a>,
) -> Result<(), CommandError> { ) -> CommandResult<'a> {
let Some(Arg::Players(targets)) = args.get(&ARG_TARGET) else { Box::pin(async move {
return Err(InvalidConsumption(Some(ARG_TARGET.into()))); let Some(Arg::Players(targets)) = args.get(&ARG_TARGET) else {
}; return Err(InvalidConsumption(Some(ARG_TARGET.into())));
};
let Some(Arg::Msg(reason)) = args.get(ARG_REASON) else { let Some(Arg::Msg(reason)) = args.get(ARG_REASON) else {
return Err(InvalidConsumption(Some(ARG_REASON.into()))); return Err(InvalidConsumption(Some(ARG_REASON.into())));
}; };
ban_player(sender, &targets[0], Some(reason.clone())).await; ban_player(sender, &targets[0], Some(reason.clone())).await;
Ok(()) Ok(())
})
} }
} }

View File

@ -2,7 +2,7 @@ use std::{net::IpAddr, str::FromStr};
use crate::{ use crate::{
command::{ command::{
CommandError, CommandExecutor, CommandSender, CommandError, CommandExecutor, CommandResult, CommandSender,
args::{Arg, ConsumedArgs, message::MsgArgConsumer, simple::SimpleArgConsumer}, args::{Arg, ConsumedArgs, message::MsgArgConsumer, simple::SimpleArgConsumer},
tree::{CommandTree, builder::argument}, tree::{CommandTree, builder::argument},
}, },
@ -13,7 +13,6 @@ use crate::{
server::Server, server::Server,
}; };
use CommandError::InvalidConsumption; use CommandError::InvalidConsumption;
use async_trait::async_trait;
use pumpkin_util::text::TextComponent; use pumpkin_util::text::TextComponent;
const NAMES: [&str; 1] = ["ban-ip"]; const NAMES: [&str; 1] = ["ban-ip"];
@ -37,43 +36,45 @@ async fn parse_ip(target: &str, server: &Server) -> Option<IpAddr> {
struct NoReasonExecutor; struct NoReasonExecutor;
#[async_trait]
impl CommandExecutor for NoReasonExecutor { impl CommandExecutor for NoReasonExecutor {
async fn execute<'a>( fn execute<'a>(
&self, &'a self,
sender: &mut CommandSender, sender: &'a CommandSender,
server: &crate::server::Server, server: &'a crate::server::Server,
args: &ConsumedArgs<'a>, args: &'a ConsumedArgs<'a>,
) -> Result<(), CommandError> { ) -> CommandResult<'a> {
let Some(Arg::Simple(target)) = args.get(&ARG_TARGET) else { Box::pin(async move {
return Err(InvalidConsumption(Some(ARG_TARGET.into()))); let Some(Arg::Simple(target)) = args.get(&ARG_TARGET) else {
}; return Err(InvalidConsumption(Some(ARG_TARGET.into())));
};
ban_ip(sender, server, target, None).await; ban_ip(sender, server, target, None).await;
Ok(()) Ok(())
})
} }
} }
struct ReasonExecutor; struct ReasonExecutor;
#[async_trait]
impl CommandExecutor for ReasonExecutor { impl CommandExecutor for ReasonExecutor {
async fn execute<'a>( fn execute<'a>(
&self, &'a self,
sender: &mut CommandSender, sender: &'a CommandSender,
server: &crate::server::Server, server: &'a crate::server::Server,
args: &ConsumedArgs<'a>, args: &'a ConsumedArgs<'a>,
) -> Result<(), CommandError> { ) -> CommandResult<'a> {
let Some(Arg::Simple(target)) = args.get(&ARG_TARGET) else { Box::pin(async move {
return Err(InvalidConsumption(Some(ARG_TARGET.into()))); let Some(Arg::Simple(target)) = args.get(&ARG_TARGET) else {
}; return Err(InvalidConsumption(Some(ARG_TARGET.into())));
};
let Some(Arg::Msg(reason)) = args.get(ARG_REASON) else { let Some(Arg::Msg(reason)) = args.get(ARG_REASON) else {
return Err(InvalidConsumption(Some(ARG_REASON.into()))); return Err(InvalidConsumption(Some(ARG_REASON.into())));
}; };
ban_ip(sender, server, target, Some(reason.clone())).await; ban_ip(sender, server, target, Some(reason.clone())).await;
Ok(()) Ok(())
})
} }
} }

View File

@ -1,14 +1,12 @@
use crate::{ use crate::{
command::{ command::{
CommandError, CommandExecutor, CommandSender, CommandError, CommandExecutor, CommandResult, CommandSender,
args::{Arg, ConsumedArgs, simple::SimpleArgConsumer}, args::{Arg, ConsumedArgs, simple::SimpleArgConsumer},
tree::CommandTree, tree::{CommandTree, builder::argument},
tree::builder::argument,
}, },
data::{banned_ip_data::BANNED_IP_LIST, banned_player_data::BANNED_PLAYER_LIST}, data::{banned_ip_data::BANNED_IP_LIST, banned_player_data::BANNED_PLAYER_LIST},
}; };
use CommandError::InvalidConsumption; use CommandError::InvalidConsumption;
use async_trait::async_trait;
use pumpkin_util::text::TextComponent; use pumpkin_util::text::TextComponent;
const NAMES: [&str; 1] = ["banlist"]; const NAMES: [&str; 1] = ["banlist"];
@ -18,91 +16,93 @@ const ARG_LIST_TYPE: &str = "ips|players";
struct ListExecutor; struct ListExecutor;
#[async_trait]
impl CommandExecutor for ListExecutor { impl CommandExecutor for ListExecutor {
async fn execute<'a>( fn execute<'a>(
&self, &'a self,
sender: &mut CommandSender, sender: &'a CommandSender,
_server: &crate::server::Server, _server: &'a crate::server::Server,
args: &ConsumedArgs<'a>, args: &'a ConsumedArgs<'a>,
) -> Result<(), CommandError> { ) -> CommandResult<'a> {
let Some(Arg::Simple(list_type)) = args.get(&ARG_LIST_TYPE) else { Box::pin(async move {
return Err(InvalidConsumption(Some(ARG_LIST_TYPE.into()))); let Some(Arg::Simple(list_type)) = args.get(&ARG_LIST_TYPE) else {
}; return Err(InvalidConsumption(Some(ARG_LIST_TYPE.into())));
};
match *list_type { match *list_type {
"ips" => { "ips" => {
let lock = &BANNED_IP_LIST.read().await; let lock = &BANNED_IP_LIST.read().await;
let entries = lock let entries = lock
.banned_ips .banned_ips
.iter() .iter()
.map(|entry| { .map(|entry| {
( (
entry.ip.to_string(), entry.ip.to_string(),
entry.source.clone(), entry.source.clone(),
entry.reason.clone(), entry.reason.clone(),
) )
}) })
.collect(); .collect();
handle_banlist(entries, sender).await; handle_banlist(entries, sender).await;
}
"players" => {
let lock = &BANNED_PLAYER_LIST.read().await;
let entries = lock
.banned_players
.iter()
.map(|entry| {
(
entry.name.clone(),
entry.source.clone(),
entry.reason.clone(),
)
})
.collect();
handle_banlist(entries, sender).await;
}
_ => {
return Err(CommandError::CommandFailed(Box::new(TextComponent::text(
"Incorrect argument for command".to_string(),
))));
}
} }
"players" => {
let lock = &BANNED_PLAYER_LIST.read().await;
let entries = lock
.banned_players
.iter()
.map(|entry| {
(
entry.name.clone(),
entry.source.clone(),
entry.reason.clone(),
)
})
.collect();
handle_banlist(entries, sender).await; Ok(())
} })
_ => {
return Err(CommandError::CommandFailed(Box::new(TextComponent::text(
"Incorrect argument for command".to_string(),
))));
}
}
Ok(())
} }
} }
struct ListAllExecutor; struct ListAllExecutor;
#[async_trait]
impl CommandExecutor for ListAllExecutor { impl CommandExecutor for ListAllExecutor {
async fn execute<'a>( fn execute<'a>(
&self, &'a self,
sender: &mut CommandSender, sender: &'a CommandSender,
_server: &crate::server::Server, _server: &'a crate::server::Server,
_args: &ConsumedArgs<'a>, _args: &'a ConsumedArgs<'a>,
) -> Result<(), CommandError> { ) -> CommandResult<'a> {
let mut entries = Vec::new(); Box::pin(async move {
for entry in &BANNED_PLAYER_LIST.read().await.banned_players { let mut entries = Vec::new();
entries.push(( for entry in &BANNED_PLAYER_LIST.read().await.banned_players {
entry.name.clone(), entries.push((
entry.source.clone(), entry.name.clone(),
entry.reason.clone(), entry.source.clone(),
)); entry.reason.clone(),
} ));
}
for entry in &BANNED_IP_LIST.read().await.banned_ips { for entry in &BANNED_IP_LIST.read().await.banned_ips {
entries.push(( entries.push((
entry.ip.to_string(), entry.ip.to_string(),
entry.source.clone(), entry.source.clone(),
entry.reason.clone(), entry.reason.clone(),
)); ));
} }
handle_banlist(entries, sender).await; handle_banlist(entries, sender).await;
Ok(()) Ok(())
})
} }
} }

View File

@ -6,16 +6,13 @@ use crate::command::args::players::PlayersArgumentConsumer;
use crate::command::args::resource_location::ResourceLocationArgumentConsumer; use crate::command::args::resource_location::ResourceLocationArgumentConsumer;
use crate::command::args::{ConsumedArgs, FindArg, FindArgDefaultName}; use crate::command::args::{ConsumedArgs, FindArg, FindArgDefaultName};
use crate::command::dispatcher::CommandError;
use crate::command::args::textcomponent::TextComponentArgConsumer; use crate::command::args::textcomponent::TextComponentArgConsumer;
use crate::command::tree::CommandTree; use crate::command::tree::CommandTree;
use crate::command::tree::builder::{argument, argument_default_name, literal}; use crate::command::tree::builder::{argument, argument_default_name, literal};
use crate::command::{CommandExecutor, CommandSender}; use crate::command::{CommandExecutor, CommandResult, CommandSender};
use crate::server::Server;
use crate::world::bossbar::Bossbar; use crate::world::bossbar::Bossbar;
use crate::world::custom_bossbar::BossbarUpdateError; use crate::world::custom_bossbar::BossbarUpdateError;
use async_trait::async_trait;
use pumpkin_util::text::TextComponent; use pumpkin_util::text::TextComponent;
use pumpkin_util::text::color::{Color, NamedColor}; use pumpkin_util::text::color::{Color, NamedColor};
use pumpkin_util::text::hover::HoverEvent; use pumpkin_util::text::hover::HoverEvent;
@ -55,361 +52,276 @@ enum CommandValueSet {
struct AddExecuter; struct AddExecuter;
#[async_trait]
impl CommandExecutor for AddExecuter { impl CommandExecutor for AddExecuter {
async fn execute<'a>( fn execute<'a>(
&self, &'a self,
sender: &mut CommandSender, sender: &'a CommandSender,
server: &Server, server: &'a crate::server::Server,
args: &ConsumedArgs<'a>, args: &'a ConsumedArgs<'a>,
) -> Result<(), CommandError> { ) -> CommandResult<'a> {
let mut namespace = non_autocomplete_consumer() Box::pin(async move {
.find_arg_default_name(args)? let mut namespace = non_autocomplete_consumer()
.to_string(); .find_arg_default_name(args)?
if !namespace.contains(':') { .to_string();
namespace = format!("minecraft:{namespace}"); if !namespace.contains(':') {
} namespace = format!("minecraft:{namespace}");
}
let text_component = TextComponentArgConsumer::find_arg(args, ARG_NAME)?; let text_component = TextComponentArgConsumer::find_arg(args, ARG_NAME)?;
if server.bossbars.lock().await.has_bossbar(&namespace) { if server.bossbars.lock().await.has_bossbar(&namespace) {
send_error_message( send_error_message(
sender, sender,
TextComponent::translate( TextComponent::translate(
"commands.bossbar.create.failed", "commands.bossbar.create.failed",
[TextComponent::text(namespace.clone())], [TextComponent::text(namespace.clone())],
), ),
) )
.await; .await;
return Ok(()); return Ok(());
} }
let bossbar = Bossbar::new(text_component); let bossbar = Bossbar::new(text_component);
server server
.bossbars .bossbars
.lock() .lock()
.await .await
.create_bossbar(namespace.clone(), bossbar.clone()); .create_bossbar(namespace.clone(), bossbar.clone());
sender sender
.send_message(TextComponent::translate( .send_message(TextComponent::translate(
"commands.bossbar.create.success", "commands.bossbar.create.success",
[bossbar_prefix(bossbar.title.clone(), namespace.clone())], [bossbar_prefix(bossbar.title.clone(), namespace.clone())],
)) ))
.await; .await;
Ok(()) Ok(())
})
} }
} }
struct GetExecuter(CommandValueGet); struct GetExecuter(CommandValueGet);
#[async_trait]
impl CommandExecutor for GetExecuter { impl CommandExecutor for GetExecuter {
async fn execute<'a>( fn execute<'a>(
&self, &'a self,
sender: &mut CommandSender, sender: &'a CommandSender,
server: &Server, server: &'a crate::server::Server,
args: &ConsumedArgs<'a>, args: &'a ConsumedArgs<'a>,
) -> Result<(), CommandError> { ) -> CommandResult<'a> {
let namespace = autocomplete_consumer() Box::pin(async move {
.find_arg_default_name(args)? let namespace = autocomplete_consumer()
.to_string(); .find_arg_default_name(args)?
.to_string();
let Some(bossbar) = server.bossbars.lock().await.get_bossbar(&namespace) else { let Some(bossbar) = server.bossbars.lock().await.get_bossbar(&namespace) else {
handle_bossbar_error( handle_bossbar_error(
sender, sender,
BossbarUpdateError::InvalidResourceLocation(namespace.clone()), BossbarUpdateError::InvalidResourceLocation(namespace.clone()),
) )
.await; .await;
return Ok(()); return Ok(());
}; };
match self.0 { match self.0 {
CommandValueGet::Max => { CommandValueGet::Max => {
sender sender
.send_message(TextComponent::translate( .send_message(TextComponent::translate(
"commands.bossbar.get.max", "commands.bossbar.get.max",
[ [
bossbar_prefix(bossbar.bossbar_data.title.clone(), namespace.clone()), bossbar_prefix(
TextComponent::text(bossbar.max.to_string()), bossbar.bossbar_data.title.clone(),
], namespace.clone(),
)) ),
.await; TextComponent::text(bossbar.max.to_string()),
return Ok(()); ],
))
.await;
return Ok(());
}
CommandValueGet::Players => {}
CommandValueGet::Value => {
sender
.send_message(TextComponent::translate(
"commands.bossbar.get.value",
[
bossbar_prefix(
bossbar.bossbar_data.title.clone(),
namespace.clone(),
),
TextComponent::text(bossbar.value.to_string()),
],
))
.await;
return Ok(());
}
CommandValueGet::Visible => {
let state = if bossbar.visible {
"commands.bossbar.get.visible.visible"
} else {
"commands.bossbar.get.visible.hidden"
};
sender
.send_message(TextComponent::translate(
state,
[bossbar_prefix(
bossbar.bossbar_data.title.clone(),
namespace.clone(),
)],
))
.await;
return Ok(());
}
} }
CommandValueGet::Players => {}
CommandValueGet::Value => {
sender
.send_message(TextComponent::translate(
"commands.bossbar.get.value",
[
bossbar_prefix(bossbar.bossbar_data.title.clone(), namespace.clone()),
TextComponent::text(bossbar.value.to_string()),
],
))
.await;
return Ok(());
}
CommandValueGet::Visible => {
let state = if bossbar.visible {
"commands.bossbar.get.visible.visible"
} else {
"commands.bossbar.get.visible.hidden"
};
sender
.send_message(TextComponent::translate(
state,
[bossbar_prefix(
bossbar.bossbar_data.title.clone(),
namespace.clone(),
)],
))
.await;
return Ok(());
}
}
Ok(()) Ok(())
})
} }
} }
struct ListExecuter; struct ListExecuter;
#[async_trait]
impl CommandExecutor for ListExecuter { impl CommandExecutor for ListExecuter {
async fn execute<'a>( fn execute<'a>(
&self, &'a self,
sender: &mut CommandSender, sender: &'a CommandSender,
server: &Server, server: &'a crate::server::Server,
_args: &ConsumedArgs<'a>, _args: &'a ConsumedArgs<'a>,
) -> Result<(), CommandError> { ) -> CommandResult<'a> {
let bossbars = server.bossbars.lock().await.get_all_bossbars(); Box::pin(async move {
let Some(bossbars) = bossbars else { let bossbars = server.bossbars.lock().await.get_all_bossbars();
sender let Some(bossbars) = bossbars else {
.send_message(TextComponent::translate( sender
"commands.bossbar.list.bars.none", .send_message(TextComponent::translate(
[], "commands.bossbar.list.bars.none",
)) [],
.await; ))
return Ok(()); .await;
}; return Ok(());
if bossbars.is_empty() { };
sender if bossbars.is_empty() {
.send_message(TextComponent::translate( sender
"commands.bossbar.list.bars.none", .send_message(TextComponent::translate(
[], "commands.bossbar.list.bars.none",
)) [],
.await; ))
return Ok(()); .await;
} return Ok(());
}
let mut bossbars_text = TextComponent::text(""); let mut bossbars_text = TextComponent::text("");
for (i, bossbar) in bossbars.iter().enumerate() { for (i, bossbar) in bossbars.iter().enumerate() {
if i == 0 { if i == 0 {
bossbars_text = bossbars_text.add_child(bossbar_prefix( bossbars_text = bossbars_text.add_child(bossbar_prefix(
bossbar.bossbar_data.title.clone(),
bossbar.namespace.clone(),
));
} else {
bossbars_text =
bossbars_text.add_child(TextComponent::text(", ").add_child(bossbar_prefix(
bossbar.bossbar_data.title.clone(), bossbar.bossbar_data.title.clone(),
bossbar.namespace.clone(), bossbar.namespace.clone(),
))); ));
} else {
bossbars_text = bossbars_text.add_child(TextComponent::text(", ").add_child(
bossbar_prefix(
bossbar.bossbar_data.title.clone(),
bossbar.namespace.clone(),
),
));
}
} }
}
sender sender
.send_message(TextComponent::translate( .send_message(TextComponent::translate(
"commands.bossbar.list.bars.some", "commands.bossbar.list.bars.some",
[ [
TextComponent::text(bossbars.len().to_string()), TextComponent::text(bossbars.len().to_string()),
bossbars_text, bossbars_text,
], ],
)) ))
.await; .await;
Ok(()) Ok(())
})
} }
} }
struct RemoveExecuter; struct RemoveExecuter;
#[async_trait]
impl CommandExecutor for RemoveExecuter { impl CommandExecutor for RemoveExecuter {
async fn execute<'a>( fn execute<'a>(
&self, &'a self,
sender: &mut CommandSender, sender: &'a CommandSender,
server: &Server, server: &'a crate::server::Server,
args: &ConsumedArgs<'a>, args: &'a ConsumedArgs<'a>,
) -> Result<(), CommandError> { ) -> CommandResult<'a> {
let namespace = autocomplete_consumer() Box::pin(async move {
.find_arg_default_name(args)? let namespace = autocomplete_consumer()
.to_string(); .find_arg_default_name(args)?
.to_string();
let Some(bossbar) = server.bossbars.lock().await.get_bossbar(&namespace) else { let Some(bossbar) = server.bossbars.lock().await.get_bossbar(&namespace) else {
handle_bossbar_error( handle_bossbar_error(
sender, sender,
BossbarUpdateError::InvalidResourceLocation(namespace), BossbarUpdateError::InvalidResourceLocation(namespace),
) )
.await; .await;
return Ok(());
};
sender
.send_message(TextComponent::translate(
"commands.bossbar.remove.success",
[bossbar_prefix(
bossbar.bossbar_data.title.clone(),
namespace.clone(),
)],
))
.await;
match server
.bossbars
.lock()
.await
.remove_bossbar(server, namespace.clone())
.await
{
Ok(()) => {}
Err(err) => {
handle_bossbar_error(sender, err).await;
return Ok(()); return Ok(());
} };
}
Ok(()) sender
.send_message(TextComponent::translate(
"commands.bossbar.remove.success",
[bossbar_prefix(
bossbar.bossbar_data.title.clone(),
namespace.clone(),
)],
))
.await;
match server
.bossbars
.lock()
.await
.remove_bossbar(server, namespace.clone())
.await
{
Ok(()) => {}
Err(err) => {
handle_bossbar_error(sender, err).await;
return Ok(());
}
}
Ok(())
})
} }
} }
struct SetExecuter(CommandValueSet); struct SetExecuter(CommandValueSet);
#[async_trait]
impl CommandExecutor for SetExecuter { impl CommandExecutor for SetExecuter {
#[expect(clippy::too_many_lines)] #[expect(clippy::too_many_lines)]
async fn execute<'a>( fn execute<'a>(
&self, &'a self,
sender: &mut CommandSender, sender: &'a CommandSender,
server: &Server, server: &'a crate::server::Server,
args: &ConsumedArgs<'a>, args: &'a ConsumedArgs<'a>,
) -> Result<(), CommandError> { ) -> CommandResult<'a> {
let namespace = autocomplete_consumer().find_arg_default_name(args)?; Box::pin(async move {
let namespace = autocomplete_consumer().find_arg_default_name(args)?;
let Some(bossbar) = server.bossbars.lock().await.get_bossbar(namespace) else { let Some(bossbar) = server.bossbars.lock().await.get_bossbar(namespace) else {
handle_bossbar_error( handle_bossbar_error(
sender, sender,
BossbarUpdateError::InvalidResourceLocation(namespace.to_string()), BossbarUpdateError::InvalidResourceLocation(namespace.to_string()),
) )
.await; .await;
return Ok(()); return Ok(());
}; };
match self.0 { match self.0 {
CommandValueSet::Color => { CommandValueSet::Color => {
let color = BossbarColorArgumentConsumer.find_arg_default_name(args)?; let color = BossbarColorArgumentConsumer.find_arg_default_name(args)?;
match server
.bossbars
.lock()
.await
.update_color(server, namespace.to_string(), color.clone())
.await
{
Ok(()) => {}
Err(err) => {
handle_bossbar_error(sender, err).await;
return Ok(());
}
}
sender
.send_message(TextComponent::translate(
"commands.bossbar.set.color.success",
[bossbar_prefix(
bossbar.bossbar_data.title.clone(),
namespace.to_string(),
)],
))
.await;
Ok(())
}
CommandValueSet::Max => {
let Ok(max_value) = max_value_consumer().find_arg_default_name(args)? else {
send_error_message(
sender,
TextComponent::translate(
"parsing.int.invalid",
[TextComponent::text(i32::MAX.to_string())],
),
)
.await;
return Ok(());
};
match server
.bossbars
.lock()
.await
.update_health(
server,
namespace.to_string(),
max_value as u32,
bossbar.value,
)
.await
{
Ok(()) => {}
Err(err) => {
handle_bossbar_error(sender, err).await;
return Ok(());
}
}
sender
.send_message(TextComponent::translate(
"commands.bossbar.set.max.success",
[
bossbar_prefix(
bossbar.bossbar_data.title.clone(),
namespace.to_string(),
),
TextComponent::text(max_value.to_string()),
],
))
.await;
Ok(())
}
CommandValueSet::Name => {
let text_component = TextComponentArgConsumer::find_arg(args, ARG_NAME)?;
match server
.bossbars
.lock()
.await
.update_name(server, namespace, text_component.clone())
.await
{
Ok(()) => {}
Err(err) => {
handle_bossbar_error(sender, err).await;
return Ok(());
}
}
sender
.send_message(TextComponent::translate(
"commands.bossbar.set.name.success",
[bossbar_prefix(text_component, namespace.to_string())],
))
.await;
Ok(())
}
CommandValueSet::Players(has_players) => {
if !has_players {
match server match server
.bossbars .bossbars
.lock() .lock()
.await .await
.update_players(server, namespace.to_string(), vec![]) .update_color(server, namespace.to_string(), color.clone())
.await .await
{ {
Ok(()) => {} Ok(()) => {}
@ -420,158 +332,255 @@ impl CommandExecutor for SetExecuter {
} }
sender sender
.send_message(TextComponent::translate( .send_message(TextComponent::translate(
"commands.bossbar.set.players.success.none", "commands.bossbar.set.color.success",
[bossbar_prefix( [bossbar_prefix(
bossbar.bossbar_data.title.clone(), bossbar.bossbar_data.title.clone(),
namespace.to_string(), namespace.to_string(),
)], )],
)) ))
.await; .await;
return Ok(()); Ok(())
} }
CommandValueSet::Max => {
let Ok(max_value) = max_value_consumer().find_arg_default_name(args)? else {
send_error_message(
sender,
TextComponent::translate(
"parsing.int.invalid",
[TextComponent::text(i32::MAX.to_string())],
),
)
.await;
return Ok(());
};
//TODO: Confirm that this is the vanilla way match server
let targets = PlayersArgumentConsumer.find_arg_default_name(args)?; .bossbars
let players: Vec<Uuid> = .lock()
targets.iter().map(|player| player.gameprofile.id).collect(); .await
let count = players.len(); .update_health(
server,
namespace.to_string(),
max_value as u32,
bossbar.value,
)
.await
{
Ok(()) => {}
Err(err) => {
handle_bossbar_error(sender, err).await;
return Ok(());
}
}
match server sender
.bossbars .send_message(TextComponent::translate(
.lock() "commands.bossbar.set.max.success",
.await [
.update_players(server, namespace.to_string(), players) bossbar_prefix(
.await bossbar.bossbar_data.title.clone(),
{ namespace.to_string(),
Ok(()) => {} ),
Err(err) => { TextComponent::text(max_value.to_string()),
handle_bossbar_error(sender, err).await; ],
))
.await;
Ok(())
}
CommandValueSet::Name => {
let text_component = TextComponentArgConsumer::find_arg(args, ARG_NAME)?;
match server
.bossbars
.lock()
.await
.update_name(server, namespace, text_component.clone())
.await
{
Ok(()) => {}
Err(err) => {
handle_bossbar_error(sender, err).await;
return Ok(());
}
}
sender
.send_message(TextComponent::translate(
"commands.bossbar.set.name.success",
[bossbar_prefix(text_component, namespace.to_string())],
))
.await;
Ok(())
}
CommandValueSet::Players(has_players) => {
if !has_players {
match server
.bossbars
.lock()
.await
.update_players(server, namespace.to_string(), vec![])
.await
{
Ok(()) => {}
Err(err) => {
handle_bossbar_error(sender, err).await;
return Ok(());
}
}
sender
.send_message(TextComponent::translate(
"commands.bossbar.set.players.success.none",
[bossbar_prefix(
bossbar.bossbar_data.title.clone(),
namespace.to_string(),
)],
))
.await;
return Ok(()); return Ok(());
} }
//TODO: Confirm that this is the vanilla way
let targets = PlayersArgumentConsumer.find_arg_default_name(args)?;
let players: Vec<Uuid> =
targets.iter().map(|player| player.gameprofile.id).collect();
let count = players.len();
match server
.bossbars
.lock()
.await
.update_players(server, namespace.to_string(), players)
.await
{
Ok(()) => {}
Err(err) => {
handle_bossbar_error(sender, err).await;
return Ok(());
}
}
let player_names: Vec<String> = targets
.iter()
.map(|player| player.gameprofile.name.clone())
.collect();
sender
.send_message(TextComponent::translate(
"commands.bossbar.set.players.success.some",
[
bossbar_prefix(
bossbar.bossbar_data.title.clone(),
namespace.to_string(),
),
TextComponent::text(count.to_string()),
TextComponent::text(player_names.join(", ").clone()),
],
))
.await;
Ok(())
} }
CommandValueSet::Style => {
let player_names: Vec<String> = targets let style = BossbarStyleArgumentConsumer.find_arg_default_name(args)?;
.iter() match server
.map(|player| player.gameprofile.name.clone()) .bossbars
.collect(); .lock()
.await
sender .update_division(server, namespace.to_string(), style.clone())
.send_message(TextComponent::translate( .await
"commands.bossbar.set.players.success.some", {
[ Ok(()) => {}
bossbar_prefix( Err(err) => {
handle_bossbar_error(sender, err).await;
return Ok(());
}
}
sender
.send_message(TextComponent::translate(
"commands.bossbar.set.style.success",
[bossbar_prefix(
bossbar.bossbar_data.title.clone(), bossbar.bossbar_data.title.clone(),
namespace.to_string(), namespace.to_string(),
)],
))
.await;
Ok(())
}
CommandValueSet::Value => {
let Ok(value) = value_consumer().find_arg_default_name(args)? else {
send_error_message(
sender,
TextComponent::translate(
"parsing.int.invalid",
[TextComponent::text(i32::MAX.to_string())],
), ),
TextComponent::text(count.to_string()), )
TextComponent::text(player_names.join(", ").clone()), .await;
],
))
.await;
Ok(())
}
CommandValueSet::Style => {
let style = BossbarStyleArgumentConsumer.find_arg_default_name(args)?;
match server
.bossbars
.lock()
.await
.update_division(server, namespace.to_string(), style.clone())
.await
{
Ok(()) => {}
Err(err) => {
handle_bossbar_error(sender, err).await;
return Ok(()); return Ok(());
} };
}
sender
.send_message(TextComponent::translate(
"commands.bossbar.set.style.success",
[bossbar_prefix(
bossbar.bossbar_data.title.clone(),
namespace.to_string(),
)],
))
.await;
Ok(())
}
CommandValueSet::Value => {
let Ok(value) = value_consumer().find_arg_default_name(args)? else {
send_error_message(
sender,
TextComponent::translate(
"parsing.int.invalid",
[TextComponent::text(i32::MAX.to_string())],
),
)
.await;
return Ok(());
};
match server match server
.bossbars .bossbars
.lock() .lock()
.await .await
.update_health(server, namespace.to_string(), bossbar.max, value as u32) .update_health(server, namespace.to_string(), bossbar.max, value as u32)
.await .await
{ {
Ok(()) => {} Ok(()) => {}
Err(err) => { Err(err) => {
handle_bossbar_error(sender, err).await; handle_bossbar_error(sender, err).await;
return Ok(()); return Ok(());
}
} }
}
sender sender
.send_message(TextComponent::translate( .send_message(TextComponent::translate(
"commands.bossbar.set.value.success", "commands.bossbar.set.value.success",
[ [
bossbar_prefix( bossbar_prefix(
bossbar.bossbar_data.title.clone(),
namespace.to_string(),
),
TextComponent::text(value.to_string()),
],
))
.await;
Ok(())
}
CommandValueSet::Visible => {
let visibility = BoolArgConsumer::find_arg(args, ARG_VISIBLE)?;
match server
.bossbars
.lock()
.await
.update_visibility(server, namespace.to_string(), visibility)
.await
{
Ok(()) => {}
Err(err) => {
handle_bossbar_error(sender, err).await;
return Ok(());
}
}
let state = if visibility {
"commands.bossbar.set.visible.success.visible"
} else {
"commands.bossbar.set.visible.success.hidden"
};
sender
.send_message(TextComponent::translate(
state,
[bossbar_prefix(
bossbar.bossbar_data.title.clone(), bossbar.bossbar_data.title.clone(),
namespace.to_string(), namespace.to_string(),
), )],
TextComponent::text(value.to_string()), ))
], .await;
)) Ok(())
.await;
Ok(())
}
CommandValueSet::Visible => {
let visibility = BoolArgConsumer::find_arg(args, ARG_VISIBLE)?;
match server
.bossbars
.lock()
.await
.update_visibility(server, namespace.to_string(), visibility)
.await
{
Ok(()) => {}
Err(err) => {
handle_bossbar_error(sender, err).await;
return Ok(());
}
} }
let state = if visibility {
"commands.bossbar.set.visible.success.visible"
} else {
"commands.bossbar.set.visible.success.hidden"
};
sender
.send_message(TextComponent::translate(
state,
[bossbar_prefix(
bossbar.bossbar_data.title.clone(),
namespace.to_string(),
)],
))
.await;
Ok(())
} }
} })
} }
} }

View File

@ -1,6 +1,5 @@
use std::sync::Arc; use std::sync::Arc;
use async_trait::async_trait;
use pumpkin_util::text::TextComponent; use pumpkin_util::text::TextComponent;
use pumpkin_util::text::color::NamedColor; use pumpkin_util::text::color::NamedColor;
use pumpkin_world::item::ItemStack; use pumpkin_world::item::ItemStack;
@ -9,7 +8,7 @@ use crate::command::args::players::PlayersArgumentConsumer;
use crate::command::args::{Arg, ConsumedArgs}; use crate::command::args::{Arg, ConsumedArgs};
use crate::command::tree::CommandTree; use crate::command::tree::CommandTree;
use crate::command::tree::builder::{argument, require}; use crate::command::tree::builder::{argument, require};
use crate::command::{CommandError, CommandExecutor, CommandSender}; use crate::command::{CommandError, CommandExecutor, CommandResult, CommandSender};
use crate::entity::EntityBase; use crate::entity::EntityBase;
use crate::entity::player::Player; use crate::entity::player::Player;
use CommandError::InvalidConsumption; use CommandError::InvalidConsumption;
@ -71,57 +70,59 @@ async fn clear_command_text_output(item_count: u64, targets: &[Arc<Player>]) ->
struct Executor; struct Executor;
#[async_trait]
impl CommandExecutor for Executor { impl CommandExecutor for Executor {
async fn execute<'a>( fn execute<'a>(
&self, &'a self,
sender: &mut CommandSender, sender: &'a CommandSender,
_server: &crate::server::Server, _server: &'a crate::server::Server,
args: &ConsumedArgs<'a>, args: &'a ConsumedArgs<'a>,
) -> Result<(), CommandError> { ) -> CommandResult<'a> {
let Some(Arg::Players(targets)) = args.get(&ARG_TARGET) else { Box::pin(async move {
return Err(InvalidConsumption(Some(ARG_TARGET.into()))); let Some(Arg::Players(targets)) = args.get(&ARG_TARGET) else {
}; return Err(InvalidConsumption(Some(ARG_TARGET.into())));
};
let mut item_count = 0; let mut item_count = 0;
for target in targets { for target in targets {
item_count += clear_player(target).await; item_count += clear_player(target).await;
} }
let msg = clear_command_text_output(item_count, targets).await; let msg = clear_command_text_output(item_count, targets).await;
sender.send_message(msg).await; sender.send_message(msg).await;
Ok(()) Ok(())
})
} }
} }
struct SelfExecutor; struct SelfExecutor;
#[async_trait]
impl CommandExecutor for SelfExecutor { impl CommandExecutor for SelfExecutor {
async fn execute<'a>( fn execute<'a>(
&self, &'a self,
sender: &mut CommandSender, sender: &'a CommandSender,
_server: &crate::server::Server, _server: &'a crate::server::Server,
_args: &ConsumedArgs<'a>, _args: &'a ConsumedArgs<'a>,
) -> Result<(), CommandError> { ) -> CommandResult<'a> {
let target = sender.as_player().ok_or(CommandError::InvalidRequirement)?; Box::pin(async move {
let target = sender.as_player().ok_or(CommandError::InvalidRequirement)?;
let item_count = clear_player(&target).await; let item_count = clear_player(&target).await;
let hold_target = [target]; let hold_target = [target];
let msg = clear_command_text_output(item_count, &hold_target).await; let msg = clear_command_text_output(item_count, &hold_target).await;
sender.send_message(msg).await; sender.send_message(msg).await;
Ok(()) Ok(())
})
} }
} }
#[allow(clippy::redundant_closure_for_method_calls)] // causes lifetime issues // #[allow(clippy::redundant_closure_for_method_calls)] // causes lifetime issues
pub fn init_command_tree() -> CommandTree { pub fn init_command_tree() -> CommandTree {
CommandTree::new(NAMES, DESCRIPTION) CommandTree::new(NAMES, DESCRIPTION)
.then(argument(ARG_TARGET, PlayersArgumentConsumer).execute(Executor)) .then(argument(ARG_TARGET, PlayersArgumentConsumer).execute(Executor))
.then(require(|sender| sender.is_player()).execute(SelfExecutor)) .then(require(super::super::CommandSender::is_player).execute(SelfExecutor))
} }

View File

@ -1,4 +1,3 @@
use async_trait::async_trait;
use pumpkin_data::damage::DamageType; use pumpkin_data::damage::DamageType;
use pumpkin_util::text::{ use pumpkin_util::text::{
TextComponent, TextComponent,
@ -6,7 +5,7 @@ use pumpkin_util::text::{
}; };
use crate::command::{ use crate::command::{
CommandError, CommandExecutor, CommandSender, CommandExecutor, CommandResult, CommandSender,
args::{ args::{
Arg, ConsumedArgs, FindArg, bounded_num::BoundedNumArgumentConsumer, Arg, ConsumedArgs, FindArg, bounded_num::BoundedNumArgumentConsumer,
entity::EntityArgumentConsumer, position_3d::Position3DArgumentConsumer, entity::EntityArgumentConsumer, position_3d::Position3DArgumentConsumer,
@ -59,100 +58,104 @@ async fn send_damage_result(
.await; .await;
} }
#[async_trait]
impl CommandExecutor for LocationExecutor { impl CommandExecutor for LocationExecutor {
async fn execute<'a>( fn execute<'a>(
&self, &'a self,
sender: &mut CommandSender, sender: &'a CommandSender,
_server: &crate::server::Server, _server: &'a crate::server::Server,
args: &ConsumedArgs<'a>, args: &'a ConsumedArgs<'a>,
) -> Result<(), CommandError> { ) -> CommandResult<'a> {
let target = EntityArgumentConsumer::find_arg(args, ARG_TARGET)?; Box::pin(async move {
let target = EntityArgumentConsumer::find_arg(args, ARG_TARGET)?;
let Ok(Ok(amount)) = BoundedNumArgumentConsumer::<f32>::find_arg(args, ARG_AMOUNT) else { let Ok(Ok(amount)) = BoundedNumArgumentConsumer::<f32>::find_arg(args, ARG_AMOUNT)
sender else {
.send_message( sender
TextComponent::text("Invalid damage amount") .send_message(
.color(Color::Named(NamedColor::Red)), TextComponent::text("Invalid damage amount")
.color(Color::Named(NamedColor::Red)),
)
.await;
return Ok(());
};
let damage_type =
args.get(ARG_DAMAGE_TYPE)
.map_or(DamageType::GENERIC, |arg| match arg {
Arg::DamageType(dt) => *dt,
_ => DamageType::GENERIC,
});
let location = Position3DArgumentConsumer::find_arg(args, ARG_LOCATION)?;
let success = target
.damage_with_context(
target.clone(),
amount,
damage_type,
Some(location),
None,
None,
) )
.await; .await;
return Ok(());
};
let damage_type = args send_damage_result(sender, success, amount, target.get_display_name().await).await;
.get(ARG_DAMAGE_TYPE)
.map_or(DamageType::GENERIC, |arg| match arg {
Arg::DamageType(dt) => *dt,
_ => DamageType::GENERIC,
});
let location = Position3DArgumentConsumer::find_arg(args, ARG_LOCATION)?; Ok(())
})
let success = target
.damage_with_context(
target.clone(),
amount,
damage_type,
Some(location),
None,
None,
)
.await;
send_damage_result(sender, success, amount, target.get_display_name().await).await;
Ok(())
} }
} }
#[async_trait]
impl CommandExecutor for EntityExecutor { impl CommandExecutor for EntityExecutor {
async fn execute<'a>( fn execute<'a>(
&self, &'a self,
sender: &mut CommandSender, sender: &'a CommandSender,
_server: &crate::server::Server, _server: &'a crate::server::Server,
args: &ConsumedArgs<'a>, args: &'a ConsumedArgs<'a>,
) -> Result<(), CommandError> { ) -> CommandResult<'a> {
let target = EntityArgumentConsumer::find_arg(args, ARG_TARGET)?; Box::pin(async move {
let target = EntityArgumentConsumer::find_arg(args, ARG_TARGET)?;
let Ok(Ok(amount)) = BoundedNumArgumentConsumer::<f32>::find_arg(args, ARG_AMOUNT) else { let Ok(Ok(amount)) = BoundedNumArgumentConsumer::<f32>::find_arg(args, ARG_AMOUNT)
sender else {
.send_message( sender
TextComponent::text("Invalid damage amount") .send_message(
.color(Color::Named(NamedColor::Red)), TextComponent::text("Invalid damage amount")
.color(Color::Named(NamedColor::Red)),
)
.await;
return Ok(());
};
let damage_type =
args.get(ARG_DAMAGE_TYPE)
.map_or(DamageType::GENERIC, |arg| match arg {
Arg::DamageType(dt) => *dt,
_ => DamageType::GENERIC,
});
let source = EntityArgumentConsumer::find_arg(args, ARG_ENTITY).ok();
let cause = if self.0 {
EntityArgumentConsumer::find_arg(args, ARG_CAUSE).ok()
} else {
None
};
let success = target
.damage_with_context(
target.clone(),
amount,
damage_type,
None,
source.as_ref().map(|e| e.as_ref() as &dyn EntityBase),
cause.as_ref().map(|e| e.as_ref() as &dyn EntityBase),
) )
.await; .await;
return Ok(());
};
let damage_type = args send_damage_result(sender, success, amount, target.get_display_name().await).await;
.get(ARG_DAMAGE_TYPE)
.map_or(DamageType::GENERIC, |arg| match arg {
Arg::DamageType(dt) => *dt,
_ => DamageType::GENERIC,
});
let source = EntityArgumentConsumer::find_arg(args, ARG_ENTITY).ok(); Ok(())
let cause = if self.0 { })
EntityArgumentConsumer::find_arg(args, ARG_CAUSE).ok()
} else {
None
};
let success = target
.damage_with_context(
target.clone(),
amount,
damage_type,
None,
source.as_ref().map(|e| e.as_ref() as &dyn EntityBase),
cause.as_ref().map(|e| e.as_ref() as &dyn EntityBase),
)
.await;
send_damage_result(sender, success, amount, target.get_display_name().await).await;
Ok(())
} }
} }

View File

@ -1,3 +1,4 @@
use crate::command::CommandResult;
use crate::command::args::entity::EntityArgumentConsumer; use crate::command::args::entity::EntityArgumentConsumer;
use crate::command::tree::builder::literal; use crate::command::tree::builder::literal;
use crate::command::{ use crate::command::{
@ -7,7 +8,6 @@ use crate::command::{
}; };
use crate::entity::NBTStorage; use crate::entity::NBTStorage;
use CommandError::InvalidConsumption; use CommandError::InvalidConsumption;
use async_trait::async_trait;
use pumpkin_nbt::compound::NbtCompound; use pumpkin_nbt::compound::NbtCompound;
use pumpkin_nbt::tag::NbtTag; use pumpkin_nbt::tag::NbtTag;
use pumpkin_util::text::TextComponent; use pumpkin_util::text::TextComponent;
@ -20,23 +20,24 @@ const ARG_ENTITY: &str = "entity";
struct GetEntityDataExecutor; struct GetEntityDataExecutor;
#[async_trait]
impl CommandExecutor for GetEntityDataExecutor { impl CommandExecutor for GetEntityDataExecutor {
async fn execute<'a>( fn execute<'a>(
&self, &'a self,
sender: &mut CommandSender, sender: &'a CommandSender,
_server: &crate::server::Server, _server: &'a crate::server::Server,
args: &ConsumedArgs<'a>, args: &'a ConsumedArgs<'a>,
) -> Result<(), CommandError> { ) -> CommandResult<'a> {
let Some(Arg::Entity(entity)) = args.get(&ARG_ENTITY) else { Box::pin(async move {
return Err(InvalidConsumption(Some(ARG_ENTITY.into()))); let Some(Arg::Entity(entity)) = args.get(&ARG_ENTITY) else {
}; return Err(InvalidConsumption(Some(ARG_ENTITY.into())));
let data_storage = entity.as_nbt_storage(); };
let data_storage = entity.as_nbt_storage();
sender sender
.send_message(display_data(data_storage, entity.get_display_name().await).await?) .send_message(display_data(data_storage, entity.get_display_name().await).await?)
.await; .await;
Ok(()) Ok(())
})
} }
} }

View File

@ -1,11 +1,9 @@
use crate::command::CommandResult;
use crate::command::args::gamemode::GamemodeArgumentConsumer; use crate::command::args::gamemode::GamemodeArgumentConsumer;
use crate::command::args::{Arg, GetCloned}; use crate::command::args::{Arg, GetCloned};
use crate::command::dispatcher::CommandError::InvalidConsumption; use crate::command::dispatcher::CommandError::InvalidConsumption;
use crate::command::tree::builder::argument; use crate::command::tree::builder::argument;
use crate::command::{ use crate::command::{CommandExecutor, CommandSender, args::ConsumedArgs, tree::CommandTree};
CommandError, CommandExecutor, CommandSender, args::ConsumedArgs, tree::CommandTree,
};
use async_trait::async_trait;
use pumpkin_config::BASIC_CONFIG; use pumpkin_config::BASIC_CONFIG;
use pumpkin_util::GameMode; use pumpkin_util::GameMode;
use pumpkin_util::text::TextComponent; use pumpkin_util::text::TextComponent;
@ -22,38 +20,39 @@ pub struct DefaultGamemode {
struct DefaultGamemodeExecutor; struct DefaultGamemodeExecutor;
#[async_trait]
impl CommandExecutor for DefaultGamemodeExecutor { impl CommandExecutor for DefaultGamemodeExecutor {
async fn execute<'a>( fn execute<'a>(
&self, &'a self,
sender: &mut CommandSender, sender: &'a CommandSender,
server: &crate::server::Server, server: &'a crate::server::Server,
args: &ConsumedArgs<'a>, args: &'a ConsumedArgs<'a>,
) -> Result<(), CommandError> { ) -> CommandResult<'a> {
let Some(Arg::GameMode(gamemode)) = args.get_cloned(&ARG_GAMEMODE) else { Box::pin(async move {
return Err(InvalidConsumption(Some(ARG_GAMEMODE.into()))); let Some(Arg::GameMode(gamemode)) = args.get_cloned(&ARG_GAMEMODE) else {
}; return Err(InvalidConsumption(Some(ARG_GAMEMODE.into())));
};
if BASIC_CONFIG.force_gamemode { if BASIC_CONFIG.force_gamemode {
for player in server.get_all_players().await { for player in server.get_all_players().await {
player.set_gamemode(gamemode).await; player.set_gamemode(gamemode).await;
}
} }
}
let gamemode_string = format!("{gamemode:?}").to_lowercase(); let gamemode_string = format!("{gamemode:?}").to_lowercase();
let gamemode_string = format!("gameMode.{gamemode_string}"); let gamemode_string = format!("gameMode.{gamemode_string}");
sender sender
.send_message(TextComponent::translate( .send_message(TextComponent::translate(
"commands.defaultgamemode.success", "commands.defaultgamemode.success",
[TextComponent::translate(gamemode_string, [])], [TextComponent::translate(gamemode_string, [])],
)) ))
.await; .await;
//Change the default gamemode (not in configuration.toml) //Change the default gamemode (not in configuration.toml)
server.defaultgamemode.lock().await.gamemode = gamemode; server.defaultgamemode.lock().await.gamemode = gamemode;
Ok(()) Ok(())
})
} }
} }

View File

@ -1,3 +1,4 @@
use crate::command::CommandResult;
use crate::entity::EntityBase; use crate::entity::EntityBase;
use crate::{ use crate::{
command::{ command::{
@ -9,7 +10,6 @@ use crate::{
data::{SaveJSONConfiguration, op_data::OPERATOR_CONFIG}, data::{SaveJSONConfiguration, op_data::OPERATOR_CONFIG},
}; };
use CommandError::InvalidConsumption; use CommandError::InvalidConsumption;
use async_trait::async_trait;
use pumpkin_util::text::TextComponent; use pumpkin_util::text::TextComponent;
const NAMES: [&str; 1] = ["deop"]; const NAMES: [&str; 1] = ["deop"];
@ -18,44 +18,45 @@ const ARG_TARGETS: &str = "targets";
struct Executor; struct Executor;
#[async_trait]
impl CommandExecutor for Executor { impl CommandExecutor for Executor {
async fn execute<'a>( fn execute<'a>(
&self, &'a self,
sender: &mut CommandSender, sender: &'a CommandSender,
server: &crate::server::Server, server: &'a crate::server::Server,
args: &ConsumedArgs<'a>, args: &'a ConsumedArgs<'a>,
) -> Result<(), CommandError> { ) -> CommandResult<'a> {
let mut config = OPERATOR_CONFIG.write().await; Box::pin(async move {
let mut config = OPERATOR_CONFIG.write().await;
let Some(Arg::Players(targets)) = args.get(&ARG_TARGETS) else { let Some(Arg::Players(targets)) = args.get(&ARG_TARGETS) else {
return Err(InvalidConsumption(Some(ARG_TARGETS.into()))); return Err(InvalidConsumption(Some(ARG_TARGETS.into())));
};
for player in targets {
if let Some(op_index) = config
.ops
.iter()
.position(|o| o.uuid == player.gameprofile.id)
{
config.ops.remove(op_index);
}
config.save();
{
let command_dispatcher = server.command_dispatcher.read().await;
player
.set_permission_lvl(pumpkin_util::PermissionLvl::Zero, &command_dispatcher)
.await;
}; };
let msg = TextComponent::translate( for player in targets {
"commands.deop.success", if let Some(op_index) = config
[player.get_display_name().await], .ops
); .iter()
sender.send_message(msg).await; .position(|o| o.uuid == player.gameprofile.id)
} {
Ok(()) config.ops.remove(op_index);
}
config.save();
{
let command_dispatcher = server.command_dispatcher.read().await;
player
.set_permission_lvl(pumpkin_util::PermissionLvl::Zero, &command_dispatcher)
.await;
};
let msg = TextComponent::translate(
"commands.deop.success",
[player.get_display_name().await],
);
sender.send_message(msg).await;
}
Ok(())
})
} }
} }

View File

@ -1,11 +1,9 @@
use crate::command::CommandResult;
use crate::command::args::difficulty::DifficultyArgumentConsumer; use crate::command::args::difficulty::DifficultyArgumentConsumer;
use crate::command::args::{Arg, GetCloned}; use crate::command::args::{Arg, GetCloned};
use crate::command::dispatcher::CommandError::InvalidConsumption; use crate::command::dispatcher::CommandError::InvalidConsumption;
use crate::command::tree::builder::argument; use crate::command::tree::builder::argument;
use crate::command::{ use crate::command::{CommandExecutor, CommandSender, args::ConsumedArgs, tree::CommandTree};
CommandError, CommandExecutor, CommandSender, args::ConsumedArgs, tree::CommandTree,
};
use async_trait::async_trait;
use pumpkin_util::text::TextComponent; use pumpkin_util::text::TextComponent;
@ -16,45 +14,46 @@ const DESCRIPTION: &str = "Change the difficulty of the world.";
pub const ARG_DIFFICULTY: &str = "difficulty"; pub const ARG_DIFFICULTY: &str = "difficulty";
struct DifficultyExecutor; struct DifficultyExecutor;
#[async_trait]
impl CommandExecutor for DifficultyExecutor { impl CommandExecutor for DifficultyExecutor {
async fn execute<'a>( fn execute<'a>(
&self, &'a self,
sender: &mut CommandSender, sender: &'a CommandSender,
server: &crate::server::Server, server: &'a crate::server::Server,
args: &ConsumedArgs<'a>, args: &'a ConsumedArgs<'a>,
) -> Result<(), CommandError> { ) -> CommandResult<'a> {
let Some(Arg::Difficulty(difficulty)) = args.get_cloned(&ARG_DIFFICULTY) else { Box::pin(async move {
return Err(InvalidConsumption(Some(ARG_DIFFICULTY.into()))); let Some(Arg::Difficulty(difficulty)) = args.get_cloned(&ARG_DIFFICULTY) else {
}; return Err(InvalidConsumption(Some(ARG_DIFFICULTY.into())));
};
let difficulty_string = format!("{difficulty:?}").to_lowercase(); let difficulty_string = format!("{difficulty:?}").to_lowercase();
let translation_key = format!("options.difficulty.{difficulty_string}"); let translation_key = format!("options.difficulty.{difficulty_string}");
{ {
let level_info = server.level_info.read().await; let level_info = server.level_info.read().await;
if level_info.difficulty == difficulty { if level_info.difficulty == difficulty {
sender sender
.send_message(TextComponent::translate( .send_message(TextComponent::translate(
"commands.difficulty.failure", "commands.difficulty.failure",
[TextComponent::translate(translation_key, [])], [TextComponent::translate(translation_key, [])],
)) ))
.await; .await;
return Ok(()); return Ok(());
}
} }
}
server.set_difficulty(difficulty, Some(true)).await; server.set_difficulty(difficulty, Some(true)).await;
sender sender
.send_message(TextComponent::translate( .send_message(TextComponent::translate(
"commands.difficulty.success", "commands.difficulty.success",
[TextComponent::translate(translation_key, [])], [TextComponent::translate(translation_key, [])],
)) ))
.await; .await;
Ok(()) Ok(())
})
} }
} }

View File

@ -4,14 +4,11 @@ use crate::command::args::bounded_num::BoundedNumArgumentConsumer;
use crate::command::args::players::PlayersArgumentConsumer; use crate::command::args::players::PlayersArgumentConsumer;
use crate::command::args::resource::effect::EffectTypeArgumentConsumer; use crate::command::args::resource::effect::EffectTypeArgumentConsumer;
use crate::command::args::{Arg, ConsumedArgs, FindArgDefaultName}; use crate::command::args::{Arg, ConsumedArgs, FindArgDefaultName};
use crate::command::dispatcher::CommandError;
use crate::command::dispatcher::CommandError::InvalidConsumption; use crate::command::dispatcher::CommandError::InvalidConsumption;
use crate::command::tree::CommandTree; use crate::command::tree::CommandTree;
use crate::command::tree::builder::{argument, literal}; use crate::command::tree::builder::{argument, literal};
use crate::command::{CommandExecutor, CommandSender}; use crate::command::{CommandExecutor, CommandResult, CommandSender};
use crate::entity::EntityBase; use crate::entity::EntityBase;
use crate::server::Server;
use async_trait::async_trait;
use pumpkin_data::potion::Effect; use pumpkin_data::potion::Effect;
use pumpkin_util::text::color::{Color, NamedColor}; use pumpkin_util::text::color::{Color, NamedColor};
@ -41,218 +38,223 @@ enum Amplifier {
struct GiveExecutor(Time, Amplifier, bool); struct GiveExecutor(Time, Amplifier, bool);
#[async_trait]
impl CommandExecutor for GiveExecutor { impl CommandExecutor for GiveExecutor {
async fn execute<'a>( fn execute<'a>(
&self, &'a self,
sender: &mut CommandSender, sender: &'a CommandSender,
_server: &Server, _server: &'a crate::server::Server,
args: &ConsumedArgs<'a>, args: &'a ConsumedArgs<'a>,
) -> Result<(), CommandError> { ) -> CommandResult<'a> {
let Some(Arg::Players(targets)) = args.get(ARG_TARGET) else { Box::pin(async move {
return Err(InvalidConsumption(Some(ARG_TARGET.into()))); let Some(Arg::Players(targets)) = args.get(ARG_TARGET) else {
}; return Err(InvalidConsumption(Some(ARG_TARGET.into())));
let Some(Arg::Effect(effect)) = args.get(ARG_EFFECT) else { };
return Err(InvalidConsumption(Some(ARG_EFFECT.into()))); let Some(Arg::Effect(effect)) = args.get(ARG_EFFECT) else {
}; return Err(InvalidConsumption(Some(ARG_EFFECT.into())));
//duration is in tick, so * 20 (not for the infinite because -1*20 cause visual glitch)
let second = match self.0 {
Time::Base => 30 * 20,
Time::Specified => {
BoundedNumArgumentConsumer::new()
.name("seconds")
.min(1)
.max(1_000_000)
.find_arg_default_name(args)??
* 20
}
Time::Infinite => -1,
};
let amplifier: i32 = match self.1 {
Amplifier::Base => 0,
Amplifier::Specified => BoundedNumArgumentConsumer::new()
.name("amplifier")
.min(0)
.max(255)
.find_arg_default_name(args)??,
};
let mut hide_particles = self.2;
//if false -> parameter is referred
if !hide_particles {
let Some(Arg::Bool(hide_particle)) = args.get(ARG_HIDE_PARTICLE) else {
return Err(InvalidConsumption(Some(ARG_HIDE_PARTICLE.into())));
}; };
hide_particles = *hide_particle; //duration is in tick, so * 20 (not for the infinite because -1*20 cause visual glitch)
} let second = match self.0 {
Time::Base => 30 * 20,
Time::Specified => {
BoundedNumArgumentConsumer::new()
.name("seconds")
.min(1)
.max(1_000_000)
.find_arg_default_name(args)??
* 20
}
Time::Infinite => -1,
};
let mut failed = 0; let amplifier: i32 = match self.1 {
Amplifier::Base => 0,
Amplifier::Specified => BoundedNumArgumentConsumer::new()
.name("amplifier")
.min(0)
.max(255)
.find_arg_default_name(args)??,
};
for target in targets { let mut hide_particles = self.2;
if target.living_entity.has_effect(effect).await //if false -> parameter is referred
&& target if !hide_particles {
.living_entity let Some(Arg::Bool(hide_particle)) = args.get(ARG_HIDE_PARTICLE) else {
.get_effect(effect) return Err(InvalidConsumption(Some(ARG_HIDE_PARTICLE.into())));
.await };
.unwrap()
.amplifier hide_particles = *hide_particle;
> amplifier as u8 }
{
failed += 1; let mut failed = 0;
for target in targets {
if target.living_entity.has_effect(effect).await
&& target
.living_entity
.get_effect(effect)
.await
.unwrap()
.amplifier
> amplifier as u8
{
failed += 1;
} else {
target
.add_effect(Effect {
effect_type: effect,
duration: second,
amplifier: amplifier as u8,
ambient: false, //this is not a beacon effect
show_particles: hide_particles,
show_icon: true,
blend: true, //Currently only used in the DARKNESS effect to apply extra void fog and adjust the gamma value for lighting.
})
.await;
}
}
let translation_name = TextComponent::translate(effect.translation_key.to_string(), []);
if failed == targets.len() {
sender
.send_message(
TextComponent::translate("commands.effect.give.failed", [])
.color(Color::Named(NamedColor::Red)),
)
.await;
} else if targets.len() == 1 {
sender
.send_message(TextComponent::translate(
"commands.effect.give.success.single",
[translation_name, targets[0].get_display_name().await],
))
.await;
} else { } else {
target sender
.add_effect(Effect { .send_message(TextComponent::translate(
effect_type: effect, "commands.effect.give.success.multiple",
duration: second, [
amplifier: amplifier as u8, translation_name,
ambient: false, //this is not a beacon effect TextComponent::text(targets.len().to_string()),
show_particles: hide_particles, ],
show_icon: true, ))
blend: true, //Currently only used in the DARKNESS effect to apply extra void fog and adjust the gamma value for lighting.
})
.await; .await;
} }
}
let translation_name = TextComponent::translate(effect.translation_key.to_string(), []); Ok(())
})
if failed == targets.len() {
sender
.send_message(
TextComponent::translate("commands.effect.give.failed", [])
.color(Color::Named(NamedColor::Red)),
)
.await;
} else if targets.len() == 1 {
sender
.send_message(TextComponent::translate(
"commands.effect.give.success.single",
[translation_name, targets[0].get_display_name().await],
))
.await;
} else {
sender
.send_message(TextComponent::translate(
"commands.effect.give.success.multiple",
[
translation_name,
TextComponent::text(targets.len().to_string()),
],
))
.await;
}
Ok(())
} }
} }
struct ClearExecutor(bool); //the param -> true = delete every effect, false = only one struct ClearExecutor(bool); //the param -> true = delete every effect, false = only one
#[async_trait]
impl CommandExecutor for ClearExecutor { impl CommandExecutor for ClearExecutor {
async fn execute<'a>( fn execute<'a>(
&self, &'a self,
sender: &mut CommandSender, sender: &'a CommandSender,
_server: &Server, _server: &'a crate::server::Server,
args: &ConsumedArgs<'a>, args: &'a ConsumedArgs<'a>,
) -> Result<(), CommandError> { ) -> CommandResult<'a> {
let Some(Arg::Players(targets)) = args.get(ARG_TARGET) else { Box::pin(async move {
return Err(InvalidConsumption(Some(ARG_TARGET.into()))); let Some(Arg::Players(targets)) = args.get(ARG_TARGET) else {
}; return Err(InvalidConsumption(Some(ARG_TARGET.into())));
let effect;
//Only one effect
if self.0 {
let mut effect_number = 0;
for target in targets {
let effect_number_temp = target.remove_all_effect().await;
if effect_number_temp > effect_number {
effect_number = effect_number_temp;
}
}
//if the player or everyplayer don't have any effect
if effect_number == 0 {
sender
.send_message(
TextComponent::translate("commands.effect.clear.everything.failed", [])
.color(Color::Named(NamedColor::Red)),
)
.await;
}
//a player have at least 1 effect
else if targets.len() == 1 {
sender
.send_message(TextComponent::translate(
"commands.effect.clear.everything.success.single",
[targets[0].get_display_name().await],
))
.await;
} else {
sender
.send_message(TextComponent::translate(
"commands.effect.clear.everything.success.multiple",
[TextComponent::text(targets.len().to_string())],
))
.await;
}
} else {
let Some(Arg::Effect(effect_type)) = args.get(ARG_EFFECT) else {
return Err(InvalidConsumption(Some(ARG_EFFECT.into())));
}; };
effect = *effect_type; let effect;
let mut has_effect = vec![]; //Only one effect
if self.0 {
for target in targets { let mut effect_number = 0;
if !target.living_entity.has_effect(effect).await { for target in targets {
target.remove_effect(effect).await; let effect_number_temp = target.remove_all_effect().await;
if effect_number_temp > effect_number {
effect_number = effect_number_temp;
}
} }
has_effect.push(target.living_entity.has_effect(effect).await);
}
if has_effect.contains(&false) { //if the player or everyplayer don't have any effect
//contain false for 1 player == don't have if effect_number == 0 {
if targets.len() == 1 || !has_effect.contains(&true) {
sender sender
.send_message( .send_message(
TextComponent::translate("commands.effect.clear.specific.failed", []) TextComponent::translate("commands.effect.clear.everything.failed", [])
.color(Color::Named(NamedColor::Red)), .color(Color::Named(NamedColor::Red)),
) )
.await; .await;
} }
} else { //a player have at least 1 effect
//true for 1 player = have the effect else if targets.len() == 1 {
if targets.len() == 1 {
sender sender
.send_message(TextComponent::translate( .send_message(TextComponent::translate(
"commands.effect.clear.specific.success.single", "commands.effect.clear.everything.success.single",
[ [targets[0].get_display_name().await],
TextComponent::translate(effect.translation_key, []), ))
targets[0].get_display_name().await, .await;
], } else {
sender
.send_message(TextComponent::translate(
"commands.effect.clear.everything.success.multiple",
[TextComponent::text(targets.len().to_string())],
)) ))
.await; .await;
} }
//contain true for everyplayer = at least 1 player have the effect } else {
else { let Some(Arg::Effect(effect_type)) = args.get(ARG_EFFECT) else {
sender return Err(InvalidConsumption(Some(ARG_EFFECT.into())));
.send_message(TextComponent::translate( };
"commands.effect.clear.specific.success.multiple",
[ effect = *effect_type;
TextComponent::translate(effect.translation_key, []), let mut has_effect = vec![];
TextComponent::text(targets.len().to_string()),
], for target in targets {
)) if !target.living_entity.has_effect(effect).await {
.await; target.remove_effect(effect).await;
}
has_effect.push(target.living_entity.has_effect(effect).await);
}
if has_effect.contains(&false) {
//contain false for 1 player == don't have
if targets.len() == 1 || !has_effect.contains(&true) {
sender
.send_message(
TextComponent::translate(
"commands.effect.clear.specific.failed",
[],
)
.color(Color::Named(NamedColor::Red)),
)
.await;
}
} else {
//true for 1 player = have the effect
if targets.len() == 1 {
sender
.send_message(TextComponent::translate(
"commands.effect.clear.specific.success.single",
[
TextComponent::translate(effect.translation_key, []),
targets[0].get_display_name().await,
],
))
.await;
}
//contain true for everyplayer = at least 1 player have the effect
else {
sender
.send_message(TextComponent::translate(
"commands.effect.clear.specific.success.multiple",
[
TextComponent::translate(effect.translation_key, []),
TextComponent::text(targets.len().to_string()),
],
))
.await;
}
} }
} }
}
Ok(()) Ok(())
})
} }
} }

View File

@ -1,4 +1,3 @@
use async_trait::async_trait;
use pumpkin_util::text::TextComponent; use pumpkin_util::text::TextComponent;
use crate::command::args::bounded_num::{BoundedNumArgumentConsumer, NotInBounds}; use crate::command::args::bounded_num::{BoundedNumArgumentConsumer, NotInBounds};
@ -7,7 +6,7 @@ use crate::command::args::resource::enchantment::EnchantmentArgumentConsumer;
use crate::command::args::{ConsumedArgs, FindArgDefaultName}; use crate::command::args::{ConsumedArgs, FindArgDefaultName};
use crate::command::tree::CommandTree; use crate::command::tree::CommandTree;
use crate::command::tree::builder::argument_default_name; use crate::command::tree::builder::argument_default_name;
use crate::command::{CommandError, CommandExecutor, CommandSender}; use crate::command::{CommandError, CommandExecutor, CommandResult, CommandSender};
use pumpkin_data::data_component_impl::EnchantmentsImpl; use pumpkin_data::data_component_impl::EnchantmentsImpl;
const NAMES: [&str; 1] = ["enchant"]; const NAMES: [&str; 1] = ["enchant"];
@ -15,134 +14,135 @@ const DESCRIPTION: &str = "Adds an enchantment to a player's selected item, subj
struct Executor; struct Executor;
#[async_trait]
impl CommandExecutor for Executor { impl CommandExecutor for Executor {
#[allow(clippy::too_many_lines)] #[allow(clippy::too_many_lines)]
async fn execute<'a>( fn execute<'a>(
&self, &'a self,
sender: &mut CommandSender, sender: &'a CommandSender,
_server: &crate::server::Server, _server: &'a crate::server::Server,
args: &ConsumedArgs<'a>, args: &'a ConsumedArgs<'a>,
) -> Result<(), CommandError> { ) -> CommandResult<'a> {
let targets = EntitiesArgumentConsumer.find_arg_default_name(args)?; Box::pin(async move {
let enchantment = EnchantmentArgumentConsumer.find_arg_default_name(args)?; let targets = EntitiesArgumentConsumer.find_arg_default_name(args)?;
let level = match enchantment_level_consumer().find_arg_default_name(args) { let enchantment = EnchantmentArgumentConsumer.find_arg_default_name(args)?;
Err(_) => 1, let level = match enchantment_level_consumer().find_arg_default_name(args) {
Ok(Ok(level)) => level, Err(_) => 1,
Ok(Err(err)) => { Ok(Ok(level)) => level,
let err_msg = match err { Ok(Err(err)) => {
NotInBounds::LowerBound(val, min) => TextComponent::translate( let err_msg = match err {
"argument.integer.low", NotInBounds::LowerBound(val, min) => TextComponent::translate(
&[ "argument.integer.low",
TextComponent::text(min.to_string()), &[
TextComponent::text(val.to_string()), TextComponent::text(min.to_string()),
], TextComponent::text(val.to_string()),
), ],
NotInBounds::UpperBound(val, max) => TextComponent::translate( ),
"argument.integer.big", NotInBounds::UpperBound(val, max) => TextComponent::translate(
&[ "argument.integer.big",
TextComponent::text(max.to_string()), &[
TextComponent::text(val.to_string()), TextComponent::text(max.to_string()),
], TextComponent::text(val.to_string()),
), ],
}; ),
};
return Err(CommandError::CommandFailed(Box::new(err_msg))); return Err(CommandError::CommandFailed(Box::new(err_msg)));
} }
};
if level > enchantment.max_level {
let msg = TextComponent::translate(
"commands.enchant.failed.level",
[
TextComponent::text(level.to_string()),
TextComponent::text(enchantment.max_level.to_string()),
],
);
return Err(CommandError::CommandFailed(Box::new(msg)));
}
let only_one = targets.len() == 1;
let mut success = 0;
for target in targets {
// let Some(target) = target.get_living_entity() else {
// if only_one {
// let msg = TextComponent::translate(
// "commands.enchant.failed.entity",
// [targets[0].get_display_name().await],
// );
// sender.send_message(msg).await;
// return Ok(());
// }
// continue;
// };
// let lock = target.entity_equipment.lock().await.get(&EquipmentSlot::MAIN_HAND); TODO this dont work
let Some(player) = target.get_player() else {
todo!()
}; };
let lock = player.inventory.held_item();
let mut item = lock.lock().await; if level > enchantment.max_level {
if item.is_empty() { let msg = TextComponent::translate(
if only_one { "commands.enchant.failed.level",
let msg = TextComponent::translate( [
"commands.enchant.failed.itemless", TextComponent::text(level.to_string()),
[targets[0].get_display_name().await], TextComponent::text(enchantment.max_level.to_string()),
); ],
return Err(CommandError::CommandFailed(Box::new(msg))); );
} return Err(CommandError::CommandFailed(Box::new(msg)));
continue;
} }
if !enchantment.can_enchant(item.item) {
if only_one { let only_one = targets.len() == 1;
let msg = TextComponent::translate( let mut success = 0;
"commands.enchant.failed.incompatible",
[item.item.translated_name()], for target in targets {
); // let Some(target) = target.get_living_entity() else {
return Err(CommandError::CommandFailed(Box::new(msg))); // if only_one {
// let msg = TextComponent::translate(
// "commands.enchant.failed.entity",
// [targets[0].get_display_name().await],
// );
// sender.send_message(msg).await;
// return Ok(());
// }
// continue;
// };
// let lock = target.entity_equipment.lock().await.get(&EquipmentSlot::MAIN_HAND); TODO this dont work
let Some(player) = target.get_player() else {
todo!()
};
let lock = player.inventory.held_item();
let mut item = lock.lock().await;
if item.is_empty() {
if only_one {
let msg = TextComponent::translate(
"commands.enchant.failed.itemless",
[targets[0].get_display_name().await],
);
return Err(CommandError::CommandFailed(Box::new(msg)));
}
continue;
} }
continue; if !enchantment.can_enchant(item.item) {
} if only_one {
if let Some(data) = item.get_data_component::<EnchantmentsImpl>() { let msg = TextComponent::translate(
if enchantment.is_enchantment_compatible(data) { "commands.enchant.failed.incompatible",
[item.item.translated_name()],
);
return Err(CommandError::CommandFailed(Box::new(msg)));
}
continue;
}
if let Some(data) = item.get_data_component::<EnchantmentsImpl>() {
if enchantment.is_enchantment_compatible(data) {
item.enchant(enchantment, level);
success += 1;
} else if only_one {
let msg = TextComponent::translate(
"commands.enchant.failed.incompatible",
[item.item.translated_name()],
);
return Err(CommandError::CommandFailed(Box::new(msg)));
}
} else {
item.enchant(enchantment, level); item.enchant(enchantment, level);
success += 1; success += 1;
} else if only_one {
let msg = TextComponent::translate(
"commands.enchant.failed.incompatible",
[item.item.translated_name()],
);
return Err(CommandError::CommandFailed(Box::new(msg)));
} }
} else {
item.enchant(enchantment, level);
success += 1;
} }
} if success == 0 {
if success == 0 { let msg = TextComponent::translate("commands.enchant.failed", []);
let msg = TextComponent::translate("commands.enchant.failed", []); return Err(CommandError::CommandFailed(Box::new(msg)));
return Err(CommandError::CommandFailed(Box::new(msg))); }
} if only_one {
if only_one { let msg = TextComponent::translate(
let msg = TextComponent::translate( "commands.enchant.success.single",
"commands.enchant.success.single", [
[ enchantment.get_fullname(level),
enchantment.get_fullname(level), targets[0].get_display_name().await,
targets[0].get_display_name().await, ],
], );
); sender.send_message(msg).await;
sender.send_message(msg).await; } else {
} else { let msg = TextComponent::translate(
let msg = TextComponent::translate( "commands.enchant.success.multiple",
"commands.enchant.success.multiple", [
[ enchantment.get_fullname(level),
enchantment.get_fullname(level), TextComponent::text(targets.len().to_string()),
TextComponent::text(targets.len().to_string()), ],
], );
); sender.send_message(msg).await;
sender.send_message(msg).await; }
} Ok(())
Ok(()) })
} }
} }

View File

@ -1,6 +1,5 @@
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
use async_trait::async_trait;
use pumpkin_util::math::experience; use pumpkin_util::math::experience;
use pumpkin_util::text::TextComponent; use pumpkin_util::text::TextComponent;
use pumpkin_util::text::color::{Color, NamedColor}; use pumpkin_util::text::color::{Color, NamedColor};
@ -10,7 +9,7 @@ use crate::command::args::players::PlayersArgumentConsumer;
use crate::command::args::{ConsumedArgs, FindArg}; use crate::command::args::{ConsumedArgs, FindArg};
use crate::command::tree::CommandTree; use crate::command::tree::CommandTree;
use crate::command::tree::builder::{argument, literal}; use crate::command::tree::builder::{argument, literal};
use crate::command::{CommandError, CommandExecutor, CommandSender}; use crate::command::{CommandExecutor, CommandResult, CommandSender};
use crate::entity::EntityBase; use crate::entity::EntityBase;
use crate::entity::player::Player; use crate::entity::player::Player;
@ -196,76 +195,77 @@ impl Executor {
} }
} }
#[async_trait]
impl CommandExecutor for Executor { impl CommandExecutor for Executor {
async fn execute<'a>( fn execute<'a>(
&self, &'a self,
sender: &mut CommandSender, sender: &'a CommandSender,
_server: &crate::server::Server, _server: &'a crate::server::Server,
args: &ConsumedArgs<'a>, args: &'a ConsumedArgs<'a>,
) -> Result<(), CommandError> { ) -> CommandResult<'a> {
let targets = PlayersArgumentConsumer::find_arg(args, ARG_TARGETS)?; Box::pin(async move {
let targets = PlayersArgumentConsumer::find_arg(args, ARG_TARGETS)?;
match self.mode { match self.mode {
Mode::Query => { Mode::Query => {
if targets.len() != 1 { if targets.len() != 1 {
// TODO: Add proper error message for multiple players in query mode // TODO: Add proper error message for multiple players in query mode
return Ok(()); return Ok(());
} }
self.handle_query(sender, &targets[0], self.exp_type.unwrap()) self.handle_query(sender, &targets[0], self.exp_type.unwrap())
.await;
}
Mode::Add | Mode::Set => {
let Ok(amount) = BoundedNumArgumentConsumer::<i32>::find_arg(args, ARG_AMOUNT)?
else {
sender
.send_message(TextComponent::translate(
"commands.experience.set.points.invalid",
[],
))
.await; .await;
return Ok(());
};
if self.mode == Mode::Set && amount < 0 {
sender
.send_message(TextComponent::translate(
"commands.experience.set.points.invalid",
[],
))
.await;
return Ok(());
} }
Mode::Add | Mode::Set => {
let Ok(amount) = BoundedNumArgumentConsumer::<i32>::find_arg(args, ARG_AMOUNT)?
else {
sender
.send_message(TextComponent::translate(
"commands.experience.set.points.invalid",
[],
))
.await;
return Ok(());
};
for target in targets { if self.mode == Mode::Set && amount < 0 {
match self sender
.handle_modify(target, amount, self.exp_type.unwrap(), self.mode) .send_message(TextComponent::translate(
.await "commands.experience.set.points.invalid",
{ [],
Ok(()) => { ))
let msg = Self::get_success_message( .await;
self.mode, return Ok(());
self.exp_type.unwrap(), }
amount,
targets.len(), for target in targets {
Some(target.get_display_name().await), match self
); .handle_modify(target, amount, self.exp_type.unwrap(), self.mode)
sender.send_message(msg).await; .await
} {
Err(error_msg) => { Ok(()) => {
sender let msg = Self::get_success_message(
.send_message( self.mode,
TextComponent::translate(error_msg, []) self.exp_type.unwrap(),
.color(Color::Named(NamedColor::Red)), amount,
) targets.len(),
.await; Some(target.get_display_name().await),
);
sender.send_message(msg).await;
}
Err(error_msg) => {
sender
.send_message(
TextComponent::translate(error_msg, [])
.color(Color::Named(NamedColor::Red)),
)
.await;
}
} }
} }
} }
} }
}
Ok(()) Ok(())
})
} }
} }

View File

@ -5,9 +5,8 @@ use crate::command::args::position_block::BlockPosArgumentConsumer;
use crate::command::args::{ConsumedArgs, FindArg}; use crate::command::args::{ConsumedArgs, FindArg};
use crate::command::tree::CommandTree; use crate::command::tree::CommandTree;
use crate::command::tree::builder::{argument, literal}; use crate::command::tree::builder::{argument, literal};
use crate::command::{CommandError, CommandExecutor, CommandSender}; use crate::command::{CommandError, CommandExecutor, CommandResult, CommandSender};
use async_trait::async_trait;
use pumpkin_data::Block; use pumpkin_data::Block;
use pumpkin_util::math::position::BlockPos; use pumpkin_util::math::position::BlockPos;
use pumpkin_util::math::vector3::Vector3; use pumpkin_util::math::vector3::Vector3;
@ -50,94 +49,69 @@ fn not_in_filter(filter: &BlockPredicate, old_block: &Block) -> bool {
} }
#[expect(clippy::too_many_lines)] #[expect(clippy::too_many_lines)]
#[async_trait]
impl CommandExecutor for Executor { impl CommandExecutor for Executor {
async fn execute<'a>( fn execute<'a>(
&self, &'a self,
sender: &mut CommandSender, sender: &'a CommandSender,
_server: &crate::server::Server, _server: &'a crate::server::Server,
args: &ConsumedArgs<'a>, args: &'a ConsumedArgs<'a>,
) -> Result<(), CommandError> { ) -> CommandResult<'a> {
let block = BlockArgumentConsumer::find_arg(args, ARG_BLOCK)?; Box::pin(async move {
let block_state_id = block.default_state.id; let block = BlockArgumentConsumer::find_arg(args, ARG_BLOCK)?;
let from = BlockPosArgumentConsumer::find_arg(args, ARG_FROM)?; let block_state_id = block.default_state.id;
let to = BlockPosArgumentConsumer::find_arg(args, ARG_TO)?; let from = BlockPosArgumentConsumer::find_arg(args, ARG_FROM)?;
let option_filter = BlockPredicateArgumentConsumer::find_arg(args, ARG_FILTER)?; let to = BlockPosArgumentConsumer::find_arg(args, ARG_TO)?;
let mode = self.0; let option_filter = BlockPredicateArgumentConsumer::find_arg(args, ARG_FILTER)?;
let mode = self.0;
let start_x = from.0.x.min(to.0.x); let start_x = from.0.x.min(to.0.x);
let start_y = from.0.y.min(to.0.y); let start_y = from.0.y.min(to.0.y);
let start_z = from.0.z.min(to.0.z); let start_z = from.0.z.min(to.0.z);
let end_x = from.0.x.max(to.0.x); let end_x = from.0.x.max(to.0.x);
let end_y = from.0.y.max(to.0.y); let end_y = from.0.y.max(to.0.y);
let end_z = from.0.z.max(to.0.z); let end_z = from.0.z.max(to.0.z);
// TODO: check isInWorldBounds and throw argument.pos.outofbounds // TODO: check isInWorldBounds and throw argument.pos.outofbounds
let world = sender.world().ok_or(CommandError::InvalidRequirement)?; let world = sender.world().ok_or(CommandError::InvalidRequirement)?;
let mut placed_blocks = 0; let mut placed_blocks = 0;
let mut to_update = Vec::new(); let mut to_update = Vec::new();
match mode { match mode {
Mode::Destroy => { Mode::Destroy => {
for x in start_x..=end_x { for x in start_x..=end_x {
for y in start_y..=end_y { for y in start_y..=end_y {
for z in start_z..=end_z { for z in start_z..=end_z {
let block_position = BlockPos(Vector3::new(x, y, z)); let block_position = BlockPos(Vector3::new(x, y, z));
if let Some(filter) = &option_filter if let Some(filter) = &option_filter
&& not_in_filter(filter, world.get_block(&block_position).await) && not_in_filter(filter, world.get_block(&block_position).await)
{ {
continue; continue;
}
world
.break_block(
&block_position,
None,
BlockFlags::SKIP_DROPS | BlockFlags::FORCE_STATE,
)
.await;
world
.set_block_state(
&block_position,
block_state_id,
BlockFlags::FORCE_STATE,
)
.await;
placed_blocks += 1;
to_update.push(block_position);
} }
world
.break_block(
&block_position,
None,
BlockFlags::SKIP_DROPS | BlockFlags::FORCE_STATE,
)
.await;
world
.set_block_state(
&block_position,
block_state_id,
BlockFlags::FORCE_STATE,
)
.await;
placed_blocks += 1;
to_update.push(block_position);
} }
} }
} }
} Mode::Replace => {
Mode::Replace => { for x in start_x..=end_x {
for x in start_x..=end_x { for y in start_y..=end_y {
for y in start_y..=end_y { for z in start_z..=end_z {
for z in start_z..=end_z { let block_position = BlockPos(Vector3::new(x, y, z));
let block_position = BlockPos(Vector3::new(x, y, z));
if let Some(filter) = &option_filter
&& not_in_filter(filter, world.get_block(&block_position).await)
{
continue;
}
world
.set_block_state(
&block_position,
block_state_id,
BlockFlags::FORCE_STATE,
)
.await;
placed_blocks += 1;
to_update.push(block_position);
}
}
}
}
Mode::Keep => {
for x in start_x..=end_x {
for y in start_y..=end_y {
for z in start_z..=end_z {
let block_position = BlockPos(Vector3::new(x, y, z));
let old_state = world.get_block_state(&block_position).await;
if old_state.is_air() {
if let Some(filter) = &option_filter if let Some(filter) = &option_filter
&& not_in_filter(filter, world.get_block(&block_position).await) && not_in_filter(filter, world.get_block(&block_position).await)
{ {
@ -156,24 +130,93 @@ impl CommandExecutor for Executor {
} }
} }
} }
} Mode::Keep => {
Mode::Hollow => { for x in start_x..=end_x {
for x in start_x..=end_x { for y in start_y..=end_y {
for y in start_y..=end_y { for z in start_z..=end_z {
for z in start_z..=end_z { let block_position = BlockPos(Vector3::new(x, y, z));
let block_position = BlockPos(Vector3::new(x, y, z)); let old_state = world.get_block_state(&block_position).await;
let is_edge = x == start_x if old_state.is_air() {
|| x == end_x if let Some(filter) = &option_filter
|| y == start_y && not_in_filter(
|| y == end_y filter,
|| z == start_z world.get_block(&block_position).await,
|| z == end_z; )
if let Some(filter) = &option_filter {
&& not_in_filter(filter, world.get_block(&block_position).await) continue;
{ }
continue; world
.set_block_state(
&block_position,
block_state_id,
BlockFlags::FORCE_STATE,
)
.await;
placed_blocks += 1;
to_update.push(block_position);
}
} }
if is_edge { }
}
}
Mode::Hollow => {
for x in start_x..=end_x {
for y in start_y..=end_y {
for z in start_z..=end_z {
let block_position = BlockPos(Vector3::new(x, y, z));
let is_edge = x == start_x
|| x == end_x
|| y == start_y
|| y == end_y
|| z == start_z
|| z == end_z;
if let Some(filter) = &option_filter
&& not_in_filter(filter, world.get_block(&block_position).await)
{
continue;
}
if is_edge {
world
.set_block_state(
&block_position,
block_state_id,
BlockFlags::FORCE_STATE,
)
.await;
} else {
world
.set_block_state(
&block_position,
0,
BlockFlags::FORCE_STATE,
)
.await;
}
placed_blocks += 1;
to_update.push(block_position);
}
}
}
}
Mode::Outline => {
for x in start_x..=end_x {
for y in start_y..=end_y {
for z in start_z..=end_z {
let block_position = BlockPos(Vector3::new(x, y, z));
let is_edge = x == start_x
|| x == end_x
|| y == start_y
|| y == end_y
|| z == start_z
|| z == end_z;
if !is_edge {
continue;
}
if let Some(filter) = &option_filter
&& not_in_filter(filter, world.get_block(&block_position).await)
{
continue;
}
world world
.set_block_state( .set_block_state(
&block_position, &block_position,
@ -181,85 +224,49 @@ impl CommandExecutor for Executor {
BlockFlags::FORCE_STATE, BlockFlags::FORCE_STATE,
) )
.await; .await;
} else { placed_blocks += 1;
to_update.push(block_position);
}
}
}
}
Mode::Strict => {
for x in start_x..=end_x {
for y in start_y..=end_y {
for z in start_z..=end_z {
let block_position = BlockPos(Vector3::new(x, y, z));
if let Some(filter) = &option_filter
&& not_in_filter(filter, world.get_block(&block_position).await)
{
continue;
}
world world
.set_block_state(&block_position, 0, BlockFlags::FORCE_STATE) .set_block_state(
&block_position,
block_state_id,
BlockFlags::SKIP_BLOCK_ADDED_CALLBACK,
)
.await; .await;
placed_blocks += 1;
} }
placed_blocks += 1;
to_update.push(block_position);
} }
} }
} }
} }
Mode::Outline => {
for x in start_x..=end_x { for i in to_update {
for y in start_y..=end_y { world.update_neighbors(&i, None).await;
for z in start_z..=end_z {
let block_position = BlockPos(Vector3::new(x, y, z));
let is_edge = x == start_x
|| x == end_x
|| y == start_y
|| y == end_y
|| z == start_z
|| z == end_z;
if !is_edge {
continue;
}
if let Some(filter) = &option_filter
&& not_in_filter(filter, world.get_block(&block_position).await)
{
continue;
}
world
.set_block_state(
&block_position,
block_state_id,
BlockFlags::FORCE_STATE,
)
.await;
placed_blocks += 1;
to_update.push(block_position);
}
}
}
} }
Mode::Strict => {
for x in start_x..=end_x {
for y in start_y..=end_y {
for z in start_z..=end_z {
let block_position = BlockPos(Vector3::new(x, y, z));
if let Some(filter) = &option_filter
&& not_in_filter(filter, world.get_block(&block_position).await)
{
continue;
}
world
.set_block_state(
&block_position,
block_state_id,
BlockFlags::SKIP_BLOCK_ADDED_CALLBACK,
)
.await;
placed_blocks += 1;
}
}
}
}
}
for i in to_update { sender
world.update_neighbors(&i, None).await; .send_message(TextComponent::translate(
} "commands.fill.success",
[TextComponent::text(placed_blocks.to_string())],
))
.await;
sender Ok(())
.send_message(TextComponent::translate( })
"commands.fill.success",
[TextComponent::text(placed_blocks.to_string())],
))
.await;
Ok(())
} }
} }

View File

@ -1,5 +1,3 @@
use async_trait::async_trait;
use crate::command::args::GetCloned; use crate::command::args::GetCloned;
use crate::command::args::gamemode::GamemodeArgumentConsumer; use crate::command::args::gamemode::GamemodeArgumentConsumer;
@ -9,13 +7,11 @@ use crate::command::args::players::PlayersArgumentConsumer;
use crate::command::CommandSender::Player; use crate::command::CommandSender::Player;
use crate::command::args::{Arg, ConsumedArgs}; use crate::command::args::{Arg, ConsumedArgs};
use crate::command::dispatcher::CommandError;
use crate::command::dispatcher::CommandError::{InvalidConsumption, InvalidRequirement}; use crate::command::dispatcher::CommandError::{InvalidConsumption, InvalidRequirement};
use crate::command::tree::CommandTree; use crate::command::tree::CommandTree;
use crate::command::tree::builder::{argument, require}; use crate::command::tree::builder::{argument, require};
use crate::command::{CommandExecutor, CommandSender}; use crate::command::{CommandExecutor, CommandResult, CommandSender};
use crate::entity::EntityBase; use crate::entity::EntityBase;
use crate::server::Server;
const NAMES: [&str; 1] = ["gamemode"]; const NAMES: [&str; 1] = ["gamemode"];
@ -26,82 +22,84 @@ const ARG_TARGET: &str = "target";
struct TargetSelfExecutor; struct TargetSelfExecutor;
#[async_trait]
impl CommandExecutor for TargetSelfExecutor { impl CommandExecutor for TargetSelfExecutor {
async fn execute<'a>( fn execute<'a>(
&self, &'a self,
sender: &mut CommandSender, sender: &'a CommandSender,
_server: &Server, _server: &'a crate::server::Server,
args: &ConsumedArgs<'a>, args: &'a ConsumedArgs<'a>,
) -> Result<(), CommandError> { ) -> CommandResult<'a> {
let Some(Arg::GameMode(gamemode)) = args.get_cloned(&ARG_GAMEMODE) else { Box::pin(async move {
return Err(InvalidConsumption(Some(ARG_GAMEMODE.into()))); let Some(Arg::GameMode(gamemode)) = args.get_cloned(&ARG_GAMEMODE) else {
}; return Err(InvalidConsumption(Some(ARG_GAMEMODE.into())));
};
if let Player(target) = sender { if let Player(target) = sender {
if target.gamemode.load() != gamemode { if target.gamemode.load() != gamemode {
target.set_gamemode(gamemode).await; target.set_gamemode(gamemode).await;
let gamemode_string = format!("{gamemode:?}").to_lowercase(); let gamemode_string = format!("{gamemode:?}").to_lowercase();
let gamemode_string = format!("gameMode.{gamemode_string}"); let gamemode_string = format!("gameMode.{gamemode_string}");
target target
.send_system_message(&TextComponent::translate( .send_system_message(&TextComponent::translate(
"commands.gamemode.success.self", "commands.gamemode.success.self",
[TextComponent::translate(gamemode_string, [])], [TextComponent::translate(gamemode_string, [])],
)) ))
.await; .await;
}
Ok(())
} else {
Err(InvalidRequirement)
} }
Ok(()) })
} else {
Err(InvalidRequirement)
}
} }
} }
struct TargetPlayerExecutor; struct TargetPlayerExecutor;
#[async_trait]
impl CommandExecutor for TargetPlayerExecutor { impl CommandExecutor for TargetPlayerExecutor {
async fn execute<'a>( fn execute<'a>(
&self, &'a self,
sender: &mut CommandSender, sender: &'a CommandSender,
_server: &Server, _server: &'a crate::server::Server,
args: &ConsumedArgs<'a>, args: &'a ConsumedArgs<'a>,
) -> Result<(), CommandError> { ) -> CommandResult<'a> {
let Some(Arg::GameMode(gamemode)) = args.get_cloned(&ARG_GAMEMODE) else { Box::pin(async move {
return Err(InvalidConsumption(Some(ARG_GAMEMODE.into()))); let Some(Arg::GameMode(gamemode)) = args.get_cloned(&ARG_GAMEMODE) else {
}; return Err(InvalidConsumption(Some(ARG_GAMEMODE.into())));
let Some(Arg::Players(targets)) = args.get(ARG_TARGET) else { };
return Err(InvalidConsumption(Some(ARG_TARGET.into()))); let Some(Arg::Players(targets)) = args.get(ARG_TARGET) else {
}; return Err(InvalidConsumption(Some(ARG_TARGET.into())));
};
let target_count = targets.len(); let target_count = targets.len();
for target in targets { for target in targets {
if target.gamemode.load() != gamemode { if target.gamemode.load() != gamemode {
target.set_gamemode(gamemode).await; target.set_gamemode(gamemode).await;
let gamemode_string = format!("{gamemode:?}").to_lowercase(); let gamemode_string = format!("{gamemode:?}").to_lowercase();
let gamemode_string = format!("gameMode.{gamemode_string}"); let gamemode_string = format!("gameMode.{gamemode_string}");
target target
.send_system_message(&TextComponent::translate( .send_system_message(&TextComponent::translate(
"gameMode.changed", "gameMode.changed",
[TextComponent::translate(gamemode_string.clone(), [])], [TextComponent::translate(gamemode_string.clone(), [])],
))
.await;
if target_count == 1 {
sender
.send_message(TextComponent::translate(
"commands.gamemode.success.other",
[
target.get_display_name().await,
TextComponent::translate(gamemode_string, []),
],
)) ))
.await; .await;
if target_count == 1 {
sender
.send_message(TextComponent::translate(
"commands.gamemode.success.other",
[
target.get_display_name().await,
TextComponent::translate(gamemode_string, []),
],
))
.await;
}
} }
} }
}
Ok(()) Ok(())
})
} }
} }

View File

@ -1,4 +1,3 @@
use async_trait::async_trait;
use pumpkin_data::game_rules::{GameRule, GameRuleRegistry, GameRuleValue}; use pumpkin_data::game_rules::{GameRule, GameRuleRegistry, GameRuleValue};
use crate::command::args::FindArg; use crate::command::args::FindArg;
@ -8,11 +7,9 @@ use crate::command::args::bounded_num::BoundedNumArgumentConsumer;
use crate::TextComponent; use crate::TextComponent;
use crate::command::args::ConsumedArgs; use crate::command::args::ConsumedArgs;
use crate::command::dispatcher::CommandError;
use crate::command::tree::CommandTree; use crate::command::tree::CommandTree;
use crate::command::tree::builder::{argument, literal}; use crate::command::tree::builder::{argument, literal};
use crate::command::{CommandExecutor, CommandSender}; use crate::command::{CommandExecutor, CommandResult, CommandSender};
use crate::server::Server;
const NAMES: [&str; 1] = ["gamerule"]; const NAMES: [&str; 1] = ["gamerule"];
@ -22,64 +19,66 @@ const ARG_NAME: &str = "value";
struct QueryExecutor(GameRule); struct QueryExecutor(GameRule);
#[async_trait]
impl CommandExecutor for QueryExecutor { impl CommandExecutor for QueryExecutor {
async fn execute<'a>( fn execute<'a>(
&self, &'a self,
sender: &mut CommandSender, sender: &'a CommandSender,
server: &Server, server: &'a crate::server::Server,
_args: &ConsumedArgs<'a>, _args: &'a ConsumedArgs<'a>,
) -> Result<(), CommandError> { ) -> CommandResult<'a> {
let key = TextComponent::text(self.0.to_string()); Box::pin(async move {
let level_info = server.level_info.read().await; let key = TextComponent::text(self.0.to_string());
let value = TextComponent::text(level_info.game_rules.get(&self.0).to_string()); let level_info = server.level_info.read().await;
drop(level_info); let value = TextComponent::text(level_info.game_rules.get(&self.0).to_string());
drop(level_info);
sender sender
.send_message(TextComponent::translate( .send_message(TextComponent::translate(
"commands.gamerule.query", "commands.gamerule.query",
[key, value], [key, value],
)) ))
.await; .await;
Ok(()) Ok(())
})
} }
} }
struct SetExecutor(GameRule); struct SetExecutor(GameRule);
#[async_trait]
impl CommandExecutor for SetExecutor { impl CommandExecutor for SetExecutor {
async fn execute<'a>( fn execute<'a>(
&self, &'a self,
sender: &mut CommandSender, sender: &'a CommandSender,
server: &Server, server: &'a crate::server::Server,
args: &ConsumedArgs<'a>, args: &'a ConsumedArgs<'a>,
) -> Result<(), CommandError> { ) -> CommandResult<'a> {
let key = TextComponent::text(self.0.to_string()); Box::pin(async move {
let mut level_info = server.level_info.write().await; let key = TextComponent::text(self.0.to_string());
let raw_value = level_info.game_rules.get_mut(&self.0); let mut level_info = server.level_info.write().await;
let raw_value = level_info.game_rules.get_mut(&self.0);
let value = TextComponent::text(match raw_value { let value = TextComponent::text(match raw_value {
GameRuleValue::Int(value) => { GameRuleValue::Int(value) => {
let arg_value = BoundedNumArgumentConsumer::<i64>::find_arg(args, ARG_NAME)??; let arg_value = BoundedNumArgumentConsumer::<i64>::find_arg(args, ARG_NAME)??;
*value = arg_value; *value = arg_value;
arg_value.to_string() arg_value.to_string()
} }
GameRuleValue::Bool(value) => { GameRuleValue::Bool(value) => {
let arg_value = BoolArgConsumer::find_arg(args, ARG_NAME)?; let arg_value = BoolArgConsumer::find_arg(args, ARG_NAME)?;
*value = arg_value; *value = arg_value;
arg_value.to_string() arg_value.to_string()
} }
}); });
drop(level_info); drop(level_info);
sender sender
.send_message(TextComponent::translate( .send_message(TextComponent::translate(
"commands.gamerule.set", "commands.gamerule.set",
[key, value], [key, value],
)) ))
.await; .await;
Ok(()) Ok(())
})
} }
} }

View File

@ -1,4 +1,3 @@
use async_trait::async_trait;
use pumpkin_data::data_component::DataComponent::MaxStackSize; use pumpkin_data::data_component::DataComponent::MaxStackSize;
use pumpkin_data::data_component_impl::{MaxStackSizeImpl, get}; use pumpkin_data::data_component_impl::{MaxStackSizeImpl, get};
use pumpkin_util::text::TextComponent; use pumpkin_util::text::TextComponent;
@ -12,7 +11,7 @@ use crate::command::args::resource::item::ItemArgumentConsumer;
use crate::command::args::{ConsumedArgs, FindArg, FindArgDefaultName}; use crate::command::args::{ConsumedArgs, FindArg, FindArgDefaultName};
use crate::command::tree::CommandTree; use crate::command::tree::CommandTree;
use crate::command::tree::builder::{argument, argument_default_name}; use crate::command::tree::builder::{argument, argument_default_name};
use crate::command::{CommandError, CommandExecutor, CommandSender}; use crate::command::{CommandExecutor, CommandResult, CommandSender};
use crate::entity::EntityBase; use crate::entity::EntityBase;
const NAMES: [&str; 1] = ["give"]; const NAMES: [&str; 1] = ["give"];
@ -30,93 +29,96 @@ fn item_count_consumer() -> BoundedNumArgumentConsumer<i32> {
struct Executor; struct Executor;
#[async_trait]
impl CommandExecutor for Executor { impl CommandExecutor for Executor {
async fn execute<'a>( fn execute<'a>(
&self, &'a self,
sender: &mut CommandSender, sender: &'a CommandSender,
_server: &crate::server::Server, _server: &'a crate::server::Server,
args: &ConsumedArgs<'a>, args: &'a ConsumedArgs<'a>,
) -> Result<(), CommandError> { ) -> CommandResult<'a> {
let targets = PlayersArgumentConsumer.find_arg_default_name(args)?; Box::pin(async move {
let targets = PlayersArgumentConsumer.find_arg_default_name(args)?;
let (item_name, item) = ItemArgumentConsumer::find_arg(args, ARG_ITEM)?; let (item_name, item) = ItemArgumentConsumer::find_arg(args, ARG_ITEM)?;
let item_count = match item_count_consumer().find_arg_default_name(args) { let item_count = match item_count_consumer().find_arg_default_name(args) {
Err(_) => 1, Err(_) => 1,
Ok(Ok(count)) => count, Ok(Ok(count)) => count,
Ok(Err(err)) => { Ok(Err(err)) => {
let err_msg = match err { let err_msg = match err {
NotInBounds::LowerBound(_, min) => { NotInBounds::LowerBound(_, min) => {
format!("Can't give less than {min} of {item_name}") format!("Can't give less than {min} of {item_name}")
} }
NotInBounds::UpperBound(_, max) => { NotInBounds::UpperBound(_, max) => {
format!("Can't give more than {max} of {item_name}") format!("Can't give more than {max} of {item_name}")
} }
}; };
sender sender
.send_message(TextComponent::text(err_msg).color(Color::Named(NamedColor::Red))) .send_message(
.await; TextComponent::text(err_msg).color(Color::Named(NamedColor::Red)),
return Ok(()); )
} .await;
}; return Ok(());
for target in targets {
let max_stack = i32::from(
item.components
.iter()
.find_map(|(id, component)| {
(id == &MaxStackSize).then(|| get::<MaxStackSizeImpl>(*component).size)
})
.unwrap(),
);
let mut remaining = item_count;
while remaining > 0 {
let take = remaining.min(max_stack);
let mut stack = ItemStack::new(take as u8, item);
target.inventory().insert_stack_anywhere(&mut stack).await;
if !stack.is_empty() {
target.drop_item(stack).await;
} }
remaining -= take; };
}
}
let msg = if targets.len() == 1 {
TextComponent::translate(
"commands.give.success.single",
[
TextComponent::text(item_count.to_string()),
TextComponent::text("[")
.add_child(item.translated_name())
.add_child(TextComponent::text("]"))
.hover_event(HoverEvent::ShowItem {
id: item_name.to_string().into(),
count: Some(item_count),
}),
targets[0].get_display_name().await,
],
)
} else {
TextComponent::translate(
"commands.give.success.multiple",
[
TextComponent::text(item_count.to_string()),
TextComponent::text("[")
.add_child(item.translated_name())
.add_child(TextComponent::text("]"))
.hover_event(HoverEvent::ShowItem {
id: item_name.to_string().into(),
count: Some(item_count),
}),
TextComponent::text(targets.len().to_string()),
],
)
};
sender.send_message(msg).await;
Ok(()) for target in targets {
let max_stack = i32::from(
item.components
.iter()
.find_map(|(id, component)| {
(id == &MaxStackSize).then(|| get::<MaxStackSizeImpl>(*component).size)
})
.unwrap(),
);
let mut remaining = item_count;
while remaining > 0 {
let take = remaining.min(max_stack);
let mut stack = ItemStack::new(take as u8, item);
target.inventory().insert_stack_anywhere(&mut stack).await;
if !stack.is_empty() {
target.drop_item(stack).await;
}
remaining -= take;
}
}
let msg = if targets.len() == 1 {
TextComponent::translate(
"commands.give.success.single",
[
TextComponent::text(item_count.to_string()),
TextComponent::text("[")
.add_child(item.translated_name())
.add_child(TextComponent::text("]"))
.hover_event(HoverEvent::ShowItem {
id: item_name.to_string().into(),
count: Some(item_count),
}),
targets[0].get_display_name().await,
],
)
} else {
TextComponent::translate(
"commands.give.success.multiple",
[
TextComponent::text(item_count.to_string()),
TextComponent::text("[")
.add_child(item.translated_name())
.add_child(TextComponent::text("]"))
.hover_event(HoverEvent::ShowItem {
id: item_name.to_string().into(),
count: Some(item_count),
}),
TextComponent::text(targets.len().to_string()),
],
)
};
sender.send_message(msg).await;
Ok(())
})
} }
} }

View File

@ -1,4 +1,3 @@
use async_trait::async_trait;
use futures::StreamExt; use futures::StreamExt;
use pumpkin_util::text::TextComponent; use pumpkin_util::text::TextComponent;
use pumpkin_util::text::click::ClickEvent; use pumpkin_util::text::click::ClickEvent;
@ -7,12 +6,10 @@ use pumpkin_util::text::color::{Color, NamedColor};
use crate::command::args::bounded_num::BoundedNumArgumentConsumer; use crate::command::args::bounded_num::BoundedNumArgumentConsumer;
use crate::command::args::command::CommandTreeArgumentConsumer; use crate::command::args::command::CommandTreeArgumentConsumer;
use crate::command::args::{Arg, ConsumedArgs, FindArgDefaultName}; use crate::command::args::{Arg, ConsumedArgs, FindArgDefaultName};
use crate::command::dispatcher::CommandError;
use crate::command::dispatcher::CommandError::InvalidConsumption; use crate::command::dispatcher::CommandError::InvalidConsumption;
use crate::command::tree::builder::{argument, argument_default_name}; use crate::command::tree::builder::{argument, argument_default_name};
use crate::command::tree::{Command, CommandTree}; use crate::command::tree::{Command, CommandTree};
use crate::command::{CommandExecutor, CommandSender}; use crate::command::{CommandExecutor, CommandResult, CommandSender};
use crate::server::Server;
const NAMES: [&str; 3] = ["help", "h", "?"]; const NAMES: [&str; 3] = ["help", "h", "?"];
@ -28,207 +25,214 @@ fn page_number_consumer() -> BoundedNumArgumentConsumer<i32> {
struct Executor; struct Executor;
#[async_trait]
impl CommandExecutor for Executor { impl CommandExecutor for Executor {
async fn execute<'a>( fn execute<'a>(
&self, &'a self,
sender: &mut CommandSender, sender: &'a CommandSender,
_server: &Server, _server: &'a crate::server::Server,
args: &ConsumedArgs<'a>, args: &'a ConsumedArgs<'a>,
) -> Result<(), CommandError> { ) -> CommandResult<'a> {
let Some(Arg::CommandTree(tree)) = args.get(&ARG_COMMAND) else { Box::pin(async move {
return Err(InvalidConsumption(Some(ARG_COMMAND.into()))); let Some(Arg::CommandTree(tree)) = args.get(&ARG_COMMAND) else {
}; return Err(InvalidConsumption(Some(ARG_COMMAND.into())));
};
let command_names = tree.names.join(", /"); let command_names = tree.names.join(", /");
let usage = format!("{tree}"); let usage = format!("{tree}");
let description = &tree.description; let description = &tree.description;
let header_text = format!(" Help - /{} ", tree.names[0]); let header_text = format!(" Help - /{} ", tree.names[0]);
let mut message = TextComponent::text("") let mut message = TextComponent::text("")
.add_child( .add_child(
TextComponent::text("-".repeat((52 - header_text.len()) / 2) + " ") TextComponent::text("-".repeat((52 - header_text.len()) / 2) + " ")
.color_named(NamedColor::Yellow), .color_named(NamedColor::Yellow),
)
.add_child(TextComponent::text(header_text.clone()))
.add_child(
TextComponent::text(
" ".to_owned() + &"-".repeat((52 - header_text.len()) / 2) + "\n",
) )
.color_named(NamedColor::Yellow), .add_child(TextComponent::text(header_text.clone()))
) .add_child(
.add_child( TextComponent::text(
TextComponent::text("Command: ") " ".to_owned() + &"-".repeat((52 - header_text.len()) / 2) + "\n",
.color_named(NamedColor::Aqua)
.add_child(
TextComponent::text(format!("/{command_names}"))
.color_named(NamedColor::Gold)
.bold(),
) )
.add_child(TextComponent::text("\n").color_named(NamedColor::White)) .color_named(NamedColor::Yellow),
.click_event(ClickEvent::SuggestCommand { )
command: format!("/{}", tree.names[0]).into(), .add_child(
}), TextComponent::text("Command: ")
) .color_named(NamedColor::Aqua)
.add_child( .add_child(
TextComponent::text("Description: ") TextComponent::text(format!("/{command_names}"))
.color_named(NamedColor::Aqua) .color_named(NamedColor::Gold)
.add_child( .bold(),
TextComponent::text(format!("{description}\n")) )
.color_named(NamedColor::White), .add_child(TextComponent::text("\n").color_named(NamedColor::White))
), .click_event(ClickEvent::SuggestCommand {
) command: format!("/{}", tree.names[0]).into(),
.add_child( }),
TextComponent::text("Usage: ") )
.color_named(NamedColor::Aqua) .add_child(
.add_child( TextComponent::text("Description: ")
TextComponent::text(format!("{usage}\n")).color_named(NamedColor::White), .color_named(NamedColor::Aqua)
) .add_child(
.click_event(ClickEvent::SuggestCommand { TextComponent::text(format!("{description}\n"))
command: format!("{tree}").into(), .color_named(NamedColor::White),
}), ),
); )
.add_child(
TextComponent::text("Usage: ")
.color_named(NamedColor::Aqua)
.add_child(
TextComponent::text(format!("{usage}\n"))
.color_named(NamedColor::White),
)
.click_event(ClickEvent::SuggestCommand {
command: format!("{tree}").into(),
}),
);
message = message = message
message.add_child(TextComponent::text("-".repeat(52)).color_named(NamedColor::Yellow)); .add_child(TextComponent::text("-".repeat(52)).color_named(NamedColor::Yellow));
sender.send_message(message).await; sender.send_message(message).await;
Ok(()) Ok(())
})
} }
} }
struct BaseHelpExecutor; struct BaseHelpExecutor;
#[async_trait]
impl CommandExecutor for BaseHelpExecutor { impl CommandExecutor for BaseHelpExecutor {
#[expect(clippy::too_many_lines)] #[expect(clippy::too_many_lines)]
async fn execute<'a>( fn execute<'a>(
&self, &'a self,
sender: &mut CommandSender, sender: &'a CommandSender,
server: &Server, server: &'a crate::server::Server,
args: &ConsumedArgs<'a>, args: &'a ConsumedArgs<'a>,
) -> Result<(), CommandError> { ) -> CommandResult<'a> {
let page_number = match page_number_consumer().find_arg_default_name(args) { Box::pin(async move {
Err(_) => 1, let page_number = match page_number_consumer().find_arg_default_name(args) {
Ok(Ok(number)) => number, Err(_) => 1,
Ok(Err(_)) => { Ok(Ok(number)) => number,
sender Ok(Err(_)) => {
.send_message( sender
TextComponent::text("Invalid page number.") .send_message(
.color(Color::Named(NamedColor::Red)), TextComponent::text("Invalid page number.")
) .color(Color::Named(NamedColor::Red)),
.await; )
return Ok(()); .await;
} return Ok(());
};
let dispatcher = server.command_dispatcher.read().await;
let commands: Vec<&CommandTree> = dispatcher
.commands
.values()
.filter_map(|cmd| match cmd {
Command::Tree(tree) => Some(tree),
Command::Alias(_) => None,
})
.collect();
let mut commands: Vec<&CommandTree> = futures::stream::iter(commands.iter())
.filter(|tree| async {
if let Some(perm) = dispatcher.permissions.get(&tree.names[0]) {
return sender.has_permission(perm.as_str()).await;
} }
false };
})
.collect()
.await;
commands.sort_by(|a, b| a.names[0].cmp(&b.names[0])); let dispatcher = server.command_dispatcher.read().await;
let commands: Vec<&CommandTree> = dispatcher
let total_pages = (commands.len() as i32 + COMMANDS_PER_PAGE - 1) / COMMANDS_PER_PAGE; .commands
let page = page_number.min(total_pages); .values()
.filter_map(|cmd| match cmd {
let start = (page - 1) * COMMANDS_PER_PAGE; Command::Tree(tree) => Some(tree),
let end = start + COMMANDS_PER_PAGE; Command::Alias(_) => None,
let page_commands = &commands[start as usize..end.min(commands.len() as i32) as usize];
let arrow_left = if page > 1 {
let cmd = format!("/help {}", page - 1);
TextComponent::text("<<<")
.color(Color::Named(NamedColor::Aqua))
.click_event(ClickEvent::RunCommand {
command: cmd.into(),
}) })
} else { .collect();
TextComponent::text("<<<").color(Color::Named(NamedColor::Gray))
};
let arrow_right = if page < total_pages { let mut commands: Vec<&CommandTree> = futures::stream::iter(commands.iter())
let cmd = format!("/help {}", page + 1); .filter(|tree| async {
TextComponent::text(">>>") if let Some(perm) = dispatcher.permissions.get(&tree.names[0]) {
.color(Color::Named(NamedColor::Aqua)) return sender.has_permission(perm.as_str()).await;
.click_event(ClickEvent::RunCommand { }
command: cmd.into(), false
}) })
} else { .collect()
TextComponent::text(">>>").color(Color::Named(NamedColor::Gray)) .await;
};
let header_text = format!(" Help - Page {page}/{total_pages} "); commands.sort_by(|a, b| a.names[0].cmp(&b.names[0]));
let mut message = TextComponent::text("") let total_pages = (commands.len() as i32 + COMMANDS_PER_PAGE - 1) / COMMANDS_PER_PAGE;
.add_child( let page = page_number.min(total_pages);
TextComponent::text("-".repeat((52 - header_text.len() - 3) / 2) + " ")
.color_named(NamedColor::Yellow), let start = (page - 1) * COMMANDS_PER_PAGE;
) let end = start + COMMANDS_PER_PAGE;
.add_child(arrow_left.clone()) let page_commands = &commands[start as usize..end.min(commands.len() as i32) as usize];
.add_child(TextComponent::text(header_text.clone()))
.add_child(arrow_right.clone()) let arrow_left = if page > 1 {
.add_child( let cmd = format!("/help {}", page - 1);
TextComponent::text( TextComponent::text("<<<")
" ".to_owned() + &"-".repeat((52 - header_text.len() - 3) / 2) + "\n", .color(Color::Named(NamedColor::Aqua))
.click_event(ClickEvent::RunCommand {
command: cmd.into(),
})
} else {
TextComponent::text("<<<").color(Color::Named(NamedColor::Gray))
};
let arrow_right = if page < total_pages {
let cmd = format!("/help {}", page + 1);
TextComponent::text(">>>")
.color(Color::Named(NamedColor::Aqua))
.click_event(ClickEvent::RunCommand {
command: cmd.into(),
})
} else {
TextComponent::text(">>>").color(Color::Named(NamedColor::Gray))
};
let header_text = format!(" Help - Page {page}/{total_pages} ");
let mut message = TextComponent::text("")
.add_child(
TextComponent::text("-".repeat((52 - header_text.len() - 3) / 2) + " ")
.color_named(NamedColor::Yellow),
) )
.color_named(NamedColor::Yellow), .add_child(arrow_left.clone())
); .add_child(TextComponent::text(header_text.clone()))
.add_child(arrow_right.clone())
for tree in page_commands { .add_child(
message = message.add_child( TextComponent::text(
TextComponent::text("/".to_owned() + &tree.names.join(", /")) " ".to_owned() + &"-".repeat((52 - header_text.len() - 3) / 2) + "\n",
.color_named(NamedColor::Gold)
.add_child(TextComponent::text(" - ").color_named(NamedColor::Yellow))
.add_child(
TextComponent::text(tree.description.clone() + "\n")
.color_named(NamedColor::White),
) )
.add_child(TextComponent::text(" Usage: ").color_named(NamedColor::Yellow)) .color_named(NamedColor::Yellow),
.add_child( );
TextComponent::text(format!("{tree}")).color_named(NamedColor::White),
for tree in page_commands {
message = message.add_child(
TextComponent::text("/".to_owned() + &tree.names.join(", /"))
.color_named(NamedColor::Gold)
.add_child(TextComponent::text(" - ").color_named(NamedColor::Yellow))
.add_child(
TextComponent::text(tree.description.clone() + "\n")
.color_named(NamedColor::White),
)
.add_child(
TextComponent::text(" Usage: ").color_named(NamedColor::Yellow),
)
.add_child(
TextComponent::text(format!("{tree}")).color_named(NamedColor::White),
)
.add_child(TextComponent::text("\n").color_named(NamedColor::White))
.click_event(ClickEvent::SuggestCommand {
command: format!("/{}", tree.names[0]).into(),
}),
);
}
let footer_text = format!(" Page {page}/{total_pages} ");
message = message
.add_child(
TextComponent::text("-".repeat((52 - footer_text.len() - 3) / 2) + " ")
.color_named(NamedColor::Yellow),
)
.add_child(arrow_left)
.add_child(TextComponent::text(footer_text.clone()))
.add_child(arrow_right)
.add_child(
TextComponent::text(
" ".to_owned() + &"-".repeat((52 - footer_text.len() - 3) / 2),
) )
.add_child(TextComponent::text("\n").color_named(NamedColor::White))
.click_event(ClickEvent::SuggestCommand {
command: format!("/{}", tree.names[0]).into(),
}),
);
}
let footer_text = format!(" Page {page}/{total_pages} ");
message = message
.add_child(
TextComponent::text("-".repeat((52 - footer_text.len() - 3) / 2) + " ")
.color_named(NamedColor::Yellow), .color_named(NamedColor::Yellow),
) );
.add_child(arrow_left)
.add_child(TextComponent::text(footer_text.clone()))
.add_child(arrow_right)
.add_child(
TextComponent::text(" ".to_owned() + &"-".repeat((52 - footer_text.len() - 3) / 2))
.color_named(NamedColor::Yellow),
);
sender.send_message(message).await; sender.send_message(message).await;
Ok(()) Ok(())
})
} }
} }

View File

@ -1,13 +1,12 @@
use async_trait::async_trait;
use pumpkin_util::text::TextComponent; use pumpkin_util::text::TextComponent;
use pumpkin_util::text::color::NamedColor; use pumpkin_util::text::color::NamedColor;
use crate::command::CommandError;
use crate::command::args::message::MsgArgConsumer; use crate::command::args::message::MsgArgConsumer;
use crate::command::args::players::PlayersArgumentConsumer; use crate::command::args::players::PlayersArgumentConsumer;
use crate::command::args::{Arg, ConsumedArgs}; use crate::command::args::{Arg, ConsumedArgs};
use crate::command::tree::CommandTree; use crate::command::tree::CommandTree;
use crate::command::tree::builder::argument; use crate::command::tree::builder::argument;
use crate::command::{CommandError, CommandResult};
use crate::command::{CommandExecutor, CommandSender}; use crate::command::{CommandExecutor, CommandSender};
use crate::entity::EntityBase; use crate::entity::EntityBase;
use crate::net::DisconnectReason; use crate::net::DisconnectReason;
@ -22,31 +21,32 @@ const ARG_REASON: &str = "reason";
struct Executor; struct Executor;
#[async_trait]
impl CommandExecutor for Executor { impl CommandExecutor for Executor {
async fn execute<'a>( fn execute<'a>(
&self, &'a self,
sender: &mut CommandSender, sender: &'a CommandSender,
_server: &crate::server::Server, _server: &'a crate::server::Server,
args: &ConsumedArgs<'a>, args: &'a ConsumedArgs<'a>,
) -> Result<(), CommandError> { ) -> CommandResult<'a> {
let Some(Arg::Players(targets)) = args.get(&ARG_TARGETS) else { Box::pin(async move {
return Err(InvalidConsumption(Some(ARG_TARGETS.into()))); let Some(Arg::Players(targets)) = args.get(&ARG_TARGETS) else {
}; return Err(InvalidConsumption(Some(ARG_TARGETS.into())));
};
let reason = match args.get(&ARG_REASON) { let reason = match args.get(&ARG_REASON) {
Some(Arg::Msg(r)) => TextComponent::text(r.clone()), Some(Arg::Msg(r)) => TextComponent::text(r.clone()),
_ => TextComponent::translate("multiplayer.disconnect.kicked", []), _ => TextComponent::translate("multiplayer.disconnect.kicked", []),
}; };
for target in targets { for target in targets {
target.kick(DisconnectReason::Kicked, reason.clone()).await; target.kick(DisconnectReason::Kicked, reason.clone()).await;
let mut msg = TextComponent::text("Kicked: "); let mut msg = TextComponent::text("Kicked: ");
msg = msg.add_child(target.get_display_name().await); msg = msg.add_child(target.get_display_name().await);
sender.send_message(msg.color_named(NamedColor::Blue)).await; sender.send_message(msg.color_named(NamedColor::Blue)).await;
} }
Ok(()) Ok(())
})
} }
} }

View File

@ -1,11 +1,10 @@
use async_trait::async_trait;
use pumpkin_util::text::TextComponent; use pumpkin_util::text::TextComponent;
use crate::command::args::entities::EntitiesArgumentConsumer; use crate::command::args::entities::EntitiesArgumentConsumer;
use crate::command::args::{Arg, ConsumedArgs}; use crate::command::args::{Arg, ConsumedArgs};
use crate::command::tree::CommandTree; use crate::command::tree::CommandTree;
use crate::command::tree::builder::{argument, require}; use crate::command::tree::builder::{argument, require};
use crate::command::{CommandError, CommandExecutor, CommandSender}; use crate::command::{CommandError, CommandExecutor, CommandResult, CommandSender};
use crate::entity::EntityBase; use crate::entity::EntityBase;
use CommandError::InvalidConsumption; use CommandError::InvalidConsumption;
@ -16,62 +15,64 @@ const ARG_TARGET: &str = "target";
struct Executor; struct Executor;
#[async_trait]
impl CommandExecutor for Executor { impl CommandExecutor for Executor {
async fn execute<'a>( fn execute<'a>(
&self, &'a self,
sender: &mut CommandSender, sender: &'a CommandSender,
_server: &crate::server::Server, _server: &'a crate::server::Server,
args: &ConsumedArgs<'a>, args: &'a ConsumedArgs<'a>,
) -> Result<(), CommandError> { ) -> CommandResult<'a> {
let Some(Arg::Entities(targets)) = args.get(&ARG_TARGET) else { Box::pin(async move {
return Err(InvalidConsumption(Some(ARG_TARGET.into()))); let Some(Arg::Entities(targets)) = args.get(&ARG_TARGET) else {
}; return Err(InvalidConsumption(Some(ARG_TARGET.into())));
};
let target_count = targets.len(); let target_count = targets.len();
for target in targets { for target in targets {
target.kill(target.clone()).await; target.kill(target.clone()).await;
} }
let msg = if target_count == 1 { let msg = if target_count == 1 {
TextComponent::translate( TextComponent::translate(
"commands.kill.success.single", "commands.kill.success.single",
[targets[0].get_display_name().await], [targets[0].get_display_name().await],
) )
} else { } else {
TextComponent::translate( TextComponent::translate(
"commands.kill.success.multiple", "commands.kill.success.multiple",
[TextComponent::text(target_count.to_string())], [TextComponent::text(target_count.to_string())],
) )
}; };
sender.send_message(msg).await; sender.send_message(msg).await;
Ok(()) Ok(())
})
} }
} }
struct SelfExecutor; struct SelfExecutor;
#[async_trait]
impl CommandExecutor for SelfExecutor { impl CommandExecutor for SelfExecutor {
async fn execute<'a>( fn execute<'a>(
&self, &'a self,
sender: &mut CommandSender, sender: &'a CommandSender,
_server: &crate::server::Server, _server: &'a crate::server::Server,
_args: &ConsumedArgs<'a>, _args: &'a ConsumedArgs<'a>,
) -> Result<(), CommandError> { ) -> CommandResult<'a> {
let target = sender.as_player().ok_or(CommandError::InvalidRequirement)?; Box::pin(async move {
target.kill(target.clone()).await; let target = sender.as_player().ok_or(CommandError::InvalidRequirement)?;
target.kill(target.clone()).await;
sender sender
.send_message(TextComponent::translate( .send_message(TextComponent::translate(
"commands.kill.success.single", "commands.kill.success.single",
[target.get_display_name().await], [target.get_display_name().await],
)) ))
.await; .await;
Ok(()) Ok(())
})
} }
} }

View File

@ -1,12 +1,11 @@
use std::sync::Arc; use std::sync::Arc;
use async_trait::async_trait;
use pumpkin_config::BASIC_CONFIG; use pumpkin_config::BASIC_CONFIG;
use pumpkin_util::text::TextComponent; use pumpkin_util::text::TextComponent;
use crate::{ use crate::{
command::{ command::{
CommandError, CommandExecutor, CommandSender, args::ConsumedArgs, tree::CommandTree, CommandExecutor, CommandResult, CommandSender, args::ConsumedArgs, tree::CommandTree,
}, },
entity::player::Player, entity::player::Player,
}; };
@ -17,28 +16,29 @@ const DESCRIPTION: &str = "Print the list of online players.";
struct Executor; struct Executor;
#[async_trait]
impl CommandExecutor for Executor { impl CommandExecutor for Executor {
async fn execute<'a>( fn execute<'a>(
&self, &'a self,
sender: &mut CommandSender, sender: &'a CommandSender,
server: &crate::server::Server, server: &'a crate::server::Server,
_args: &ConsumedArgs<'a>, _args: &'a ConsumedArgs<'a>,
) -> Result<(), CommandError> { ) -> CommandResult<'a> {
let players: Vec<Arc<Player>> = server.get_all_players().await; Box::pin(async move {
let players: Vec<Arc<Player>> = server.get_all_players().await;
sender sender
.send_message(TextComponent::translate( .send_message(TextComponent::translate(
"commands.list.players", "commands.list.players",
[ [
TextComponent::text(players.len().to_string()), TextComponent::text(players.len().to_string()),
TextComponent::text(BASIC_CONFIG.max_players.to_string()), TextComponent::text(BASIC_CONFIG.max_players.to_string()),
TextComponent::text(get_player_names(players)), TextComponent::text(get_player_names(players)),
], ],
)) ))
.await; .await;
Ok(()) Ok(())
})
} }
} }

View File

@ -1,12 +1,10 @@
use async_trait::async_trait;
use pumpkin_data::world::EMOTE_COMMAND; use pumpkin_data::world::EMOTE_COMMAND;
use pumpkin_util::text::TextComponent; use pumpkin_util::text::TextComponent;
use crate::command::{ use crate::command::{
CommandError, CommandExecutor, CommandSender, CommandError, CommandExecutor, CommandResult, CommandSender,
args::{Arg, ConsumedArgs, message::MsgArgConsumer}, args::{Arg, ConsumedArgs, message::MsgArgConsumer},
tree::CommandTree, tree::{CommandTree, builder::argument},
tree::builder::argument,
}; };
use CommandError::InvalidConsumption; use CommandError::InvalidConsumption;
@ -18,27 +16,28 @@ const ARG_MESSAGE: &str = "action";
struct Executor; struct Executor;
#[async_trait]
impl CommandExecutor for Executor { impl CommandExecutor for Executor {
async fn execute<'a>( fn execute<'a>(
&self, &'a self,
sender: &mut CommandSender, sender: &'a CommandSender,
server: &crate::server::Server, server: &'a crate::server::Server,
args: &ConsumedArgs<'a>, args: &'a ConsumedArgs<'a>,
) -> Result<(), CommandError> { ) -> CommandResult<'a> {
let Some(Arg::Msg(msg)) = args.get(ARG_MESSAGE) else { Box::pin(async move {
return Err(InvalidConsumption(Some(ARG_MESSAGE.into()))); let Some(Arg::Msg(msg)) = args.get(ARG_MESSAGE) else {
}; return Err(InvalidConsumption(Some(ARG_MESSAGE.into())));
};
server server
.broadcast_message( .broadcast_message(
&TextComponent::text(msg.clone()), &TextComponent::text(msg.clone()),
&TextComponent::text(format!("{sender}")), &TextComponent::text(format!("{sender}")),
EMOTE_COMMAND, EMOTE_COMMAND,
None, None,
) )
.await; .await;
Ok(()) Ok(())
})
} }
} }

View File

@ -1,15 +1,16 @@
use async_trait::async_trait;
use pumpkin_data::world::{MSG_COMMAND_INCOMING, MSG_COMMAND_OUTGOING}; use pumpkin_data::world::{MSG_COMMAND_INCOMING, MSG_COMMAND_OUTGOING};
use pumpkin_util::text::TextComponent; use pumpkin_util::text::TextComponent;
use crate::command::{ use crate::command::{
CommandError, CommandExecutor, CommandSender, CommandError, CommandExecutor, CommandResult, CommandSender,
args::{ args::{
Arg, ConsumedArgs, FindArgDefaultName, message::MsgArgConsumer, Arg, ConsumedArgs, FindArgDefaultName, message::MsgArgConsumer,
players::PlayersArgumentConsumer, players::PlayersArgumentConsumer,
}, },
tree::CommandTree, tree::{
tree::builder::{argument, argument_default_name}, CommandTree,
builder::{argument, argument_default_name},
},
}; };
use crate::entity::EntityBase; use crate::entity::EntityBase;
use CommandError::InvalidConsumption; use CommandError::InvalidConsumption;
@ -22,42 +23,43 @@ const ARG_MESSAGE: &str = "message";
struct Executor; struct Executor;
#[async_trait]
impl CommandExecutor for Executor { impl CommandExecutor for Executor {
async fn execute<'a>( fn execute<'a>(
&self, &'a self,
sender: &mut CommandSender, sender: &'a CommandSender,
_server: &crate::server::Server, _server: &'a crate::server::Server,
args: &ConsumedArgs<'a>, args: &'a ConsumedArgs<'a>,
) -> Result<(), CommandError> { ) -> CommandResult<'a> {
let Some(Arg::Msg(msg)) = args.get(ARG_MESSAGE) else { Box::pin(async move {
return Err(InvalidConsumption(Some(ARG_MESSAGE.into()))); let Some(Arg::Msg(msg)) = args.get(ARG_MESSAGE) else {
}; return Err(InvalidConsumption(Some(ARG_MESSAGE.into())));
let targets = PlayersArgumentConsumer.find_arg_default_name(args)?; };
let player = sender.as_player().ok_or(CommandError::InvalidRequirement)?; let targets = PlayersArgumentConsumer.find_arg_default_name(args)?;
let player = sender.as_player().ok_or(CommandError::InvalidRequirement)?;
for target in targets { for target in targets {
player player
.send_message( .send_message(
&TextComponent::text(msg.clone()), &TextComponent::text(msg.clone()),
MSG_COMMAND_OUTGOING, MSG_COMMAND_OUTGOING,
&player.get_display_name().await, &player.get_display_name().await,
Some(&target.get_display_name().await), Some(&target.get_display_name().await),
) )
.await; .await;
} }
for target in targets { for target in targets {
target target
.send_message( .send_message(
&TextComponent::text(msg.clone()), &TextComponent::text(msg.clone()),
MSG_COMMAND_INCOMING, MSG_COMMAND_INCOMING,
&player.get_display_name().await, &player.get_display_name().await,
Some(&target.get_display_name().await), Some(&target.get_display_name().await),
) )
.await; .await;
} }
Ok(()) Ok(())
})
} }
} }

View File

@ -1,3 +1,4 @@
use crate::command::CommandResult;
use crate::entity::EntityBase; use crate::entity::EntityBase;
use crate::{ use crate::{
command::{ command::{
@ -9,7 +10,6 @@ use crate::{
data::{SaveJSONConfiguration, op_data::OPERATOR_CONFIG}, data::{SaveJSONConfiguration, op_data::OPERATOR_CONFIG},
}; };
use CommandError::InvalidConsumption; use CommandError::InvalidConsumption;
use async_trait::async_trait;
use pumpkin_config::{BASIC_CONFIG, op::Op}; use pumpkin_config::{BASIC_CONFIG, op::Op};
use pumpkin_util::text::TextComponent; use pumpkin_util::text::TextComponent;
@ -19,66 +19,67 @@ const ARG_TARGETS: &str = "targets";
struct Executor; struct Executor;
#[async_trait]
impl CommandExecutor for Executor { impl CommandExecutor for Executor {
async fn execute<'a>( fn execute<'a>(
&self, &'a self,
sender: &mut CommandSender, sender: &'a CommandSender,
server: &crate::server::Server, server: &'a crate::server::Server,
args: &ConsumedArgs<'a>, args: &'a ConsumedArgs<'a>,
) -> Result<(), CommandError> { ) -> CommandResult<'a> {
let mut config = OPERATOR_CONFIG.write().await; Box::pin(async move {
let mut config = OPERATOR_CONFIG.write().await;
let Some(Arg::Players(targets)) = args.get(&ARG_TARGETS) else { let Some(Arg::Players(targets)) = args.get(&ARG_TARGETS) else {
return Err(InvalidConsumption(Some(ARG_TARGETS.into()))); return Err(InvalidConsumption(Some(ARG_TARGETS.into())));
};
for player in targets {
let new_level = BASIC_CONFIG
.op_permission_level
.min(sender.permission_lvl());
if player.permission_lvl.load() == new_level {
sender
.send_message(TextComponent::translate("commands.op.failed", []))
.await;
continue;
}
if let Some(op) = config
.ops
.iter_mut()
.find(|o| o.uuid == player.gameprofile.id)
{
op.level = new_level;
} else {
let op_entry = Op::new(
player.gameprofile.id,
player.gameprofile.name.clone(),
new_level,
false,
);
config.ops.push(op_entry);
}
config.save();
{
let command_dispatcher = server.command_dispatcher.read().await;
player
.set_permission_lvl(new_level, &command_dispatcher)
.await;
}; };
sender for player in targets {
.send_message(TextComponent::translate( let new_level = BASIC_CONFIG
"commands.op.success", .op_permission_level
[player.get_display_name().await], .min(sender.permission_lvl());
))
.await;
}
Ok(()) if player.permission_lvl.load() == new_level {
sender
.send_message(TextComponent::translate("commands.op.failed", []))
.await;
continue;
}
if let Some(op) = config
.ops
.iter_mut()
.find(|o| o.uuid == player.gameprofile.id)
{
op.level = new_level;
} else {
let op_entry = Op::new(
player.gameprofile.id,
player.gameprofile.name.clone(),
new_level,
false,
);
config.ops.push(op_entry);
}
config.save();
{
let command_dispatcher = server.command_dispatcher.read().await;
player
.set_permission_lvl(new_level, &command_dispatcher)
.await;
};
sender
.send_message(TextComponent::translate(
"commands.op.success",
[player.get_display_name().await],
))
.await;
}
Ok(())
})
} }
} }

View File

@ -1,14 +1,12 @@
use crate::{ use crate::{
command::{ command::{
CommandError, CommandExecutor, CommandSender, CommandError, CommandExecutor, CommandResult, CommandSender,
args::{Arg, ConsumedArgs, simple::SimpleArgConsumer}, args::{Arg, ConsumedArgs, simple::SimpleArgConsumer},
tree::CommandTree, tree::{CommandTree, builder::argument},
tree::builder::argument,
}, },
data::{SaveJSONConfiguration, banned_player_data::BANNED_PLAYER_LIST}, data::{SaveJSONConfiguration, banned_player_data::BANNED_PLAYER_LIST},
}; };
use CommandError::InvalidConsumption; use CommandError::InvalidConsumption;
use async_trait::async_trait;
use pumpkin_util::text::TextComponent; use pumpkin_util::text::TextComponent;
const NAMES: [&str; 1] = ["pardon"]; const NAMES: [&str; 1] = ["pardon"];
@ -18,43 +16,44 @@ const ARG_TARGET: &str = "player";
struct Executor; struct Executor;
#[async_trait]
impl CommandExecutor for Executor { impl CommandExecutor for Executor {
async fn execute<'a>( fn execute<'a>(
&self, &'a self,
sender: &mut CommandSender, sender: &'a CommandSender,
_server: &crate::server::Server, _server: &'a crate::server::Server,
args: &ConsumedArgs<'a>, args: &'a ConsumedArgs<'a>,
) -> Result<(), CommandError> { ) -> CommandResult<'a> {
let Some(Arg::Simple(target)) = args.get(&ARG_TARGET) else { Box::pin(async move {
return Err(InvalidConsumption(Some(ARG_TARGET.into()))); let Some(Arg::Simple(target)) = args.get(&ARG_TARGET) else {
}; return Err(InvalidConsumption(Some(ARG_TARGET.into())));
let target = (*target).to_string(); };
let target = (*target).to_string();
let mut lock = BANNED_PLAYER_LIST.write().await; let mut lock = BANNED_PLAYER_LIST.write().await;
if let Some(idx) = lock
.banned_players
.iter()
.position(|entry| entry.name == target)
{
lock.banned_players.remove(idx);
} else {
sender
.send_message(TextComponent::translate("commands.pardon.failed", []))
.await;
return Ok(());
}
lock.save();
if let Some(idx) = lock
.banned_players
.iter()
.position(|entry| entry.name == target)
{
lock.banned_players.remove(idx);
} else {
sender sender
.send_message(TextComponent::translate("commands.pardon.failed", [])) .send_message(TextComponent::translate(
"commands.pardon.success",
[TextComponent::text(target)],
))
.await; .await;
return Ok(()); Ok(())
} })
lock.save();
sender
.send_message(TextComponent::translate(
"commands.pardon.success",
[TextComponent::text(target)],
))
.await;
Ok(())
} }
} }

View File

@ -2,15 +2,13 @@ use std::{net::IpAddr, str::FromStr};
use crate::{ use crate::{
command::{ command::{
CommandError, CommandExecutor, CommandSender, CommandError, CommandExecutor, CommandResult, CommandSender,
args::{Arg, ConsumedArgs, simple::SimpleArgConsumer}, args::{Arg, ConsumedArgs, simple::SimpleArgConsumer},
tree::CommandTree, tree::{CommandTree, builder::argument},
tree::builder::argument,
}, },
data::{SaveJSONConfiguration, banned_ip_data::BANNED_IP_LIST}, data::{SaveJSONConfiguration, banned_ip_data::BANNED_IP_LIST},
}; };
use CommandError::InvalidConsumption; use CommandError::InvalidConsumption;
use async_trait::async_trait;
use pumpkin_util::text::TextComponent; use pumpkin_util::text::TextComponent;
const NAMES: [&str; 1] = ["pardon-ip"]; const NAMES: [&str; 1] = ["pardon-ip"];
@ -20,45 +18,46 @@ const ARG_TARGET: &str = "ip";
struct Executor; struct Executor;
#[async_trait]
impl CommandExecutor for Executor { impl CommandExecutor for Executor {
async fn execute<'a>( fn execute<'a>(
&self, &'a self,
sender: &mut CommandSender, sender: &'a CommandSender,
_server: &crate::server::Server, _server: &'a crate::server::Server,
args: &ConsumedArgs<'a>, args: &'a ConsumedArgs<'a>,
) -> Result<(), CommandError> { ) -> CommandResult<'a> {
let Some(Arg::Simple(target)) = args.get(&ARG_TARGET) else { Box::pin(async move {
return Err(InvalidConsumption(Some(ARG_TARGET.into()))); let Some(Arg::Simple(target)) = args.get(&ARG_TARGET) else {
}; return Err(InvalidConsumption(Some(ARG_TARGET.into())));
};
let Ok(ip) = IpAddr::from_str(target) else {
sender
.send_message(TextComponent::translate("commands.pardonip.invalid", []))
.await;
return Ok(());
};
let mut lock = BANNED_IP_LIST.write().await;
if let Some(idx) = lock.banned_ips.iter().position(|entry| entry.ip == ip) {
lock.banned_ips.remove(idx);
} else {
sender
.send_message(TextComponent::translate("commands.pardonip.failed", []))
.await;
return Ok(());
}
lock.save();
let Ok(ip) = IpAddr::from_str(target) else {
sender sender
.send_message(TextComponent::translate("commands.pardonip.invalid", [])) .send_message(TextComponent::translate(
"commands.pardonip.success",
[TextComponent::text(ip.to_string())],
))
.await; .await;
return Ok(()); Ok(())
}; })
let mut lock = BANNED_IP_LIST.write().await;
if let Some(idx) = lock.banned_ips.iter().position(|entry| entry.ip == ip) {
lock.banned_ips.remove(idx);
} else {
sender
.send_message(TextComponent::translate("commands.pardonip.failed", []))
.await;
return Ok(());
}
lock.save();
sender
.send_message(TextComponent::translate(
"commands.pardonip.success",
[TextComponent::text(ip.to_string())],
))
.await;
Ok(())
} }
} }

View File

@ -1,8 +1,7 @@
use async_trait::async_trait;
use pumpkin_util::{math::vector3::Vector3, text::TextComponent}; use pumpkin_util::{math::vector3::Vector3, text::TextComponent};
use crate::command::{ use crate::command::{
CommandError, CommandExecutor, CommandSender, CommandError, CommandExecutor, CommandResult, CommandSender,
args::{ args::{
ConsumedArgs, FindArg, bounded_num::BoundedNumArgumentConsumer, ConsumedArgs, FindArg, bounded_num::BoundedNumArgumentConsumer,
position_3d::Position3DArgumentConsumer, resource::particle::ParticleArgumentConsumer, position_3d::Position3DArgumentConsumer, resource::particle::ParticleArgumentConsumer,
@ -22,63 +21,64 @@ const ARG_COUNT: &str = "count";
struct Executor; struct Executor;
#[async_trait]
impl CommandExecutor for Executor { impl CommandExecutor for Executor {
async fn execute<'a>( fn execute<'a>(
&self, &'a self,
sender: &mut CommandSender, sender: &'a CommandSender,
server: &crate::server::Server, server: &'a crate::server::Server,
args: &ConsumedArgs<'a>, args: &'a ConsumedArgs<'a>,
) -> Result<(), CommandError> { ) -> CommandResult<'a> {
let particle = ParticleArgumentConsumer::find_arg(args, ARG_NAME)?; Box::pin(async move {
let pos = Position3DArgumentConsumer::find_arg(args, ARG_POS); let particle = ParticleArgumentConsumer::find_arg(args, ARG_NAME)?;
let delta = Position3DArgumentConsumer::find_arg(args, ARG_DELTA); let pos = Position3DArgumentConsumer::find_arg(args, ARG_POS);
let speed = BoundedNumArgumentConsumer::<f32>::find_arg(args, ARG_SPEED); let delta = Position3DArgumentConsumer::find_arg(args, ARG_DELTA);
let count = BoundedNumArgumentConsumer::<i32>::find_arg(args, ARG_COUNT); let speed = BoundedNumArgumentConsumer::<f32>::find_arg(args, ARG_SPEED);
let count = BoundedNumArgumentConsumer::<i32>::find_arg(args, ARG_COUNT);
let delta = delta.unwrap_or(Vector3::new(0.0, 0.0, 0.0)); let delta = delta.unwrap_or(Vector3::new(0.0, 0.0, 0.0));
let delta: Vector3<f32> = Vector3::new(delta.x as f32, delta.y as f32, delta.z as f32); let delta: Vector3<f32> = Vector3::new(delta.x as f32, delta.y as f32, delta.z as f32);
let speed = speed.unwrap_or(Ok(0.0))?; let speed = speed.unwrap_or(Ok(0.0))?;
let count = count.unwrap_or(Ok(0))?; let count = count.unwrap_or(Ok(0))?;
let (world, pos) = match sender { let (world, pos) = match sender {
CommandSender::Console | CommandSender::Rcon(_) => { CommandSender::Console | CommandSender::Rcon(_) => {
let guard = server.worlds.read().await; let guard = server.worlds.read().await;
let world = guard let world = guard
.first() .first()
.cloned() .cloned()
.ok_or(CommandError::InvalidRequirement)?; .ok_or(CommandError::InvalidRequirement)?;
// default position for spawning a player, in this case for particle // default position for spawning a player, in this case for particle
let pos = { let pos = {
let info = &world.level_info.read().await; let info = &world.level_info.read().await;
// default position for spawning a player, in this case for mob // default position for spawning a player, in this case for mob
pos.unwrap_or(Vector3::new( pos.unwrap_or(Vector3::new(
f64::from(info.spawn_x) + 0.5, f64::from(info.spawn_x) + 0.5,
f64::from(info.spawn_y) + 1.0, f64::from(info.spawn_y) + 1.0,
f64::from(info.spawn_z) + 0.5, f64::from(info.spawn_z) + 0.5,
)) ))
}; };
(world, pos) (world, pos)
} }
CommandSender::Player(player) => { CommandSender::Player(player) => {
let pos = pos.unwrap_or(player.living_entity.entity.pos.load()); let pos = pos.unwrap_or(player.living_entity.entity.pos.load());
(player.world().clone(), pos) (player.world().clone(), pos)
} }
}; };
world world
.spawn_particle(pos, delta, speed, count, *particle) .spawn_particle(pos, delta, speed, count, *particle)
.await; .await;
sender sender
.send_message(TextComponent::translate( .send_message(TextComponent::translate(
"commands.particle.success", "commands.particle.success",
[TextComponent::text(format!("{particle:?}"))], [TextComponent::text(format!("{particle:?}"))],
)) ))
.await; .await;
Ok(()) Ok(())
})
} }
} }

View File

@ -1,17 +1,15 @@
use async_trait::async_trait;
use pumpkin_data::sound::SoundCategory; use pumpkin_data::sound::SoundCategory;
use pumpkin_util::text::TextComponent; use pumpkin_util::text::TextComponent;
use rand::{Rng, rng}; use rand::{Rng, rng};
use crate::command::{ use crate::command::{
CommandError, CommandExecutor, CommandSender, CommandExecutor, CommandResult, CommandSender,
args::{ args::{
Arg, ConsumedArgs, FindArg, bounded_num::BoundedNumArgumentConsumer, Arg, ConsumedArgs, FindArg, bounded_num::BoundedNumArgumentConsumer,
players::PlayersArgumentConsumer, position_3d::Position3DArgumentConsumer, players::PlayersArgumentConsumer, position_3d::Position3DArgumentConsumer,
sound::SoundArgumentConsumer, sound_category::SoundCategoryArgumentConsumer, sound::SoundArgumentConsumer, sound_category::SoundCategoryArgumentConsumer,
}, },
tree::CommandTree, tree::{CommandTree, builder::argument},
tree::builder::argument,
}; };
use crate::entity::EntityBase; use crate::entity::EntityBase;
@ -60,109 +58,112 @@ fn min_volume_consumer() -> BoundedNumArgumentConsumer<f32> {
struct Executor; struct Executor;
#[async_trait]
impl CommandExecutor for Executor { impl CommandExecutor for Executor {
async fn execute<'a>( fn execute<'a>(
&self, &'a self,
sender: &mut CommandSender, sender: &'a CommandSender,
_server: &crate::server::Server, _server: &'a crate::server::Server,
args: &ConsumedArgs<'a>, args: &'a ConsumedArgs<'a>,
) -> Result<(), CommandError> { ) -> CommandResult<'a> {
// Get required sound argument Box::pin(async move {
let sound = SoundArgumentConsumer::find_arg(args, ARG_SOUND)?; // Get required sound argument
let sound = SoundArgumentConsumer::find_arg(args, ARG_SOUND)?;
// Get optional sound category, defaults to Master // Get optional sound category, defaults to Master
let source = args let source = args
.get(ARG_SOURCE) .get(ARG_SOURCE)
.map_or(SoundCategory::Master, |arg| match arg { .map_or(SoundCategory::Master, |arg| match arg {
Arg::SoundCategory(category) => *category, Arg::SoundCategory(category) => *category,
_ => SoundCategory::Master, _ => SoundCategory::Master,
}); });
// Get target players, defaults to sender if not specified // Get target players, defaults to sender if not specified
let targets = if let Ok(players) = PlayersArgumentConsumer::find_arg(args, ARG_TARGETS) { let targets = if let Ok(players) = PlayersArgumentConsumer::find_arg(args, ARG_TARGETS)
players {
} else if let Some(player) = sender.as_player() { players
&[player] } else if let Some(player) = sender.as_player() {
} else { &[player]
return Ok(()); } else {
}; return Ok(());
};
// Get optional position, defaults to target's position // Get optional position, defaults to target's position
let position = Position3DArgumentConsumer::find_arg(args, ARG_POS).ok(); let position = Position3DArgumentConsumer::find_arg(args, ARG_POS).ok();
// Get optional volume parameter // Get optional volume parameter
let volume = match BoundedNumArgumentConsumer::<f32>::find_arg(args, ARG_VOLUME) { let volume = match BoundedNumArgumentConsumer::<f32>::find_arg(args, ARG_VOLUME) {
Ok(Ok(v)) => v, Ok(Ok(v)) => v,
_ => 1.0, // Default volume _ => 1.0, // Default volume
}; };
// Get optional pitch parameter // Get optional pitch parameter
let pitch = match BoundedNumArgumentConsumer::<f32>::find_arg(args, ARG_PITCH) { let pitch = match BoundedNumArgumentConsumer::<f32>::find_arg(args, ARG_PITCH) {
Ok(Ok(p)) => p.max(0.5), // Values below 0.5 are clamped Ok(Ok(p)) => p.max(0.5), // Values below 0.5 are clamped
_ => 1.0, // Default pitch _ => 1.0, // Default pitch
}; };
// Get optional minimum volume (currently unused in implementation) // Get optional minimum volume (currently unused in implementation)
let min_volume = match BoundedNumArgumentConsumer::<f32>::find_arg(args, ARG_MIN_VOLUME) { let min_volume = match BoundedNumArgumentConsumer::<f32>::find_arg(args, ARG_MIN_VOLUME)
Ok(Ok(v)) => v, {
_ => 0.0, // Default minimum volume Ok(Ok(v)) => v,
}; _ => 0.0, // Default minimum volume
};
// Use same random seed for all targets to ensure sound synchronization // Use same random seed for all targets to ensure sound synchronization
let seed = rng().random::<f64>(); let seed = rng().random::<f64>();
// Track how many players actually received the sound // Track how many players actually received the sound
let mut players_who_heard = 0; let mut players_who_heard = 0;
// Play sound for each target player // Play sound for each target player
for target in targets { for target in targets {
let pos = position.unwrap_or(target.living_entity.entity.pos.load()); let pos = position.unwrap_or(target.living_entity.entity.pos.load());
// Check if player can hear the sound based on volume and distance // Check if player can hear the sound based on volume and distance
let player_pos = target.living_entity.entity.pos.load(); let player_pos = target.living_entity.entity.pos.load();
let distance = player_pos.squared_distance_to_vec(pos); let distance = player_pos.squared_distance_to_vec(pos);
let max_distance: f64 = (16.0 * volume).into(); // 16 blocks is base distance at volume 1.0 let max_distance: f64 = (16.0 * volume).into(); // 16 blocks is base distance at volume 1.0
if distance <= max_distance || min_volume > 0.0 { if distance <= max_distance || min_volume > 0.0 {
target target
.play_sound(sound as u16, source, &pos, volume, pitch, seed) .play_sound(sound as u16, source, &pos, volume, pitch, seed)
.await; .await;
players_who_heard += 1; players_who_heard += 1;
}
} }
}
// Send appropriate message based on results // Send appropriate message based on results
if players_who_heard == 0 { if players_who_heard == 0 {
sender
.send_message(TextComponent::translate("commands.playsound.failed", []))
.await;
} else {
let sound_name = sound.to_name();
if players_who_heard == 1 {
sender sender
.send_message(TextComponent::translate( .send_message(TextComponent::translate("commands.playsound.failed", []))
"commands.playsound.success.single",
[
TextComponent::text(sound_name),
targets[0].get_display_name().await,
],
))
.await; .await;
} else { } else {
sender let sound_name = sound.to_name();
.send_message(TextComponent::translate( if players_who_heard == 1 {
"commands.playsound.success.multiple", sender
[ .send_message(TextComponent::translate(
TextComponent::text(sound_name), "commands.playsound.success.single",
TextComponent::text(players_who_heard.to_string()), [
], TextComponent::text(sound_name),
)) targets[0].get_display_name().await,
.await; ],
))
.await;
} else {
sender
.send_message(TextComponent::translate(
"commands.playsound.success.multiple",
[
TextComponent::text(sound_name),
TextComponent::text(players_who_heard.to_string()),
],
))
.await;
}
} }
}
Ok(()) Ok(())
})
} }
} }

View File

@ -1,6 +1,5 @@
use std::path::Path; use std::path::Path;
use async_trait::async_trait;
use pumpkin_util::{ use pumpkin_util::{
PermissionLvl, PermissionLvl,
text::{TextComponent, color::NamedColor, hover::HoverEvent}, text::{TextComponent, color::NamedColor, hover::HoverEvent},
@ -9,7 +8,7 @@ use pumpkin_util::{
use crate::{ use crate::{
PLUGIN_MANAGER, PLUGIN_MANAGER,
command::{ command::{
CommandError, CommandExecutor, CommandSender, CommandExecutor, CommandResult, CommandSender,
args::{Arg, ConsumedArgs, simple::SimpleArgConsumer}, args::{Arg, ConsumedArgs, simple::SimpleArgConsumer},
tree::{ tree::{
CommandTree, CommandTree,
@ -28,143 +27,154 @@ const PLUGIN_NAME: &str = "plugin_name";
struct ListExecutor; struct ListExecutor;
#[async_trait]
impl CommandExecutor for ListExecutor { impl CommandExecutor for ListExecutor {
async fn execute<'a>( fn execute<'a>(
&self, &'a self,
sender: &mut CommandSender, sender: &'a CommandSender,
_server: &crate::server::Server, _server: &'a crate::server::Server,
_args: &ConsumedArgs<'a>, _args: &'a ConsumedArgs<'a>,
) -> Result<(), CommandError> { ) -> CommandResult<'a> {
let plugins = PLUGIN_MANAGER.active_plugins().await; Box::pin(async move {
let plugins = PLUGIN_MANAGER.active_plugins().await;
let message_text = if plugins.is_empty() { let message_text = if plugins.is_empty() {
"There are no loaded plugins.".to_string() "There are no loaded plugins.".to_string()
} else if plugins.len() == 1 { } else if plugins.len() == 1 {
"There is 1 plugin loaded:\n".to_string() "There is 1 plugin loaded:\n".to_string()
} else {
format!("There are {} plugins loaded:\n", plugins.len())
};
let mut message = TextComponent::text(message_text);
for (i, metadata) in plugins.clone().into_iter().enumerate() {
let fmt = if i == plugins.len() - 1 {
metadata.name.to_string()
} else { } else {
format!("{}, ", metadata.name) format!("There are {} plugins loaded:\n", plugins.len())
}; };
let hover_text = format!( let mut message = TextComponent::text(message_text);
"Version: {}\nAuthors: {}\nDescription: {}",
metadata.version, metadata.authors, metadata.description
);
let component = TextComponent::text(fmt)
.color_named(NamedColor::Green)
.hover_event(HoverEvent::show_text(TextComponent::text(hover_text)));
message = message.add_child(component); for (i, metadata) in plugins.clone().into_iter().enumerate() {
} let fmt = if i == plugins.len() - 1 {
metadata.name.to_string()
} else {
format!("{}, ", metadata.name)
};
let hover_text = format!(
"Version: {}\nAuthors: {}\nDescription: {}",
metadata.version, metadata.authors, metadata.description
);
let component = TextComponent::text(fmt)
.color_named(NamedColor::Green)
.hover_event(HoverEvent::show_text(TextComponent::text(hover_text)));
sender.send_message(message).await; message = message.add_child(component);
}
Ok(()) sender.send_message(message).await;
Ok(())
})
} }
} }
struct LoadExecutor; struct LoadExecutor;
#[async_trait]
impl CommandExecutor for LoadExecutor { impl CommandExecutor for LoadExecutor {
async fn execute<'a>( fn execute<'a>(
&self, &'a self,
sender: &mut CommandSender, sender: &'a CommandSender,
_server: &crate::server::Server, _server: &'a crate::server::Server,
args: &ConsumedArgs<'a>, args: &'a ConsumedArgs<'a>,
) -> Result<(), CommandError> { ) -> CommandResult<'a> {
let Some(Arg::Simple(plugin_name)) = args.get(PLUGIN_NAME) else { Box::pin(async move {
return Err(InvalidConsumption(Some(PLUGIN_NAME.into()))); let Some(Arg::Simple(plugin_name)) = args.get(PLUGIN_NAME) else {
}; return Err(InvalidConsumption(Some(PLUGIN_NAME.into())));
};
if PLUGIN_MANAGER.is_plugin_active(plugin_name).await { if PLUGIN_MANAGER.is_plugin_active(plugin_name).await {
sender
.send_message(
TextComponent::text(format!("Plugin {plugin_name} is already loaded"))
.color_named(NamedColor::Red),
)
.await;
return Ok(());
}
let result = PLUGIN_MANAGER.try_load_plugin(Path::new(plugin_name)).await;
match result {
Ok(()) => {
sender sender
.send_message( .send_message(
TextComponent::text(format!("Plugin {plugin_name} loaded successfully")) TextComponent::text(format!("Plugin {plugin_name} is already loaded"))
.color_named(NamedColor::Green),
)
.await;
}
Err(e) => {
sender
.send_message(
TextComponent::text(format!("Failed to load plugin {plugin_name}: {e}"))
.color_named(NamedColor::Red), .color_named(NamedColor::Red),
) )
.await; .await;
return Ok(());
} }
}
Ok(()) let result = PLUGIN_MANAGER.try_load_plugin(Path::new(plugin_name)).await;
match result {
Ok(()) => {
sender
.send_message(
TextComponent::text(format!(
"Plugin {plugin_name} loaded successfully"
))
.color_named(NamedColor::Green),
)
.await;
}
Err(e) => {
sender
.send_message(
TextComponent::text(format!(
"Failed to load plugin {plugin_name}: {e}"
))
.color_named(NamedColor::Red),
)
.await;
}
}
Ok(())
})
} }
} }
struct UnloadExecutor; struct UnloadExecutor;
#[async_trait]
impl CommandExecutor for UnloadExecutor { impl CommandExecutor for UnloadExecutor {
async fn execute<'a>( fn execute<'a>(
&self, &'a self,
sender: &mut CommandSender, sender: &'a CommandSender,
_server: &crate::server::Server, _server: &'a crate::server::Server,
args: &ConsumedArgs<'a>, args: &'a ConsumedArgs<'a>,
) -> Result<(), CommandError> { ) -> CommandResult<'a> {
let Some(Arg::Simple(plugin_name)) = args.get(PLUGIN_NAME) else { Box::pin(async move {
return Err(InvalidConsumption(Some(PLUGIN_NAME.into()))); let Some(Arg::Simple(plugin_name)) = args.get(PLUGIN_NAME) else {
}; return Err(InvalidConsumption(Some(PLUGIN_NAME.into())));
};
if !PLUGIN_MANAGER.is_plugin_active(plugin_name).await { if !PLUGIN_MANAGER.is_plugin_active(plugin_name).await {
sender
.send_message(
TextComponent::text(format!("Plugin {plugin_name} is not loaded"))
.color_named(NamedColor::Red),
)
.await;
return Ok(());
}
let result = PLUGIN_MANAGER.unload_plugin(plugin_name).await;
match result {
Ok(()) => {
sender sender
.send_message( .send_message(
TextComponent::text(format!("Plugin {plugin_name} unloaded successfully",)) TextComponent::text(format!("Plugin {plugin_name} is not loaded"))
.color_named(NamedColor::Green),
)
.await;
}
Err(e) => {
sender
.send_message(
TextComponent::text(format!("Failed to unload plugin {plugin_name}: {e}"))
.color_named(NamedColor::Red), .color_named(NamedColor::Red),
) )
.await; .await;
return Ok(());
} }
}
Ok(()) let result = PLUGIN_MANAGER.unload_plugin(plugin_name).await;
match result {
Ok(()) => {
sender
.send_message(
TextComponent::text(format!(
"Plugin {plugin_name} unloaded successfully",
))
.color_named(NamedColor::Green),
)
.await;
}
Err(e) => {
sender
.send_message(
TextComponent::text(format!(
"Failed to unload plugin {plugin_name}: {e}"
))
.color_named(NamedColor::Red),
)
.await;
}
}
Ok(())
})
} }
} }

View File

@ -1,10 +1,9 @@
use async_trait::async_trait;
use pumpkin_util::text::{TextComponent, color::NamedColor, hover::HoverEvent}; use pumpkin_util::text::{TextComponent, color::NamedColor, hover::HoverEvent};
use crate::{ use crate::{
PLUGIN_MANAGER, PLUGIN_MANAGER,
command::{ command::{
CommandError, CommandExecutor, CommandSender, args::ConsumedArgs, tree::CommandTree, CommandExecutor, CommandResult, CommandSender, args::ConsumedArgs, tree::CommandTree,
}, },
}; };
@ -14,44 +13,45 @@ const DESCRIPTION: &str = "List all available plugins.";
struct Executor; struct Executor;
#[async_trait]
impl CommandExecutor for Executor { impl CommandExecutor for Executor {
async fn execute<'a>( fn execute<'a>(
&self, &'a self,
sender: &mut CommandSender, sender: &'a CommandSender,
_server: &crate::server::Server, _server: &'a crate::server::Server,
_args: &ConsumedArgs<'a>, _args: &'a ConsumedArgs<'a>,
) -> Result<(), CommandError> { ) -> CommandResult<'a> {
let plugins = PLUGIN_MANAGER.active_plugins().await; Box::pin(async move {
let plugins = PLUGIN_MANAGER.active_plugins().await;
let message_text = if plugins.is_empty() { let message_text = if plugins.is_empty() {
"There are no loaded plugins.".to_string() "There are no loaded plugins.".to_string()
} else if plugins.len() == 1 { } else if plugins.len() == 1 {
"There is 1 plugin loaded:\n".to_string() "There is 1 plugin loaded:\n".to_string()
} else {
format!("There are {} plugins loaded:\n", plugins.len())
};
let mut message = TextComponent::text(message_text);
for (i, metadata) in plugins.clone().into_iter().enumerate() {
let fmt = if i == plugins.len() - 1 {
metadata.name.to_string()
} else { } else {
format!("{}, ", metadata.name) format!("There are {} plugins loaded:\n", plugins.len())
}; };
let hover_text = format!( let mut message = TextComponent::text(message_text);
"Version: {}\nAuthors: {}\nDescription: {}",
metadata.version, metadata.authors, metadata.description
);
let component = TextComponent::text(fmt)
.color_named(NamedColor::Green)
.hover_event(HoverEvent::show_text(TextComponent::text(hover_text)));
message = message.add_child(component);
}
sender.send_message(message).await; for (i, metadata) in plugins.clone().into_iter().enumerate() {
let fmt = if i == plugins.len() - 1 {
metadata.name.to_string()
} else {
format!("{}, ", metadata.name)
};
let hover_text = format!(
"Version: {}\nAuthors: {}\nDescription: {}",
metadata.version, metadata.authors, metadata.description
);
let component = TextComponent::text(fmt)
.color_named(NamedColor::Green)
.hover_event(HoverEvent::show_text(TextComponent::text(hover_text)));
message = message.add_child(component);
}
Ok(()) sender.send_message(message).await;
Ok(())
})
} }
} }

View File

@ -1,10 +1,11 @@
use async_trait::async_trait;
use pumpkin_util::text::TextComponent; use pumpkin_util::text::TextComponent;
use pumpkin_util::text::color::NamedColor; use pumpkin_util::text::color::NamedColor;
use crate::command::CommandResult;
use crate::command::args::ConsumedArgs; use crate::command::args::ConsumedArgs;
use crate::command::tree::CommandTree; use crate::command::tree::CommandTree;
use crate::command::{CommandError, CommandExecutor, CommandSender}; use crate::command::{CommandExecutor, CommandSender};
use crate::{HEAP_PROFILER, stop_server}; use crate::{HEAP_PROFILER, stop_server};
const NAMES: [&str; 1] = ["mem_profile"]; const NAMES: [&str; 1] = ["mem_profile"];
@ -13,27 +14,29 @@ const DESCRIPTION: &str = "Stop the server and dump a memory profile.";
struct Executor; struct Executor;
#[async_trait]
impl CommandExecutor for Executor { impl CommandExecutor for Executor {
async fn execute<'a>( fn execute<'a>(
&self, &'a self,
sender: &mut CommandSender, sender: &'a CommandSender,
_server: &crate::server::Server, _server: &'a crate::server::Server,
_args: &ConsumedArgs<'a>, _args: &'a ConsumedArgs<'a>,
) -> Result<(), CommandError> { ) -> CommandResult<'a> {
sender Box::pin(async move {
.send_message( sender
TextComponent::translate("commands.stop.stopping", []).color_named(NamedColor::Red), .send_message(
) TextComponent::translate("commands.stop.stopping", [])
.await; .color_named(NamedColor::Red),
)
.await;
let mut profiler = HEAP_PROFILER.lock().await; let mut profiler = HEAP_PROFILER.lock().await;
let p = profiler.take().unwrap(); let p = profiler.take().unwrap();
// Do the actual profiling drop(p);
drop(p);
stop_server(); stop_server();
Ok(())
Ok(())
})
} }
} }

View File

@ -1,4 +1,3 @@
use async_trait::async_trait;
use pumpkin_data::packet::CURRENT_MC_PROTOCOL; use pumpkin_data::packet::CURRENT_MC_PROTOCOL;
use pumpkin_util::text::click::ClickEvent; use pumpkin_util::text::click::ClickEvent;
use pumpkin_util::text::hover::HoverEvent; use pumpkin_util::text::hover::HoverEvent;
@ -7,9 +6,8 @@ use pumpkin_util::translation::get_translation_text;
use pumpkin_world::CURRENT_MC_VERSION; use pumpkin_world::CURRENT_MC_VERSION;
use std::borrow::Cow; use std::borrow::Cow;
use crate::command::{ use crate::command::CommandResult;
CommandError, CommandExecutor, CommandSender, args::ConsumedArgs, tree::CommandTree, use crate::command::{CommandExecutor, CommandSender, args::ConsumedArgs, tree::CommandTree};
};
const NAMES: [&str; 2] = ["pumpkin", "version"]; const NAMES: [&str; 2] = ["pumpkin", "version"];
@ -19,117 +17,122 @@ struct Executor;
const CARGO_PKG_VERSION: &str = env!("CARGO_PKG_VERSION"); const CARGO_PKG_VERSION: &str = env!("CARGO_PKG_VERSION");
#[async_trait]
#[allow(clippy::too_many_lines)] #[allow(clippy::too_many_lines)]
impl CommandExecutor for Executor { impl CommandExecutor for Executor {
async fn execute<'a>( fn execute<'a>(
&self, &'a self,
sender: &mut CommandSender, sender: &'a CommandSender,
_server: &crate::server::Server, _server: &'a crate::server::Server,
_args: &ConsumedArgs<'a>, _args: &'a ConsumedArgs<'a>,
) -> Result<(), CommandError> { ) -> CommandResult<'a> {
let locale = sender.get_locale().await; Box::pin(async move {
sender let locale = sender.get_locale().await;
.send_message( sender
TextComponent::custom( .send_message(
"pumpkin", TextComponent::custom(
"commands.pumpkin.version", "pumpkin",
locale, "commands.pumpkin.version",
vec![TextComponent::text(CARGO_PKG_VERSION)], locale,
) vec![TextComponent::text(CARGO_PKG_VERSION)],
.hover_event(HoverEvent::show_text(TextComponent::custom( )
"pumpkin", .hover_event(HoverEvent::show_text(TextComponent::custom(
"commands.pumpkin.version.hover", "pumpkin",
locale, "commands.pumpkin.version.hover",
vec![], locale,
))) vec![],
.click_event(ClickEvent::CopyToClipboard { )))
value: Cow::from( .click_event(ClickEvent::CopyToClipboard {
get_translation_text( value: Cow::from(
"pumpkin:commands.pumpkin.version", get_translation_text(
"pumpkin:commands.pumpkin.version",
locale,
vec![TextComponent::text(CARGO_PKG_VERSION).0],
)
.replace('\n', ""),
),
})
.color_named(NamedColor::Green)
.add_child(
TextComponent::custom(
"pumpkin",
"commands.pumpkin.description",
locale, locale,
vec![TextComponent::text(CARGO_PKG_VERSION).0], vec![],
) )
.replace('\n', ""), .click_event(ClickEvent::CopyToClipboard {
), value: Cow::from(
}) get_translation_text(
.color_named(NamedColor::Green) "pumpkin:commands.pumpkin.description",
.add_child( locale,
TextComponent::custom( vec![],
"pumpkin", )
"commands.pumpkin.description", .replace('\n', ""),
locale, ),
vec![],
)
.click_event(ClickEvent::CopyToClipboard {
value: Cow::from(
get_translation_text(
"pumpkin:commands.pumpkin.description",
locale,
vec![],
)
.replace('\n', ""),
),
})
.hover_event(HoverEvent::show_text(TextComponent::custom(
"pumpkin",
"commands.pumpkin.description.hover",
locale,
vec![],
)))
.color_named(NamedColor::White),
)
.add_child(
TextComponent::custom(
"pumpkin",
"commands.pumpkin.minecraft_version",
locale,
vec![
TextComponent::text(CURRENT_MC_VERSION),
TextComponent::text(format!("{CURRENT_MC_PROTOCOL}")),
],
)
.click_event(ClickEvent::CopyToClipboard {
value: Cow::from(
get_translation_text(
"pumpkin:commands.pumpkin.minecraft_version",
locale,
vec![
TextComponent::text(CURRENT_MC_VERSION).0,
TextComponent::text(format!("{CURRENT_MC_PROTOCOL}")).0,
],
)
.replace('\n', ""),
),
})
.hover_event(HoverEvent::show_text(TextComponent::custom(
"pumpkin",
"commands.pumpkin.minecraft_version.hover",
locale,
vec![],
)))
.color_named(NamedColor::Gold),
)
// https://pumpkinmc.org/
.add_child(
TextComponent::custom("pumpkin", "commands.pumpkin.github", locale, vec![])
.click_event(ClickEvent::OpenUrl {
url: Cow::from("https://github.com/Pumpkin-MC/Pumpkin"),
}) })
.hover_event(HoverEvent::show_text(TextComponent::custom( .hover_event(HoverEvent::show_text(TextComponent::custom(
"pumpkin", "pumpkin",
"commands.pumpkin.github.hover", "commands.pumpkin.description.hover",
locale, locale,
vec![], vec![],
))) )))
.color_named(NamedColor::Blue) .color_named(NamedColor::White),
.bold() )
.underlined(), .add_child(
) TextComponent::custom(
// Added docs. and a space for spacing "pumpkin",
.add_child(TextComponent::text(" ")) "commands.pumpkin.minecraft_version",
.add_child( locale,
TextComponent::custom("pumpkin", "commands.pumpkin.website", locale, vec![]) vec![
TextComponent::text(CURRENT_MC_VERSION),
TextComponent::text(format!("{CURRENT_MC_PROTOCOL}")),
],
)
.click_event(ClickEvent::CopyToClipboard {
value: Cow::from(
get_translation_text(
"pumpkin:commands.pumpkin.minecraft_version",
locale,
vec![
TextComponent::text(CURRENT_MC_VERSION).0,
TextComponent::text(format!("{CURRENT_MC_PROTOCOL}")).0,
],
)
.replace('\n', ""),
),
})
.hover_event(HoverEvent::show_text(TextComponent::custom(
"pumpkin",
"commands.pumpkin.minecraft_version.hover",
locale,
vec![],
)))
.color_named(NamedColor::Gold),
)
// https://pumpkinmc.org/
.add_child(
TextComponent::custom("pumpkin", "commands.pumpkin.github", locale, vec![])
.click_event(ClickEvent::OpenUrl {
url: Cow::from("https://github.com/Pumpkin-MC/Pumpkin"),
})
.hover_event(HoverEvent::show_text(TextComponent::custom(
"pumpkin",
"commands.pumpkin.github.hover",
locale,
vec![],
)))
.color_named(NamedColor::Blue)
.bold()
.underlined(),
)
// Added docs. and a space for spacing
.add_child(TextComponent::text(" "))
.add_child(
TextComponent::custom(
"pumpkin",
"commands.pumpkin.website",
locale,
vec![],
)
.click_event(ClickEvent::OpenUrl { .click_event(ClickEvent::OpenUrl {
url: Cow::from("https://pumpkinmc.org/"), url: Cow::from("https://pumpkinmc.org/"),
}) })
@ -142,10 +145,11 @@ impl CommandExecutor for Executor {
.color_named(NamedColor::Blue) .color_named(NamedColor::Blue)
.bold() .bold()
.underlined(), .underlined(),
), ),
) )
.await; .await;
Ok(()) Ok(())
})
} }
} }

View File

@ -1,12 +1,10 @@
use async_trait::async_trait;
use pumpkin_data::world::SAY_COMMAND; use pumpkin_data::world::SAY_COMMAND;
use pumpkin_util::text::TextComponent; use pumpkin_util::text::TextComponent;
use crate::command::{ use crate::command::{
CommandError, CommandExecutor, CommandSender, CommandError, CommandExecutor, CommandResult, CommandSender,
args::{Arg, ConsumedArgs, message::MsgArgConsumer}, args::{Arg, ConsumedArgs, message::MsgArgConsumer},
tree::CommandTree, tree::{CommandTree, builder::argument},
tree::builder::argument,
}; };
use CommandError::InvalidConsumption; use CommandError::InvalidConsumption;
@ -18,27 +16,28 @@ const ARG_MESSAGE: &str = "message";
struct Executor; struct Executor;
#[async_trait]
impl CommandExecutor for Executor { impl CommandExecutor for Executor {
async fn execute<'a>( fn execute<'a>(
&self, &'a self,
sender: &mut CommandSender, sender: &'a CommandSender,
server: &crate::server::Server, server: &'a crate::server::Server,
args: &ConsumedArgs<'a>, args: &'a ConsumedArgs<'a>,
) -> Result<(), CommandError> { ) -> CommandResult<'a> {
let Some(Arg::Msg(msg)) = args.get(ARG_MESSAGE) else { Box::pin(async move {
return Err(InvalidConsumption(Some(ARG_MESSAGE.into()))); let Some(Arg::Msg(msg)) = args.get(ARG_MESSAGE) else {
}; return Err(InvalidConsumption(Some(ARG_MESSAGE.into())));
};
server server
.broadcast_message( .broadcast_message(
&TextComponent::text(msg.clone()), &TextComponent::text(msg.clone()),
&TextComponent::text(format!("{sender}")), &TextComponent::text(format!("{sender}")),
SAY_COMMAND, SAY_COMMAND,
None, None,
) )
.await; .await;
Ok(()) Ok(())
})
} }
} }

View File

@ -1,7 +1,7 @@
use crate::command::CommandResult;
use crate::command::{ use crate::command::{
CommandError, CommandExecutor, CommandSender, args::ConsumedArgs, tree::CommandTree, CommandError, CommandExecutor, CommandSender, args::ConsumedArgs, tree::CommandTree,
}; };
use async_trait::async_trait;
use pumpkin_util::text::click::ClickEvent; use pumpkin_util::text::click::ClickEvent;
use pumpkin_util::text::hover::HoverEvent; use pumpkin_util::text::hover::HoverEvent;
use pumpkin_util::text::{TextComponent, color::NamedColor}; use pumpkin_util::text::{TextComponent, color::NamedColor};
@ -13,43 +13,44 @@ const DESCRIPTION: &str = "Displays the world seed.";
struct Executor; struct Executor;
#[async_trait]
impl CommandExecutor for Executor { impl CommandExecutor for Executor {
async fn execute<'a>( fn execute<'a>(
&self, &'a self,
sender: &mut CommandSender, sender: &'a CommandSender,
server: &crate::server::Server, server: &'a crate::server::Server,
_args: &ConsumedArgs<'a>, _args: &'a ConsumedArgs<'a>,
) -> Result<(), CommandError> { ) -> CommandResult<'a> {
let seed = match sender { Box::pin(async move {
CommandSender::Player(player) => player.living_entity.entity.world.level.seed.0, let seed = match sender {
// TODO: Maybe ask player for world, or get the current world CommandSender::Player(player) => player.living_entity.entity.world.level.seed.0,
_ => match server.worlds.read().await.first() { // TODO: Maybe ask player for world, or get the current world
Some(world) => world.level.seed.0, _ => match server.worlds.read().await.first() {
None => { Some(world) => world.level.seed.0,
return Err(CommandError::CommandFailed(Box::new(TextComponent::text( None => {
"Unable to get Seed", return Err(CommandError::CommandFailed(Box::new(TextComponent::text(
)))); "Unable to get Seed",
} ))));
}, }
}; },
let seed = (seed as i64).to_string(); };
let seed = (seed as i64).to_string();
sender sender
.send_message(TextComponent::translate( .send_message(TextComponent::translate(
"commands.seed.success", "commands.seed.success",
[TextComponent::text(seed.clone()) [TextComponent::text(seed.clone())
.hover_event(HoverEvent::show_text(TextComponent::translate( .hover_event(HoverEvent::show_text(TextComponent::translate(
Cow::from("chat.copy.click"), Cow::from("chat.copy.click"),
[], [],
))) )))
.click_event(ClickEvent::CopyToClipboard { .click_event(ClickEvent::CopyToClipboard {
value: Cow::from(seed), value: Cow::from(seed),
}) })
.color_named(NamedColor::Green)], .color_named(NamedColor::Green)],
)) ))
.await; .await;
Ok(()) Ok(())
})
} }
} }

View File

@ -1,4 +1,3 @@
use async_trait::async_trait;
use pumpkin_util::text::TextComponent; use pumpkin_util::text::TextComponent;
use pumpkin_world::world::BlockFlags; use pumpkin_world::world::BlockFlags;
@ -7,7 +6,7 @@ use crate::command::args::position_block::BlockPosArgumentConsumer;
use crate::command::args::{ConsumedArgs, FindArg}; use crate::command::args::{ConsumedArgs, FindArg};
use crate::command::tree::CommandTree; use crate::command::tree::CommandTree;
use crate::command::tree::builder::{argument, literal}; use crate::command::tree::builder::{argument, literal};
use crate::command::{CommandError, CommandExecutor, CommandSender}; use crate::command::{CommandError, CommandExecutor, CommandResult, CommandSender};
const NAMES: [&str; 1] = ["setblock"]; const NAMES: [&str; 1] = ["setblock"];
@ -30,57 +29,35 @@ enum Mode {
struct Executor(Mode); struct Executor(Mode);
#[async_trait]
impl CommandExecutor for Executor { impl CommandExecutor for Executor {
async fn execute<'a>( fn execute<'a>(
&self, &'a self,
sender: &mut CommandSender, sender: &'a CommandSender,
server: &crate::server::Server, server: &'a crate::server::Server,
args: &ConsumedArgs<'a>, args: &'a ConsumedArgs<'a>,
) -> Result<(), CommandError> { ) -> CommandResult<'a> {
let block = BlockArgumentConsumer::find_arg(args, ARG_BLOCK)?; Box::pin(async move {
let block_state_id = block.default_state.id; let block = BlockArgumentConsumer::find_arg(args, ARG_BLOCK)?;
let pos = BlockPosArgumentConsumer::find_arg(args, ARG_BLOCK_POS)?; let block_state_id = block.default_state.id;
let mode = self.0; let pos = BlockPosArgumentConsumer::find_arg(args, ARG_BLOCK_POS)?;
let world = match sender { let mode = self.0;
CommandSender::Console | CommandSender::Rcon(_) => { let world = match sender {
let guard = server.worlds.read().await; CommandSender::Console | CommandSender::Rcon(_) => {
let guard = server.worlds.read().await;
guard guard
.first() .first()
.cloned() .cloned()
.ok_or(CommandError::InvalidRequirement)? .ok_or(CommandError::InvalidRequirement)?
} }
CommandSender::Player(player) => player.world().clone(), CommandSender::Player(player) => player.world().clone(),
}; };
let success = match mode { let success = match mode {
Mode::Destroy => { Mode::Destroy => {
world world
.clone() .clone()
.break_block(&pos, None, BlockFlags::SKIP_DROPS | BlockFlags::FORCE_STATE) .break_block(&pos, None, BlockFlags::SKIP_DROPS | BlockFlags::FORCE_STATE)
.await; .await;
world
.set_block_state(
&pos,
block_state_id,
BlockFlags::FORCE_STATE | BlockFlags::NOTIFY_NEIGHBORS,
)
.await;
true
}
Mode::Replace => {
world
.set_block_state(
&pos,
block_state_id,
BlockFlags::FORCE_STATE | BlockFlags::NOTIFY_NEIGHBORS,
)
.await;
true
}
Mode::Keep => {
let old_state = world.get_block_state(&pos).await;
if old_state.is_air() {
world world
.set_block_state( .set_block_state(
&pos, &pos,
@ -89,28 +66,51 @@ impl CommandExecutor for Executor {
) )
.await; .await;
true true
} else {
false
} }
} Mode::Replace => {
}; world
.set_block_state(
&pos,
block_state_id,
BlockFlags::FORCE_STATE | BlockFlags::NOTIFY_NEIGHBORS,
)
.await;
true
}
Mode::Keep => {
let old_state = world.get_block_state(&pos).await;
if old_state.is_air() {
world
.set_block_state(
&pos,
block_state_id,
BlockFlags::FORCE_STATE | BlockFlags::NOTIFY_NEIGHBORS,
)
.await;
true
} else {
false
}
}
};
sender sender
.send_message(if success { .send_message(if success {
TextComponent::translate( TextComponent::translate(
"commands.setblock.success", "commands.setblock.success",
[ [
TextComponent::text(pos.0.x.to_string()), TextComponent::text(pos.0.x.to_string()),
TextComponent::text(pos.0.y.to_string()), TextComponent::text(pos.0.y.to_string()),
TextComponent::text(pos.0.z.to_string()), TextComponent::text(pos.0.z.to_string()),
], ],
) )
} else { } else {
TextComponent::translate("commands.setblock.failed", []) TextComponent::translate("commands.setblock.failed", [])
}) })
.await; .await;
Ok(()) Ok(())
})
} }
} }

View File

@ -1,3 +1,4 @@
use crate::command::CommandResult;
use crate::command::dispatcher::CommandError::InvalidConsumption; use crate::command::dispatcher::CommandError::InvalidConsumption;
use crate::command::{ use crate::command::{
CommandExecutor, CommandSender, CommandExecutor, CommandSender,
@ -9,7 +10,6 @@ use crate::command::{
tree::{CommandTree, builder::argument}, tree::{CommandTree, builder::argument},
}; };
use crate::server::Server; use crate::server::Server;
use async_trait::async_trait;
use pumpkin_registry::VanillaDimensionType; use pumpkin_registry::VanillaDimensionType;
use pumpkin_util::{math::position::BlockPos, text::TextComponent}; use pumpkin_util::{math::position::BlockPos, text::TextComponent};
@ -23,72 +23,75 @@ const ARG_ANGLE: &str = "angle";
struct NoArgsWorldSpawnExecutor; struct NoArgsWorldSpawnExecutor;
#[async_trait]
impl CommandExecutor for NoArgsWorldSpawnExecutor { impl CommandExecutor for NoArgsWorldSpawnExecutor {
async fn execute<'a>( fn execute<'a>(
&self, &'a self,
sender: &mut CommandSender, sender: &'a CommandSender,
server: &crate::server::Server, server: &'a crate::server::Server,
_args: &ConsumedArgs<'a>, _args: &'a ConsumedArgs<'a>,
) -> Result<(), CommandError> { ) -> CommandResult<'a> {
let Some(block_pos) = sender.position() else { Box::pin(async move {
let level_info_guard = server.level_info.read().await; let Some(block_pos) = sender.position() else {
sender let level_info_guard = server.level_info.read().await;
.send_message(TextComponent::translate( sender
"commands.setworldspawn.success", .send_message(TextComponent::translate(
[ "commands.setworldspawn.success",
TextComponent::text(level_info_guard.spawn_x.to_string()), [
TextComponent::text(level_info_guard.spawn_y.to_string()), TextComponent::text(level_info_guard.spawn_x.to_string()),
TextComponent::text(level_info_guard.spawn_z.to_string()), TextComponent::text(level_info_guard.spawn_y.to_string()),
TextComponent::text(level_info_guard.spawn_angle.to_string()), TextComponent::text(level_info_guard.spawn_z.to_string()),
], TextComponent::text(level_info_guard.spawn_angle.to_string()),
)) ],
.await; ))
.await;
return Ok(()); return Ok(());
}; };
setworldspawn(sender, server, block_pos.to_block_pos(), 0.0).await setworldspawn(sender, server, block_pos.to_block_pos(), 0.0).await
})
} }
} }
struct DefaultWorldSpawnExecutor; struct DefaultWorldSpawnExecutor;
#[async_trait]
impl CommandExecutor for DefaultWorldSpawnExecutor { impl CommandExecutor for DefaultWorldSpawnExecutor {
async fn execute<'a>( fn execute<'a>(
&self, &'a self,
sender: &mut CommandSender, sender: &'a CommandSender,
server: &crate::server::Server, server: &'a crate::server::Server,
args: &ConsumedArgs<'a>, args: &'a ConsumedArgs<'a>,
) -> Result<(), CommandError> { ) -> CommandResult<'a> {
let Some(Arg::BlockPos(block_pos)) = args.get(ARG_BLOCK_POS) else { Box::pin(async move {
return Err(InvalidConsumption(Some(ARG_BLOCK_POS.into()))); let Some(Arg::BlockPos(block_pos)) = args.get(ARG_BLOCK_POS) else {
}; return Err(InvalidConsumption(Some(ARG_BLOCK_POS.into())));
};
setworldspawn(sender, server, *block_pos, 0.0).await setworldspawn(sender, server, *block_pos, 0.0).await
})
} }
} }
struct AngleWorldSpawnExecutor; struct AngleWorldSpawnExecutor;
#[async_trait]
impl CommandExecutor for AngleWorldSpawnExecutor { impl CommandExecutor for AngleWorldSpawnExecutor {
async fn execute<'a>( fn execute<'a>(
&self, &'a self,
sender: &mut CommandSender, sender: &'a CommandSender,
server: &Server, server: &'a crate::server::Server,
args: &ConsumedArgs<'a>, args: &'a ConsumedArgs<'a>,
) -> Result<(), CommandError> { ) -> CommandResult<'a> {
let Some(Arg::BlockPos(block_pos)) = args.get(ARG_BLOCK_POS) else { Box::pin(async move {
return Err(InvalidConsumption(Some(ARG_BLOCK_POS.into()))); let Some(Arg::BlockPos(block_pos)) = args.get(ARG_BLOCK_POS) else {
}; return Err(InvalidConsumption(Some(ARG_BLOCK_POS.into())));
};
let Some(Arg::Rotation(_, yaw)) = args.get(ARG_ANGLE) else { let Some(Arg::Rotation(_, yaw)) = args.get(ARG_ANGLE) else {
return Err(InvalidConsumption(Some(ARG_ANGLE.into()))); return Err(InvalidConsumption(Some(ARG_ANGLE.into())));
}; };
setworldspawn(sender, server, *block_pos, *yaw).await setworldspawn(sender, server, *block_pos, *yaw).await
})
} }
} }

View File

@ -1,10 +1,9 @@
use async_trait::async_trait;
use pumpkin_util::text::TextComponent; use pumpkin_util::text::TextComponent;
use pumpkin_util::text::color::NamedColor; use pumpkin_util::text::color::NamedColor;
use crate::command::args::ConsumedArgs; use crate::command::args::ConsumedArgs;
use crate::command::tree::CommandTree; use crate::command::tree::CommandTree;
use crate::command::{CommandError, CommandExecutor, CommandSender}; use crate::command::{CommandExecutor, CommandResult, CommandSender};
use crate::stop_server; use crate::stop_server;
const NAMES: [&str; 1] = ["stop"]; const NAMES: [&str; 1] = ["stop"];
@ -13,21 +12,23 @@ const DESCRIPTION: &str = "Stop the server.";
struct Executor; struct Executor;
#[async_trait]
impl CommandExecutor for Executor { impl CommandExecutor for Executor {
async fn execute<'a>( fn execute<'a>(
&self, &'a self,
sender: &mut CommandSender, sender: &'a CommandSender,
_server: &crate::server::Server, _server: &'a crate::server::Server,
_args: &ConsumedArgs<'a>, _args: &'a ConsumedArgs<'a>,
) -> Result<(), CommandError> { ) -> CommandResult<'a> {
sender Box::pin(async move {
.send_message( sender
TextComponent::translate("commands.stop.stopping", []).color_named(NamedColor::Red), .send_message(
) TextComponent::translate("commands.stop.stopping", [])
.await; .color_named(NamedColor::Red),
stop_server(); )
Ok(()) .await;
stop_server();
Ok(())
})
} }
} }

View File

@ -1,13 +1,11 @@
use crate::command::{ use crate::command::{
CommandExecutor, CommandSender, CommandExecutor, CommandResult, CommandSender,
args::{ args::{
ConsumedArgs, FindArg, players::PlayersArgumentConsumer, sound::SoundArgumentConsumer, ConsumedArgs, FindArg, players::PlayersArgumentConsumer, sound::SoundArgumentConsumer,
sound_category::SoundCategoryArgumentConsumer, sound_category::SoundCategoryArgumentConsumer,
}, },
dispatcher::CommandError,
tree::{CommandTree, builder::argument}, tree::{CommandTree, builder::argument},
}; };
use async_trait::async_trait;
use pumpkin_util::resource_location::ResourceLocation; use pumpkin_util::resource_location::ResourceLocation;
use pumpkin_util::text::TextComponent; use pumpkin_util::text::TextComponent;
@ -20,54 +18,55 @@ const ARG_SOUND: &str = "sound";
pub struct Executor; pub struct Executor;
#[async_trait]
impl CommandExecutor for Executor { impl CommandExecutor for Executor {
async fn execute<'a>( fn execute<'a>(
&self, &'a self,
sender: &mut CommandSender, sender: &'a CommandSender,
_server: &crate::server::Server, _server: &'a crate::server::Server,
args: &ConsumedArgs<'a>, args: &'a ConsumedArgs<'a>,
) -> Result<(), CommandError> { ) -> CommandResult<'a> {
let targets = PlayersArgumentConsumer::find_arg(args, ARG_TARGETS)?; Box::pin(async move {
let targets = PlayersArgumentConsumer::find_arg(args, ARG_TARGETS)?;
let category = SoundCategoryArgumentConsumer::find_arg(args, ARG_SOURCE); let category = SoundCategoryArgumentConsumer::find_arg(args, ARG_SOURCE);
let sound = SoundArgumentConsumer::find_arg(args, ARG_SOUND); let sound = SoundArgumentConsumer::find_arg(args, ARG_SOUND);
for target in targets { for target in targets {
target target
.stop_sound( .stop_sound(
sound sound
.as_ref() .as_ref()
.cloned() .cloned()
.map(|s| ResourceLocation::vanilla(s.to_name())) .map(|s| ResourceLocation::vanilla(s.to_name()))
.ok(), .ok(),
category.as_ref().map(|s| **s).ok(), category.as_ref().map(|s| **s).ok(),
) )
.await; .await;
}
let text = match (category, sound) {
(Ok(c), Ok(s)) => TextComponent::translate(
"commands.stopsound.success.source.sound",
[
TextComponent::text(s.to_name()),
TextComponent::text(c.to_name()),
],
),
(Ok(c), Err(_)) => TextComponent::translate(
"commands.stopsound.success.source.any",
[TextComponent::text(c.to_name())],
),
(Err(_), Ok(s)) => TextComponent::translate(
"commands.stopsound.success.sourceless.sound",
[TextComponent::text(s.to_name())],
),
(Err(_), Err(_)) => {
TextComponent::translate("commands.stopsound.success.sourceless.any", [])
} }
}; let text = match (category, sound) {
sender.send_message(text).await; (Ok(c), Ok(s)) => TextComponent::translate(
"commands.stopsound.success.source.sound",
[
TextComponent::text(s.to_name()),
TextComponent::text(c.to_name()),
],
),
(Ok(c), Err(_)) => TextComponent::translate(
"commands.stopsound.success.source.any",
[TextComponent::text(c.to_name())],
),
(Err(_), Ok(s)) => TextComponent::translate(
"commands.stopsound.success.sourceless.sound",
[TextComponent::text(s.to_name())],
),
(Err(_), Err(_)) => {
TextComponent::translate("commands.stopsound.success.sourceless.any", [])
}
};
sender.send_message(text).await;
Ok(()) Ok(())
})
} }
} }

View File

@ -1,10 +1,9 @@
use async_trait::async_trait;
use pumpkin_util::{math::vector3::Vector3, text::TextComponent}; use pumpkin_util::{math::vector3::Vector3, text::TextComponent};
use uuid::Uuid; use uuid::Uuid;
use crate::{ use crate::{
command::{ command::{
CommandError, CommandExecutor, CommandSender, CommandError, CommandExecutor, CommandResult, CommandSender,
args::{ args::{
ConsumedArgs, FindArg, position_3d::Position3DArgumentConsumer, ConsumedArgs, FindArg, position_3d::Position3DArgumentConsumer,
summonable_entities::SummonableEntitiesArgumentConsumer, summonable_entities::SummonableEntitiesArgumentConsumer,
@ -23,52 +22,53 @@ const ARG_POS: &str = "pos";
struct Executor; struct Executor;
#[async_trait]
impl CommandExecutor for Executor { impl CommandExecutor for Executor {
async fn execute<'a>( fn execute<'a>(
&self, &'a self,
sender: &mut CommandSender, sender: &'a CommandSender,
server: &crate::server::Server, server: &'a crate::server::Server,
args: &ConsumedArgs<'a>, args: &'a ConsumedArgs<'a>,
) -> Result<(), CommandError> { ) -> CommandResult<'a> {
let entity = SummonableEntitiesArgumentConsumer::find_arg(args, ARG_ENTITY)?; Box::pin(async move {
let pos = Position3DArgumentConsumer::find_arg(args, ARG_POS); let entity = SummonableEntitiesArgumentConsumer::find_arg(args, ARG_ENTITY)?;
let (world, pos) = match sender { let pos = Position3DArgumentConsumer::find_arg(args, ARG_POS);
CommandSender::Console | CommandSender::Rcon(_) => { let (world, pos) = match sender {
let guard = server.worlds.read().await; CommandSender::Console | CommandSender::Rcon(_) => {
let world = guard let guard = server.worlds.read().await;
.first() let world = guard
.cloned() .first()
.ok_or(CommandError::InvalidRequirement)?; .cloned()
let pos = { .ok_or(CommandError::InvalidRequirement)?;
let info = &world.level_info.read().await; let pos = {
// default position for spawning a player, in this case for mob let info = &world.level_info.read().await;
pos.unwrap_or(Vector3::new( // default position for spawning a player, in this case for mob
f64::from(info.spawn_x) + 0.5, pos.unwrap_or(Vector3::new(
f64::from(info.spawn_y) + 1.0, f64::from(info.spawn_x) + 0.5,
f64::from(info.spawn_z) + 0.5, f64::from(info.spawn_y) + 1.0,
)) f64::from(info.spawn_z) + 0.5,
}; ))
};
(world, pos) (world, pos)
} }
CommandSender::Player(player) => { CommandSender::Player(player) => {
let pos = pos.unwrap_or(player.living_entity.entity.pos.load()); let pos = pos.unwrap_or(player.living_entity.entity.pos.load());
(player.world().clone(), pos) (player.world().clone(), pos)
} }
}; };
let mob = from_type(entity, pos, &world, Uuid::new_v4()).await; let mob = from_type(entity, pos, &world, Uuid::new_v4()).await;
world.spawn_entity(mob).await; world.spawn_entity(mob).await;
sender sender
.send_message(TextComponent::translate( .send_message(TextComponent::translate(
"commands.summon.success", "commands.summon.success",
[TextComponent::text(format!("{entity:?}"))], [TextComponent::text(format!("{entity:?}"))],
)) ))
.await; .await;
Ok(()) Ok(())
})
} }
} }

View File

@ -1,8 +1,8 @@
use async_trait::async_trait;
use pumpkin_util::math::vector3::Vector3; use pumpkin_util::math::vector3::Vector3;
use pumpkin_util::text::TextComponent; use pumpkin_util::text::TextComponent;
use crate::command::CommandError; use crate::command::CommandError;
use crate::command::CommandResult;
use crate::command::args::ConsumedArgs; use crate::command::args::ConsumedArgs;
use crate::command::args::FindArg; use crate::command::args::FindArg;
use crate::command::args::entities::EntitiesArgumentConsumer; use crate::command::args::entities::EntitiesArgumentConsumer;
@ -53,256 +53,264 @@ fn yaw_pitch_facing_position(
struct EntitiesToEntityExecutor; struct EntitiesToEntityExecutor;
#[async_trait]
impl CommandExecutor for EntitiesToEntityExecutor { impl CommandExecutor for EntitiesToEntityExecutor {
async fn execute<'a>( fn execute<'a>(
&self, &'a self,
_sender: &mut CommandSender, _sender: &'a CommandSender,
_server: &crate::server::Server, _server: &'a crate::server::Server,
args: &ConsumedArgs<'a>, args: &'a ConsumedArgs<'a>,
) -> Result<(), CommandError> { ) -> CommandResult<'a> {
let targets = EntitiesArgumentConsumer::find_arg(args, ARG_TARGETS)?; Box::pin(async move {
let targets = EntitiesArgumentConsumer::find_arg(args, ARG_TARGETS)?;
let destination = EntityArgumentConsumer::find_arg(args, ARG_DESTINATION)?; let destination = EntityArgumentConsumer::find_arg(args, ARG_DESTINATION)?;
let pos = destination.get_entity().pos.load(); let pos = destination.get_entity().pos.load();
if !World::is_valid(pos) { if !World::is_valid(pos) {
return Err(CommandError::CommandFailed(Box::new( return Err(CommandError::CommandFailed(Box::new(
TextComponent::translate("argument.pos.outofbounds", []), TextComponent::translate("argument.pos.outofbounds", []),
))); )));
} }
for target in targets { for target in targets {
let base_entity = target.get_entity(); let base_entity = target.get_entity();
let yaw = base_entity.yaw.load(); let yaw = base_entity.yaw.load();
let pitch = base_entity.pitch.load(); let pitch = base_entity.pitch.load();
let world = base_entity.world.clone(); let world = base_entity.world.clone();
target target
.clone() .clone()
.teleport(pos, yaw.into(), pitch.into(), world) .teleport(pos, yaw.into(), pitch.into(), world)
.await; .await;
} }
Ok(()) Ok(())
})
} }
} }
struct EntitiesToPosFacingPosExecutor; struct EntitiesToPosFacingPosExecutor;
#[async_trait]
impl CommandExecutor for EntitiesToPosFacingPosExecutor { impl CommandExecutor for EntitiesToPosFacingPosExecutor {
async fn execute<'a>( fn execute<'a>(
&self, &'a self,
sender: &mut CommandSender, sender: &'a CommandSender,
server: &crate::server::Server, server: &'a crate::server::Server,
args: &ConsumedArgs<'a>, args: &'a ConsumedArgs<'a>,
) -> Result<(), CommandError> { ) -> CommandResult<'a> {
let targets = EntitiesArgumentConsumer::find_arg(args, ARG_TARGETS)?; Box::pin(async move {
let targets = EntitiesArgumentConsumer::find_arg(args, ARG_TARGETS)?;
let pos = Position3DArgumentConsumer::find_arg(args, ARG_LOCATION)?; let pos = Position3DArgumentConsumer::find_arg(args, ARG_LOCATION)?;
if !World::is_valid(pos) { if !World::is_valid(pos) {
return Err(CommandError::CommandFailed(Box::new( return Err(CommandError::CommandFailed(Box::new(
TextComponent::translate("argument.pos.outofbounds", []), TextComponent::translate("argument.pos.outofbounds", []),
))); )));
}
let facing_pos = Position3DArgumentConsumer::find_arg(args, ARG_FACING_LOCATION)?;
let (yaw, pitch) = yaw_pitch_facing_position(&pos, &facing_pos);
//todo
let world = match sender {
CommandSender::Rcon(_) | CommandSender::Console => {
server.worlds.read().await.first().unwrap().clone()
} }
CommandSender::Player(player) => player.world().clone(), let facing_pos = Position3DArgumentConsumer::find_arg(args, ARG_FACING_LOCATION)?;
}; let (yaw, pitch) = yaw_pitch_facing_position(&pos, &facing_pos);
//todo
let world = match sender {
CommandSender::Rcon(_) | CommandSender::Console => {
server.worlds.read().await.first().unwrap().clone()
}
CommandSender::Player(player) => player.world().clone(),
};
for target in targets { for target in targets {
target target
.clone() .clone()
.teleport(pos, Some(yaw), Some(pitch), world.clone()) .teleport(pos, Some(yaw), Some(pitch), world.clone())
.await; .await;
} }
Ok(()) Ok(())
})
} }
} }
struct EntitiesToPosFacingEntityExecutor; struct EntitiesToPosFacingEntityExecutor;
#[async_trait]
impl CommandExecutor for EntitiesToPosFacingEntityExecutor { impl CommandExecutor for EntitiesToPosFacingEntityExecutor {
async fn execute<'a>( fn execute<'a>(
&self, &'a self,
_sender: &mut CommandSender, _sender: &'a CommandSender,
_server: &crate::server::Server, _server: &'a crate::server::Server,
args: &ConsumedArgs<'a>, args: &'a ConsumedArgs<'a>,
) -> Result<(), CommandError> { ) -> CommandResult<'a> {
let targets = EntitiesArgumentConsumer::find_arg(args, ARG_TARGETS)?; Box::pin(async move {
let targets = EntitiesArgumentConsumer::find_arg(args, ARG_TARGETS)?;
let pos = Position3DArgumentConsumer::find_arg(args, ARG_LOCATION)?; let pos = Position3DArgumentConsumer::find_arg(args, ARG_LOCATION)?;
if !World::is_valid(pos) { if !World::is_valid(pos) {
return Err(CommandError::CommandFailed(Box::new( return Err(CommandError::CommandFailed(Box::new(
TextComponent::translate("argument.pos.outofbounds", []), TextComponent::translate("argument.pos.outofbounds", []),
))); )));
} }
let facing_entity = EntityArgumentConsumer::find_arg(args, ARG_FACING_ENTITY)?; let facing_entity = EntityArgumentConsumer::find_arg(args, ARG_FACING_ENTITY)?;
let (yaw, pitch) = yaw_pitch_facing_position(&pos, &facing_entity.get_entity().pos.load()); let (yaw, pitch) =
yaw_pitch_facing_position(&pos, &facing_entity.get_entity().pos.load());
for target in targets { for target in targets {
target target
.clone() .clone()
.teleport( .teleport(
pos, pos,
Some(yaw), Some(yaw),
Some(pitch), Some(pitch),
facing_entity.get_entity().world.clone(), facing_entity.get_entity().world.clone(),
) )
.await; .await;
} }
Ok(()) Ok(())
})
} }
} }
struct EntitiesToPosWithRotationExecutor; struct EntitiesToPosWithRotationExecutor;
#[async_trait]
impl CommandExecutor for EntitiesToPosWithRotationExecutor { impl CommandExecutor for EntitiesToPosWithRotationExecutor {
async fn execute<'a>( fn execute<'a>(
&self, &'a self,
_sender: &mut CommandSender, _sender: &'a CommandSender,
server: &crate::server::Server, server: &'a crate::server::Server,
args: &ConsumedArgs<'a>, args: &'a ConsumedArgs<'a>,
) -> Result<(), CommandError> { ) -> CommandResult<'a> {
let targets = EntitiesArgumentConsumer::find_arg(args, ARG_TARGETS)?; Box::pin(async move {
let targets = EntitiesArgumentConsumer::find_arg(args, ARG_TARGETS)?;
let pos = Position3DArgumentConsumer::find_arg(args, ARG_LOCATION)?; let pos = Position3DArgumentConsumer::find_arg(args, ARG_LOCATION)?;
if !World::is_valid(pos) { if !World::is_valid(pos) {
return Err(CommandError::CommandFailed(Box::new( return Err(CommandError::CommandFailed(Box::new(
TextComponent::translate("argument.pos.outofbounds", []), TextComponent::translate("argument.pos.outofbounds", []),
))); )));
} }
let (yaw, pitch) = RotationArgumentConsumer::find_arg(args, ARG_ROTATION)?; let (yaw, pitch) = RotationArgumentConsumer::find_arg(args, ARG_ROTATION)?;
// todo command context // todo command context
let world = server.worlds.read().await.first().unwrap().clone(); let world = server.worlds.read().await.first().unwrap().clone();
for target in targets { for target in targets {
target target
.clone() .clone()
.teleport(pos, Some(yaw), Some(pitch), world.clone()) .teleport(pos, Some(yaw), Some(pitch), world.clone())
.await; .await;
} }
Ok(()) Ok(())
})
} }
} }
struct EntitiesToPosExecutor; struct EntitiesToPosExecutor;
#[async_trait]
impl CommandExecutor for EntitiesToPosExecutor { impl CommandExecutor for EntitiesToPosExecutor {
async fn execute<'a>( fn execute<'a>(
&self, &'a self,
sender: &mut CommandSender, sender: &'a CommandSender,
server: &crate::server::Server, server: &'a crate::server::Server,
args: &ConsumedArgs<'a>, args: &'a ConsumedArgs<'a>,
) -> Result<(), CommandError> { ) -> CommandResult<'a> {
let targets = EntitiesArgumentConsumer::find_arg(args, ARG_TARGETS)?; Box::pin(async move {
let targets = EntitiesArgumentConsumer::find_arg(args, ARG_TARGETS)?;
let pos = Position3DArgumentConsumer::find_arg(args, ARG_LOCATION)?; let pos = Position3DArgumentConsumer::find_arg(args, ARG_LOCATION)?;
if !World::is_valid(pos) { if !World::is_valid(pos) {
return Err(CommandError::CommandFailed(Box::new( return Err(CommandError::CommandFailed(Box::new(
TextComponent::translate("argument.pos.outofbounds", []), TextComponent::translate("argument.pos.outofbounds", []),
))); )));
} }
// todo command context // todo command context
let world = match sender { let world = match sender {
CommandSender::Rcon(_) | CommandSender::Console => { CommandSender::Rcon(_) | CommandSender::Console => {
server.worlds.read().await.first().unwrap().clone() server.worlds.read().await.first().unwrap().clone()
}
CommandSender::Player(player) => player.world().clone(),
};
for target in targets {
let yaw = target.get_entity().yaw.load();
let pitch = target.get_entity().pitch.load();
target
.clone()
.teleport(pos, Some(yaw), Some(pitch), world.clone())
.await;
} }
CommandSender::Player(player) => player.world().clone(),
};
for target in targets {
let yaw = target.get_entity().yaw.load();
let pitch = target.get_entity().pitch.load();
target
.clone()
.teleport(pos, Some(yaw), Some(pitch), world.clone())
.await;
}
Ok(()) Ok(())
})
} }
} }
struct SelfToEntityExecutor; struct SelfToEntityExecutor;
#[async_trait]
impl CommandExecutor for SelfToEntityExecutor { impl CommandExecutor for SelfToEntityExecutor {
async fn execute<'a>( fn execute<'a>(
&self, &'a self,
sender: &mut CommandSender, sender: &'a CommandSender,
_server: &crate::server::Server, _server: &'a crate::server::Server,
args: &ConsumedArgs<'a>, args: &'a ConsumedArgs<'a>,
) -> Result<(), CommandError> { ) -> CommandResult<'a> {
let destination = EntityArgumentConsumer::find_arg(args, ARG_DESTINATION)?; Box::pin(async move {
let pos = destination.get_entity().pos.load(); let destination = EntityArgumentConsumer::find_arg(args, ARG_DESTINATION)?;
let world = destination.get_entity().world.clone(); let pos = destination.get_entity().pos.load();
let world = destination.get_entity().world.clone();
match sender { match sender {
CommandSender::Player(player) => { CommandSender::Player(player) => {
let yaw = player.living_entity.entity.yaw.load(); let yaw = player.living_entity.entity.yaw.load();
let pitch = player.living_entity.entity.pitch.load(); let pitch = player.living_entity.entity.pitch.load();
if !World::is_valid(pos) { if !World::is_valid(pos) {
return Err(CommandError::CommandFailed(Box::new( return Err(CommandError::CommandFailed(Box::new(
TextComponent::translate("argument.pos.outofbounds", []), TextComponent::translate("argument.pos.outofbounds", []),
))); )));
}
player
.clone()
.teleport(pos, Some(yaw), Some(pitch), world)
.await;
}
_ => {
sender
.send_message(TextComponent::translate("permissions.requires.player", []))
.await;
} }
player
.clone()
.teleport(pos, Some(yaw), Some(pitch), world)
.await;
} }
_ => {
sender
.send_message(TextComponent::translate("permissions.requires.player", []))
.await;
}
}
Ok(()) Ok(())
})
} }
} }
struct SelfToPosExecutor; struct SelfToPosExecutor;
#[async_trait]
impl CommandExecutor for SelfToPosExecutor { impl CommandExecutor for SelfToPosExecutor {
async fn execute<'a>( fn execute<'a>(
&self, &'a self,
sender: &mut CommandSender, sender: &'a CommandSender,
_server: &crate::server::Server, _server: &'a crate::server::Server,
args: &ConsumedArgs<'a>, args: &'a ConsumedArgs<'a>,
) -> Result<(), CommandError> { ) -> CommandResult<'a> {
match sender { Box::pin(async move {
CommandSender::Player(player) => { match sender {
let pos = Position3DArgumentConsumer::find_arg(args, ARG_LOCATION)?; CommandSender::Player(player) => {
let yaw = player.living_entity.entity.yaw.load(); let pos = Position3DArgumentConsumer::find_arg(args, ARG_LOCATION)?;
let pitch = player.living_entity.entity.pitch.load(); let yaw = player.living_entity.entity.yaw.load();
if !World::is_valid(pos) { let pitch = player.living_entity.entity.pitch.load();
return Err(CommandError::CommandFailed(Box::new( if !World::is_valid(pos) {
TextComponent::translate("argument.pos.outofbounds", []), return Err(CommandError::CommandFailed(Box::new(
))); TextComponent::translate("argument.pos.outofbounds", []),
)));
}
player
.clone()
.teleport(pos, Some(yaw), Some(pitch), player.world().clone())
.await;
}
_ => {
sender
.send_message(TextComponent::translate("permissions.requires.player", []))
.await;
} }
player
.clone()
.teleport(pos, Some(yaw), Some(pitch), player.world().clone())
.await;
} }
_ => {
sender
.send_message(TextComponent::translate("permissions.requires.player", []))
.await;
}
}
Ok(()) Ok(())
})
} }
} }

View File

@ -1,13 +1,11 @@
use crate::command::{ use crate::command::{
CommandError, CommandExecutor, CommandSender, CommandError, CommandExecutor, CommandResult, CommandSender,
args::{ args::{
Arg, ConsumedArgs, FindArg, players::PlayersArgumentConsumer, Arg, ConsumedArgs, FindArg, players::PlayersArgumentConsumer,
textcomponent::TextComponentArgConsumer, textcomponent::TextComponentArgConsumer,
}, },
tree::CommandTree, tree::{CommandTree, builder::argument},
tree::builder::argument,
}; };
use async_trait::async_trait;
const NAMES: [&str; 1] = ["tellraw"]; const NAMES: [&str; 1] = ["tellraw"];
@ -19,23 +17,24 @@ const ARG_MESSAGE: &str = "message";
struct TellRawExecutor; struct TellRawExecutor;
#[async_trait]
impl CommandExecutor for TellRawExecutor { impl CommandExecutor for TellRawExecutor {
async fn execute<'a>( fn execute<'a>(
&self, &'a self,
_sender: &mut CommandSender, _sender: &'a CommandSender,
_server: &crate::server::Server, _server: &'a crate::server::Server,
args: &ConsumedArgs<'a>, args: &'a ConsumedArgs<'a>,
) -> Result<(), CommandError> { ) -> CommandResult<'a> {
let Some(Arg::Players(targets)) = args.get(&ARG_TARGETS) else { Box::pin(async move {
return Err(CommandError::InvalidConsumption(Some(ARG_TARGETS.into()))); let Some(Arg::Players(targets)) = args.get(&ARG_TARGETS) else {
}; return Err(CommandError::InvalidConsumption(Some(ARG_TARGETS.into())));
};
let text = TextComponentArgConsumer::find_arg(args, ARG_MESSAGE)?; let text = TextComponentArgConsumer::find_arg(args, ARG_MESSAGE)?;
for target in targets { for target in targets {
target.send_system_message(&text).await; target.send_system_message(&text).await;
} }
Ok(()) Ok(())
})
} }
} }

View File

@ -1,4 +1,3 @@
use async_trait::async_trait;
use pumpkin_config::BASIC_CONFIG; use pumpkin_config::BASIC_CONFIG;
use pumpkin_util::text::{ use pumpkin_util::text::{
TextComponent, TextComponent,
@ -7,7 +6,7 @@ use pumpkin_util::text::{
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
use crate::command::{ use crate::command::{
CommandExecutor, CommandSender, CommandExecutor, CommandResult, CommandSender,
args::{ args::{
ConsumedArgs, FindArg, bounded_num::BoundedNumArgumentConsumer, time::TimeArgumentConsumer, ConsumedArgs, FindArg, bounded_num::BoundedNumArgumentConsumer, time::TimeArgumentConsumer,
}, },
@ -199,104 +198,110 @@ impl TickExecutor {
} }
} }
#[async_trait]
impl CommandExecutor for TickExecutor { impl CommandExecutor for TickExecutor {
async fn execute<'a>( fn execute<'a>(
&self, &'a self,
sender: &mut CommandSender, sender: &'a CommandSender,
server: &crate::server::Server, server: &'a crate::server::Server,
args: &ConsumedArgs<'a>, args: &'a ConsumedArgs<'a>,
) -> Result<(), CommandError> { ) -> CommandResult<'a> {
let manager = &server.tick_rate_manager; Box::pin(async move {
let manager = &server.tick_rate_manager;
match self.0 { match self.0 {
SubCommand::Query => self.handle_query(sender, server, manager).await, SubCommand::Query => self.handle_query(sender, server, manager).await,
SubCommand::Rate => { SubCommand::Rate => {
let rate = BoundedNumArgumentConsumer::<f32>::find_arg(args, "rate")??; let rate = BoundedNumArgumentConsumer::<f32>::find_arg(args, "rate")??;
manager.set_tick_rate(server, rate).await; manager.set_tick_rate(server, rate).await;
sender
.send_message(TextComponent::translate(
"commands.tick.rate.success",
[TextComponent::text(format!("{rate:.1}"))],
))
.await;
Ok(())
}
SubCommand::RateLiteral(rate) => {
manager.set_tick_rate(server, rate).await;
sender
.send_message(TextComponent::translate(
"commands.tick.rate.success",
[TextComponent::text(format!("{rate:.1}"))],
))
.await;
Ok(())
}
SubCommand::Freeze(freeze) => {
manager.set_frozen(server, freeze).await;
let message_key = if freeze {
"commands.tick.status.frozen"
} else {
"commands.tick.status.running"
};
sender
.send_message(TextComponent::translate(message_key, []))
.await;
Ok(())
}
SubCommand::StepDefault => self.handle_step_command(sender, server, manager, 1).await,
SubCommand::StepTimed => {
let ticks = TimeArgumentConsumer::find_arg(args, "time")?;
self.handle_step_command(sender, server, manager, ticks)
.await
}
SubCommand::StepLiteral(ticks) => {
self.handle_step_command(sender, server, manager, ticks)
.await
}
SubCommand::StepStop => {
if manager.stop_stepping(server).await {
sender sender
.send_message(TextComponent::translate( .send_message(TextComponent::translate(
"commands.tick.step.stop.success", "commands.tick.rate.success",
[], [TextComponent::text(format!("{rate:.1}"))],
)) ))
.await; .await;
} else { Ok(())
sender
.send_message(TextComponent::translate("commands.tick.step.stop.fail", []))
.await;
} }
Ok(()) SubCommand::RateLiteral(rate) => {
} manager.set_tick_rate(server, rate).await;
SubCommand::SprintTimed => {
let ticks = TimeArgumentConsumer::find_arg(args, "time")?;
self.handle_sprint_command(sender, server, manager, ticks)
.await
}
SubCommand::SprintLiteral(ticks) => {
self.handle_sprint_command(sender, server, manager, ticks)
.await
}
SubCommand::SprintStop => {
if manager.stop_sprinting(server).await {
sender sender
.send_message(TextComponent::translate( .send_message(TextComponent::translate(
"commands.tick.sprint.stop.success", "commands.tick.rate.success",
[], [TextComponent::text(format!("{rate:.1}"))],
)) ))
.await; .await;
} else { Ok(())
sender }
.send_message( SubCommand::Freeze(freeze) => {
TextComponent::translate("commands.tick.sprint.stop.fail", []) manager.set_frozen(server, freeze).await;
.color(Color::Named(NamedColor::Red)), let message_key = if freeze {
) "commands.tick.status.frozen"
.await; } else {
"commands.tick.status.running"
};
sender
.send_message(TextComponent::translate(message_key, []))
.await;
Ok(())
}
SubCommand::StepDefault => {
self.handle_step_command(sender, server, manager, 1).await
}
SubCommand::StepTimed => {
let ticks = TimeArgumentConsumer::find_arg(args, "time")?;
self.handle_step_command(sender, server, manager, ticks)
.await
}
SubCommand::StepLiteral(ticks) => {
self.handle_step_command(sender, server, manager, ticks)
.await
}
SubCommand::StepStop => {
if manager.stop_stepping(server).await {
sender
.send_message(TextComponent::translate(
"commands.tick.step.stop.success",
[],
))
.await;
} else {
sender
.send_message(TextComponent::translate(
"commands.tick.step.stop.fail",
[],
))
.await;
}
Ok(())
}
SubCommand::SprintTimed => {
let ticks = TimeArgumentConsumer::find_arg(args, "time")?;
self.handle_sprint_command(sender, server, manager, ticks)
.await
}
SubCommand::SprintLiteral(ticks) => {
self.handle_sprint_command(sender, server, manager, ticks)
.await
}
SubCommand::SprintStop => {
if manager.stop_sprinting(server).await {
sender
.send_message(TextComponent::translate(
"commands.tick.sprint.stop.success",
[],
))
.await;
} else {
sender
.send_message(
TextComponent::translate("commands.tick.sprint.stop.fail", [])
.color(Color::Named(NamedColor::Red)),
)
.await;
}
Ok(())
} }
Ok(())
} }
} })
} }
} }

View File

@ -1,12 +1,10 @@
use async_trait::async_trait;
use pumpkin_util::text::TextComponent; use pumpkin_util::text::TextComponent;
use pumpkin_util::text::color::{Color, NamedColor}; use pumpkin_util::text::color::{Color, NamedColor};
use crate::command::CommandResult;
use crate::command::args::{FindArg, time::TimeArgumentConsumer}; use crate::command::args::{FindArg, time::TimeArgumentConsumer};
use crate::command::tree::builder::{argument, literal}; use crate::command::tree::builder::{argument, literal};
use crate::command::{ use crate::command::{CommandExecutor, CommandSender, ConsumedArgs, tree::CommandTree};
CommandError, CommandExecutor, CommandSender, ConsumedArgs, tree::CommandTree,
};
const NAMES: [&str; 1] = ["time"]; const NAMES: [&str; 1] = ["time"];
const DESCRIPTION: &str = "Query the world time."; const DESCRIPTION: &str = "Query the world time.";
@ -46,107 +44,109 @@ enum QueryMode {
struct QueryExecutor(QueryMode); struct QueryExecutor(QueryMode);
#[async_trait]
impl CommandExecutor for QueryExecutor { impl CommandExecutor for QueryExecutor {
async fn execute<'a>( fn execute<'a>(
&self, &'a self,
sender: &mut CommandSender, sender: &'a CommandSender,
server: &crate::server::Server, server: &'a crate::server::Server,
_args: &ConsumedArgs<'a>, _args: &'a ConsumedArgs<'a>,
) -> Result<(), CommandError> { ) -> CommandResult<'a> {
let mode = self.0; Box::pin(async move {
// TODO: Maybe ask player for world, or get the current world let mode = self.0;
let worlds = server.worlds.read().await; // TODO: Maybe ask player for world, or get the current world
let world = worlds let worlds = server.worlds.read().await;
.first() let world = worlds
.expect("There should always be at least one world"); .first()
let level_time = world.level_time.lock().await; .expect("There should always be at least one world");
let level_time = world.level_time.lock().await;
let msg = match mode { let msg = match mode {
QueryMode::DayTime => { QueryMode::DayTime => {
let curr_time = level_time.query_daytime(); let curr_time = level_time.query_daytime();
TextComponent::translate( TextComponent::translate(
"commands.time.query", "commands.time.query",
[TextComponent::text(curr_time.to_string())], [TextComponent::text(curr_time.to_string())],
) )
} }
QueryMode::GameTime => { QueryMode::GameTime => {
let curr_time = level_time.query_gametime(); let curr_time = level_time.query_gametime();
TextComponent::translate( TextComponent::translate(
"commands.time.query", "commands.time.query",
[TextComponent::text(curr_time.to_string())], [TextComponent::text(curr_time.to_string())],
) )
} }
QueryMode::Day => { QueryMode::Day => {
let curr_time = level_time.query_day(); let curr_time = level_time.query_day();
TextComponent::translate( TextComponent::translate(
"commands.time.query", "commands.time.query",
[TextComponent::text(curr_time.to_string())], [TextComponent::text(curr_time.to_string())],
) )
} }
}; };
sender.send_message(msg).await; sender.send_message(msg).await;
Ok(()) Ok(())
})
} }
} }
struct ChangeExecutor(Mode); struct ChangeExecutor(Mode);
#[async_trait]
impl CommandExecutor for ChangeExecutor { impl CommandExecutor for ChangeExecutor {
async fn execute<'a>( fn execute<'a>(
&self, &'a self,
sender: &mut CommandSender, sender: &'a CommandSender,
server: &crate::server::Server, server: &'a crate::server::Server,
args: &ConsumedArgs<'a>, args: &'a ConsumedArgs<'a>,
) -> Result<(), CommandError> { ) -> CommandResult<'a> {
let time_count = if let Mode::Set(Some(preset)) = &self.0 { Box::pin(async move {
preset.to_ticks() let time_count = if let Mode::Set(Some(preset)) = &self.0 {
} else if let Ok(ticks) = TimeArgumentConsumer::find_arg(args, ARG_TIME) { preset.to_ticks()
ticks } else if let Ok(ticks) = TimeArgumentConsumer::find_arg(args, ARG_TIME) {
} else { ticks
sender } else {
.send_message( sender
TextComponent::text("Invalid time specified.") .send_message(
.color(Color::Named(NamedColor::Red)), TextComponent::text("Invalid time specified.")
) .color(Color::Named(NamedColor::Red)),
.await; )
return Ok(()); .await;
}; return Ok(());
};
let mode = self.0; let mode = self.0;
// TODO: Maybe ask player for world, or get the current world // TODO: Maybe ask player for world, or get the current world
let worlds = server.worlds.read().await; let worlds = server.worlds.read().await;
let world = worlds let world = worlds
.first() .first()
.expect("There should always be at least one world"); .expect("There should always be at least one world");
let mut level_time = world.level_time.lock().await; let mut level_time = world.level_time.lock().await;
let msg = match mode { let msg = match mode {
Mode::Add => { Mode::Add => {
// add // add
level_time.add_time(time_count.into()); level_time.add_time(time_count.into());
level_time.send_time(world).await; level_time.send_time(world).await;
let curr_time = level_time.query_daytime(); let curr_time = level_time.query_daytime();
TextComponent::translate( TextComponent::translate(
"commands.time.set", "commands.time.set",
[TextComponent::text(curr_time.to_string())], [TextComponent::text(curr_time.to_string())],
) )
} }
Mode::Set(_) => { Mode::Set(_) => {
// set // set
level_time.set_time(time_count.into()); level_time.set_time(time_count.into());
level_time.send_time(world).await; level_time.send_time(world).await;
TextComponent::translate( TextComponent::translate(
"commands.time.set", "commands.time.set",
[TextComponent::text(time_count.to_string())], [TextComponent::text(time_count.to_string())],
) )
} }
}; };
sender.send_message(msg).await; sender.send_message(msg).await;
Ok(()) Ok(())
})
} }
} }

View File

@ -1,7 +1,7 @@
use async_trait::async_trait;
use pumpkin_protocol::java::client::play::CClearTitle; use pumpkin_protocol::java::client::play::CClearTitle;
use pumpkin_util::text::TextComponent; use pumpkin_util::text::TextComponent;
use crate::command::CommandResult;
use crate::entity::EntityBase; use crate::entity::EntityBase;
use crate::{ use crate::{
command::{ command::{
@ -27,82 +27,84 @@ const ARG_TITLE: &str = "title";
/// bool: Whether to reset or not /// bool: Whether to reset or not
struct ClearOrResetExecutor(bool); struct ClearOrResetExecutor(bool);
#[async_trait]
impl CommandExecutor for ClearOrResetExecutor { impl CommandExecutor for ClearOrResetExecutor {
async fn execute<'a>( fn execute<'a>(
&self, &'a self,
sender: &mut CommandSender, sender: &'a CommandSender,
_server: &crate::server::Server, _server: &'a crate::server::Server,
args: &ConsumedArgs<'a>, args: &'a ConsumedArgs<'a>,
) -> Result<(), CommandError> { ) -> CommandResult<'a> {
let Some(Arg::Players(targets)) = args.get(&ARG_TARGETS) else { Box::pin(async move {
return Err(CommandError::InvalidConsumption(Some(ARG_TARGETS.into()))); let Some(Arg::Players(targets)) = args.get(&ARG_TARGETS) else {
}; return Err(CommandError::InvalidConsumption(Some(ARG_TARGETS.into())));
let reset = self.0; };
let reset = self.0;
for target in targets { for target in targets {
target.client.enqueue_packet(&CClearTitle::new(reset)).await; target.client.enqueue_packet(&CClearTitle::new(reset)).await;
} }
sender sender
.send_message(if targets.len() == 1 { .send_message(if targets.len() == 1 {
let text = if reset { let text = if reset {
"commands.title.reset.single" "commands.title.reset.single"
} else {
"commands.title.cleared.single"
};
TextComponent::translate(text, [targets[0].get_display_name().await])
} else { } else {
"commands.title.cleared.single" let text = if reset {
}; "commands.title.reset.multiple"
TextComponent::translate(text, [targets[0].get_display_name().await]) } else {
} else { "commands.title.cleared.multiple"
let text = if reset { };
"commands.title.reset.multiple" TextComponent::translate(text, [TextComponent::text(targets.len().to_string())])
} else { })
"commands.title.cleared.multiple" .await;
};
TextComponent::translate(text, [TextComponent::text(targets.len().to_string())])
})
.await;
Ok(()) Ok(())
})
} }
} }
struct TitleExecutor(TitleMode); struct TitleExecutor(TitleMode);
#[async_trait]
impl CommandExecutor for TitleExecutor { impl CommandExecutor for TitleExecutor {
async fn execute<'a>( fn execute<'a>(
&self, &'a self,
sender: &mut CommandSender, sender: &'a CommandSender,
_server: &crate::server::Server, _server: &'a crate::server::Server,
args: &ConsumedArgs<'a>, args: &'a ConsumedArgs<'a>,
) -> Result<(), CommandError> { ) -> CommandResult<'a> {
let Some(Arg::Players(targets)) = args.get(&ARG_TARGETS) else { Box::pin(async move {
return Err(CommandError::InvalidConsumption(Some(ARG_TARGETS.into()))); let Some(Arg::Players(targets)) = args.get(&ARG_TARGETS) else {
}; return Err(CommandError::InvalidConsumption(Some(ARG_TARGETS.into())));
};
let text = TextComponentArgConsumer::find_arg(args, ARG_TITLE)?; let text = TextComponentArgConsumer::find_arg(args, ARG_TITLE)?;
let mode = &self.0; let mode = &self.0;
for target in targets { for target in targets {
target.show_title(&text, mode).await; target.show_title(&text, mode).await;
} }
let mode_name = format!("{mode:?}").to_lowercase(); let mode_name = format!("{mode:?}").to_lowercase();
sender sender
.send_message(if targets.len() == 1 { .send_message(if targets.len() == 1 {
TextComponent::translate( TextComponent::translate(
format!("commands.title.show.{mode_name}.single"), format!("commands.title.show.{mode_name}.single"),
[targets[0].get_display_name().await], [targets[0].get_display_name().await],
) )
} else { } else {
TextComponent::translate( TextComponent::translate(
format!("commands.title.show.{mode_name}.multiple"), format!("commands.title.show.{mode_name}.multiple"),
[TextComponent::text(targets.len().to_string())], [TextComponent::text(targets.len().to_string())],
) )
}) })
.await; .await;
Ok(()) Ok(())
})
} }
} }

View File

@ -1,18 +1,16 @@
use async_trait::async_trait;
use pumpkin_protocol::codec::var_int::VarInt; use pumpkin_protocol::codec::var_int::VarInt;
use pumpkin_protocol::java::client::play::CTransfer; use pumpkin_protocol::java::client::play::CTransfer;
use pumpkin_util::text::TextComponent; use pumpkin_util::text::TextComponent;
use pumpkin_util::text::color::{Color, NamedColor}; use pumpkin_util::text::color::{Color, NamedColor};
use crate::command::CommandResult;
use crate::command::args::bounded_num::BoundedNumArgumentConsumer; use crate::command::args::bounded_num::BoundedNumArgumentConsumer;
use crate::command::args::players::PlayersArgumentConsumer; use crate::command::args::players::PlayersArgumentConsumer;
use crate::command::args::simple::SimpleArgConsumer; use crate::command::args::simple::SimpleArgConsumer;
use crate::command::args::{Arg, FindArgDefaultName}; use crate::command::args::{Arg, FindArgDefaultName};
use crate::command::dispatcher::CommandError::{InvalidConsumption, InvalidRequirement}; use crate::command::dispatcher::CommandError::{InvalidConsumption, InvalidRequirement};
use crate::command::tree::builder::{argument, argument_default_name, require}; use crate::command::tree::builder::{argument, argument_default_name, require};
use crate::command::{ use crate::command::{CommandExecutor, CommandSender, args::ConsumedArgs, tree::CommandTree};
CommandError, CommandExecutor, CommandSender, args::ConsumedArgs, tree::CommandTree,
};
const NAMES: [&str; 1] = ["transfer"]; const NAMES: [&str; 1] = ["transfer"];
@ -31,89 +29,91 @@ fn port_consumer() -> BoundedNumArgumentConsumer<i32> {
struct TargetSelfExecutor; struct TargetSelfExecutor;
#[async_trait]
impl CommandExecutor for TargetSelfExecutor { impl CommandExecutor for TargetSelfExecutor {
async fn execute<'a>( fn execute<'a>(
&self, &'a self,
sender: &mut CommandSender, sender: &'a CommandSender,
_server: &crate::server::Server, _server: &'a crate::server::Server,
args: &ConsumedArgs<'a>, args: &'a ConsumedArgs<'a>,
) -> Result<(), CommandError> { ) -> CommandResult<'a> {
let Some(Arg::Simple(hostname)) = args.get(ARG_HOSTNAME) else { Box::pin(async move {
return Err(InvalidConsumption(Some(ARG_HOSTNAME.into()))); let Some(Arg::Simple(hostname)) = args.get(ARG_HOSTNAME) else {
}; return Err(InvalidConsumption(Some(ARG_HOSTNAME.into())));
};
let port = match port_consumer().find_arg_default_name(args) { let port = match port_consumer().find_arg_default_name(args) {
Err(_) => 25565, Err(_) => 25565,
Ok(Ok(count)) => count, Ok(Ok(count)) => count,
Ok(Err(_)) => { Ok(Err(_)) => {
sender sender
.send_message( .send_message(
TextComponent::text("Port must be between 1 and 65535.") TextComponent::text("Port must be between 1 and 65535.")
.color(Color::Named(NamedColor::Red)), .color(Color::Named(NamedColor::Red)),
) )
.await;
return Ok(());
}
};
if let CommandSender::Player(player) = sender {
let name = &player.gameprofile.name;
log::info!("[{name}: Transferring {name} to {hostname}:{port}]");
player
.client
.enqueue_packet(&CTransfer::new(hostname, VarInt(port)))
.await; .await;
return Ok(()); Ok(())
} else {
Err(InvalidRequirement)
} }
}; })
if let CommandSender::Player(player) = sender {
let name = &player.gameprofile.name;
log::info!("[{name}: Transferring {name} to {hostname}:{port}]");
player
.client
.enqueue_packet(&CTransfer::new(hostname, VarInt(port)))
.await;
Ok(())
} else {
Err(InvalidRequirement)
}
} }
} }
struct TargetPlayerExecutor; struct TargetPlayerExecutor;
#[async_trait]
impl CommandExecutor for TargetPlayerExecutor { impl CommandExecutor for TargetPlayerExecutor {
async fn execute<'a>( fn execute<'a>(
&self, &'a self,
sender: &mut CommandSender, sender: &'a CommandSender,
_server: &crate::server::Server, _server: &'a crate::server::Server,
args: &ConsumedArgs<'a>, args: &'a ConsumedArgs<'a>,
) -> Result<(), CommandError> { ) -> CommandResult<'a> {
let Some(Arg::Simple(hostname)) = args.get(ARG_HOSTNAME) else { Box::pin(async move {
return Err(InvalidConsumption(Some(ARG_HOSTNAME.into()))); let Some(Arg::Simple(hostname)) = args.get(ARG_HOSTNAME) else {
}; return Err(InvalidConsumption(Some(ARG_HOSTNAME.into())));
};
let port = match port_consumer().find_arg_default_name(args) { let port = match port_consumer().find_arg_default_name(args) {
Err(_) => 25565, Err(_) => 25565,
Ok(Ok(count)) => count, Ok(Ok(count)) => count,
Ok(Err(_)) => { Ok(Err(_)) => {
sender sender
.send_message( .send_message(
TextComponent::text("Port must be between 1 and 65535.") TextComponent::text("Port must be between 1 and 65535.")
.color(Color::Named(NamedColor::Red)), .color(Color::Named(NamedColor::Red)),
) )
.await;
return Ok(());
}
};
let Some(Arg::Players(players)) = args.get(ARG_PLAYERS) else {
return Err(InvalidConsumption(Some(ARG_PLAYERS.into())));
};
for p in players {
p.client
.enqueue_packet(&CTransfer::new(hostname, VarInt(port)))
.await; .await;
return Ok(()); log::info!(
"[{sender}: Transferring {} to {hostname}:{port}]",
p.gameprofile.name
);
} }
};
let Some(Arg::Players(players)) = args.get(ARG_PLAYERS) else { Ok(())
return Err(InvalidConsumption(Some(ARG_PLAYERS.into()))); })
};
for p in players {
p.client
.enqueue_packet(&CTransfer::new(hostname, VarInt(port)))
.await;
log::info!(
"[{sender}: Transferring {} to {hostname}:{port}]",
p.gameprofile.name
);
}
Ok(())
} }
} }

View File

@ -1,11 +1,12 @@
use async_trait::async_trait;
use pumpkin_util::text::TextComponent; use pumpkin_util::text::TextComponent;
use crate::command::{ use crate::command::{
CommandError, CommandExecutor, CommandSender, CommandError, CommandExecutor, CommandResult, CommandSender,
args::{ConsumedArgs, FindArg, time::TimeArgumentConsumer}, args::{ConsumedArgs, FindArg, time::TimeArgumentConsumer},
tree::CommandTree, tree::{
tree::builder::{argument, literal}, CommandTree,
builder::{argument, literal},
},
}; };
const NAMES: [&str; 1] = ["weather"]; const NAMES: [&str; 1] = ["weather"];
@ -22,53 +23,54 @@ enum WeatherMode {
Thunder, Thunder,
} }
#[async_trait]
impl CommandExecutor for Executor { impl CommandExecutor for Executor {
async fn execute<'a>( fn execute<'a>(
&self, &'a self,
sender: &mut CommandSender, sender: &'a CommandSender,
server: &crate::server::Server, server: &'a crate::server::Server,
args: &ConsumedArgs<'a>, args: &'a ConsumedArgs<'a>,
) -> Result<(), CommandError> { ) -> CommandResult<'a> {
let duration = TimeArgumentConsumer::find_arg(args, ARG_DURATION).unwrap_or(6000); Box::pin(async move {
let world = { let duration = TimeArgumentConsumer::find_arg(args, ARG_DURATION).unwrap_or(6000);
let guard = server.worlds.read().await; let world = {
let guard = server.worlds.read().await;
guard guard
.first() .first()
.cloned() .cloned()
.ok_or(CommandError::InvalidRequirement)? .ok_or(CommandError::InvalidRequirement)?
}; };
let mut weather = world.weather.lock().await; let mut weather = world.weather.lock().await;
match self.mode { match self.mode {
WeatherMode::Clear => { WeatherMode::Clear => {
weather weather
.set_weather_parameters(&world, duration, 0, false, false) .set_weather_parameters(&world, duration, 0, false, false)
.await; .await;
sender sender
.send_message(TextComponent::translate("commands.weather.set.clear", [])) .send_message(TextComponent::translate("commands.weather.set.clear", []))
.await; .await;
}
WeatherMode::Rain => {
weather
.set_weather_parameters(&world, 0, duration, true, false)
.await;
sender
.send_message(TextComponent::translate("commands.weather.set.rain", []))
.await;
}
WeatherMode::Thunder => {
weather
.set_weather_parameters(&world, 0, duration, true, true)
.await;
sender
.send_message(TextComponent::translate("commands.weather.set.thunder", []))
.await;
}
} }
WeatherMode::Rain => {
weather
.set_weather_parameters(&world, 0, duration, true, false)
.await;
sender
.send_message(TextComponent::translate("commands.weather.set.rain", []))
.await;
}
WeatherMode::Thunder => {
weather
.set_weather_parameters(&world, 0, duration, true, true)
.await;
sender
.send_message(TextComponent::translate("commands.weather.set.thunder", []))
.await;
}
}
Ok(()) Ok(())
})
} }
} }

View File

@ -1,9 +1,9 @@
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
use async_trait::async_trait;
use pumpkin_config::{BASIC_CONFIG, whitelist::WhitelistEntry}; use pumpkin_config::{BASIC_CONFIG, whitelist::WhitelistEntry};
use pumpkin_util::text::TextComponent; use pumpkin_util::text::TextComponent;
use crate::command::CommandResult;
use crate::entity::EntityBase; use crate::entity::EntityBase;
use crate::{ use crate::{
command::{ command::{
@ -46,205 +46,211 @@ async fn kick_non_whitelisted_players(server: &Server) {
struct OnExecutor; struct OnExecutor;
#[async_trait]
impl CommandExecutor for OnExecutor { impl CommandExecutor for OnExecutor {
async fn execute<'a>( fn execute<'a>(
&self, &'a self,
sender: &mut CommandSender, sender: &'a CommandSender,
server: &Server, server: &'a crate::server::Server,
_args: &ConsumedArgs<'a>, _args: &'a ConsumedArgs<'a>,
) -> Result<(), CommandError> { ) -> CommandResult<'a> {
let previous = server.white_list.swap(true, Ordering::Relaxed); Box::pin(async move {
if previous { let previous = server.white_list.swap(true, Ordering::Relaxed);
sender if previous {
.send_message(TextComponent::translate( sender
"commands.whitelist.alreadyOn", .send_message(TextComponent::translate(
&[], "commands.whitelist.alreadyOn",
)) &[],
.await; ))
} else { .await;
kick_non_whitelisted_players(server).await; } else {
sender kick_non_whitelisted_players(server).await;
.send_message(TextComponent::translate("commands.whitelist.enabled", &[])) sender
.await; .send_message(TextComponent::translate("commands.whitelist.enabled", &[]))
} .await;
Ok(()) }
Ok(())
})
} }
} }
struct OffExecutor; struct OffExecutor;
#[async_trait]
impl CommandExecutor for OffExecutor { impl CommandExecutor for OffExecutor {
async fn execute<'a>( fn execute<'a>(
&self, &'a self,
sender: &mut CommandSender, sender: &'a CommandSender,
server: &Server, server: &'a crate::server::Server,
_args: &ConsumedArgs<'a>, _args: &'a ConsumedArgs<'a>,
) -> Result<(), CommandError> { ) -> CommandResult<'a> {
let previous = server.white_list.swap(false, Ordering::Relaxed); Box::pin(async move {
if previous { let previous = server.white_list.swap(false, Ordering::Relaxed);
sender if previous {
.send_message(TextComponent::translate("commands.whitelist.disabled", &[])) sender
.await; .send_message(TextComponent::translate("commands.whitelist.disabled", &[]))
} else { .await;
sender } else {
.send_message(TextComponent::translate( sender
"commands.whitelist.alreadyOff", .send_message(TextComponent::translate(
&[], "commands.whitelist.alreadyOff",
)) &[],
.await; ))
} .await;
Ok(()) }
Ok(())
})
} }
} }
struct ListExecutor; struct ListExecutor;
#[async_trait]
impl CommandExecutor for ListExecutor { impl CommandExecutor for ListExecutor {
async fn execute<'a>( fn execute<'a>(
&self, &'a self,
sender: &mut CommandSender, sender: &'a CommandSender,
_server: &Server, _server: &'a crate::server::Server,
_args: &ConsumedArgs<'a>, _args: &'a ConsumedArgs<'a>,
) -> Result<(), CommandError> { ) -> CommandResult<'a> {
let whitelist = &WHITELIST_CONFIG.read().await.whitelist; Box::pin(async move {
if whitelist.is_empty() { let whitelist = &WHITELIST_CONFIG.read().await.whitelist;
if whitelist.is_empty() {
sender
.send_message(TextComponent::translate("commands.whitelist.none", []))
.await;
return Ok(());
}
let names = whitelist
.iter()
.map(|entry| entry.name.as_str())
.collect::<Vec<&str>>()
.join(", ");
sender sender
.send_message(TextComponent::translate("commands.whitelist.none", [])) .send_message(TextComponent::translate(
"commands.whitelist.list",
[
TextComponent::text(whitelist.len().to_string()),
TextComponent::text(names),
],
))
.await; .await;
return Ok(());
}
let names = whitelist Ok(())
.iter() })
.map(|entry| entry.name.as_str())
.collect::<Vec<&str>>()
.join(", ");
sender
.send_message(TextComponent::translate(
"commands.whitelist.list",
[
TextComponent::text(whitelist.len().to_string()),
TextComponent::text(names),
],
))
.await;
Ok(())
} }
} }
struct ReloadExecutor; struct ReloadExecutor;
#[async_trait]
impl CommandExecutor for ReloadExecutor { impl CommandExecutor for ReloadExecutor {
async fn execute<'a>( fn execute<'a>(
&self, &'a self,
sender: &mut CommandSender, sender: &'a CommandSender,
server: &Server, server: &'a crate::server::Server,
_args: &ConsumedArgs<'a>, _args: &'a ConsumedArgs<'a>,
) -> Result<(), CommandError> { ) -> CommandResult<'a> {
*WHITELIST_CONFIG.write().await = WhitelistConfig::load(); Box::pin(async move {
kick_non_whitelisted_players(server).await; *WHITELIST_CONFIG.write().await = WhitelistConfig::load();
sender kick_non_whitelisted_players(server).await;
.send_message(TextComponent::translate("commands.whitelist.reloaded", &[])) sender
.await; .send_message(TextComponent::translate("commands.whitelist.reloaded", &[]))
Ok(()) .await;
Ok(())
})
} }
} }
pub struct AddExecutor; pub struct AddExecutor;
#[async_trait]
impl CommandExecutor for AddExecutor { impl CommandExecutor for AddExecutor {
async fn execute<'a>( fn execute<'a>(
&self, &'a self,
sender: &mut CommandSender, sender: &'a CommandSender,
_server: &Server, _server: &'a crate::server::Server,
args: &ConsumedArgs<'a>, args: &'a ConsumedArgs<'a>,
) -> Result<(), CommandError> { ) -> CommandResult<'a> {
let Some(Arg::Players(targets)) = args.get(&ARG_TARGETS) else { Box::pin(async move {
return Err(CommandError::InvalidConsumption(Some(ARG_TARGETS.into()))); let Some(Arg::Players(targets)) = args.get(&ARG_TARGETS) else {
}; return Err(CommandError::InvalidConsumption(Some(ARG_TARGETS.into())));
};
let mut whitelist = WHITELIST_CONFIG.write().await; let mut whitelist = WHITELIST_CONFIG.write().await;
for player in targets { for player in targets {
let profile = &player.gameprofile; let profile = &player.gameprofile;
if whitelist.is_whitelisted(profile) { if whitelist.is_whitelisted(profile) {
sender
.send_message(TextComponent::translate(
"commands.whitelist.add.failed",
&[],
))
.await;
continue;
}
whitelist
.whitelist
.push(WhitelistEntry::new(profile.id, profile.name.clone()));
sender sender
.send_message(TextComponent::translate( .send_message(TextComponent::translate(
"commands.whitelist.add.failed", "commands.whitelist.add.success",
&[], [TextComponent::text(profile.name.clone())],
)) ))
.await; .await;
continue;
} }
whitelist
.whitelist
.push(WhitelistEntry::new(profile.id, profile.name.clone()));
sender
.send_message(TextComponent::translate(
"commands.whitelist.add.success",
[TextComponent::text(profile.name.clone())],
))
.await;
}
whitelist.save(); whitelist.save();
Ok(()) Ok(())
})
} }
} }
pub struct RemoveExecutor; pub struct RemoveExecutor;
#[async_trait]
impl CommandExecutor for RemoveExecutor { impl CommandExecutor for RemoveExecutor {
async fn execute<'a>( fn execute<'a>(
&self, &'a self,
sender: &mut CommandSender, sender: &'a CommandSender,
server: &Server, server: &'a crate::server::Server,
args: &ConsumedArgs<'a>, args: &'a ConsumedArgs<'a>,
) -> Result<(), CommandError> { ) -> CommandResult<'a> {
let Some(Arg::Players(targets)) = args.get(&ARG_TARGETS) else { Box::pin(async move {
return Err(CommandError::InvalidConsumption(Some(ARG_TARGETS.into()))); let Some(Arg::Players(targets)) = args.get(&ARG_TARGETS) else {
}; return Err(CommandError::InvalidConsumption(Some(ARG_TARGETS.into())));
};
let mut whitelist = WHITELIST_CONFIG.write().await; let mut whitelist = WHITELIST_CONFIG.write().await;
for player in targets { for player in targets {
let i = whitelist let i = whitelist
.whitelist .whitelist
.iter() .iter()
.position(|entry| entry.uuid == player.gameprofile.id); .position(|entry| entry.uuid == player.gameprofile.id);
match i { match i {
Some(i) => { Some(i) => {
whitelist.whitelist.remove(i); whitelist.whitelist.remove(i);
sender sender
.send_message(TextComponent::translate( .send_message(TextComponent::translate(
"commands.whitelist.remove.success", "commands.whitelist.remove.success",
[player.get_display_name().await], [player.get_display_name().await],
)) ))
.await; .await;
} }
None => { None => {
sender sender
.send_message(TextComponent::translate( .send_message(TextComponent::translate(
"commands.whitelist.remove.failed", "commands.whitelist.remove.failed",
[], [],
)) ))
.await; .await;
}
} }
} }
}
whitelist.save(); whitelist.save();
drop(whitelist); drop(whitelist);
kick_non_whitelisted_players(server).await; kick_non_whitelisted_players(server).await;
Ok(()) Ok(())
})
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -64,7 +64,7 @@ pub struct CommandDispatcher {
impl CommandDispatcher { impl CommandDispatcher {
pub async fn handle_command<'a>( pub async fn handle_command<'a>(
&'a self, &'a self,
sender: &mut CommandSender, sender: &CommandSender,
server: &'a Server, server: &'a Server,
cmd: &'a str, cmd: &'a str,
) { ) {
@ -242,7 +242,7 @@ impl CommandDispatcher {
/// Execute a command using its corresponding [`CommandTree`]. /// Execute a command using its corresponding [`CommandTree`].
pub(crate) async fn dispatch<'a>( pub(crate) async fn dispatch<'a>(
&'a self, &'a self,
src: &mut CommandSender, src: &CommandSender,
server: &'a Server, server: &'a Server,
cmd: &'a str, cmd: &'a str,
) -> Result<(), CommandError> { ) -> Result<(), CommandError> {
@ -302,7 +302,7 @@ impl CommandDispatcher {
} }
async fn try_is_fitting_path<'a>( async fn try_is_fitting_path<'a>(
src: &mut CommandSender, src: &'a CommandSender,
server: &'a Server, server: &'a Server,
path: &[usize], path: &[usize],
tree: &'a CommandTree, tree: &'a CommandTree,
@ -357,7 +357,7 @@ impl CommandDispatcher {
} }
async fn try_find_suggestions_on_path<'a>( async fn try_find_suggestions_on_path<'a>(
src: &CommandSender, src: &'a CommandSender,
server: &'a Server, server: &'a Server,
path: &[usize], path: &[usize],
tree: &'a CommandTree, tree: &'a CommandTree,

View File

@ -1,4 +1,5 @@
use std::fmt; use std::fmt;
use std::pin::Pin;
use std::str::FromStr; use std::str::FromStr;
use std::sync::Arc; use std::sync::Arc;
@ -6,7 +7,6 @@ use crate::entity::player::Player;
use crate::server::Server; use crate::server::Server;
use crate::world::World; use crate::world::World;
use args::ConsumedArgs; use args::ConsumedArgs;
use async_trait::async_trait;
use dispatcher::CommandError; use dispatcher::CommandError;
use pumpkin_util::math::vector3::Vector3; use pumpkin_util::math::vector3::Vector3;
@ -118,12 +118,13 @@ impl CommandSender {
} }
} }
#[async_trait] pub type CommandResult<'a> = Pin<Box<dyn Future<Output = Result<(), CommandError>> + Send + 'a>>;
pub trait CommandExecutor: Sync {
async fn execute<'a>( pub trait CommandExecutor: Sync + Send {
&self, fn execute<'a>(
sender: &mut CommandSender, &'a self,
server: &Server, sender: &'a CommandSender,
args: &ConsumedArgs<'a>, server: &'a Server,
) -> Result<(), CommandError>; args: &'a ConsumedArgs<'a>,
) -> CommandResult<'a>;
} }

View File

@ -39,7 +39,7 @@ impl CommandTree {
/// ///
/// Also see [`NonLeafNodeBuilder::execute`]. /// Also see [`NonLeafNodeBuilder::execute`].
#[must_use] #[must_use]
pub fn execute(mut self, executor: impl CommandExecutor + 'static + Send) -> Self { pub fn execute(mut self, executor: impl CommandExecutor + 'static) -> Self {
let node = Node { let node = Node {
node_type: NodeType::ExecuteLeaf { node_type: NodeType::ExecuteLeaf {
executor: Arc::new(executor), executor: Arc::new(executor),
@ -116,7 +116,7 @@ impl NonLeafNodeBuilder {
/// ///
/// Also see [`CommandTree::execute`]. /// Also see [`CommandTree::execute`].
#[must_use] #[must_use]
pub fn execute(mut self, executor: impl CommandExecutor + 'static + Send) -> Self { pub fn execute(mut self, executor: impl CommandExecutor + 'static) -> Self {
self.leaf_nodes.push(LeafNodeBuilder { self.leaf_nodes.push(LeafNodeBuilder {
node_type: NodeType::ExecuteLeaf { node_type: NodeType::ExecuteLeaf {
executor: Arc::new(executor), executor: Arc::new(executor),
@ -149,7 +149,7 @@ pub fn literal(string: impl Into<String>) -> NonLeafNodeBuilder {
/// reversed, so [`Vec::pop`] can be used to obtain args in ltr order. /// reversed, so [`Vec::pop`] can be used to obtain args in ltr order.
pub fn argument( pub fn argument(
name: impl Into<String>, name: impl Into<String>,
consumer: impl ArgumentConsumer + 'static + Send, consumer: impl ArgumentConsumer + 'static,
) -> NonLeafNodeBuilder { ) -> NonLeafNodeBuilder {
NonLeafNodeBuilder { NonLeafNodeBuilder {
node_type: NodeType::Argument { node_type: NodeType::Argument {

View File

@ -489,7 +489,7 @@ async fn setup_stdin_console(server: Arc<Server>) {
'after: { 'after: {
let dispatcher = &server.command_dispatcher.read().await; let dispatcher = &server.command_dispatcher.read().await;
dispatcher dispatcher
.handle_command(&mut command::CommandSender::Console, &server, command.as_str()) .handle_command(&command::CommandSender::Console, &server, command.as_str())
.await; .await;
}; };
}} }}
@ -522,7 +522,7 @@ fn setup_console(rl: Readline, server: Arc<Server>) {
let dispatcher = server.command_dispatcher.read().await; let dispatcher = server.command_dispatcher.read().await;
dispatcher dispatcher
.handle_command(&mut command::CommandSender::Console, &server, &line) .handle_command(&command::CommandSender::Console, &server, &line)
.await; .await;
rl.add_history_entry(line).unwrap(); rl.add_history_entry(line).unwrap();
} }

View File

@ -225,7 +225,7 @@ impl BedrockClient {
let dispatcher = server_clone.command_dispatcher.read().await; let dispatcher = server_clone.command_dispatcher.read().await;
dispatcher dispatcher
.handle_command( .handle_command(
&mut CommandSender::Player(player_clone), &CommandSender::Player(player_clone),
&server_clone, &server_clone,
&command_clone, &command_clone,
) )

View File

@ -544,7 +544,7 @@ impl JavaClient {
server.spawn_task(async move { server.spawn_task(async move {
server_clone.command_dispatcher.read().await server_clone.command_dispatcher.read().await
.handle_command( .handle_command(
&mut CommandSender::Player(player_clone), &CommandSender::Player(player_clone),
&server_clone, &server_clone,
&command_clone, &command_clone,
) )

View File

@ -131,7 +131,7 @@ impl RCONClient {
.read() .read()
.await .await
.handle_command( .handle_command(
&mut crate::command::CommandSender::Rcon(output_clone), &crate::command::CommandSender::Rcon(output_clone),
&server_clone, &server_clone,
&packet_body, &packet_body,
) )