chore(command): add argument type helpers (#1972)

* added helpers for all currently existing argument types

* implemented helper for `/setidletimeout`

* fixed formatting

* changed `/kill` to use helper
This commit is contained in:
SomeYellowGuy
2026-04-08 23:02:11 +05:30
committed by GitHub
parent eecf6e0afa
commit 4c12e33152
14 changed files with 144 additions and 39 deletions

View File

@@ -1,9 +1,11 @@
use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
use crate::command::argument_types::coordinates::Coordinates;
use crate::command::context::command_context::CommandContext;
use crate::command::errors::command_syntax_error::CommandSyntaxError;
use crate::command::errors::error_types::CommandErrorType;
use crate::command::string_reader::StringReader;
use pumpkin_data::translation;
use pumpkin_util::math::vector3::Vector3;
pub const INCOMPLETE_ERROR_TYPE: CommandErrorType<0> =
CommandErrorType::new(translation::ARGUMENT_POS3D_INCOMPLETE);
@@ -52,6 +54,24 @@ impl ArgumentType for Vec3ArgumentType {
}
}
impl Vec3ArgumentType {
/// Returns a [`CommandContext`]'s parsed three-dimensional vector as a set of [`Coordinates`].
pub fn get_coordinates(
context: &CommandContext,
name: &str,
) -> Result<Coordinates, CommandSyntaxError> {
Ok(*context.get_argument(name)?)
}
/// Returns a [`CommandContext`]'s parsed three-dimensional vector and resolves it to a [`Vector3`].
pub fn get_vector3(
context: &CommandContext,
name: &str,
) -> Result<Vector3<f64>, CommandSyntaxError> {
Ok(Self::get_coordinates(context, name)?.resolve(context.source.as_ref()))
}
}
#[cfg(test)]
mod test {
use crate::command::argument_types::argument_type::ArgumentType;

View File

@@ -24,6 +24,8 @@ impl ArgumentType for BoolArgumentType {
}
}
impl_copy_get!(BoolArgumentType, bool);
#[cfg(test)]
mod test {
use crate::command::{

View File

@@ -43,6 +43,8 @@ impl ArgumentType for DoubleArgumentType {
}
}
impl_copy_get!(DoubleArgumentType, f64);
impl DoubleArgumentType {
/// Constructs a new [`DoubleArgumentType`] with no minimum or maximum bounds.
#[must_use]

View File

@@ -43,6 +43,8 @@ impl ArgumentType for FloatArgumentType {
}
}
impl_copy_get!(FloatArgumentType, f32);
impl FloatArgumentType {
/// Constructs a new [`FloatArgumentType`] with no minimum or maximum bounds.
#[must_use]

View File

@@ -43,6 +43,8 @@ impl ArgumentType for IntegerArgumentType {
}
}
impl_copy_get!(IntegerArgumentType, i32);
impl IntegerArgumentType {
/// Constructs a new [`IntegerArgumentType`] with no minimum or maximum bounds.
#[must_use]

View File

@@ -43,6 +43,8 @@ impl ArgumentType for LongArgumentType {
}
}
impl_copy_get!(LongArgumentType, i64);
impl LongArgumentType {
/// Constructs a new [`LongArgumentType`] with no minimum or maximum bounds.
#[must_use]

View File

@@ -1,5 +1,6 @@
use pumpkin_protocol::java::client::play::StringProtoArgBehavior;
use crate::command::context::command_context::CommandContext;
use crate::command::{
argument_types::argument_type::{ArgumentType, JavaClientArgumentType},
errors::command_syntax_error::CommandSyntaxError,
@@ -49,6 +50,13 @@ impl ArgumentType for StringArgumentType {
}
}
impl StringArgumentType {
/// Returns a [`CommandContext`]'s parsed `String` argument as a string slice.
pub fn get<'a>(context: &'a CommandContext, name: &str) -> Result<&'a str, CommandSyntaxError> {
Ok(context.get_argument::<String>(name)?.as_str())
}
}
#[cfg(test)]
mod test {
use crate::command::{

View File

@@ -1,10 +1,14 @@
use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
use crate::command::argument_types::entity_selector::EntitySelector;
use crate::command::argument_types::entity_selector::parser::EntitySelectorParser;
use crate::command::context::command_context::CommandContext;
use crate::command::errors::command_syntax_error::CommandSyntaxError;
use crate::command::errors::error_types::CommandErrorType;
use crate::command::string_reader::StringReader;
use crate::entity::EntityBase;
use crate::entity::player::Player;
use pumpkin_data::translation;
use std::sync::Arc;
/// A [`CommandErrorType`] to tell that no entities could be found.
pub const NO_ENTITIES_ERROR_TYPE: CommandErrorType<0> =
@@ -101,4 +105,74 @@ impl EntityArgumentType {
Ok(selector)
}
}
/// Tries to get a single entity from a parsed argument of the provided [`CommandContext`].
pub async fn get_entity(
context: &CommandContext<'_>,
name: &str,
) -> Result<Arc<dyn EntityBase>, CommandSyntaxError> {
context
.get_argument::<EntitySelector>(name)?
.find_single_entity(context.source.as_ref())
.await
}
/// Tries to get at least 1 entity from a parsed argument of the provided [`CommandContext`].
pub async fn get_entities(
context: &CommandContext<'_>,
name: &str,
) -> Result<Vec<Arc<dyn EntityBase>>, CommandSyntaxError> {
let entities = Self::get_optional_entities(context, name).await?;
if entities.is_empty() {
Err(NO_ENTITIES_ERROR_TYPE.create_without_context())
} else {
Ok(entities)
}
}
/// Tries to get any number of entities from a parsed argument of the provided [`CommandContext`].
pub async fn get_optional_entities(
context: &CommandContext<'_>,
name: &str,
) -> Result<Vec<Arc<dyn EntityBase>>, CommandSyntaxError> {
context
.get_argument::<EntitySelector>(name)?
.find_entities(context.source.as_ref())
.await
}
/// Tries to get a single player from a parsed argument of the provided [`CommandContext`].
pub async fn get_player(
context: &CommandContext<'_>,
name: &str,
) -> Result<Arc<Player>, CommandSyntaxError> {
context
.get_argument::<EntitySelector>(name)?
.find_single_player(context.source.as_ref())
.await
}
/// Tries to get at least 1 player from a parsed argument of the provided [`CommandContext`].
pub async fn get_players(
context: &CommandContext<'_>,
name: &str,
) -> Result<Vec<Arc<Player>>, CommandSyntaxError> {
let players = Self::get_optional_players(context, name).await?;
if players.is_empty() {
Err(NO_PLAYERS_ERROR_TYPE.create_without_context())
} else {
Ok(players)
}
}
/// Tries to get any number of players from a parsed argument of the provided [`CommandContext`].
pub async fn get_optional_players(
context: &CommandContext<'_>,
name: &str,
) -> Result<Vec<Arc<Player>>, CommandSyntaxError> {
context
.get_argument::<EntitySelector>(name)?
.find_players(context.source.as_ref())
.await
}
}

View File

@@ -2,9 +2,7 @@ mod option;
pub mod parser;
use crate::command::argument_types::entity;
use crate::command::argument_types::entity::{
ENTITY_SELECTOR_PERMISSION, NO_ENTITIES_ERROR_TYPE, NO_PLAYERS_ERROR_TYPE,
};
use crate::command::argument_types::entity::ENTITY_SELECTOR_PERMISSION;
use crate::command::argument_types::entity_selector::parser::SELECTORS_NOT_ALLOWED_ERROR_TYPE;
use crate::command::context::command_source::CommandSource;
use crate::command::errors::command_syntax_error::CommandSyntaxError;
@@ -84,7 +82,7 @@ impl EntitySelector {
&self,
source: &CommandSource,
) -> Result<Arc<dyn EntityBase>, CommandSyntaxError> {
let list = self.find_optional_entities(source).await?;
let list = self.find_entities(source).await?;
match list.as_slice() {
[] => Err(entity::NO_ENTITIES_ERROR_TYPE.create_without_context()),
[entity] => Ok(entity.clone()),
@@ -92,28 +90,14 @@ impl EntitySelector {
}
}
/// Tries to find any entities represented by this selector.
/// If none are found, an error is returned.
pub async fn find_entities(
&self,
source: &CommandSource,
) -> Result<Vec<Arc<dyn EntityBase>>, CommandSyntaxError> {
let entities = self.find_optional_entities(source).await?;
if entities.is_empty() {
Err(NO_ENTITIES_ERROR_TYPE.create_without_context())
} else {
Ok(entities)
}
}
/// Tries to find any entities represented by this selector. If none are found, an empty `Vec` will still be returned.
pub async fn find_optional_entities(
pub async fn find_entities(
&self,
source: &CommandSource,
) -> Result<Vec<Arc<dyn EntityBase>>, CommandSyntaxError> {
self.check_permissions(source).await?;
if !self.includes_entities {
self.find_optional_players(source)
self.find_players(source)
.await
.map(|v| v.into_iter().map(|p| p as Arc<dyn EntityBase>).collect())
} else if let Some(name) = self.player_name.as_ref() {
@@ -176,7 +160,7 @@ impl EntitySelector {
&self,
source: &CommandSource,
) -> Result<Arc<Player>, CommandSyntaxError> {
let list = self.find_optional_players(source).await?;
let list = self.find_players(source).await?;
if list.len() == 1 {
Ok(list.first().unwrap().clone())
} else {
@@ -185,23 +169,9 @@ impl EntitySelector {
}
/// Tries to find any players represented by this selector.
/// If none are found, an error is returned.
pub async fn find_players(
&self,
source: &CommandSource,
) -> Result<Vec<Arc<Player>>, CommandSyntaxError> {
let players = self.find_optional_players(source).await?;
if players.is_empty() {
Err(NO_PLAYERS_ERROR_TYPE.create_without_context())
} else {
Ok(players)
}
}
/// Tries to find any players represented by this selector. If none are found, an empty `Vec` will still be returned.
pub async fn find_optional_players(
&self,
source: &CommandSource,
) -> Result<Vec<Arc<Player>>, CommandSyntaxError> {
self.check_permissions(source).await?;
if let Some(name) = self.player_name.as_ref() {

View File

@@ -51,6 +51,18 @@ macro_rules! assert_parse_err_reset {
};
}
/// Macro to implement a single `get()` function for an argument type whose `Item` is `Copy`.
macro_rules! impl_copy_get {
($ty:ty, $item:ty) => {
impl $ty {
#[doc = concat!("Returns a [`CommandContext`]'s parsed `", stringify!($item), "` argument.")]
pub fn get(context: &$crate::command::context::command_context::CommandContext, name: &str) -> Result<$item, CommandSyntaxError> {
Ok(*context.get_argument(name)?)
}
}
};
}
const EMPTY_BOUNDS_ERROR_TYPE: CommandErrorType<0> =
CommandErrorType::new(translation::ARGUMENT_RANGE_EMPTY);
const SWAPPED_BOUNDS_ERROR_TYPE: CommandErrorType<0> =

View File

@@ -32,6 +32,8 @@ impl ArgumentType for IntRangeArgumentType {
}
}
impl_copy_get!(IntRangeArgumentType, IntBounds);
/// Parses an inclusive range of `f64`s that can be represented in the following ways:
/// - `value`: Only includes the number `value`.
/// - `min..`: All numbers above or equal to `min`.
@@ -55,6 +57,8 @@ impl ArgumentType for FloatRangeArgumentType {
}
}
impl_copy_get!(FloatRangeArgumentType, DoubleBounds);
#[cfg(test)]
mod test {
use pumpkin_util::math::bounds::{DoubleBounds, IntBounds};

View File

@@ -1,4 +1,5 @@
use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
use crate::command::context::command_context::CommandContext;
use crate::command::errors::command_syntax_error::CommandSyntaxError;
use crate::command::errors::error_types::CommandErrorType;
use crate::command::string_reader::StringReader;
@@ -71,6 +72,14 @@ impl ArgumentType for TimeArgumentType {
}
}
impl TimeArgumentType {
/// Returns a [`CommandContext`]'s parsed time argument in the form
/// of its duration, in ticks.
pub fn get(context: &CommandContext, name: &str) -> Result<i32, CommandSyntaxError> {
Ok(*context.get_argument(name)?)
}
}
#[cfg(test)]
mod test {
use crate::command::{

View File

@@ -1,6 +1,5 @@
use crate::command::argument_builder::{ArgumentBuilder, argument, command};
use crate::command::argument_types::entity::EntityArgumentType;
use crate::command::argument_types::entity_selector::EntitySelector;
use crate::command::context::command_context::CommandContext;
use crate::command::node::dispatcher::CommandDispatcher;
use crate::command::node::{CommandExecutor, CommandExecutorResult};
@@ -20,8 +19,7 @@ struct TargetsExecutor;
impl CommandExecutor for TargetsExecutor {
fn execute<'a>(&'a self, context: &'a CommandContext) -> CommandExecutorResult<'a> {
Box::pin(async move {
let selector: &EntitySelector = context.get_argument(ARG_TARGETS)?;
let targets = selector.find_entities(&context.source).await?;
let targets = EntityArgumentType::get_entities(context, ARG_TARGETS).await?;
let target_count = targets.len();
for target in &targets {

View File

@@ -23,7 +23,7 @@ struct SetIdleTimeoutExecutor;
impl CommandExecutor for SetIdleTimeoutExecutor {
fn execute<'a>(&'a self, context: &'a CommandContext) -> CommandExecutorResult<'a> {
Box::pin(async move {
let minutes: i32 = *context.get_argument(ARG_MINUTES)?;
let minutes: i32 = IntegerArgumentType::get(context, ARG_MINUTES)?;
context
.server()