chore(refactor): add pumpkin-command crate

This commit is contained in:
Alexander Medvedev 2026-09-06 20:55:19 +02:00
parent b961f97760
commit 4f88d77ab1
95 changed files with 2986 additions and 2030 deletions

2
.gitignore vendored
View File

@ -111,6 +111,7 @@ data/*
# project's configurations
pumpkin.toml
nethernet-key.der
.pumpkin-server-id
*.local.nbt
@ -150,6 +151,7 @@ whitelist.json
.console_history
bukkit.yml
commands.yml
/config/
help.yml
permissions.yml
spigot.yml

20
Cargo.lock generated
View File

@ -3356,6 +3356,7 @@ dependencies = [
"ordered-float",
"postcard",
"pumpkin-codecs",
"pumpkin-command",
"pumpkin-config",
"pumpkin-data",
"pumpkin-gametest",
@ -3435,6 +3436,25 @@ dependencies = [
"wit-encoder",
]
[[package]]
name = "pumpkin-command"
version = "0.1.0+26.2-26.45"
dependencies = [
"itertools 0.15.0",
"pumpkin-codecs",
"pumpkin-data",
"pumpkin-nbt",
"pumpkin-protocol",
"pumpkin-util",
"pumpkin-world",
"rustc-hash",
"serde",
"serde_json",
"thiserror 2.0.20",
"tracing",
"uuid",
]
[[package]]
name = "pumpkin-config"
version = "0.1.0+26.2-26.45"

View File

@ -4,6 +4,7 @@ members = [
"crates/pumpkin",
"crates/pumpkin-api-macros",
"crates/pumpkin-codecs",
"crates/pumpkin-command",
"crates/pumpkin-config",
"crates/pumpkin-data",
"crates/pumpkin-gametest",
@ -176,6 +177,7 @@ p384 = { version = "0.14.0", default-features = false, features = ["std", "arith
phf = { version = "0.14.0", default-features = false, features = ["std"] }
proc-macro2 = { version = "1.0", default-features = false, features = ["proc-macro"] }
pumpkin-codecs = { path = "crates/pumpkin-codecs", default-features = false }
pumpkin-command = { path = "crates/pumpkin-command", default-features = false }
pumpkin-config = { path = "crates/pumpkin-config", default-features = false }
pumpkin-data = { path = "crates/pumpkin-data", default-features = false }
pumpkin-inventory = { path = "crates/pumpkin-inventory", default-features = false }

View File

@ -0,0 +1,25 @@
[package]
name = "pumpkin-command"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
license.workspace = true
[dependencies]
pumpkin-util.workspace = true
pumpkin-protocol = { workspace = true, features = ["default"] }
pumpkin-data = { workspace = true, features = ["default"] }
pumpkin-nbt.workspace = true
pumpkin-world.workspace = true
pumpkin-codecs.workspace = true
uuid.workspace = true
rustc-hash.workspace = true
thiserror.workspace = true
serde.workspace = true
serde_json.workspace = true
itertools.workspace = true
tracing.workspace = true
[lints]
workspace = true

View File

@ -0,0 +1,589 @@
use crate::argument_builder::private::Sealed;
use crate::argument_types::argument_type::AnyArgumentType;
use crate::node::detached::{
ArgumentDetachedNode, CommandDetachedNode, DetachedNode, GlobalNodeId, LiteralDetachedNode,
};
use crate::node::{
Command, CommandExecutor, RedirectModifier, Redirection, Requirement, Requirements,
};
use crate::source::{CommandSource, DummySource};
use crate::suggestion::provider::SuggestionProvider;
use rustc_hash::FxHashMap;
use std::borrow::Cow;
use std::sync::Arc;
/// Represents an intermediate struct for
/// building arguments for commands.
///
/// # Note
///
/// This is an implementation detail.
struct CommonArgumentBuilder<S: CommandSource = DummySource> {
pub global_id: GlobalNodeId,
pub arguments: FxHashMap<String, DetachedNode<S>>,
pub command: Option<Command<S>>,
pub requirements: Requirements<S>,
pub target: Option<Redirection>,
pub modifier: RedirectModifier<S>,
pub forks: bool,
}
impl<S: CommandSource> CommonArgumentBuilder<S> {
fn new() -> Self {
Self {
global_id: GlobalNodeId::new(),
arguments: FxHashMap::default(),
command: None,
requirements: Requirements::new(),
target: None,
modifier: RedirectModifier::KeepSource,
forks: false,
}
}
}
impl<S: CommandSource> Default for CommonArgumentBuilder<S> {
fn default() -> Self {
Self::new()
}
}
/// A short-form way to create a new [`CommandArgumentBuilder`]
/// from a literal and a command description.
///
/// This can be imported directly without a prefix, or imported with the `argument_builder::` prefix. Here's an example of usage:
/// ```
/// use pumpkin_command::argument_builder::command;
/// use pumpkin_command::source::DummySource;
///
/// let builder = command::<DummySource>("foo", "A test command");
/// ```
///
/// The builder returned will eventually construct a [`CommandDetachedNode`].
/// This node can then be registered into a dispatcher.
pub fn command<S: CommandSource>(
literal: impl Into<Cow<'static, str>>,
description: impl Into<Cow<'static, str>>,
) -> CommandArgumentBuilder<S> {
CommandArgumentBuilder::new(literal, description)
}
/// A short-form way to create a new [`LiteralArgumentBuilder`]
/// from a literal.
///
/// This can be imported directly without a prefix, or imported with the `argument_builder::` prefix. Here's an example of usage:
/// ```
/// use pumpkin_command::argument_builder::literal;
/// use pumpkin_command::source::DummySource;
///
/// let builder = literal::<DummySource>("bar");
/// ```
///
/// The builder returned will eventually construct a [`LiteralDetachedNode`].
pub fn literal<S: CommandSource>(
literal: impl Into<Cow<'static, str>>,
) -> LiteralArgumentBuilder<S> {
LiteralArgumentBuilder::new(literal)
}
/// A short-form way to create a new [`RequiredArgumentBuilder`]
/// from an argument type and name.
///
/// This can be imported directly without a prefix, or imported with the `argument_builder::` prefix. Here's an example of usage:
/// ```
/// use pumpkin_command::{
/// argument_builder::argument,
/// argument_types::core::integer::IntegerArgumentType,
/// source::DummySource,
/// };
///
/// let argument_builder = argument::<DummySource>("bar", IntegerArgumentType::new(1, 10));
/// ```
///
/// The builder returned will eventually construct a [`ArgumentDetachedNode`].
pub fn argument<S: CommandSource>(
name: impl Into<Cow<'static, str>>,
arg_type: impl AnyArgumentType<S> + 'static,
) -> RequiredArgumentBuilder<S> {
RequiredArgumentBuilder::new(name, arg_type)
}
/// A builder that builds a literal, non-command [`DetachedNode`].
pub struct LiteralArgumentBuilder<S: CommandSource = DummySource> {
common: CommonArgumentBuilder<S>,
literal: Cow<'static, str>,
}
/// A builder that builds a command [`DetachedNode`].
pub struct CommandArgumentBuilder<S: CommandSource = DummySource> {
common: CommonArgumentBuilder<S>,
literal: Cow<'static, str>,
description: Cow<'static, str>,
source: Option<String>,
}
/// A builder that builds an argument [`DetachedNode`].
pub struct RequiredArgumentBuilder<S: CommandSource = DummySource> {
common: CommonArgumentBuilder<S>,
name: Cow<'static, str>,
argument_type: Arc<dyn AnyArgumentType<S>>,
suggestion_provider: Option<Arc<dyn SuggestionProvider<S>>>,
}
mod private {
// We want to make this trait private so that
// we can implement it for only our
// argument builders defined here.
pub trait Sealed {}
}
pub trait ArgumentBuilder<
S: CommandSource = DummySource,
N: Into<DetachedNode<S>> = DetachedNode<S>,
>: Sized + Sealed
{
/// Puts an argument to be specified, right after this one is specified.
///
/// # Panics
///
/// Panics if this node is redirected to another node, or the child
/// provided is of the type [`CommandDetachedNode`].
#[must_use]
fn then(self, child: impl Into<DetachedNode<S>>) -> Self;
/// Gets the command to execute for the node being built.
#[must_use]
fn command(&self) -> Option<Command<S>>;
/// Sets the command to execute for the node being built.
#[must_use]
fn executes(self, command: impl CommandExecutor<S> + 'static) -> Self {
self.executes_arc(Arc::new(command))
}
/// Sets the command to execute for the node being built.
#[must_use]
fn executes_arc(self, command: Arc<dyn CommandExecutor<S> + 'static>) -> Self;
/// Sets the redirect target of the node being built to another, without a modifier.
#[must_use]
fn redirect(self, redirection: impl Into<Redirection>) -> Self;
/// Sets the redirect target of the node being built to another, with a given modifier.
#[must_use]
fn redirect_with_modifier(
self,
redirection: impl Into<Redirection>,
redirect_modifier: RedirectModifier<S>,
) -> Self;
/// Forks the given context, using multiple for later.
#[must_use]
fn fork(
self,
redirection: impl Into<Redirection>,
redirect_modifier: RedirectModifier<S>,
) -> Self;
/// Forwards the given context, with the given `fork` flag.
#[must_use]
fn forward(
self,
redirection: impl Into<Redirection>,
redirect_modifier: RedirectModifier<S>,
fork: bool,
) -> Self;
/// Gets a reference to the arguments of the node to be built.
#[must_use]
fn arguments(&self) -> &FxHashMap<String, DetachedNode<S>>;
/// Gets the node to which the node being built by this [`ArgumentBuilder`] redirects.
#[must_use]
fn target(&self) -> Option<Redirection>;
/// Adds a given predicate to this list of requirements of the node being built.
///
/// This means that it is possible to chain multiple predicates together, which must all
/// be satisfied.
///
/// Permissions can also be inserted directly into this method as a `requirement`.
#[must_use]
fn requires(self, requirement: impl Into<Requirement<S>>) -> Self;
/// Overwrites the current requirements of this node to a new value.
#[must_use]
fn overwrite_requirements(self, requirements: Requirements<S>) -> Self;
/// Gets the redirect modifier of the node this [`ArgumentBuilder`] is building.
#[must_use]
fn redirect_modifier(&self) -> RedirectModifier<S>;
/// Whether this builder forks.
#[must_use]
fn forks(&self) -> bool;
/// Returns the 'future [`GlobalNodeId`]' of the node that will be produced by this Builder.
/// Very useful for redirects.
#[must_use]
fn id(&self) -> GlobalNodeId;
/// Builds the node represented by this builder, consuming itself in the process.
#[must_use]
fn build(self) -> N;
}
// Implement the private trait for our builders!
impl<S: CommandSource> Sealed for LiteralArgumentBuilder<S> {}
impl<S: CommandSource> Sealed for CommandArgumentBuilder<S> {}
impl<S: CommandSource> Sealed for RequiredArgumentBuilder<S> {}
/// Helper macro to implement repeated code of `ArgumentBuilder` for our types.
macro_rules! impl_boilerplate_argument_builder {
($S:ident) => {
fn then(mut self, argument: impl Into<DetachedNode<$S>>) -> Self {
assert!(
self.target().is_none(),
"Cannot add children to a redirected node"
);
let node = argument.into();
assert!(
!matches!(node, DetachedNode::Command(_)),
"Cannot add a CommandDetachedNode as a child of a builder"
);
self.common.arguments.insert(node.name(), node);
self
}
fn command(&self) -> Option<Command<$S>> {
self.common.command.clone()
}
fn executes_arc(mut self, command: Arc<dyn CommandExecutor<$S> + 'static>) -> Self {
self.common.command = Some(command);
self
}
fn requires(mut self, requirement: impl Into<Requirement<$S>>) -> Self {
self.common.requirements.0.push(requirement.into());
self
}
fn overwrite_requirements(mut self, requirements: Requirements<$S>) -> Self {
self.common.requirements = requirements;
self
}
fn redirect(self, redirection: impl Into<Redirection>) -> Self {
self.forward(redirection.into(), RedirectModifier::KeepSource, false)
}
fn redirect_with_modifier(
self,
redirection: impl Into<Redirection>,
redirect_modifier: RedirectModifier<$S>,
) -> Self {
self.forward(redirection.into(), redirect_modifier, false)
}
fn fork(
self,
redirection: impl Into<Redirection>,
redirect_modifier: RedirectModifier<$S>,
) -> Self {
self.forward(redirection.into(), redirect_modifier, true)
}
fn forward(
mut self,
redirection: impl Into<Redirection>,
redirect_modifier: RedirectModifier<$S>,
fork: bool,
) -> Self {
assert!(
self.common.arguments.is_empty(),
"Cannot forward a node with children. The node must have no children to redirect somewhere else"
);
self.common.target = Some(redirection.into());
self.common.modifier = redirect_modifier;
self.common.forks = fork;
self
}
fn arguments(&self) -> &FxHashMap<String, DetachedNode<$S>> {
&self.common.arguments
}
fn target(&self) -> Option<Redirection> {
self.common.target.clone()
}
fn redirect_modifier(&self) -> RedirectModifier<$S> {
self.common.modifier.clone()
}
fn forks(&self) -> bool {
self.common.forks
}
fn id(&self) -> GlobalNodeId {
self.common.global_id
}
};
}
/// Helper macro to generate `From` impl blocks for each builder.
macro_rules! impl_builder_from_impls {
($builder: ident => $detached_node: ident) => {
impl<S: CommandSource> From<$builder<S>> for $detached_node<S> {
fn from(value: $builder<S>) -> Self {
value.build()
}
}
impl<S: CommandSource> From<$builder<S>> for DetachedNode<S> {
fn from(value: $builder<S>) -> Self {
value.build().into()
}
}
};
}
impl_builder_from_impls!(LiteralArgumentBuilder => LiteralDetachedNode);
impl_builder_from_impls!(CommandArgumentBuilder => CommandDetachedNode);
impl_builder_from_impls!(RequiredArgumentBuilder => ArgumentDetachedNode);
impl<S: CommandSource> LiteralArgumentBuilder<S> {
/// Creates a new [`LiteralArgumentBuilder`] from a literal.
pub fn new(literal: impl Into<Cow<'static, str>>) -> Self {
Self {
common: CommonArgumentBuilder::new(),
literal: literal.into(),
}
}
}
impl<S: CommandSource> CommandArgumentBuilder<S> {
/// Creates a new [`CommandArgumentBuilder`] from a literal and a command description.
pub fn new(
literal: impl Into<Cow<'static, str>>,
description: impl Into<Cow<'static, str>>,
) -> Self {
Self {
common: CommonArgumentBuilder::new(),
literal: literal.into(),
description: description.into(),
source: None,
}
}
/// Sets the source (e.g. plugin name) that registered this command.
#[must_use]
pub fn with_source(mut self, source: impl Into<String>) -> Self {
self.source = Some(source.into());
self
}
}
impl<S: CommandSource> RequiredArgumentBuilder<S> {
/// Creates a new [`RequiredArgumentBuilder`] from a name and an argument type.
pub fn new(
name: impl Into<Cow<'static, str>>,
arg_type: impl AnyArgumentType<S> + 'static,
) -> Self {
Self {
common: CommonArgumentBuilder::new(),
name: name.into(),
argument_type: Arc::new(arg_type),
suggestion_provider: None,
}
}
/// Sets the [`SuggestionProvider`] of this builder for the `ArgumentDetachedNode`.
#[must_use]
pub fn suggests(self, provider: impl SuggestionProvider<S> + 'static) -> Self {
self.suggests_arc(Arc::new(provider))
}
/// Sets the [`SuggestionProvider`] of this builder for the `ArgumentDetachedNode`.
#[must_use]
pub fn suggests_arc(mut self, provider: Arc<dyn SuggestionProvider<S>>) -> Self {
self.suggestion_provider = Some(provider);
self
}
}
impl<S: CommandSource> ArgumentBuilder<S, LiteralDetachedNode<S>> for LiteralArgumentBuilder<S> {
impl_boilerplate_argument_builder!(S);
fn build(self) -> LiteralDetachedNode<S> {
let mut node = LiteralDetachedNode::new(
self.common.global_id,
self.literal,
self.common.command,
self.common.requirements,
self.common.target,
self.common.modifier,
self.common.forks,
);
node.children = self.common.arguments;
node
}
}
impl<S: CommandSource> ArgumentBuilder<S, CommandDetachedNode<S>> for CommandArgumentBuilder<S> {
impl_boilerplate_argument_builder!(S);
fn build(self) -> CommandDetachedNode<S> {
let mut node = CommandDetachedNode::new(
self.common.global_id,
self.literal,
self.description,
self.common.command,
self.common.requirements,
self.common.target,
self.common.modifier,
self.common.forks,
);
node.meta.source = self.source;
node.children = self.common.arguments;
node
}
}
impl<S: CommandSource> ArgumentBuilder<S, ArgumentDetachedNode<S>> for RequiredArgumentBuilder<S> {
impl_boilerplate_argument_builder!(S);
fn build(self) -> ArgumentDetachedNode<S> {
let mut node = ArgumentDetachedNode::new(
self.common.global_id,
self.name,
self.argument_type,
self.common.command,
self.common.requirements,
self.common.target,
self.common.modifier,
self.common.forks,
self.suggestion_provider,
);
node.children = self.common.arguments;
node
}
}
#[cfg(test)]
mod test {
use crate::argument_builder::{ArgumentBuilder, argument, command, literal};
use crate::argument_types::core::double::DoubleArgumentType;
use crate::argument_types::core::integer::IntegerArgumentType;
use crate::argument_types::core::string::StringArgumentType;
use crate::errors::error_types;
use crate::node::Redirection;
use crate::node::attached::AttachedNode;
use crate::node::tree::Tree;
use crate::source::DummySource;
use crate::string_reader::StringReader;
#[test]
fn literal_one() {
let builder = literal::<DummySource>("test");
let node = builder.build();
assert_eq!(node.meta.literal, "test");
}
#[test]
fn required_one() {
let builder = argument::<DummySource>("test", IntegerArgumentType::new(1, 10));
let node = builder.build();
assert_eq!(node.meta.name, "test");
let mut reader1 = StringReader::new("5");
let mut reader2 = StringReader::new("11");
let boxed_result = node
.meta
.argument_type
.parse(&mut reader1)
.expect("The parsing should not have errored");
let result = boxed_result
.downcast::<i32>()
.expect("Downcasting shouldn't have failed");
assert_eq!(result, Box::new(5));
let error = node
.meta
.argument_type
.parse(&mut reader2)
.expect_err("The parsing should have errored as 11 is outside the range");
assert!(error.is(&error_types::INTEGER_TOO_HIGH));
}
#[test]
fn literal_multiple() {
let mut builder = command::<DummySource>("letter", "A test command");
for letter in 'a'..='z' {
// Add a node per letter for the argument.
let letter_string = letter.to_string();
builder = builder.then(literal(letter_string));
}
let node = builder.build();
assert_eq!(node.children.len(), 26);
}
#[test]
fn required_multiple() {
let builder = command::<DummySource>("test", "A test command")
.then(argument("number", DoubleArgumentType::any()))
.then(argument("word", StringArgumentType::SingleWord));
let node = builder.build();
assert_eq!(node.children.len(), 2);
}
#[test]
fn redirect() {
let builder = command::<DummySource>("test", "A test command").redirect(Redirection::Root);
let mut tree = Tree::<DummySource>::new();
let node_id = tree.add_child_to_root(builder);
let node = &tree[node_id];
let redirect = node
.redirect
.expect("Redirection should exist as it was added before");
let target_id = tree
.resolve(redirect)
.expect("Target should have been resolved properly");
let target = &tree[target_id];
assert!(matches!(target, AttachedNode::Root(_)));
}
#[test]
#[should_panic = "Cannot forward a node with children. The node must have no children to redirect somewhere else"]
fn redirect_after_child() {
let _ = command::<DummySource>("test", "A test command")
.then(literal("child"))
.redirect(Redirection::Root);
}
#[test]
#[should_panic = "Cannot add children to a redirected node"]
fn redirect_before_child() {
let _ = command::<DummySource>("test", "A test command")
.redirect(Redirection::Root)
.then(literal("child"));
}
#[test]
#[should_panic = "Cannot add a CommandDetachedNode as a child of a builder"]
fn add_command_as_child() {
let _ = command::<DummySource>("foo", "A test command")
.then(command("bar", "Another test command"));
}
}

View File

@ -1,20 +1,17 @@
use pumpkin_protocol::java::client::play::SuggestionProviders;
use crate::command::argument_types::argument_type::sealed::Sealed;
use crate::command::context::command_context::CommandContext;
use crate::command::context::command_source::CommandSource;
use crate::command::suggestion::suggestions::Suggestions;
use crate::command::suggestion::suggestions::SuggestionsBuilder;
use crate::command::{
errors::command_syntax_error::CommandSyntaxError, string_reader::StringReader,
};
use crate::context::command_context::CommandContext;
use crate::source::{CommandSource, DummySource};
use crate::suggestion::suggestions::Suggestions;
use crate::suggestion::suggestions::SuggestionsBuilder;
use crate::{errors::command_syntax_error::CommandSyntaxError, string_reader::StringReader};
use std::any::Any;
pub type JavaClientArgumentType = pumpkin_protocol::java::client::play::ArgumentType;
pub type ParseWithSourceAnyResult = Result<Box<dyn Any + Send + Sync>, CommandSyntaxError>;
/// Represents an argument type that parses a particular type `Item`.
pub trait ArgumentType: Send + Sync {
pub trait ArgumentType<S: CommandSource = DummySource>: Send + Sync {
/// The data type that this argument type parses.
type Item: Send + Sync;
@ -32,7 +29,7 @@ pub trait ArgumentType: Send + Sync {
fn parse_with_source(
&self,
reader: &mut StringReader,
_source: &CommandSource,
_source: &S,
) -> Result<Self::Item, CommandSyntaxError> {
self.parse(reader)
}
@ -41,7 +38,7 @@ pub trait ArgumentType: Send + Sync {
#[must_use]
fn list_suggestions(
&self,
_context: &CommandContext,
_context: &CommandContext<S>,
_builder: SuggestionsBuilder,
) -> Suggestions {
Suggestions::empty()
@ -73,11 +70,11 @@ pub trait ArgumentType: Send + Sync {
// only for `AnyArgumentType`.
mod sealed {
/// Private trait to ensure only types implementing `ArgumentType` can implement `AnyArgumentType`.
pub trait Sealed {}
pub trait Sealed<S> {}
}
/// Represents an argument type with any parsable type.
pub trait AnyArgumentType: Sealed + Send + Sync {
pub trait AnyArgumentType<S: CommandSource = DummySource>: sealed::Sealed<S> + Send + Sync {
/// Parses a value by using a [`StringReader`]. Call this only if you have no source.
///
/// Errors should be propagated using the `?` operator, which will
@ -91,17 +88,13 @@ pub trait AnyArgumentType: Sealed + Send + Sync {
///
/// Errors should be propagated using the `?` operator, which will
/// replicate Brigadier's behavior of exceptions.
fn parse_with_source(
&self,
reader: &mut StringReader,
source: &CommandSource,
) -> ParseWithSourceAnyResult;
fn parse_with_source(&self, reader: &mut StringReader, source: &S) -> ParseWithSourceAnyResult;
/// Provides a list of suggestions from this argument type.
#[must_use]
fn list_suggestions(
&self,
context: &CommandContext,
context: &CommandContext<S>,
builder: SuggestionsBuilder,
) -> Suggestions;
@ -129,9 +122,12 @@ pub trait AnyArgumentType: Sealed + Send + Sync {
}
// Implement our private trait for all argument types.
impl<U: ArgumentType<Item = T>, T: Send + Sync + 'static> Sealed for U {}
impl<U: ArgumentType<S>, S: CommandSource> sealed::Sealed<S> for U {}
impl<U: ArgumentType<Item = T> + 'static, T: Send + Sync + 'static> AnyArgumentType for U {
impl<U: ArgumentType<S> + 'static, S: CommandSource + 'static> AnyArgumentType<S> for U
where
U::Item: 'static,
{
fn parse(
&self,
reader: &mut StringReader,
@ -142,11 +138,7 @@ impl<U: ArgumentType<Item = T> + 'static, T: Send + Sync + 'static> AnyArgumentT
}
}
fn parse_with_source(
&self,
reader: &mut StringReader,
source: &CommandSource,
) -> ParseWithSourceAnyResult {
fn parse_with_source(&self, reader: &mut StringReader, source: &S) -> ParseWithSourceAnyResult {
match self.parse_with_source(reader, source) {
Ok(value) => {
let value: Box<dyn Any + Send + Sync> = Box::new(value);
@ -158,7 +150,7 @@ impl<U: ArgumentType<Item = T> + 'static, T: Send + Sync + 'static> AnyArgumentT
fn list_suggestions(
&self,
context: &CommandContext,
context: &CommandContext<S>,
builder: SuggestionsBuilder,
) -> Suggestions {
self.list_suggestions(context, builder)

View File

@ -1,4 +1,4 @@
use crate::command::{
use crate::{
argument_types::FromStringReader,
argument_types::argument_type::{ArgumentType, JavaClientArgumentType},
context::command_context::CommandContext,
@ -20,7 +20,7 @@ pub const INVALID_ATTRIBUTE_ERROR_TYPE: CommandErrorType<1> = CommandErrorType::
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct AttributeArgumentType;
impl ArgumentType for AttributeArgumentType {
impl<S: crate::source::CommandSource> ArgumentType<S> for AttributeArgumentType {
type Item = Attributes;
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
@ -46,7 +46,7 @@ impl ArgumentType for AttributeArgumentType {
fn list_suggestions(
&self,
_context: &CommandContext,
_context: &CommandContext<S>,
builder: SuggestionsBuilder,
) -> Suggestions {
builder
@ -56,7 +56,10 @@ impl ArgumentType for AttributeArgumentType {
}
impl AttributeArgumentType {
pub fn get(context: &CommandContext, name: &str) -> Result<Attributes, CommandSyntaxError> {
pub fn get<S: crate::source::CommandSource>(
context: &CommandContext<S>,
name: &str,
) -> Result<Attributes, CommandSyntaxError> {
context.get_argument::<Attributes>(name).cloned()
}
}

View File

@ -1,4 +1,4 @@
use crate::command::{
use crate::{
argument_types::argument_type::{ArgumentType, JavaClientArgumentType},
context::command_context::CommandContext,
errors::command_syntax_error::CommandSyntaxError,
@ -17,7 +17,7 @@ pub const INVALID_BLOCK_ERROR_TYPE: CommandErrorType<1> = CommandErrorType::new(
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct BlockArgumentType;
impl ArgumentType for BlockArgumentType {
impl<S: crate::source::CommandSource> ArgumentType<S> for BlockArgumentType {
type Item = &'static Block;
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
@ -46,7 +46,7 @@ impl ArgumentType for BlockArgumentType {
fn list_suggestions(
&self,
_context: &CommandContext,
_context: &CommandContext<S>,
builder: SuggestionsBuilder,
) -> Suggestions {
builder.build()
@ -54,7 +54,10 @@ impl ArgumentType for BlockArgumentType {
}
impl BlockArgumentType {
pub fn get(context: &CommandContext, name: &str) -> Result<&'static Block, CommandSyntaxError> {
pub fn get<S: crate::source::CommandSource>(
context: &CommandContext<S>,
name: &str,
) -> Result<&'static Block, CommandSyntaxError> {
context.get_argument::<&'static Block>(name).copied()
}
}

View File

@ -5,14 +5,14 @@ use pumpkin_data::{Block, translation};
use pumpkin_nbt::compound::NbtCompound;
use pumpkin_util::text::TextComponent;
use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
use crate::command::argument_types::block::INVALID_BLOCK_ERROR_TYPE;
use crate::command::argument_types::nbt::NbtCompoundArgumentType;
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::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
use crate::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
use crate::argument_types::block::INVALID_BLOCK_ERROR_TYPE;
use crate::argument_types::nbt::NbtCompoundArgumentType;
use crate::context::command_context::CommandContext;
use crate::errors::command_syntax_error::CommandSyntaxError;
use crate::errors::error_types::CommandErrorType;
use crate::string_reader::StringReader;
use crate::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
pub const ERROR_UNKNOWN_TAG: CommandErrorType<1> = CommandErrorType::new(
translation::java::ARGUMENTS_BLOCK_TAG_UNKNOWN,
@ -117,7 +117,8 @@ fn parse_properties(
fn parse_nbt(reader: &mut StringReader) -> Result<Option<NbtCompound>, CommandSyntaxError> {
if reader.peek() == Some('{') {
let tag = NbtCompoundArgumentType.parse(reader)?;
let tag =
ArgumentType::<crate::source::DummySource>::parse(&NbtCompoundArgumentType, reader)?;
Ok(Some(tag))
} else {
Ok(None)
@ -126,7 +127,7 @@ fn parse_nbt(reader: &mut StringReader) -> Result<Option<NbtCompound>, CommandSy
pub struct BlockPredicateArgumentType;
impl ArgumentType for BlockPredicateArgumentType {
impl<S: crate::source::CommandSource> ArgumentType<S> for BlockPredicateArgumentType {
type Item = BlockPredicate;
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
@ -214,7 +215,7 @@ impl ArgumentType for BlockPredicateArgumentType {
fn list_suggestions(
&self,
_context: &CommandContext,
_context: &CommandContext<S>,
builder: SuggestionsBuilder,
) -> Suggestions {
builder.build()
@ -222,7 +223,10 @@ impl ArgumentType for BlockPredicateArgumentType {
}
impl BlockPredicateArgumentType {
pub fn get(context: &CommandContext, name: &str) -> Result<BlockPredicate, CommandSyntaxError> {
pub fn get<S: crate::source::CommandSource>(
context: &CommandContext<S>,
name: &str,
) -> Result<BlockPredicate, CommandSyntaxError> {
Ok(context.get_argument::<BlockPredicate>(name)?.clone())
}
}

View File

@ -1,12 +1,12 @@
use pumpkin_data::translation;
use pumpkin_util::text::TextComponent;
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;
use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
use crate::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
use crate::context::command_context::CommandContext;
use crate::errors::command_syntax_error::CommandSyntaxError;
use crate::errors::error_types::CommandErrorType;
use crate::string_reader::StringReader;
use crate::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
pub const ERROR_INVALID_JSON: CommandErrorType<1> = CommandErrorType::new(
translation::java::ARGUMENT_COMPONENT_INVALID,
@ -16,7 +16,7 @@ pub const ERROR_INVALID_JSON: CommandErrorType<1> = CommandErrorType::new(
#[derive(Clone, Copy)]
pub struct ComponentArgumentType;
impl ArgumentType for ComponentArgumentType {
impl<S: crate::source::CommandSource> ArgumentType<S> for ComponentArgumentType {
type Item = TextComponent;
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
@ -40,7 +40,7 @@ impl ArgumentType for ComponentArgumentType {
fn list_suggestions(
&self,
_context: &CommandContext,
_context: &CommandContext<S>,
builder: SuggestionsBuilder,
) -> Suggestions {
builder.build()
@ -48,7 +48,10 @@ impl ArgumentType for ComponentArgumentType {
}
impl ComponentArgumentType {
pub fn get(context: &CommandContext, name: &str) -> Result<TextComponent, CommandSyntaxError> {
pub fn get<S: crate::source::CommandSource>(
context: &CommandContext<S>,
name: &str,
) -> Result<TextComponent, CommandSyntaxError> {
context.get_argument::<TextComponent>(name).cloned()
}
}

View File

@ -1,12 +1,12 @@
use pumpkin_data::translation;
use pumpkin_util::math::wrap_degrees;
use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
use crate::command::context::command_context::CommandContext;
use crate::command::context::command_source::CommandSource;
use crate::command::errors::command_syntax_error::CommandSyntaxError;
use crate::command::errors::error_types::CommandErrorType;
use crate::command::string_reader::StringReader;
use crate::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
use crate::context::command_context::CommandContext;
use crate::errors::command_syntax_error::CommandSyntaxError;
use crate::errors::error_types::CommandErrorType;
use crate::source::CommandSource;
use crate::string_reader::StringReader;
pub const ERROR_NOT_COMPLETE: CommandErrorType<0> = CommandErrorType::new(
translation::java::ARGUMENT_ANGLE_INCOMPLETE,
@ -31,9 +31,9 @@ impl Angle {
}
#[must_use]
pub fn get_angle(&self, source: &CommandSource) -> f32 {
pub fn get_angle(&self, source: &impl CommandSource) -> f32 {
let base = if self.is_relative {
source.rotation.y
source.rotation().y
} else {
0.0
};
@ -43,7 +43,7 @@ impl Angle {
pub struct AngleArgumentType;
impl ArgumentType for AngleArgumentType {
impl<S: crate::source::CommandSource> ArgumentType<S> for AngleArgumentType {
type Item = Angle;
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
@ -82,7 +82,10 @@ impl ArgumentType for AngleArgumentType {
}
impl AngleArgumentType {
pub fn get(context: &CommandContext, name: &str) -> Result<Angle, CommandSyntaxError> {
pub fn get<S: crate::source::CommandSource>(
context: &CommandContext<S>,
name: &str,
) -> Result<Angle, CommandSyntaxError> {
context.get_argument::<Angle>(name).copied()
}
}

View File

@ -1,11 +1,10 @@
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 crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder, TextCoordinates};
use crate::world::World;
use crate::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
use crate::argument_types::coordinates::Coordinates;
use crate::context::command_context::CommandContext;
use crate::errors::command_syntax_error::CommandSyntaxError;
use crate::errors::error_types::CommandErrorType;
use crate::string_reader::StringReader;
use crate::suggestion::suggestions::{Suggestions, SuggestionsBuilder, TextCoordinates};
use pumpkin_data::translation;
use pumpkin_util::math::position::BlockPos;
@ -35,7 +34,7 @@ pub const OUT_OF_BOUNDS_ERROR_TYPE: CommandErrorType<0> = CommandErrorType::new(
/// a player can spawn.
pub struct BlockPosArgumentType;
impl ArgumentType for BlockPosArgumentType {
impl<S: crate::source::CommandSource> ArgumentType<S> for BlockPosArgumentType {
type Item = Coordinates;
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
@ -56,7 +55,7 @@ impl ArgumentType for BlockPosArgumentType {
fn list_suggestions(
&self,
_context: &CommandContext,
_context: &CommandContext<S>,
builder: SuggestionsBuilder,
) -> Suggestions {
let remainder = builder.remaining();
@ -68,7 +67,7 @@ impl ArgumentType for BlockPosArgumentType {
};
builder.suggest_3d_coordinates(suggested_coordinates, |value| {
self.parse(&mut StringReader::new(value)).is_ok()
ArgumentType::<S>::parse(self, &mut StringReader::new(value)).is_ok()
})
}
}
@ -83,8 +82,8 @@ impl BlockPosArgumentType {
/// # Returns
/// The loaded `BlockPos` containing the position represented by the parsed argument, wrapped in an `Ok`,
/// or an `Err` with the appropriate [`CommandSyntaxError`] if it could not be resolved.
pub fn get_block_pos(
context: &CommandContext,
pub fn get_block_pos<S: crate::source::CommandSource>(
context: &CommandContext<S>,
name: &str,
) -> Result<BlockPos, CommandSyntaxError> {
Ok(BlockPos::floored_v(
@ -104,41 +103,13 @@ impl BlockPosArgumentType {
/// # Returns
/// The loaded `BlockPos` containing the position represented by the parsed argument, wrapped in an `Ok`,
/// or an `Err` with the appropriate [`CommandSyntaxError`] if it could not be resolved.
pub fn get_loaded_block_pos(
context: &CommandContext,
pub fn get_loaded_block_pos<S: crate::source::CommandSource>(
context: &CommandContext<S>,
name: &str,
) -> Result<BlockPos, CommandSyntaxError> {
Self::get_loaded_block_pos_in_world(context, name, context.source.world())
}
/// Returns a [`CommandContext`]'s parsed coordinate argument in the form of a loaded [`BlockPos`]
/// in the provided world.
///
/// # Arguments
/// * `context` - The [`CommandContext`] that has the parsed `Coordinates` with the provided argument name.
/// * `name` - The name of the argument that was parsed.
/// * `world` - The world to check the load status in.
///
/// # Returns
/// The loaded `BlockPos` containing the position represented by the parsed argument, wrapped in an `Ok`,
/// or an `Err` with the appropriate [`CommandSyntaxError`] if it could not be resolved.
pub fn get_loaded_block_pos_in_world(
context: &CommandContext,
name: &str,
world: &World,
) -> Result<BlockPos, CommandSyntaxError> {
let pos = Self::get_block_pos(context, name)?;
if world
.level
.read_chunk_sync(&pos.chunk_position(), |_| ())
.is_none()
{
Err(NOT_LOADED_ERROR_TYPE.create_without_context())
} else if !world.is_in_build_limit(pos) {
Err(OUT_OF_WORLD_ERROR_TYPE.create_without_context())
} else {
Ok(pos)
}
context.source.check_block_loaded(&pos)?;
Ok(pos)
}
/// Returns a [`CommandContext`]'s parsed coordinate argument in the form of a [`BlockPos`]
@ -151,15 +122,22 @@ impl BlockPosArgumentType {
/// # Returns
/// The loaded `BlockPos` containing the position represented by the parsed argument, wrapped in an `Ok`,
/// or an `Err` with the appropriate [`CommandSyntaxError`] if it could not be resolved.
pub fn get_spawnable_pos(
context: &CommandContext,
pub fn get_spawnable_pos<S: crate::source::CommandSource>(
context: &CommandContext<S>,
name: &str,
) -> Result<BlockPos, CommandSyntaxError> {
let pos = Self::get_block_pos(context, name)?;
if World::is_valid(pos) {
if is_valid_block_pos(pos) {
Ok(pos)
} else {
Err(OUT_OF_BOUNDS_ERROR_TYPE.create_without_context())
}
}
}
#[must_use]
pub fn is_valid_block_pos(dest: BlockPos) -> bool {
(-30_000_000..30_000_000).contains(&dest.0.x)
&& (-30_000_000..30_000_000).contains(&dest.0.z)
&& (-20_000_000..20_000_000).contains(&dest.0.y)
}

View File

@ -3,8 +3,8 @@ use pumpkin_util::math::position::{BlockPos, ColumnPos};
use pumpkin_util::math::vector2::Vector2;
use pumpkin_util::math::vector3::Vector3;
use crate::command::suggestion::suggestions::TextCoordinates;
use crate::command::{
use crate::suggestion::suggestions::TextCoordinates;
use crate::{
argument_types::{
argument_type::{ArgumentType, JavaClientArgumentType},
coordinates::{Coordinates, WorldCoordinate},
@ -22,7 +22,7 @@ pub const INCOMPLETE_ERROR_TYPE: CommandErrorType<0> = CommandErrorType::new(
pub struct ColumnPosArgumentType;
impl ArgumentType for ColumnPosArgumentType {
impl<S: crate::source::CommandSource> ArgumentType<S> for ColumnPosArgumentType {
type Item = Coordinates;
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
@ -48,7 +48,7 @@ impl ArgumentType for ColumnPosArgumentType {
fn list_suggestions(
&self,
_context: &CommandContext,
_context: &CommandContext<S>,
builder: SuggestionsBuilder,
) -> Suggestions {
let remainder = builder.remaining();
@ -60,7 +60,7 @@ impl ArgumentType for ColumnPosArgumentType {
};
builder.suggest_2d_coordinates(suggested_coordinates, |value| {
self.parse(&mut StringReader::new(value)).is_ok()
ArgumentType::<S>::parse(self, &mut StringReader::new(value)).is_ok()
})
}
@ -83,8 +83,8 @@ impl ColumnPosArgumentType {
/// # Returns
/// The `ColumnPos` containing the position represented by the parsed argument, wrapped in an `Ok`,
/// or an `Err` with the appropriate [`CommandSyntaxError`] if it could not be resolved.
pub fn get_column_pos(
context: &CommandContext,
pub fn get_column_pos<S: crate::source::CommandSource>(
context: &CommandContext<S>,
name: &str,
) -> Result<ColumnPos, CommandSyntaxError> {
let block_pos = BlockPos::floored_v(

View File

@ -1,9 +1,7 @@
use crate::command::context::command_source::CommandSource;
use crate::command::errors::command_syntax_error::CommandSyntaxError;
use crate::command::errors::error_types::{
CommandErrorType, READER_EXPECTED_DOUBLE, READER_EXPECTED_INT,
};
use crate::command::string_reader::StringReader;
use crate::errors::command_syntax_error::CommandSyntaxError;
use crate::errors::error_types::{CommandErrorType, READER_EXPECTED_DOUBLE, READER_EXPECTED_INT};
use crate::source::CommandSource;
use crate::string_reader::StringReader;
use pumpkin_data::translation;
use pumpkin_util::math::vector2::Vector2;
use pumpkin_util::math::vector3::{Axis, Vector3};
@ -179,10 +177,10 @@ impl Coordinates {
/// Returns the physical position that these [`Coordinates`] represent.
#[must_use]
pub fn resolve(&self, source: &CommandSource) -> Vector3<f64> {
pub fn resolve(&self, source: &impl CommandSource) -> Vector3<f64> {
match self {
Self::World(vector) => {
let pos = source.position;
let pos = source.position();
Vector3::new(
vector.x.resolve(pos.x),
vector.y.resolve(pos.y),
@ -190,8 +188,8 @@ impl Coordinates {
)
}
Self::Local { left, up, forward } => {
let start = source.entity_anchor.position_at_source(source);
convert_local_coordinates(*left, *up, *forward, source.rotation).add(&start)
let start = source.entity_anchor().position_at_source(source);
convert_local_coordinates(*left, *up, *forward, source.rotation()).add(&start)
}
}
}
@ -201,10 +199,10 @@ impl Coordinates {
/// - The returned *x*-component of the vector is the **pitch**.
/// - The returned *y*-component of the vector is the **yaw**.
#[must_use]
pub const fn rotation(&self, source: &CommandSource) -> Vector2<f32> {
pub fn rotation(&self, source: &impl CommandSource) -> Vector2<f32> {
match self {
Self::World(coords) => {
let rotation = source.rotation;
let rotation = source.rotation();
Vector2::new(
coords.x.resolve(rotation.x as f64) as f32,
coords.y.resolve(rotation.y as f64) as f32,

View File

@ -1,9 +1,9 @@
use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
use crate::command::argument_types::coordinates::{Coordinates, WorldCoordinate};
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::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
use crate::argument_types::coordinates::{Coordinates, WorldCoordinate};
use crate::context::command_context::CommandContext;
use crate::errors::command_syntax_error::CommandSyntaxError;
use crate::errors::error_types::CommandErrorType;
use crate::string_reader::StringReader;
use pumpkin_data::translation;
use pumpkin_util::math::vector3::Vector3;
@ -14,7 +14,7 @@ pub const NOT_COMPLETE_ERROR_TYPE: CommandErrorType<0> = CommandErrorType::new(
pub struct RotationArgumentType;
impl ArgumentType for RotationArgumentType {
impl<S: crate::source::CommandSource> ArgumentType<S> for RotationArgumentType {
type Item = Coordinates;
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
@ -60,7 +60,10 @@ impl RotationArgumentType {
/// - The returned *x*-coordinate of the coordinates is the absolute/relative **pitch**.
/// - The returned *y*-coordinate of the coordinates is the absolute/relative **yaw**.
/// - The returned *z*-coordinate of the coordinates is always 0.
pub fn get(context: &CommandContext, name: &str) -> Result<Coordinates, CommandSyntaxError> {
pub fn get<S: crate::source::CommandSource>(
context: &CommandContext<S>,
name: &str,
) -> Result<Coordinates, CommandSyntaxError> {
context.get_argument::<Coordinates>(name).copied()
}
}

View File

@ -1,11 +1,11 @@
use pumpkin_data::translation;
use pumpkin_util::math::vector3::Vector3;
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;
use crate::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
use crate::context::command_context::CommandContext;
use crate::errors::command_syntax_error::CommandSyntaxError;
use crate::errors::error_types::CommandErrorType;
use crate::string_reader::StringReader;
pub const ERROR_INVALID_SWIZZLE: CommandErrorType<0> = CommandErrorType::new(
translation::java::ARGUMENTS_SWIZZLE_INVALID,
@ -32,7 +32,7 @@ impl Swizzle {
pub struct SwizzleArgumentType;
impl ArgumentType for SwizzleArgumentType {
impl<S: crate::source::CommandSource> ArgumentType<S> for SwizzleArgumentType {
type Item = Swizzle;
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
@ -82,7 +82,10 @@ impl ArgumentType for SwizzleArgumentType {
}
impl SwizzleArgumentType {
pub fn get(context: &CommandContext, name: &str) -> Result<Swizzle, CommandSyntaxError> {
pub fn get<S: crate::source::CommandSource>(
context: &CommandContext<S>,
name: &str,
) -> Result<Swizzle, CommandSyntaxError> {
Ok(*context.get_argument::<Swizzle>(name)?)
}
}

View File

@ -1,10 +1,10 @@
use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
use crate::command::argument_types::coordinates::{Coordinates, WorldCoordinate};
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::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder, TextCoordinates};
use crate::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
use crate::argument_types::coordinates::{Coordinates, WorldCoordinate};
use crate::context::command_context::CommandContext;
use crate::errors::command_syntax_error::CommandSyntaxError;
use crate::errors::error_types::CommandErrorType;
use crate::string_reader::StringReader;
use crate::suggestion::suggestions::{Suggestions, SuggestionsBuilder, TextCoordinates};
use pumpkin_data::translation;
use pumpkin_util::math::vector2::Vector2;
use pumpkin_util::math::vector3::Vector3;
@ -37,7 +37,7 @@ impl Vec2ArgumentType {
}
}
impl ArgumentType for Vec2ArgumentType {
impl<S: crate::source::CommandSource> ArgumentType<S> for Vec2ArgumentType {
type Item = Coordinates;
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
@ -76,7 +76,7 @@ impl ArgumentType for Vec2ArgumentType {
fn list_suggestions(
&self,
_context: &CommandContext,
_context: &CommandContext<S>,
builder: SuggestionsBuilder,
) -> Suggestions {
let remainder = builder.remaining();
@ -88,21 +88,21 @@ impl ArgumentType for Vec2ArgumentType {
};
builder.suggest_2d_coordinates(suggested_coordinates, |value| {
self.parse(&mut StringReader::new(value)).is_ok()
ArgumentType::<S>::parse(self, &mut StringReader::new(value)).is_ok()
})
}
}
impl Vec2ArgumentType {
pub fn get_coordinates(
context: &CommandContext,
pub fn get_coordinates<S: crate::source::CommandSource>(
context: &CommandContext<S>,
name: &str,
) -> Result<Coordinates, CommandSyntaxError> {
Ok(*context.get_argument(name)?)
}
pub fn get_vector2(
context: &CommandContext,
pub fn get_vector2<S: crate::source::CommandSource>(
context: &CommandContext<S>,
name: &str,
) -> Result<Vector2<f64>, CommandSyntaxError> {
let resolved = Self::get_coordinates(context, name)?.resolve(context.source.as_ref());

View File

@ -1,10 +1,10 @@
use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
use crate::command::argument_types::coordinates::{Coordinates, MIXED_TYPE_ERROR_TYPE};
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::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder, TextCoordinates};
use crate::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
use crate::argument_types::coordinates::{Coordinates, MIXED_TYPE_ERROR_TYPE};
use crate::context::command_context::CommandContext;
use crate::errors::command_syntax_error::CommandSyntaxError;
use crate::errors::error_types::CommandErrorType;
use crate::string_reader::StringReader;
use crate::suggestion::suggestions::{Suggestions, SuggestionsBuilder, TextCoordinates};
use pumpkin_data::translation;
use pumpkin_util::math::vector3::Vector3;
@ -51,7 +51,7 @@ impl Vec3ArgumentType {
}
}
impl ArgumentType for Vec3ArgumentType {
impl<S: crate::source::CommandSource> ArgumentType<S> for Vec3ArgumentType {
type Item = Coordinates;
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
@ -79,7 +79,7 @@ impl ArgumentType for Vec3ArgumentType {
fn list_suggestions(
&self,
_context: &CommandContext,
_context: &CommandContext<S>,
builder: SuggestionsBuilder,
) -> Suggestions {
let remainder = builder.remaining();
@ -91,23 +91,23 @@ impl ArgumentType for Vec3ArgumentType {
};
builder.suggest_3d_coordinates(suggestioned_coordinates, |value| {
self.parse(&mut StringReader::new(value)).is_ok()
ArgumentType::<S>::parse(self, &mut StringReader::new(value)).is_ok()
})
}
}
impl Vec3ArgumentType {
/// Returns a [`CommandContext`]'s parsed three-dimensional vector as a set of [`Coordinates`].
pub fn get_coordinates(
context: &CommandContext,
pub fn get_coordinates<S: crate::source::CommandSource>(
context: &CommandContext<S>,
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,
pub fn get_vector3<S: crate::source::CommandSource>(
context: &CommandContext<S>,
name: &str,
) -> Result<Vector3<f64>, CommandSyntaxError> {
Ok(Self::get_coordinates(context, name)?.resolve(context.source.as_ref()))

View File

@ -1,4 +1,4 @@
use crate::command::{
use crate::{
argument_types::argument_type::{ArgumentType, JavaClientArgumentType},
errors::command_syntax_error::CommandSyntaxError,
string_reader::StringReader,
@ -8,7 +8,7 @@ use crate::command::{
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct BoolArgumentType;
impl ArgumentType for BoolArgumentType {
impl<S: crate::source::CommandSource> ArgumentType<S> for BoolArgumentType {
type Item = bool;
fn parse(&self, reader: &mut StringReader) -> Result<bool, CommandSyntaxError> {
@ -28,9 +28,8 @@ impl_copy_get!(BoolArgumentType, bool);
#[cfg(test)]
mod test {
use crate::command::{
argument_types::{argument_type::ArgumentType, core::bool::BoolArgumentType},
errors::error_types,
use crate::{
argument_types::core::bool::BoolArgumentType, errors::error_types,
string_reader::StringReader,
};

View File

@ -1,4 +1,4 @@
use crate::command::{
use crate::{
argument_types::{
argument_type::{ArgumentType, JavaClientArgumentType},
core::within_or_err,
@ -14,7 +14,7 @@ pub struct DoubleArgumentType {
pub max: f64,
}
impl ArgumentType for DoubleArgumentType {
impl<S: crate::source::CommandSource> ArgumentType<S> for DoubleArgumentType {
type Item = f64;
fn parse(&self, reader: &mut StringReader) -> Result<f64, CommandSyntaxError> {
@ -76,9 +76,8 @@ impl DoubleArgumentType {
#[cfg(test)]
mod test {
use crate::command::{
argument_types::{argument_type::ArgumentType, core::double::DoubleArgumentType},
errors::error_types,
use crate::{
argument_types::core::double::DoubleArgumentType, errors::error_types,
string_reader::StringReader,
};

View File

@ -1,4 +1,4 @@
use crate::command::{
use crate::{
argument_types::{
argument_type::{ArgumentType, JavaClientArgumentType},
core::within_or_err,
@ -14,7 +14,7 @@ pub struct FloatArgumentType {
pub max: f32,
}
impl ArgumentType for FloatArgumentType {
impl<S: crate::source::CommandSource> ArgumentType<S> for FloatArgumentType {
type Item = f32;
fn parse(&self, reader: &mut StringReader) -> Result<f32, CommandSyntaxError> {
@ -76,9 +76,8 @@ impl FloatArgumentType {
#[cfg(test)]
mod test {
use crate::command::{
argument_types::{argument_type::ArgumentType, core::float::FloatArgumentType},
errors::error_types,
use crate::{
argument_types::core::float::FloatArgumentType, errors::error_types,
string_reader::StringReader,
};

View File

@ -1,4 +1,4 @@
use crate::command::{
use crate::{
argument_types::{
argument_type::{ArgumentType, JavaClientArgumentType},
core::within_or_err,
@ -14,7 +14,7 @@ pub struct IntegerArgumentType {
pub max: i32,
}
impl ArgumentType for IntegerArgumentType {
impl<S: crate::source::CommandSource> ArgumentType<S> for IntegerArgumentType {
type Item = i32;
fn parse(&self, reader: &mut StringReader) -> Result<i32, CommandSyntaxError> {
@ -76,9 +76,8 @@ impl IntegerArgumentType {
#[cfg(test)]
mod test {
use crate::command::{
argument_types::{argument_type::ArgumentType, core::integer::IntegerArgumentType},
errors::error_types,
use crate::{
argument_types::core::integer::IntegerArgumentType, errors::error_types,
string_reader::StringReader,
};

View File

@ -1,4 +1,4 @@
use crate::command::{
use crate::{
argument_types::{
argument_type::{ArgumentType, JavaClientArgumentType},
core::within_or_err,
@ -14,7 +14,7 @@ pub struct LongArgumentType {
pub max: i64,
}
impl ArgumentType for LongArgumentType {
impl<S: crate::source::CommandSource> ArgumentType<S> for LongArgumentType {
type Item = i64;
fn parse(&self, reader: &mut StringReader) -> Result<i64, CommandSyntaxError> {
@ -76,9 +76,8 @@ impl LongArgumentType {
#[cfg(test)]
mod test {
use crate::command::{
argument_types::{argument_type::ArgumentType, core::long::LongArgumentType},
errors::error_types,
use crate::{
argument_types::core::long::LongArgumentType, errors::error_types,
string_reader::StringReader,
};

View File

@ -1,6 +1,6 @@
use pumpkin_util::text::TextComponent;
use crate::command::{
use crate::{
errors::{command_syntax_error::CommandSyntaxError, error_types::CommandErrorType},
string_reader::StringReader,
};

View File

@ -1,7 +1,7 @@
use pumpkin_protocol::java::client::play::StringProtoArgBehavior;
use crate::command::context::command_context::CommandContext;
use crate::command::{
use crate::context::command_context::CommandContext;
use crate::{
argument_types::argument_type::{ArgumentType, JavaClientArgumentType},
errors::command_syntax_error::CommandSyntaxError,
string_reader::StringReader,
@ -18,7 +18,7 @@ pub enum StringArgumentType {
GreedyPhrase,
}
impl ArgumentType for StringArgumentType {
impl<S: crate::source::CommandSource> ArgumentType<S> for StringArgumentType {
type Item = String;
fn parse(&self, reader: &mut StringReader) -> Result<String, CommandSyntaxError> {
@ -52,16 +52,18 @@ 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> {
pub fn get<'a, S: crate::source::CommandSource>(
context: &'a CommandContext<S>,
name: &str,
) -> Result<&'a str, CommandSyntaxError> {
Ok(context.get_argument::<String>(name)?.as_str())
}
}
#[cfg(test)]
mod test {
use crate::command::{
argument_types::{argument_type::ArgumentType, core::string::StringArgumentType},
errors::error_types,
use crate::{
argument_types::core::string::StringArgumentType, errors::error_types,
string_reader::StringReader,
};

View File

@ -1,7 +1,7 @@
use pumpkin_nbt::compound::NbtCompound;
use pumpkin_util::identifier::Identifier;
use crate::command::{
use crate::{
argument_types::{
FromStringReader,
argument_type::{ArgumentType, JavaClientArgumentType},
@ -20,12 +20,12 @@ pub enum DialogArg {
pub struct DialogArgumentType;
impl ArgumentType for DialogArgumentType {
impl<S: crate::source::CommandSource> ArgumentType<S> for DialogArgumentType {
type Item = DialogArg;
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
if reader.peek() == Some('{') {
let compound = NbtCompoundArgumentType.parse(reader)?;
let compound = ArgumentType::<S>::parse(&NbtCompoundArgumentType, reader)?;
Ok(DialogArg::Nbt(compound))
} else {
let id = Identifier::from_reader(reader)?;
@ -48,8 +48,8 @@ impl ArgumentType for DialogArgumentType {
impl DialogArgumentType {
/// Returns the parsed [`DialogArg`] from the name of the argument.
pub fn get<'a>(
context: &'a CommandContext,
pub fn get<'a, S: crate::source::CommandSource>(
context: &'a CommandContext<S>,
name: &'_ str,
) -> Result<&'a DialogArg, CommandSyntaxError> {
context.get_argument(name)

View File

@ -0,0 +1,85 @@
use crate::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
use crate::context::command_context::CommandContext;
use crate::errors::command_syntax_error::CommandSyntaxError;
use crate::errors::error_types::CommandErrorType;
use crate::source::CommandSource;
use crate::string_reader::StringReader;
use crate::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
use pumpkin_data::translation;
use pumpkin_util::math::vector3::Vector3;
use pumpkin_util::text::TextComponent;
pub const INVALID_ERROR_TYPE: CommandErrorType<1> = CommandErrorType::new(
translation::java::ARGUMENT_ANCHOR_INVALID,
translation::java::ARGUMENT_ANCHOR_INVALID,
);
pub struct EntityAnchorArgumentType;
impl<S: crate::source::CommandSource> ArgumentType<S> for EntityAnchorArgumentType {
type Item = EntityAnchor;
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
let i = reader.cursor();
let anchor = reader.read_unquoted_string();
EntityAnchor::from_id(anchor.as_str()).map_or_else(
|| {
reader.set_cursor(i);
Err(INVALID_ERROR_TYPE.create(reader, TextComponent::text(anchor)))
},
Ok,
)
}
fn client_side_parser(&'_ self) -> JavaClientArgumentType {
JavaClientArgumentType::EntityAnchor
}
fn list_suggestions(
&self,
_context: &CommandContext<S>,
builder: SuggestionsBuilder,
) -> Suggestions {
builder.filter_and_suggest(&["eyes", "feet"]).build()
}
fn examples(&self) -> Vec<String> {
examples!("eyes", "feet")
}
}
impl_copy_get!(EntityAnchorArgumentType, EntityAnchor);
#[derive(Copy, Clone, PartialEq, Eq, Debug, Default)]
pub enum EntityAnchor {
#[default]
Feet,
Eyes,
}
impl EntityAnchor {
/// Gets the [`EntityAnchor`] whose identity is the ID provided.
#[must_use]
pub fn from_id(id: &str) -> Option<Self> {
match id {
"feet" => Some(Self::Feet),
"eyes" => Some(Self::Eyes),
_ => None,
}
}
/// Gets the ID of this [`EntityAnchor`]
#[must_use]
pub const fn id(self) -> &'static str {
match self {
Self::Feet => "feet",
Self::Eyes => "eyes",
}
}
/// Gets the position of a source with respect to this anchor.
#[must_use]
pub fn position_at_source(self, command_source: &impl CommandSource) -> Vector3<f64> {
command_source.anchor_position(self)
}
}

View File

@ -1,9 +1,9 @@
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;
use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
use crate::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
use crate::context::command_context::CommandContext;
use crate::errors::command_syntax_error::CommandSyntaxError;
use crate::errors::error_types::CommandErrorType;
use crate::string_reader::StringReader;
use crate::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
use pumpkin_data::translation;
use pumpkin_util::GameMode;
use pumpkin_util::text::TextComponent;
@ -16,7 +16,7 @@ pub const INVALID_ERROR_TYPE: CommandErrorType<1> = CommandErrorType::new(
pub struct GameModeArgumentType;
impl ArgumentType for GameModeArgumentType {
impl<S: crate::source::CommandSource> ArgumentType<S> for GameModeArgumentType {
type Item = GameMode;
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
@ -31,7 +31,7 @@ impl ArgumentType for GameModeArgumentType {
fn list_suggestions(
&self,
_context: &CommandContext,
_context: &CommandContext<S>,
mut builder: SuggestionsBuilder,
) -> Suggestions {
for gamemode in GameMode::VALUES {

View File

@ -1,9 +1,9 @@
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, DISPATCHER_PARSE_EXCEPTION};
use crate::command::string_reader::StringReader;
use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
use crate::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
use crate::context::command_context::CommandContext;
use crate::errors::command_syntax_error::CommandSyntaxError;
use crate::errors::error_types::{CommandErrorType, DISPATCHER_PARSE_EXCEPTION};
use crate::string_reader::StringReader;
use crate::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
use pumpkin_data::translation::java::ARGUMENT_HEXCOLOR_INVALID;
use pumpkin_util::text::TextComponent;
use pumpkin_util::text::color::RGBColor;
@ -13,7 +13,7 @@ pub const INVALID_HEX_ERROR_TYPE: CommandErrorType<1> =
pub struct HexColorArgumentType;
impl ArgumentType for HexColorArgumentType {
impl<S: crate::source::CommandSource> ArgumentType<S> for HexColorArgumentType {
type Item = RGBColor;
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
@ -39,7 +39,7 @@ impl ArgumentType for HexColorArgumentType {
fn list_suggestions(
&self,
_context: &CommandContext,
_context: &CommandContext<S>,
builder: SuggestionsBuilder,
) -> Suggestions {
builder.filter_and_suggest(&["F00", "FF0000"]).build()

View File

@ -1,6 +1,6 @@
use pumpkin_util::identifier::Identifier;
use crate::command::{
use crate::{
argument_types::{
FromStringReader,
argument_type::{ArgumentType, JavaClientArgumentType},
@ -12,7 +12,7 @@ use crate::command::{
/// An argument type that parses a generic [`Identifier`] with a namespace and path.
pub struct IdentifierArgumentType;
impl ArgumentType for IdentifierArgumentType {
impl<S: crate::source::CommandSource> ArgumentType<S> for IdentifierArgumentType {
type Item = Identifier;
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
@ -29,8 +29,8 @@ impl ArgumentType for IdentifierArgumentType {
}
impl IdentifierArgumentType {
pub fn get(
context: &crate::command::context::command_context::CommandContext,
pub fn get<S: crate::source::CommandSource>(
context: &crate::context::command_context::CommandContext<S>,
name: &str,
) -> Result<Identifier, CommandSyntaxError> {
Ok(context.get_argument::<Identifier>(name)?.clone())

View File

@ -4,13 +4,13 @@ use pumpkin_data::item_stack::ItemStack;
use pumpkin_data::translation;
use pumpkin_util::text::TextComponent;
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::snbt::SnbtParser;
use crate::command::string_reader::StringReader;
use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
use crate::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
use crate::context::command_context::CommandContext;
use crate::errors::command_syntax_error::CommandSyntaxError;
use crate::errors::error_types::CommandErrorType;
use crate::snbt::SnbtParser;
use crate::string_reader::StringReader;
use crate::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
pub const ERROR_UNKNOWN_ITEM: CommandErrorType<1> = CommandErrorType::new(
translation::java::ARGUMENT_ITEM_ID_INVALID,
@ -20,7 +20,7 @@ pub const ERROR_UNKNOWN_ITEM: CommandErrorType<1> = CommandErrorType::new(
#[derive(Clone, Copy)]
pub struct ItemStackArgumentType;
impl ArgumentType for ItemStackArgumentType {
impl<S: crate::source::CommandSource> ArgumentType<S> for ItemStackArgumentType {
type Item = ItemStack;
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
@ -100,7 +100,7 @@ impl ArgumentType for ItemStackArgumentType {
fn list_suggestions(
&self,
_context: &CommandContext,
_context: &CommandContext<S>,
builder: SuggestionsBuilder,
) -> Suggestions {
builder.build()
@ -108,7 +108,10 @@ impl ArgumentType for ItemStackArgumentType {
}
impl ItemStackArgumentType {
pub fn get(context: &CommandContext, name: &str) -> Result<ItemStack, CommandSyntaxError> {
pub fn get<S: crate::source::CommandSource>(
context: &CommandContext<S>,
name: &str,
) -> Result<ItemStack, CommandSyntaxError> {
context.get_argument::<ItemStack>(name).cloned()
}
}

View File

@ -4,12 +4,12 @@ use pumpkin_data::tag::{RegistryKey, get_tag_ids};
use pumpkin_data::translation;
use pumpkin_util::text::TextComponent;
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;
use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
use crate::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
use crate::context::command_context::CommandContext;
use crate::errors::command_syntax_error::CommandSyntaxError;
use crate::errors::error_types::CommandErrorType;
use crate::string_reader::StringReader;
use crate::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
pub const ERROR_UNKNOWN_ITEM: CommandErrorType<1> = CommandErrorType::new(
translation::java::ARGUMENT_ITEM_ID_INVALID,
@ -41,7 +41,7 @@ impl ItemPredicate {
pub struct ItemPredicateArgumentType;
impl ArgumentType for ItemPredicateArgumentType {
impl<S: crate::source::CommandSource> ArgumentType<S> for ItemPredicateArgumentType {
type Item = ItemPredicate;
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
@ -131,7 +131,7 @@ impl ArgumentType for ItemPredicateArgumentType {
fn list_suggestions(
&self,
_context: &CommandContext,
_context: &CommandContext<S>,
builder: SuggestionsBuilder,
) -> Suggestions {
builder.build()
@ -164,7 +164,10 @@ fn skip_components_and_nbt(reader: &mut StringReader) {
}
impl ItemPredicateArgumentType {
pub fn get(context: &CommandContext, name: &str) -> Result<ItemPredicate, CommandSyntaxError> {
pub fn get<S: crate::source::CommandSource>(
context: &CommandContext<S>,
name: &str,
) -> Result<ItemPredicate, CommandSyntaxError> {
Ok(context.get_argument::<ItemPredicate>(name)?.clone())
}
}

View File

@ -0,0 +1,263 @@
use crate::errors::command_syntax_error::CommandSyntaxError;
use crate::errors::error_types::{
CommandErrorType, READER_INVALID_DOUBLE, READER_INVALID_FLOAT, READER_INVALID_INT,
};
use crate::string_reader::StringReader;
use pumpkin_data::translation;
use pumpkin_util::identifier::Identifier;
use pumpkin_util::math::bounds::{Bounds, DoubleBounds, FloatDegreeBounds, IntBounds};
use pumpkin_util::text::TextComponent;
use std::str::FromStr;
/// Creates a [`Vec<String>`] of examples from
/// the given string literals.
macro_rules! examples {
( $( $example:literal ),* ) => {
vec! [
$( $example.to_string(), )*
]
};
}
// Helper methods for assertion with a `StringReader`:
/// Asserts that the result read by `reader` with the argument
/// type `$argument_type` used to parse is equal to `Ok($value)`.
/// Also resets the reader's cursor back to the start.
#[cfg(test)]
macro_rules! assert_parse_ok_reset {
($reader: expr, $argument_type: expr, $value: expr) => {{
assert_eq!(
$crate::argument_types::argument_type::ArgumentType::<crate::source::DummySource>::parse(
&$argument_type,
&mut $reader
),
Ok($value)
);
$reader.set_cursor(0)
}};
($reader: expr, $argument_type: expr) => {{
assert!(
$crate::argument_types::argument_type::ArgumentType::<crate::source::DummySource>::parse(
&$argument_type,
&mut $reader
)
.is_ok()
);
$reader.set_cursor(0)
}};
}
/// Asserts that the result read by `reader` with the argument
/// type `$argument_type` used to parse is an `Err` containing the type of error as `$error_type`.
/// Also resets the reader's cursor back to the start.
#[cfg(test)]
macro_rules! assert_parse_err_reset {
($reader: expr, $argument_type: expr, $error_type: expr) => {
let error_type_dyn: &'static dyn crate::errors::error_types::AnyCommandErrorType =
$error_type;
assert_eq!(
$crate::argument_types::argument_type::ArgumentType::<crate::source::DummySource>::parse(
&$argument_type,
&mut $reader
)
.map_err(|e| e.error_type),
Err(error_type_dyn)
);
$reader.set_cursor(0)
};
}
/// 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<S: $crate::source::CommandSource>(context: &$crate::context::command_context::CommandContext<S>, name: &str) -> Result<$item, CommandSyntaxError> {
Ok(*context.get_argument(name)?)
}
}
};
}
const EMPTY_BOUNDS_ERROR_TYPE: CommandErrorType<0> = CommandErrorType::new(
translation::java::ARGUMENT_RANGE_EMPTY,
translation::java::ARGUMENT_RANGE_EMPTY,
);
const SWAPPED_BOUNDS_ERROR_TYPE: CommandErrorType<0> = CommandErrorType::new(
translation::java::ARGUMENT_RANGE_SWAPPED,
translation::java::ARGUMENT_RANGE_SWAPPED,
);
const INVALID_IDENTIFIER_ERROR_TYPE: CommandErrorType<0> = CommandErrorType::new(
translation::java::ARGUMENT_ID_INVALID,
translation::java::ARGUMENT_ID_INVALID,
);
/// A trait to try getting a value from a [`StringReader`].
pub trait FromStringReader: Sized {
/// Tries to parse a value of this type from a [`StringReader`].
///
/// If this fails, a [`CommandSyntaxError`] is returned with the erroring details.
fn from_reader(reader: &mut StringReader) -> Result<Self, CommandSyntaxError>;
}
fn bounds_from_reader<T: Copy + FromStr + PartialOrd>(
reader: &mut StringReader,
error_type: &'static CommandErrorType<1>,
) -> Result<Bounds<T>, CommandSyntaxError> {
if reader.can_read_char() {
let i = reader.cursor();
try_bounds_from_reader(reader, error_type).map_err(|e| {
// On catching any error, set the reader's cursor position to be at where the range starts.
if let Some(mut context) = e.context {
context.cursor = i;
CommandSyntaxError::create(e.error_type, e.message, &context)
} else {
CommandSyntaxError::create_without_context(e.error_type, e.message)
}
})
} else {
Err(EMPTY_BOUNDS_ERROR_TYPE.create(reader))
}
}
fn try_bounds_from_reader<T: Copy + FromStr + PartialOrd>(
reader: &mut StringReader,
error_type: &'static CommandErrorType<1>,
) -> Result<Bounds<T>, CommandSyntaxError> {
let min = read_number_from_reader(reader, error_type)?;
let max = if reader.peek() == Some('.') && reader.peek_with_offset(1) == Some('.') {
reader.skip();
reader.skip();
read_number_from_reader(reader, error_type)?
} else {
min
};
if min.is_none() && max.is_none() {
Err(EMPTY_BOUNDS_ERROR_TYPE.create(reader))
} else {
Ok(Bounds::<T>::new(min, max))
}
}
/// Tries to read a number of a type from a reader.
fn read_number_from_reader<T: FromStr>(
reader: &mut StringReader,
error_type: &'static CommandErrorType<1>,
) -> Result<Option<T>, CommandSyntaxError> {
let i = reader.cursor();
while has_allowed_peaked_character(reader) {
reader.skip();
}
let string = &reader.string()[i..reader.cursor()].to_string();
if string.is_empty() {
Ok(None)
} else {
string.parse::<T>().map_or_else(
|_| Err(error_type.create(reader, TextComponent::text(string.clone()))),
|t| Ok(Some(t)),
)
}
}
fn has_allowed_peaked_character(reader: &StringReader) -> bool {
let c = reader.peek();
if matches!(c, Some('0'..='9' | '-')) {
true
} else {
c.is_some_and(|c| {
c == '.' && (!reader.can_read_chars(2) || reader.peek_with_offset(1) != Some('.'))
})
}
}
impl FromStringReader for IntBounds {
fn from_reader(reader: &mut StringReader) -> Result<Self, CommandSyntaxError> {
let i = reader.cursor();
let bounds = bounds_from_reader(reader, &READER_INVALID_INT)?;
if bounds.are_swapped() {
reader.set_cursor(i);
Err(SWAPPED_BOUNDS_ERROR_TYPE.create(reader))
} else {
Ok(Self::from_bounds(bounds))
}
}
}
impl FromStringReader for DoubleBounds {
fn from_reader(reader: &mut StringReader) -> Result<Self, CommandSyntaxError> {
let i = reader.cursor();
let bounds = bounds_from_reader(reader, &READER_INVALID_DOUBLE)?;
if bounds.are_swapped() {
reader.set_cursor(i);
Err(SWAPPED_BOUNDS_ERROR_TYPE.create(reader))
} else {
Ok(Self::from_bounds(bounds))
}
}
}
impl FromStringReader for FloatDegreeBounds {
fn from_reader(reader: &mut StringReader) -> Result<Self, CommandSyntaxError> {
let i = reader.cursor();
let bounds = bounds_from_reader(reader, &READER_INVALID_FLOAT)?;
if bounds.are_swapped() {
reader.set_cursor(i);
Err(SWAPPED_BOUNDS_ERROR_TYPE.create(reader))
} else {
Ok(Self::from_bounds(bounds))
}
}
}
impl FromStringReader for Identifier {
fn from_reader(reader: &mut StringReader) -> Result<Self, CommandSyntaxError> {
let start = reader.cursor();
while let Some(c) = reader.peek()
&& Self::is_valid_char(c)
{
reader.skip();
}
let raw_identifier = &reader.string()[start..reader.cursor()];
let identifier_result = Self::parse(raw_identifier);
identifier_result.map_or_else(
|_| {
reader.set_cursor(start);
Err(INVALID_IDENTIFIER_ERROR_TYPE.create(reader))
},
Ok,
)
}
}
pub mod argument_type;
pub mod attribute;
pub mod block;
pub mod block_predicate;
pub mod component;
pub mod coordinates;
pub mod core;
pub mod dialog;
pub mod entity_anchor;
pub mod gamemode;
pub mod hex_color;
pub mod identifier;
pub mod item;
pub mod item_predicate;
pub mod nbt;
pub mod nbt_path;
pub mod particle;
pub mod placed_feature;
pub mod pool;
pub mod range;
pub mod resource;
pub mod resource_or_tag;
pub mod slot;
pub mod sound_category;
pub mod structure;
pub mod team_color;
pub mod template;
pub mod time;
pub mod uuid;

View File

@ -1,6 +1,6 @@
use pumpkin_nbt::tag::NbtTag;
use crate::command::{
use crate::{
argument_types::argument_type::{ArgumentType, JavaClientArgumentType},
context::command_context::CommandContext,
errors::{command_syntax_error::CommandSyntaxError, error_types::CommandErrorType},
@ -15,7 +15,7 @@ use pumpkin_nbt::compound::NbtCompound;
/// Parses any type of NBT tag from SNBT.
pub struct NbtTagArgumentType;
impl ArgumentType for NbtTagArgumentType {
impl<S: crate::source::CommandSource> ArgumentType<S> for NbtTagArgumentType {
type Item = NbtTag;
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
@ -28,7 +28,7 @@ impl ArgumentType for NbtTagArgumentType {
fn list_suggestions(
&self,
_context: &CommandContext,
_context: &CommandContext<S>,
builder: SuggestionsBuilder,
) -> Suggestions {
SnbtParser::parse_for_suggestions(builder)
@ -41,8 +41,8 @@ impl ArgumentType for NbtTagArgumentType {
impl NbtTagArgumentType {
/// Returns the parsed [`NbtTag`] from the name of the argument.
pub fn get<'a>(
context: &'a CommandContext,
pub fn get<'a, S: crate::source::CommandSource>(
context: &'a CommandContext<S>,
name: &'_ str,
) -> Result<&'a NbtTag, CommandSyntaxError> {
context.get_argument(name)
@ -57,7 +57,7 @@ pub const EXPECTED_COMPOUND_ERROR_TYPE: CommandErrorType<0> = CommandErrorType::
/// Parses **only** compound NBT tags from SNBT.
pub struct NbtCompoundArgumentType;
impl ArgumentType for NbtCompoundArgumentType {
impl<S: crate::source::CommandSource> ArgumentType<S> for NbtCompoundArgumentType {
type Item = NbtCompound;
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
@ -81,8 +81,8 @@ impl ArgumentType for NbtCompoundArgumentType {
impl NbtCompoundArgumentType {
/// Returns the parsed [`NbtCompound`] from the name of the argument.
pub fn get<'a>(
context: &'a CommandContext,
pub fn get<'a, S: crate::source::CommandSource>(
context: &'a CommandContext<S>,
name: &'_ str,
) -> Result<&'a NbtCompound, CommandSyntaxError> {
context.get_argument(name)

View File

@ -21,12 +21,12 @@ use pumpkin_nbt::compound::NbtCompound;
use pumpkin_nbt::tag::NbtTag;
use pumpkin_util::text::TextComponent;
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::snbt::SnbtParser;
use crate::command::string_reader::StringReader;
use crate::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
use crate::context::command_context::CommandContext;
use crate::errors::command_syntax_error::CommandSyntaxError;
use crate::errors::error_types::CommandErrorType;
use crate::snbt::SnbtParser;
use crate::string_reader::StringReader;
pub const ERROR_INVALID_NODE: CommandErrorType<0> = CommandErrorType::new(
translation::java::ARGUMENTS_NBTPATH_NODE_INVALID,
@ -666,7 +666,7 @@ impl std::fmt::Display for NbtPath {
/// Argument type for parsing NBT Paths.
pub struct NbtPathArgumentType;
impl ArgumentType for NbtPathArgumentType {
impl<S: crate::source::CommandSource> ArgumentType<S> for NbtPathArgumentType {
type Item = NbtPath;
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
@ -706,8 +706,8 @@ impl ArgumentType for NbtPathArgumentType {
}
impl NbtPathArgumentType {
pub fn get<'a>(
context: &'a CommandContext,
pub fn get<'a, S: crate::source::CommandSource>(
context: &'a CommandContext<S>,
name: &'_ str,
) -> Result<&'a NbtPath, CommandSyntaxError> {
context.get_argument(name)

View File

@ -1,10 +1,10 @@
use crate::command::argument_types::FromStringReader;
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;
use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
use crate::argument_types::FromStringReader;
use crate::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
use crate::context::command_context::CommandContext;
use crate::errors::command_syntax_error::CommandSyntaxError;
use crate::errors::error_types::CommandErrorType;
use crate::string_reader::StringReader;
use crate::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
use pumpkin_data::particle::Particle;
use pumpkin_data::translation;
use pumpkin_util::identifier::Identifier;
@ -17,7 +17,7 @@ pub const ERROR_UNKNOWN_PARTICLE: CommandErrorType<1> = CommandErrorType::new(
pub struct ParticleArgumentType;
impl ArgumentType for ParticleArgumentType {
impl<S: crate::source::CommandSource> ArgumentType<S> for ParticleArgumentType {
type Item = Particle;
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
@ -43,7 +43,7 @@ impl ArgumentType for ParticleArgumentType {
fn list_suggestions(
&self,
_context: &CommandContext,
_context: &CommandContext<S>,
builder: SuggestionsBuilder,
) -> Suggestions {
let particles = (0..=124u16)
@ -55,7 +55,10 @@ impl ArgumentType for ParticleArgumentType {
}
impl ParticleArgumentType {
pub fn get(context: &CommandContext, name: &str) -> Result<Particle, CommandSyntaxError> {
pub fn get<S: crate::source::CommandSource>(
context: &CommandContext<S>,
name: &str,
) -> Result<Particle, CommandSyntaxError> {
Ok(*context.get_argument::<Particle>(name)?)
}
}

View File

@ -1,16 +1,16 @@
use pumpkin_protocol::java::client::play::SuggestionProviders;
use pumpkin_util::identifier::Identifier;
use crate::command::argument_types::FromStringReader;
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::string_reader::StringReader;
use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
use crate::argument_types::FromStringReader;
use crate::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
use crate::context::command_context::CommandContext;
use crate::errors::command_syntax_error::CommandSyntaxError;
use crate::string_reader::StringReader;
use crate::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
pub struct PlacedFeatureNameArgumentType;
impl ArgumentType for PlacedFeatureNameArgumentType {
impl<S: crate::source::CommandSource> ArgumentType<S> for PlacedFeatureNameArgumentType {
type Item = Identifier;
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
@ -19,7 +19,7 @@ impl ArgumentType for PlacedFeatureNameArgumentType {
fn list_suggestions(
&self,
_context: &CommandContext,
_context: &CommandContext<S>,
builder: SuggestionsBuilder,
) -> Suggestions {
let names = pumpkin_world::generation::feature::placed_features::all_placed_feature_names();

View File

@ -1,15 +1,15 @@
use crate::command::argument_types::FromStringReader;
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::string_reader::StringReader;
use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
use crate::argument_types::FromStringReader;
use crate::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
use crate::context::command_context::CommandContext;
use crate::errors::command_syntax_error::CommandSyntaxError;
use crate::string_reader::StringReader;
use crate::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
use pumpkin_protocol::java::client::play::SuggestionProviders;
use pumpkin_util::identifier::Identifier;
pub struct PoolNameArgumentType;
impl ArgumentType for PoolNameArgumentType {
impl<S: crate::source::CommandSource> ArgumentType<S> for PoolNameArgumentType {
type Item = Identifier;
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
@ -18,7 +18,7 @@ impl ArgumentType for PoolNameArgumentType {
fn list_suggestions(
&self,
_context: &CommandContext,
_context: &CommandContext<S>,
builder: SuggestionsBuilder,
) -> Suggestions {
let names = pumpkin_world::generation::structure::template::all_pool_names();

View File

@ -1,6 +1,6 @@
use pumpkin_util::math::bounds::{DoubleBounds, IntBounds};
use crate::command::{
use crate::{
argument_types::{
FromStringReader,
argument_type::{ArgumentType, JavaClientArgumentType},
@ -16,7 +16,7 @@ use crate::command::{
/// - `min..max`: All integers that are between `min` and `max`, inclusive. Mathematically representable as `[min, max]`.
pub struct IntRangeArgumentType;
impl ArgumentType for IntRangeArgumentType {
impl<S: crate::source::CommandSource> ArgumentType<S> for IntRangeArgumentType {
type Item = IntBounds;
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
@ -41,7 +41,7 @@ impl_copy_get!(IntRangeArgumentType, IntBounds);
/// - `min..max`: All numbers that are between `min` and `max`, inclusive. Mathematically representable as `[min, max]`.
pub struct FloatRangeArgumentType;
impl ArgumentType for FloatRangeArgumentType {
impl<S: crate::source::CommandSource> ArgumentType<S> for FloatRangeArgumentType {
type Item = DoubleBounds;
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
@ -63,10 +63,9 @@ impl_copy_get!(FloatRangeArgumentType, DoubleBounds);
mod test {
use pumpkin_util::math::bounds::{DoubleBounds, IntBounds};
use crate::command::{
use crate::{
argument_types::{
SWAPPED_BOUNDS_ERROR_TYPE,
argument_type::ArgumentType,
range::{FloatRangeArgumentType, IntRangeArgumentType},
},
string_reader::StringReader,

View File

@ -1,11 +1,11 @@
use crate::command::argument_types::FromStringReader;
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, DISPATCHER_PARSE_EXCEPTION};
use crate::command::node::attached::AttachedNode;
use crate::command::string_reader::StringReader;
use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
use crate::argument_types::FromStringReader;
use crate::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
use crate::context::command_context::CommandContext;
use crate::errors::command_syntax_error::CommandSyntaxError;
use crate::errors::error_types::{CommandErrorType, DISPATCHER_PARSE_EXCEPTION};
use crate::node::attached::AttachedNode;
use crate::string_reader::StringReader;
use crate::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
use pumpkin_data::entity::EntityType;
use pumpkin_data::translation;
use pumpkin_protocol::java::client::play::SuggestionProviders;
@ -123,7 +123,7 @@ pub struct ResourceArgument(
pub &'static (dyn Fn(Identifier) -> Option<&'static (dyn Any + Send + Sync)> + Send + Sync),
);
impl ArgumentType for ResourceArgument {
impl<S: crate::source::CommandSource> ArgumentType<S> for ResourceArgument {
type Item = &'static (dyn Any + Send + Sync);
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
@ -139,7 +139,7 @@ impl ArgumentType for ResourceArgument {
fn list_suggestions(
&self,
_context: &CommandContext,
_context: &CommandContext<S>,
suggestions_builder: SuggestionsBuilder,
) -> Suggestions {
if self.0 == ENTITY_TYPE_REGISTRY {
@ -225,8 +225,8 @@ impl ArgumentType for ResourceArgument {
}
impl ResourceArgument {
pub fn get_resource<T: 'static>(
context: &CommandContext,
pub fn get_resource<T: 'static, S: crate::source::CommandSource>(
context: &CommandContext<S>,
name: &str,
registry_key: &Identifier,
) -> Result<&'static T, CommandSyntaxError> {
@ -291,36 +291,36 @@ impl ResourceArgument {
}
}
pub fn get_entity_type(
context: &CommandContext,
pub fn get_entity_type<S: crate::source::CommandSource>(
context: &CommandContext<S>,
name: &str,
) -> Result<&'static EntityType, CommandSyntaxError> {
Self::get_resource(context, name, ENTITY_TYPE_REGISTRY)
}
pub fn get_enchantment(
context: &CommandContext,
pub fn get_enchantment<S: crate::source::CommandSource>(
context: &CommandContext<S>,
name: &str,
) -> Result<&'static pumpkin_data::Enchantment, CommandSyntaxError> {
Self::get_resource(context, name, ENCHANTMENT_REGISTRY)
}
pub fn get_mob_effect(
context: &CommandContext,
pub fn get_mob_effect<S: crate::source::CommandSource>(
context: &CommandContext<S>,
name: &str,
) -> Result<&'static pumpkin_data::effect::StatusEffect, CommandSyntaxError> {
Self::get_resource(context, name, MOB_EFFECT_REGISTRY)
}
pub fn get_damage_type(
context: &CommandContext,
pub fn get_damage_type<S: crate::source::CommandSource>(
context: &CommandContext<S>,
name: &str,
) -> Result<&'static pumpkin_data::damage::DamageType, CommandSyntaxError> {
Self::get_resource(context, name, DAMAGE_TYPE_REGISTRY)
}
pub fn get_summonable_entity_type(
context: &CommandContext,
pub fn get_summonable_entity_type<S: crate::source::CommandSource>(
context: &CommandContext<S>,
name: &str,
) -> Result<&'static EntityType, CommandSyntaxError> {
let val: &'static EntityType = Self::get_resource(context, name, ENTITY_TYPE_REGISTRY)?;

View File

@ -1,21 +1,18 @@
use std::collections::BTreeSet;
use pumpkin_data::structures::StructureSet;
use pumpkin_data::tag::{self, RegistryKey};
use pumpkin_data::translation;
use pumpkin_util::identifier::Identifier;
use pumpkin_util::text::TextComponent;
use pumpkin_world::poi::POI_TYPE_NETHER_PORTAL;
use crate::command::argument_types::FromStringReader;
use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
use crate::command::argument_types::resource_key::BIOME_REGISTRY;
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::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
use crate::argument_types::FromStringReader;
use crate::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
use crate::context::command_context::CommandContext;
use crate::errors::command_syntax_error::CommandSyntaxError;
use crate::errors::error_types::CommandErrorType;
use crate::string_reader::StringReader;
use crate::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
pub static BIOME_REGISTRY: Identifier = Identifier::vanilla_static("worldgen/biome");
pub static STRUCTURE_REGISTRY: Identifier = Identifier::vanilla_static("worldgen/structure");
pub static POI_REGISTRY: Identifier = Identifier::vanilla_static("point_of_interest_type");
@ -78,7 +75,7 @@ fn suggest_for_registry(registry: &Identifier, builder: SuggestionsBuilder) -> S
.map(|name| format!("minecraft:{name}")),
)
.build()
} else if *registry == *BIOME_REGISTRY {
} else if *registry == BIOME_REGISTRY {
let biomes = pumpkin_data::biome::Biome::ALL
.iter()
.map(|biome| format!("minecraft:{}", biome.registry_id));
@ -88,21 +85,38 @@ fn suggest_for_registry(registry: &Identifier, builder: SuggestionsBuilder) -> S
} else if *registry == POI_REGISTRY {
// There is no generated POI type registry (yet), so offer the types
// known from tag data plus the ones the server actually creates.
let mut names: BTreeSet<String> = tag::get_latest_map(RegistryKey::PointOfInterestType)
.into_iter()
.flat_map(|map| map.values().flat_map(|tag| tag.0.iter()))
.map(|name| format!("minecraft:{name}"))
.collect();
names.insert(POI_TYPE_NETHER_PORTAL.to_string());
let poi_types = [
"minecraft:armorer",
"minecraft:butcher",
"minecraft:cartographer",
"minecraft:cleric",
"minecraft:farmer",
"minecraft:fisherman",
"minecraft:fletcher",
"minecraft:leatherworker",
"minecraft:librarian",
"minecraft:mason",
"minecraft:shepherd",
"minecraft:toolsmith",
"minecraft:weaponsmith",
"minecraft:home",
"minecraft:meeting",
"minecraft:beehive",
"minecraft:bee_nest",
"minecraft:nether_portal",
"minecraft:lodestone",
"minecraft:lightning_rod",
];
builder
.filter_and_suggest_iter(
names
.into_iter()
poi_types
.iter()
.map(|&s| s.to_string())
.chain(tag_names(RegistryKey::PointOfInterestType)),
)
.build()
} else {
Suggestions::empty()
builder.build()
}
}
@ -113,7 +127,7 @@ fn suggest_for_registry(registry: &Identifier, builder: SuggestionsBuilder) -> S
/// the command.
pub struct ResourceOrTagKeyArgument(pub Identifier);
impl ArgumentType for ResourceOrTagKeyArgument {
impl<S: crate::source::CommandSource> ArgumentType<S> for ResourceOrTagKeyArgument {
type Item = ResourceOrTag;
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
@ -122,13 +136,13 @@ impl ArgumentType for ResourceOrTagKeyArgument {
fn list_suggestions(
&self,
_context: &CommandContext,
_context: &CommandContext<S>,
builder: SuggestionsBuilder,
) -> Suggestions {
suggest_for_registry(&self.0, builder)
}
fn client_side_parser(&'_ self) -> JavaClientArgumentType {
fn client_side_parser(&self) -> JavaClientArgumentType {
JavaClientArgumentType::ResourceOrTagKey {
identifier: self.0.clone(),
}
@ -148,7 +162,7 @@ impl ArgumentType for ResourceOrTagKeyArgument {
/// generated entry data (such as POI types) are accepted as-is.
pub struct ResourceOrTagArgument(pub Identifier);
impl ArgumentType for ResourceOrTagArgument {
impl<S: crate::source::CommandSource> ArgumentType<S> for ResourceOrTagArgument {
type Item = ResourceOrTag;
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
@ -156,7 +170,7 @@ impl ArgumentType for ResourceOrTagArgument {
match &value {
ResourceOrTag::Resource(id) => {
if self.0 == *BIOME_REGISTRY
if self.0 == BIOME_REGISTRY
&& !(id.is_vanilla()
&& pumpkin_data::biome::Biome::from_name(id.path()).is_some())
{
@ -167,7 +181,7 @@ impl ArgumentType for ResourceOrTagArgument {
}
}
ResourceOrTag::Tag(id) => {
let registry_key = if self.0 == *BIOME_REGISTRY {
let registry_key = if self.0 == BIOME_REGISTRY {
Some(RegistryKey::WorldgenBiome)
} else if self.0 == POI_REGISTRY {
Some(RegistryKey::PointOfInterestType)
@ -191,7 +205,7 @@ impl ArgumentType for ResourceOrTagArgument {
fn list_suggestions(
&self,
_context: &CommandContext,
_context: &CommandContext<S>,
builder: SuggestionsBuilder,
) -> Suggestions {
suggest_for_registry(&self.0, builder)

View File

@ -1,9 +1,9 @@
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;
use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
use crate::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
use crate::context::command_context::CommandContext;
use crate::errors::command_syntax_error::CommandSyntaxError;
use crate::errors::error_types::CommandErrorType;
use crate::string_reader::StringReader;
use crate::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
use pumpkin_data::slot_ranges::{
SLOT_RANGE_ALL_NAMES, SLOT_RANGE_SINGLE_SLOT_NAMES, get_slot_range,
};
@ -22,7 +22,7 @@ pub const ONLY_SINGLE_SLOT_ALLOWED_ERROR_TYPE: CommandErrorType<1> = CommandErro
pub struct SlotArgumentType;
impl ArgumentType for SlotArgumentType {
impl<S: crate::source::CommandSource> ArgumentType<S> for SlotArgumentType {
type Item = usize;
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
@ -46,7 +46,7 @@ impl ArgumentType for SlotArgumentType {
fn list_suggestions(
&self,
_context: &CommandContext,
_context: &CommandContext<S>,
builder: SuggestionsBuilder,
) -> Suggestions {
builder
@ -63,7 +63,7 @@ impl_copy_get!(SlotArgumentType, usize);
pub struct SlotsArgumentType;
impl ArgumentType for SlotsArgumentType {
impl<S: crate::source::CommandSource> ArgumentType<S> for SlotsArgumentType {
type Item = &'static [usize];
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
@ -83,7 +83,7 @@ impl ArgumentType for SlotsArgumentType {
fn list_suggestions(
&self,
_context: &CommandContext,
_context: &CommandContext<S>,
builder: SuggestionsBuilder,
) -> Suggestions {
builder.filter_and_suggest(&SLOT_RANGE_ALL_NAMES).build()

View File

@ -2,12 +2,12 @@ use pumpkin_data::sound::SoundCategory;
use pumpkin_data::translation;
use pumpkin_util::text::TextComponent;
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;
use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
use crate::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
use crate::context::command_context::CommandContext;
use crate::errors::command_syntax_error::CommandSyntaxError;
use crate::errors::error_types::CommandErrorType;
use crate::string_reader::StringReader;
use crate::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
pub const ERROR_INVALID_SOURCE: CommandErrorType<1> = CommandErrorType::new(
translation::java::ARGUMENT_ENUM_INVALID,
@ -22,7 +22,7 @@ const CATEGORIES: [&str; 10] = [
#[derive(Clone, Copy)]
pub struct SoundCategoryArgumentType;
impl ArgumentType for SoundCategoryArgumentType {
impl<S: crate::source::CommandSource> ArgumentType<S> for SoundCategoryArgumentType {
type Item = SoundCategory;
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
@ -54,7 +54,7 @@ impl ArgumentType for SoundCategoryArgumentType {
fn list_suggestions(
&self,
_context: &CommandContext,
_context: &CommandContext<S>,
builder: SuggestionsBuilder,
) -> Suggestions {
builder.filter_and_suggest(&CATEGORIES).build()
@ -62,7 +62,10 @@ impl ArgumentType for SoundCategoryArgumentType {
}
impl SoundCategoryArgumentType {
pub fn get(context: &CommandContext, name: &str) -> Result<SoundCategory, CommandSyntaxError> {
pub fn get<S: crate::source::CommandSource>(
context: &CommandContext<S>,
name: &str,
) -> Result<SoundCategory, CommandSyntaxError> {
context.get_argument::<SoundCategory>(name).copied()
}
}

View File

@ -1,15 +1,15 @@
use crate::command::argument_types::FromStringReader;
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::string_reader::StringReader;
use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
use crate::argument_types::FromStringReader;
use crate::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
use crate::context::command_context::CommandContext;
use crate::errors::command_syntax_error::CommandSyntaxError;
use crate::string_reader::StringReader;
use crate::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
use pumpkin_protocol::java::client::play::SuggestionProviders;
use pumpkin_util::identifier::Identifier;
pub struct StructureNameArgumentType;
impl ArgumentType for StructureNameArgumentType {
impl<S: crate::source::CommandSource> ArgumentType<S> for StructureNameArgumentType {
type Item = Identifier;
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
@ -18,7 +18,7 @@ impl ArgumentType for StructureNameArgumentType {
fn list_suggestions(
&self,
_context: &CommandContext,
_context: &CommandContext<S>,
builder: SuggestionsBuilder,
) -> Suggestions {
let names = pumpkin_world::generation::structure::template::all_structure_names();

View File

@ -1,9 +1,9 @@
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;
use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
use crate::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
use crate::context::command_context::CommandContext;
use crate::errors::command_syntax_error::CommandSyntaxError;
use crate::errors::error_types::CommandErrorType;
use crate::string_reader::StringReader;
use crate::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
use pumpkin_data::translation::java::ARGUMENT_COLOR_INVALID;
use pumpkin_util::text::TextComponent;
use pumpkin_util::text::color::NamedColor;
@ -13,7 +13,7 @@ pub const INVALID_VALUE_ERROR_TYPE: CommandErrorType<1> =
pub struct TeamColorArgumentType;
impl ArgumentType for TeamColorArgumentType {
impl<S: crate::source::CommandSource> ArgumentType<S> for TeamColorArgumentType {
type Item = NamedColor;
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
@ -31,7 +31,7 @@ impl ArgumentType for TeamColorArgumentType {
fn list_suggestions(
&self,
_context: &CommandContext,
_context: &CommandContext<S>,
builder: SuggestionsBuilder,
) -> Suggestions {
builder

View File

@ -1,15 +1,15 @@
use crate::command::argument_types::FromStringReader;
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::string_reader::StringReader;
use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
use crate::argument_types::FromStringReader;
use crate::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
use crate::context::command_context::CommandContext;
use crate::errors::command_syntax_error::CommandSyntaxError;
use crate::string_reader::StringReader;
use crate::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
use pumpkin_protocol::java::client::play::SuggestionProviders;
use pumpkin_util::identifier::Identifier;
pub struct TemplateNameArgumentType;
impl ArgumentType for TemplateNameArgumentType {
impl<S: crate::source::CommandSource> ArgumentType<S> for TemplateNameArgumentType {
type Item = String;
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
@ -19,7 +19,7 @@ impl ArgumentType for TemplateNameArgumentType {
fn list_suggestions(
&self,
_context: &CommandContext,
_context: &CommandContext<S>,
builder: SuggestionsBuilder,
) -> Suggestions {
let names = pumpkin_world::generation::structure::template::all_template_names();

View File

@ -1,9 +1,9 @@
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;
use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
use crate::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
use crate::context::command_context::CommandContext;
use crate::errors::command_syntax_error::CommandSyntaxError;
use crate::errors::error_types::CommandErrorType;
use crate::string_reader::StringReader;
use crate::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
use pumpkin_data::translation;
use pumpkin_util::text::TextComponent;
@ -43,7 +43,7 @@ impl TimeArgumentType {
}
}
impl ArgumentType for TimeArgumentType {
impl<S: crate::source::CommandSource> ArgumentType<S> for TimeArgumentType {
type Item = i32;
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
@ -74,7 +74,7 @@ impl ArgumentType for TimeArgumentType {
fn list_suggestions(
&self,
_context: &CommandContext,
_context: &CommandContext<S>,
suggestions_builder: SuggestionsBuilder,
) -> Suggestions {
let mut reader = StringReader::new(suggestions_builder.remaining());
@ -95,15 +95,18 @@ 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> {
pub fn get<S: crate::source::CommandSource>(
context: &CommandContext<S>,
name: &str,
) -> Result<i32, CommandSyntaxError> {
Ok(*context.get_argument(name)?)
}
}
#[cfg(test)]
mod test {
use crate::command::{
argument_types::{argument_type::ArgumentType, time, time::TimeArgumentType},
use crate::{
argument_types::{time, time::TimeArgumentType},
string_reader::StringReader,
};

View File

@ -2,7 +2,7 @@ use pumpkin_data::translation::java::ARGUMENT_UUID_INVALID;
use pumpkin_util::uuid::parse_uuid;
use uuid::Uuid;
use crate::command::{
use crate::{
argument_types::argument_type::{ArgumentType, JavaClientArgumentType},
errors::{command_syntax_error::CommandSyntaxError, error_types::CommandErrorType},
string_reader::StringReader,
@ -15,7 +15,7 @@ pub const INVALID_UUID_ERROR_TYPE: CommandErrorType<0> =
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct UuidArgumentType;
impl ArgumentType for UuidArgumentType {
impl<S: crate::source::CommandSource> ArgumentType<S> for UuidArgumentType {
type Item = Uuid;
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {

View File

@ -1,13 +1,11 @@
use crate::command::context::command_source::{CommandSource, ReturnValue};
use crate::command::context::string_range::StringRange;
use crate::command::errors::command_syntax_error::CommandSyntaxError;
use crate::command::errors::error_types::DISPATCHER_PARSE_EXCEPTION;
use crate::command::node::attached::NodeId;
use crate::command::node::dispatcher::{CommandDispatcher, ResultConsumer};
use crate::command::node::tree::Tree;
use crate::command::node::{Command, RedirectModifier};
use crate::server::Server;
use crate::world::World;
use crate::context::string_range::StringRange;
use crate::errors::command_syntax_error::CommandSyntaxError;
use crate::errors::error_types::DISPATCHER_PARSE_EXCEPTION;
use crate::node::attached::NodeId;
use crate::node::dispatcher::{CommandDispatcher, ResultConsumer};
use crate::node::tree::Tree;
use crate::node::{Command, RedirectModifier};
use crate::source::{CommandSource, DummySource, ReturnValue};
use pumpkin_util::text::TextComponent;
use rustc_hash::FxHashMap;
use std::any::Any;
@ -53,9 +51,9 @@ impl ParsedArgument {
/// Represents the context used when commands are run.
#[derive(Clone)]
pub struct CommandContext<'a> {
pub struct CommandContext<'a, S: CommandSource = DummySource> {
/// The source running the commands.
pub source: Arc<CommandSource>,
pub source: Arc<S>,
/// The input string ran as the command.
pub input: String,
@ -65,7 +63,7 @@ pub struct CommandContext<'a> {
pub arguments: FxHashMap<String, Arc<ParsedArgument>>,
/// The tree this context is related to.
pub tree: &'a Tree,
pub tree: &'a Tree<S>,
/// The root that this context will use, bound to the tree.
/// Not necessarily the root node of the tree, however.
@ -81,20 +79,28 @@ pub struct CommandContext<'a> {
pub child: Option<Arc<Self>>,
/// The redirect modifier of this context.
pub modifier: RedirectModifier,
pub modifier: RedirectModifier<S>,
/// Whether this context forks or not.
pub forks: bool,
/// The command stored in this context which
/// is run to get a command result.
pub command: Option<Command>,
pub command: Option<Command<S>>,
}
impl CommandContext<'_> {
impl<S: CommandSource> std::ops::Deref for CommandContext<'_, S> {
type Target = S;
fn deref(&self) -> &Self::Target {
&self.source
}
}
impl<S: CommandSource> CommandContext<'_, S> {
/// Copies this context with the source provided.
#[must_use]
pub fn with_source(&self, source: Arc<CommandSource>) -> Self {
pub fn with_source(&self, source: Arc<S>) -> Self {
Self {
source,
input: self.input.clone(),
@ -136,8 +142,8 @@ impl CommandContext<'_> {
/// A simple example that takes two arguments specified from the node
/// and returns their sum as the status output of the `Executor`:
/// ```
/// use pumpkin::command::context::command_context::CommandContext;
/// use pumpkin::command::node::{CommandExecutor, CommandExecutorResult};
/// use pumpkin_command::context::command_context::CommandContext;
/// use pumpkin_command::node::{CommandExecutor, CommandExecutorResult};
///
/// struct Executor;
/// impl CommandExecutor for Executor {
@ -170,38 +176,29 @@ impl CommandContext<'_> {
)))
})
}
/// Gets the server of this context.
#[must_use]
pub fn server(&self) -> &Arc<Server> {
self.source.server()
}
/// Gets the world of this context.
#[must_use]
pub fn world(&self) -> &Arc<World> {
self.source.world()
}
}
/// Represents a linked chain of [`CommandContext`]s, where the previous links to the next as a child.
#[derive(Clone)]
pub struct ContextChain<'a> {
pub struct ContextChain<'a, S: CommandSource = DummySource> {
/// The modifiers of this context chain.
modifiers: Vec<Arc<CommandContext<'a>>>,
modifiers: Vec<Arc<CommandContext<'a, S>>>,
/// That specific [`CommandContext`] to execute.
execute: Arc<CommandContext<'a>>,
execute: Arc<CommandContext<'a, S>>,
}
impl<'a> ContextChain<'a> {
impl<'a, S: CommandSource> ContextChain<'a, S> {
/// Creates a new chain of contexts from a vector of them and one to execute.
///
/// # Panics
///
/// Panics if the `execute` given is non-executable.
#[must_use]
pub fn new(modifiers: Vec<Arc<CommandContext<'a>>>, execute: Arc<CommandContext<'a>>) -> Self {
pub fn new(
modifiers: Vec<Arc<CommandContext<'a, S>>>,
execute: Arc<CommandContext<'a, S>>,
) -> Self {
assert!(
execute.command.is_some(),
"Expected last command in chain to be executable"
@ -212,7 +209,7 @@ impl<'a> ContextChain<'a> {
/// Tries to flatten a [`CommandContext`]. If no command
/// is available at the end of chain, [`None`] is returned.
#[must_use]
pub fn try_flatten(root: &CommandContext<'a>) -> Option<Self> {
pub fn try_flatten(root: &CommandContext<'a, S>) -> Option<Self> {
let mut modifiers = Vec::new();
let mut current = Arc::new(root.clone());
loop {
@ -231,11 +228,11 @@ impl<'a> ContextChain<'a> {
/// Runs the given modifier with provided details.
pub fn run_modifier(
modifier: &CommandContext<'a>,
source: &Arc<CommandSource>,
result_consumer: &dyn ResultConsumer,
modifier: &CommandContext<'a, S>,
source: &Arc<S>,
result_consumer: &dyn ResultConsumer<S>,
forked_mode: bool,
) -> Result<Vec<Arc<CommandSource>>, CommandSyntaxError> {
) -> Result<Vec<Arc<S>>, CommandSyntaxError> {
let source_modifier = &modifier.modifier;
if matches!(source_modifier, RedirectModifier::KeepSource) {
@ -261,9 +258,9 @@ impl<'a> ContextChain<'a> {
///
/// Panics if the `executable` provided cannot be executed.
pub fn run_executable(
executable: &CommandContext<'a>,
source: &Arc<CommandSource>,
result_consumer: &dyn ResultConsumer,
executable: &CommandContext<'a, S>,
source: &Arc<S>,
result_consumer: &dyn ResultConsumer<S>,
forked_mode: bool,
) -> Result<i32, CommandSyntaxError> {
let context_to_use = executable.with_source(source.clone());
@ -288,15 +285,15 @@ impl<'a> ContextChain<'a> {
/// Executes all contexts in the chain, returning the ultimate result.
pub fn execute_all(
&self,
source: &Arc<CommandSource>,
result_consumer: &dyn ResultConsumer,
source: &Arc<S>,
result_consumer: &dyn ResultConsumer<S>,
) -> Result<i32, CommandSyntaxError> {
if self.modifiers.is_empty() {
return Self::run_executable(&self.execute, source, result_consumer, false);
}
let mut forked_mode = false;
let mut current_sources: Vec<Arc<CommandSource>> = vec![source.clone()];
let mut current_sources: Vec<Arc<S>> = vec![source.clone()];
for modifier in &self.modifiers {
forked_mode |= modifier.forks;
@ -338,7 +335,7 @@ impl<'a> ContextChain<'a> {
/// Gets a reference to the top context of this chain.
#[must_use]
pub fn get_top_context(&'_ self) -> &'_ Arc<CommandContext<'_>> {
pub fn get_top_context(&'_ self) -> &'_ Arc<CommandContext<'a, S>> {
if self.modifiers.is_empty() {
&self.execute
} else {
@ -347,7 +344,7 @@ impl<'a> ContextChain<'a> {
}
/// Gets a mutable reference to the top context of this chain.
pub fn get_top_context_mut(&'_ mut self) -> &mut Arc<CommandContext<'a>> {
pub fn get_top_context_mut(&'_ mut self) -> &mut Arc<CommandContext<'a, S>> {
if self.modifiers.is_empty() {
&mut self.execute
} else {
@ -373,12 +370,12 @@ impl<'a> ContextChain<'a> {
///
/// This builder's lifetime is bound to the dispatcher provided to it.
#[derive(Clone)]
pub struct CommandContextBuilder<'a> {
pub struct CommandContextBuilder<'a, S: CommandSource = DummySource> {
/// The dispatcher this builder is related to.
pub dispatcher: &'a CommandDispatcher,
pub dispatcher: &'a CommandDispatcher<S>,
/// The source running the commands.
pub source: Arc<CommandSource>,
pub source: Arc<S>,
/// Arguments which have been parsed and
/// can be fetched for command execution.
@ -398,24 +395,24 @@ pub struct CommandContextBuilder<'a> {
pub child: Option<Box<Self>>,
/// The redirect modifier of this context.
pub modifier: RedirectModifier,
pub modifier: RedirectModifier<S>,
/// Whether this context forks or not.
pub forks: bool,
/// The command stored in this context which
/// is run to get a command result.
pub command: Option<Command>,
pub command: Option<Command<S>>,
}
impl<'a> CommandContextBuilder<'a> {
impl<'a, S: CommandSource> CommandContextBuilder<'a, S> {
/// Creates a new [`CommandContextBuilder`] from the properties required to initialize one.
///
/// Note that builder's lifetime is bound to the dispatcher provided to it.
#[must_use]
pub fn new(
dispatcher: &'a CommandDispatcher,
source: Arc<CommandSource>,
dispatcher: &'a CommandDispatcher<S>,
source: Arc<S>,
root: NodeId,
start: usize,
) -> Self {
@ -435,7 +432,7 @@ impl<'a> CommandContextBuilder<'a> {
/// Builds the required [`CommandContext`], consuming itself in the process.
#[must_use]
pub fn build(self, input: &str) -> CommandContext<'a> {
pub fn build(self, input: &str) -> CommandContext<'a, S> {
CommandContext {
source: self.source,
input: input.to_string(),
@ -452,7 +449,7 @@ impl<'a> CommandContextBuilder<'a> {
}
/// Mutates itself with the new source set.
pub fn with_source(&mut self, source: Arc<CommandSource>) {
pub fn with_source(&mut self, source: Arc<S>) {
self.source = source;
}
@ -462,7 +459,7 @@ impl<'a> CommandContextBuilder<'a> {
}
/// Mutates itself with the new command set.
pub fn with_command(&mut self, command: Option<Command>) {
pub fn with_command(&mut self, command: Option<Command<S>>) {
self.command = command;
}
@ -538,18 +535,16 @@ impl<'a> CommandContextBuilder<'a> {
#[cfg(test)]
mod test {
use crate::command::argument_builder::{ArgumentBuilder, CommandArgumentBuilder};
use crate::command::context::command_context::{
use crate::argument_builder::{ArgumentBuilder, CommandArgumentBuilder};
use crate::context::command_context::{
CommandContext, CommandContextBuilder, ContextChain, ParsedArgument, Stage,
};
use crate::command::context::command_source::CommandSource;
use crate::command::context::string_range::StringRange;
use crate::command::errors::command_syntax_error::CommandSyntaxError;
use crate::command::node::dispatcher::{CommandDispatcher, EmptyResultConsumer};
use crate::command::node::tree::ROOT_NODE_ID;
use crate::command::node::{
CommandExecutor, CommandExecutorResult, RedirectModifier, Redirection,
};
use crate::context::string_range::StringRange;
use crate::errors::command_syntax_error::CommandSyntaxError;
use crate::node::dispatcher::{CommandDispatcher, EmptyResultConsumer};
use crate::node::tree::ROOT_NODE_ID;
use crate::node::{CommandExecutor, CommandExecutorResult, RedirectModifier, Redirection};
use crate::source::DummySource;
use pumpkin_util::math::vector3::Vector3;
use std::sync::Arc;
@ -562,12 +557,8 @@ mod test {
// For testing purposes
fn builder(dispatcher: &'_ CommandDispatcher) -> CommandContextBuilder<'_> {
let mut builder = CommandContextBuilder::new(
dispatcher,
Arc::new(CommandSource::dummy()),
ROOT_NODE_ID,
0,
);
let mut builder =
CommandContextBuilder::new(dispatcher, Arc::new(DummySource::dummy()), ROOT_NODE_ID, 0);
let parsed_argument = ParsedArgument::new(StringRange::between(0, 1), Box::new(6789i32));
@ -611,7 +602,7 @@ mod test {
dispatcher
.register(CommandArgumentBuilder::new("foo", "A test command").executes(TenExecutor));
let source = Arc::new(CommandSource::dummy());
let source = Arc::new(DummySource::dummy());
let result = dispatcher.parse_input("foo", &source);
let top_context = result.context.build("foo");
let chain = ContextChain::try_flatten(&top_context)
@ -629,7 +620,7 @@ mod test {
CommandArgumentBuilder::new("bar", "Another test command").redirect(Redirection::Root),
);
let source = Arc::new(CommandSource::dummy());
let source = Arc::new(DummySource::dummy());
let result = dispatcher.parse_input("bar foo", &source);
let top_context = result.context.build("bar foo");
let chain = ContextChain::try_flatten(&top_context)
@ -644,7 +635,7 @@ mod test {
dispatcher
.register(CommandArgumentBuilder::new("foo", "A test command").executes(TenExecutor));
let source = Arc::new(CommandSource::dummy());
let source = Arc::new(DummySource::dummy());
let result = dispatcher.parse_input("foo", &source);
let top_context = result.context.build("foo");
let chain = ContextChain::try_flatten(&top_context)
@ -667,7 +658,7 @@ mod test {
.redirect(Redirection::Root),
);
let source = Arc::new(CommandSource::dummy());
let source = Arc::new(DummySource::dummy());
let result = dispatcher.parse_input("bar qux foo", &source);
let top_context = result.context.build("bar qux foo");
let chain = ContextChain::try_flatten(&top_context)
@ -691,7 +682,7 @@ mod test {
let mut dispatcher = CommandDispatcher::new();
dispatcher.register(CommandArgumentBuilder::new("foo", "A test command"));
let source = Arc::new(CommandSource::dummy());
let source = DummySource::dummy();
let result = dispatcher.parse_input("foo", &source);
let top_context = result.context.build("foo");
@ -716,15 +707,15 @@ mod test {
dispatcher.register(
CommandArgumentBuilder::new("bar", "Another test command").redirect_with_modifier(
Redirection::Root,
RedirectModifier::Custom(Arc::new(|context| {
RedirectModifier::Custom(Arc::new(|context: &CommandContext<DummySource>| {
let mut new_source = context.source.as_ref().clone();
new_source.position = Vector3::new(0f64, 10f64, 0f64);
Ok(vec![Arc::new(new_source)])
})),
),
);
let source = Arc::new(CommandSource::dummy());
let result = dispatcher.parse_input("bar foo", &source);
let source = Arc::new(DummySource::dummy());
let result = dispatcher.parse_input("bar foo", source.as_ref());
let top_context = result.context.build("bar foo");
let chain = ContextChain::try_flatten(&top_context)
.expect("The context should have properly flattened, as it has a command to execute");

View File

@ -0,0 +1,5 @@
pub mod command_context;
pub mod string_range;
pub use command_context::*;
pub use string_range::*;

View File

@ -0,0 +1,76 @@
use std::ops::Range;
use crate::string_reader::StringReader;
/// Indicates a range that is effectively
/// a substring of a string from its `start`
/// and `end` byte-indices.
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct StringRange {
pub start: usize,
pub end: usize,
}
impl StringRange {
/// Constructs a new substring range, with indices
/// inclusive to `start`, but exclusive to `end`.
#[must_use]
pub const fn between(start: usize, end: usize) -> Self {
Self { start, end }
}
/// Constructs an empty range on the left side of
/// one character with index `pos`.
#[must_use]
pub const fn at(pos: usize) -> Self {
Self::between(pos, pos)
}
/// Constructs a new substring range encompassing two
/// [`StringRange`]s, returning a new one that covers
/// both the required ranges.
#[must_use]
pub fn encompass(a: Self, b: Self) -> Self {
Self::between(a.start.min(b.start), a.end.max(b.end))
}
/// Gets a [`str`] substring slice bound
/// to a [`StringReader`] from this range.
#[must_use]
pub fn slice_from_reader<'a>(&self, reader: &'a StringReader) -> &'a str {
&reader.string()[self.start..self.end]
}
/// Gets a [`str`] substring slice bound
/// to a [`String`] from this range.
#[must_use]
pub fn substring_slice<'a>(&self, string: &'a str) -> &'a str {
&string[self.start..self.end]
}
/// Returns if the length of this range is zero.
#[must_use]
#[inline]
pub const fn is_empty(&self) -> bool {
self.start == self.end
}
/// Returns the length of this range.
#[must_use]
#[inline]
pub const fn len(&self) -> usize {
self.end - self.start
}
}
impl From<Range<usize>> for StringRange {
fn from(value: Range<usize>) -> Self {
Self::between(value.start, value.end)
}
}
impl From<StringRange> for Range<usize> {
fn from(value: StringRange) -> Self {
value.start..value.end
}
}

View File

@ -0,0 +1 @@
pub use crate::node::dispatcher::*;

View File

@ -1,6 +1,6 @@
use pumpkin_util::text::TextComponent;
use crate::command::errors::error_types::AnyCommandErrorType;
use crate::errors::error_types::AnyCommandErrorType;
/// A struct detailing the context of a syntax error, including where it happened.
#[derive(Debug, Clone, PartialEq, Eq)]

View File

@ -113,7 +113,7 @@ pub const DISPATCHER_PARSE_EXCEPTION: CommandErrorType<1> = CommandErrorType::ne
translation::bedrock::COMMANDS_GENERIC_SYNTAX,
);
use crate::command::errors::{
use crate::errors::{
command_syntax_error::{CommandSyntaxError, ContextProvider},
error_types::sealed::Sealed,
};
@ -163,6 +163,7 @@ impl<const N: usize> CommandErrorType<N> {
/// Creates an error without context from itself and from a slice of `N` arguments.
#[must_use]
#[expect(deprecated)]
pub fn create_without_context_args_slice(
&'static self,
args: &[TextComponent; N],
@ -178,6 +179,7 @@ impl<const N: usize> CommandErrorType<N> {
}
/// Creates an error with context from itself and from a slice of `N` arguments.
#[expect(deprecated)]
pub fn create_args_slice<C>(
&'static self,
context_provider: &C,
@ -344,8 +346,8 @@ error_type_no_arg_slice_impl!(N = 4 => arg1, arg2, arg3, arg4 | "by taking 4 tra
#[cfg(test)]
mod test {
use crate::command::errors::error_types::{CommandErrorType, LiteralCommandErrorType};
use crate::command::string_reader::StringReader;
use crate::errors::error_types::{CommandErrorType, LiteralCommandErrorType};
use crate::string_reader::StringReader;
use pumpkin_util::text::TextComponent;
const TEST_LITERAL_ERROR_TYPE: LiteralCommandErrorType =
@ -382,6 +384,7 @@ mod test {
}
#[test]
#[expect(deprecated)]
fn create_translatable_error() {
let mut reader = StringReader::new("foo bar");
reader.set_cursor(4);

View File

@ -0,0 +1,20 @@
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
pub mod argument_builder;
pub mod argument_types;
pub mod context;
pub mod dispatcher;
pub mod errors;
pub mod node;
pub mod parser;
pub mod snbt;
pub mod source;
pub mod string_reader;
pub mod suggestion;
pub use argument_builder::{ArgumentBuilder, CommandArgumentBuilder, LiteralArgumentBuilder};
pub use argument_types::argument_type::{AnyArgumentType, ArgumentType};
pub use context::command_context::CommandContext;
pub use node::dispatcher::CommandDispatcher;
pub use node::{Command, CommandExecutor, CommandExecutorResult, Requirement};
pub use source::CommandSource;

View File

@ -1,13 +1,14 @@
use crate::command::context::string_range::StringRange;
use crate::command::errors::command_syntax_error::CommandSyntaxError;
use crate::command::errors::error_types::LITERAL_INCORRECT;
use crate::command::node::detached::GlobalNodeId;
use crate::command::node::tree::ROOT_NODE_ID;
use crate::command::node::{
use crate::context::string_range::StringRange;
use crate::errors::command_syntax_error::CommandSyntaxError;
use crate::errors::error_types::LITERAL_INCORRECT;
use crate::node::detached::GlobalNodeId;
use crate::node::tree::ROOT_NODE_ID;
use crate::node::{
ArgumentNodeMetadata, Command, CommandNodeMetadata, LiteralNodeMetadata, NodeMetadata,
OwnedNodeData, RedirectModifier, Redirection, Requirements,
};
use crate::command::string_reader::StringReader;
use crate::source::{CommandSource, DummySource};
use crate::string_reader::StringReader;
use pumpkin_util::text::TextComponent;
use rustc_hash::FxHashMap;
use std::num::NonZero;
@ -70,18 +71,18 @@ impl From<ArgumentNodeId> for NodeId {
/// Represents a node which has been attached as the root of a `Tree`.
#[derive(Clone)]
pub struct RootAttachedNode {
pub owned: OwnedNodeData,
pub struct RootAttachedNode<S: CommandSource = DummySource> {
pub owned: OwnedNodeData<S>,
pub children: FxHashMap<String, NodeId>,
}
impl Default for RootAttachedNode {
impl<S: CommandSource> Default for RootAttachedNode<S> {
fn default() -> Self {
Self::new()
}
}
impl RootAttachedNode {
impl<S: CommandSource> RootAttachedNode<S> {
#[must_use]
pub fn new() -> Self {
Self {
@ -100,8 +101,8 @@ impl RootAttachedNode {
/// Represents a literal, non-command node that has already been attached
/// to a `Tree`.
#[derive(Clone)]
pub struct LiteralAttachedNode {
pub owned: OwnedNodeData,
pub struct LiteralAttachedNode<S: CommandSource = DummySource> {
pub owned: OwnedNodeData<S>,
pub children: FxHashMap<String, NodeId>,
pub redirect: Option<Redirection>,
pub meta: LiteralNodeMetadata,
@ -110,8 +111,8 @@ pub struct LiteralAttachedNode {
/// Represents a literal, command node that has already been attached
/// to a `Tree`.
#[derive(Clone)]
pub struct CommandAttachedNode {
pub owned: OwnedNodeData,
pub struct CommandAttachedNode<S: CommandSource = DummySource> {
pub owned: OwnedNodeData<S>,
pub children: FxHashMap<String, NodeId>,
pub redirect: Option<Redirection>,
pub meta: CommandNodeMetadata,
@ -120,11 +121,11 @@ pub struct CommandAttachedNode {
/// Represents a node that accepts a specific type of argument that has already been attached
/// to a `Tree`.
#[derive(Clone)]
pub struct ArgumentAttachedNode {
pub owned: OwnedNodeData,
pub struct ArgumentAttachedNode<S: CommandSource = DummySource> {
pub owned: OwnedNodeData<S>,
pub children: FxHashMap<String, NodeId>,
pub redirect: Option<Redirection>,
pub meta: ArgumentNodeMetadata,
pub meta: ArgumentNodeMetadata<S>,
}
/// Allows a way to store the kind of node
@ -139,24 +140,24 @@ pub enum NodeClassification {
/// Represents a node not attached to a `Tree` yet.
#[derive(Clone)]
pub enum AttachedNode {
Root(RootAttachedNode),
Literal(LiteralAttachedNode),
Command(CommandAttachedNode),
Argument(ArgumentAttachedNode),
pub enum AttachedNode<S: CommandSource = DummySource> {
Root(RootAttachedNode<S>),
Literal(LiteralAttachedNode<S>),
Command(CommandAttachedNode<S>),
Argument(ArgumentAttachedNode<S>),
}
/// Returned when a literal could not be parsed.
pub struct CouldNotParseLiteral;
impl AttachedNode {
impl<S: CommandSource> AttachedNode<S> {
/// Creates an [`AttachedNode`] from its properties allowing any [`NodeMetadata`].
#[must_use]
pub fn from_parts(
owned: OwnedNodeData,
owned: OwnedNodeData<S>,
children: FxHashMap<String, NodeId>,
redirect: Option<Redirection>,
meta: NodeMetadata,
meta: NodeMetadata<S>,
) -> Self {
match meta {
NodeMetadata::Root => Self::Root(RootAttachedNode { owned, children }),
@ -201,7 +202,7 @@ impl AttachedNode {
/// Gets a reference to the owned data of this node.
#[must_use]
pub const fn owned_node_data_ref(&self) -> &OwnedNodeData {
pub const fn owned_node_data_ref(&self) -> &OwnedNodeData<S> {
match self {
Self::Root(node) => &node.owned,
Self::Literal(node) => &node.owned,
@ -211,7 +212,7 @@ impl AttachedNode {
}
/// Gets a mutable reference to the owned data of this node.
pub const fn owned_node_data_mut_ref(&mut self) -> &mut OwnedNodeData {
pub const fn owned_node_data_mut_ref(&mut self) -> &mut OwnedNodeData<S> {
match self {
Self::Root(node) => &mut node.owned,
Self::Literal(node) => &mut node.owned,
@ -285,23 +286,23 @@ impl AttachedNode {
/// Gets all the requirements required for this node to be run.
#[must_use]
pub const fn requirements(&self) -> &Requirements {
pub const fn requirements(&self) -> &Requirements<S> {
&self.owned_node_data_ref().requirements
}
/// Overrides all the requirements for this node to be run to a value.
pub fn set_requirement(&mut self, requirements: Requirements) {
pub fn set_requirement(&mut self, requirements: Requirements<S>) {
self.owned_node_data_mut_ref().requirements = requirements;
}
/// Gets the modifier for this node to be run.
#[must_use]
pub const fn modifier(&self) -> &RedirectModifier {
pub const fn modifier(&self) -> &RedirectModifier<S> {
&self.owned_node_data_ref().modifier
}
/// Sets the modifier for this node to a value.
pub fn set_modifier(&mut self, modifier: RedirectModifier) {
pub fn set_modifier(&mut self, modifier: RedirectModifier<S>) {
self.owned_node_data_mut_ref().modifier = modifier;
}
@ -318,12 +319,12 @@ impl AttachedNode {
/// Gets the executable command for this node.
#[must_use]
pub fn command(&self) -> &Option<Command> {
pub fn command(&self) -> &Option<Command<S>> {
&self.owned_node_data_ref().command
}
/// Sets the executable command for this node.
pub fn set_command(&mut self, command: Option<Command>) {
pub fn set_command(&mut self, command: Option<Command<S>>) {
self.owned_node_data_mut_ref().command = command;
}

View File

@ -1,9 +1,10 @@
use crate::command::argument_types::argument_type::AnyArgumentType;
use crate::command::node::{
use crate::argument_types::argument_type::AnyArgumentType;
use crate::node::{
ArgumentNodeMetadata, Command, CommandNodeMetadata, LiteralNodeMetadata, NodeMetadata,
OwnedNodeData, RedirectModifier, Redirection, Requirements,
};
use crate::command::suggestion::provider::SuggestionProvider;
use crate::source::{CommandSource, DummySource};
use crate::suggestion::provider::SuggestionProvider;
use rustc_hash::FxHashMap;
use std::borrow::Cow;
use std::num::NonZero;
@ -44,26 +45,26 @@ impl Default for GlobalNodeId {
///
/// To be of any utility, this must be attached to a tree later.
#[derive(Clone)]
pub struct LiteralDetachedNode {
pub owned: OwnedNodeData,
pub children: FxHashMap<String, DetachedNode>,
pub struct LiteralDetachedNode<S: CommandSource = DummySource> {
pub owned: OwnedNodeData<S>,
pub children: FxHashMap<String, DetachedNode<S>>,
pub redirect: Option<Redirection>,
pub meta: LiteralNodeMetadata,
}
impl LiteralDetachedNode {
impl<S: CommandSource> LiteralDetachedNode<S> {
/// Creates a detached literal node from its properties,
/// without any children.
///
/// # Note
/// Prefer using the [`literal`](crate::command::argument_builder::literal) function over this one.
/// Prefer using the [`literal`](crate::argument_builder::literal) function over this one.
pub fn new(
global_id: GlobalNodeId,
literal: impl Into<Cow<'static, str>>,
command: Option<Command>,
requirements: Requirements,
command: Option<Command<S>>,
requirements: Requirements<S>,
redirect: Option<Redirection>,
modifier: RedirectModifier,
modifier: RedirectModifier<S>,
forks: bool,
) -> Self {
Self {
@ -88,28 +89,28 @@ impl LiteralDetachedNode {
///
/// To be of any utility, this must be attached to a tree later.
#[derive(Clone)]
pub struct CommandDetachedNode {
pub owned: OwnedNodeData,
pub children: FxHashMap<String, DetachedNode>,
pub struct CommandDetachedNode<S: CommandSource = DummySource> {
pub owned: OwnedNodeData<S>,
pub children: FxHashMap<String, DetachedNode<S>>,
pub redirect: Option<Redirection>,
pub meta: CommandNodeMetadata,
}
impl CommandDetachedNode {
impl<S: CommandSource> CommandDetachedNode<S> {
/// Creates a detached literal node from its properties,
/// without any children.
///
/// # Note
/// Prefer using the [`command`](crate::command::argument_builder::command) function over this one.
/// Prefer using the [`command`](crate::argument_builder::command) function over this one.
#[expect(clippy::too_many_arguments)]
pub fn new(
global_id: GlobalNodeId,
literal: impl Into<Cow<'static, str>>,
description: impl Into<Cow<'static, str>>,
command: Option<Command>,
requirements: Requirements,
command: Option<Command<S>>,
requirements: Requirements<S>,
redirect: Option<Redirection>,
modifier: RedirectModifier,
modifier: RedirectModifier<S>,
forks: bool,
) -> Self {
Self {
@ -131,30 +132,30 @@ impl CommandDetachedNode {
///
/// To be of any utility, this must be attached to a tree later.
#[derive(Clone)]
pub struct ArgumentDetachedNode {
pub owned: OwnedNodeData,
pub children: FxHashMap<String, DetachedNode>,
pub struct ArgumentDetachedNode<S: CommandSource = DummySource> {
pub owned: OwnedNodeData<S>,
pub children: FxHashMap<String, DetachedNode<S>>,
pub redirect: Option<Redirection>,
pub meta: ArgumentNodeMetadata,
pub meta: ArgumentNodeMetadata<S>,
}
impl ArgumentDetachedNode {
impl<S: CommandSource> ArgumentDetachedNode<S> {
/// Creates a detached argument node from its properties,
/// without any children.
///
/// # Note
/// Prefer using the [`argument`](crate::command::argument_builder::argument) function over this one.
/// Prefer using the [`argument`](crate::argument_builder::argument) function over this one.
#[expect(clippy::too_many_arguments)]
pub fn new(
global_id: GlobalNodeId,
name: impl Into<Cow<'static, str>>,
argument_type: Arc<dyn AnyArgumentType>,
command: Option<Command>,
requirements: Requirements,
argument_type: Arc<dyn AnyArgumentType<S>>,
command: Option<Command<S>>,
requirements: Requirements<S>,
redirect: Option<Redirection>,
modifier: RedirectModifier,
modifier: RedirectModifier<S>,
forks: bool,
suggestion_provider: Option<Arc<dyn SuggestionProvider>>,
suggestion_provider: Option<Arc<dyn SuggestionProvider<S>>>,
) -> Self {
Self {
owned: OwnedNodeData {
@ -173,45 +174,45 @@ impl ArgumentDetachedNode {
/// Represents a node not attached to a `Tree` yet.
#[derive(Clone)]
pub enum DetachedNode {
Literal(LiteralDetachedNode),
Command(CommandDetachedNode),
Argument(ArgumentDetachedNode),
pub enum DetachedNode<S: CommandSource = DummySource> {
Literal(LiteralDetachedNode<S>),
Command(CommandDetachedNode<S>),
Argument(ArgumentDetachedNode<S>),
}
/// Represents a [`DetachedNode`] that has been irreversibly
/// decomposed into its elements so that it can be recast
/// into a new `AttachedNode`.
pub struct DecomposedNode {
pub owned: OwnedNodeData,
pub children: FxHashMap<String, DetachedNode>,
pub struct DecomposedNode<S: CommandSource = DummySource> {
pub owned: OwnedNodeData<S>,
pub children: FxHashMap<String, DetachedNode<S>>,
pub redirect: Option<Redirection>,
pub meta: NodeMetadata,
pub meta: NodeMetadata<S>,
}
impl From<LiteralDetachedNode> for DetachedNode {
fn from(node: LiteralDetachedNode) -> Self {
impl<S: CommandSource> From<LiteralDetachedNode<S>> for DetachedNode<S> {
fn from(node: LiteralDetachedNode<S>) -> Self {
Self::Literal(node)
}
}
impl From<CommandDetachedNode> for DetachedNode {
fn from(node: CommandDetachedNode) -> Self {
impl<S: CommandSource> From<CommandDetachedNode<S>> for DetachedNode<S> {
fn from(node: CommandDetachedNode<S>) -> Self {
Self::Command(node)
}
}
impl From<ArgumentDetachedNode> for DetachedNode {
fn from(node: ArgumentDetachedNode) -> Self {
impl<S: CommandSource> From<ArgumentDetachedNode<S>> for DetachedNode<S> {
fn from(node: ArgumentDetachedNode<S>) -> Self {
Self::Argument(node)
}
}
impl DetachedNode {
impl<S: CommandSource> DetachedNode<S> {
/// Irreversibly decomposes this [`DetachedNode`] into its constituent elements.
/// This allows it to then be recast into a new `AttachedNode`.
#[must_use]
pub fn decompose(self) -> DecomposedNode {
pub fn decompose(self) -> DecomposedNode<S> {
match self {
Self::Literal(node) => DecomposedNode {
owned: node.owned,

View File

@ -1,19 +1,17 @@
use crate::command::argument_builder::{ArgumentBuilder, CommandArgumentBuilder};
use crate::command::context::command_context::{
CommandContext, CommandContextBuilder, ContextChain,
};
use crate::command::context::command_source::{CommandSource, ReturnValue};
use crate::command::errors::command_syntax_error::CommandSyntaxError;
use crate::command::errors::error_types::{
use crate::argument_builder::{ArgumentBuilder, CommandArgumentBuilder};
use crate::context::command_context::{CommandContext, CommandContextBuilder, ContextChain};
use crate::errors::command_syntax_error::CommandSyntaxError;
use crate::errors::error_types::{
DISPATCHER_EXPECTED_ARGUMENT_SEPARATOR, DISPATCHER_UNKNOWN_ARGUMENT,
DISPATCHER_UNKNOWN_COMMAND, LiteralCommandErrorType,
};
use crate::command::node::Redirection;
use crate::command::node::attached::{CommandNodeId, NodeId};
use crate::command::node::detached::CommandDetachedNode;
use crate::command::node::tree::{NodeIdClassification, ROOT_NODE_ID, Tree};
use crate::command::string_reader::StringReader;
use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
use crate::node::Redirection;
use crate::node::attached::{CommandNodeId, NodeId};
use crate::node::detached::CommandDetachedNode;
use crate::node::tree::{NodeIdClassification, ROOT_NODE_ID, Tree};
use crate::source::{CommandSource, DummySource, ReturnValue};
use crate::string_reader::StringReader;
use crate::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
use pumpkin_data::translation::java::COMMAND_CONTEXT_HERE;
use pumpkin_protocol::java::client::play::CommandSuggestion;
use pumpkin_util::text::TextComponent;
@ -21,7 +19,7 @@ use pumpkin_util::text::click::ClickEvent;
use pumpkin_util::text::color::{Color, NamedColor};
use rustc_hash::{FxHashMap, FxHashSet};
use std::collections::BTreeMap;
use std::sync::{Arc, LazyLock};
use std::sync::Arc;
pub const ARG_SEPARATOR: &str = " ";
pub const ARG_SEPARATOR_CHAR: char = ' ';
@ -38,47 +36,41 @@ pub const UNRESOLVED_REDIRECT: LiteralCommandErrorType =
LiteralCommandErrorType::new("Could not resolve redirect to node");
/// Represents the result of parsing.
pub struct ParsingResult<'a> {
pub context: CommandContextBuilder<'a>,
pub struct ParsingResult<'a, S: CommandSource = DummySource> {
pub context: CommandContextBuilder<'a, S>,
pub errors: FxHashMap<NodeId, CommandSyntaxError>,
pub reader: StringReader<'static>,
}
/// Structs implementing this trait are able to execute upon command completion.
pub trait ResultConsumer: Sync + Send {
fn on_command_completion(&self, context: &CommandContext, result: ReturnValue);
pub trait ResultConsumer<S: CommandSource = DummySource>: Sync + Send {
fn on_command_completion(&self, context: &CommandContext<S>, result: ReturnValue);
}
/// A [`ResultConsumer`] which does nothing.
pub struct EmptyResultConsumer;
impl ResultConsumer for EmptyResultConsumer {
fn on_command_completion(&self, _context: &CommandContext, _result: ReturnValue) {}
impl<S: CommandSource> ResultConsumer<S> for EmptyResultConsumer {
fn on_command_completion(&self, _context: &CommandContext<S>, _result: ReturnValue) {}
}
pub static EMPTY_CONSUMER: LazyLock<Arc<EmptyResultConsumer>> =
LazyLock::new(|| Arc::new(EmptyResultConsumer));
/// A [`ResultConsumer`] which defers the given result to the source provided.
pub struct ResultDeferrer;
impl ResultConsumer for ResultDeferrer {
fn on_command_completion(&self, context: &CommandContext, result: ReturnValue) {
context.source.command_result_taker.call(result);
impl<S: CommandSource> ResultConsumer<S> for ResultDeferrer {
fn on_command_completion(&self, context: &CommandContext<S>, result: ReturnValue) {
context.source.call_result(result);
}
}
pub static RESULT_DEFERRER: LazyLock<Arc<ResultDeferrer>> =
LazyLock::new(|| Arc::new(ResultDeferrer));
/// The core command dispatcher, used to register, parse and execute commands.
///
/// Internally, this dispatcher stores a [`Tree`]. Refer to its documentation
/// for more information about nodes.
#[derive(Clone)]
pub struct CommandDispatcher {
pub tree: Tree,
pub consumer: Arc<dyn ResultConsumer>,
pub struct CommandDispatcher<S: CommandSource = DummySource> {
pub tree: Tree<S>,
pub consumer: Arc<dyn ResultConsumer<S>>,
/// Primary names of commands that have been turned off through the server
/// configuration. A disabled command behaves as if it does not exist: it
@ -91,13 +83,13 @@ pub struct CommandDispatcher {
inactive_plugin_commands: FxHashSet<String>,
}
impl Default for CommandDispatcher {
impl<S: CommandSource> Default for CommandDispatcher<S> {
fn default() -> Self {
Self::new()
}
}
impl CommandDispatcher {
impl<S: CommandSource> CommandDispatcher<S> {
/// Creates a new [`CommandDispatcher`] with a new [`Tree`].
#[must_use]
pub fn new() -> Self {
@ -105,10 +97,11 @@ impl CommandDispatcher {
}
/// Creates this [`CommandDispatcher`] from a pre-existing tree.
pub fn from_existing_tree(tree: Tree) -> Self {
#[must_use]
pub fn from_existing_tree(tree: Tree<S>) -> Self {
Self {
tree,
consumer: RESULT_DEFERRER.clone(),
consumer: Arc::new(ResultDeferrer),
disabled: FxHashSet::default(),
inactive_plugin_commands: FxHashSet::default(),
}
@ -133,7 +126,7 @@ impl CommandDispatcher {
}
/// Makes a plugin command and its aliases unavailable until registration.
pub(crate) fn deactivate_plugin_command_and_aliases(&mut self, name: &str) {
pub fn deactivate_plugin_command_and_aliases(&mut self, name: &str) {
let primary_name = self.primary_command_name(name);
for alias in self.tree_alias_names(&primary_name) {
self.deactivate_plugin_command(alias);
@ -142,7 +135,7 @@ impl CommandDispatcher {
}
/// Makes every root command registered by a plugin source unavailable.
pub(crate) fn deactivate_commands_from_source(&mut self, source: &str) {
pub fn deactivate_commands_from_source(&mut self, source: &str) {
let primary_names = self
.tree
.get_root_children()
@ -253,7 +246,7 @@ impl CommandDispatcher {
/// Note that, at least for now with this system, there is no way to
/// unregister a command. This is due to redirection to
/// potentially unregistered (freed) nodes.
pub fn register(&mut self, command_node: impl Into<CommandDetachedNode>) -> CommandNodeId {
pub fn register(&mut self, command_node: impl Into<CommandDetachedNode<S>>) -> CommandNodeId {
let mut node = command_node.into();
let name = Self::normalize_command_name(&node.meta.literal);
node.meta.literal = name.clone().into();
@ -268,17 +261,13 @@ impl CommandDispatcher {
if self.tree.get(&single_slash).is_none() {
let main_node = &self.tree[main_node_id];
let description = main_node.meta.description.clone();
let mut alias = crate::command::argument_builder::CommandArgumentBuilder::new(
single_slash,
description,
);
let mut alias =
crate::argument_builder::CommandArgumentBuilder::new(single_slash, description);
if let Some(executor) = &main_node.owned.command {
alias = alias.executes_arc(executor.clone());
alias = alias.overwrite_requirements(main_node.owned.requirements.clone());
}
alias = alias.redirect(crate::command::node::Redirection::Local(
main_node_id.into(),
));
alias = alias.redirect(crate::node::Redirection::Local(main_node_id.into()));
self.tree.add_child_to_root(alias.build());
}
} else if let Some(stripped) = name.strip_prefix('/') {
@ -286,17 +275,13 @@ impl CommandDispatcher {
if self.tree.get(&double_slash).is_none() {
let main_node = &self.tree[main_node_id];
let description = main_node.meta.description.clone();
let mut alias = crate::command::argument_builder::CommandArgumentBuilder::new(
double_slash,
description,
);
let mut alias =
crate::argument_builder::CommandArgumentBuilder::new(double_slash, description);
if let Some(executor) = &main_node.owned.command {
alias = alias.executes_arc(executor.clone());
alias = alias.overwrite_requirements(main_node.owned.requirements.clone());
}
alias = alias.redirect(crate::command::node::Redirection::Local(
main_node_id.into(),
));
alias = alias.redirect(crate::node::Redirection::Local(main_node_id.into()));
self.tree.add_child_to_root(alias.build());
}
}
@ -313,10 +298,10 @@ impl CommandDispatcher {
/// Note that, at least for now with this system, there is no way to
/// unregister a command. This is due to redirection to
/// potentially unregistered (freed) nodes.
pub fn register_with_aliases<S: AsRef<str>>(
pub fn register_with_aliases<Str: AsRef<str>>(
&mut self,
command_node: impl Into<CommandDetachedNode>,
aliases: &[S],
command_node: impl Into<CommandDetachedNode<S>>,
aliases: &[Str],
) -> CommandNodeId {
let main_node_id = self.register(command_node);
@ -365,11 +350,7 @@ impl CommandDispatcher {
///
/// # Note
/// This does not cache parsed input.
pub fn execute_input(
&self,
input: &str,
source: &CommandSource,
) -> Result<i32, CommandSyntaxError> {
pub fn execute_input(&self, input: &str, source: &S) -> Result<i32, CommandSyntaxError> {
let mut reader = StringReader::new(input);
// A disabled command must behave as if it does not exist from every
@ -390,14 +371,14 @@ impl CommandDispatcher {
pub fn execute_reader(
&self,
reader: &mut StringReader<'_>,
source: &CommandSource,
source: &S,
) -> Result<i32, CommandSyntaxError> {
let parsed = self.parse(reader, source);
self.execute(parsed)
}
/// Executes a given result that has already been parsed from an input.
pub fn execute(&self, parsed: ParsingResult<'_>) -> Result<i32, CommandSyntaxError> {
pub fn execute(&self, parsed: ParsingResult<'_, S>) -> Result<i32, CommandSyntaxError> {
if parsed.reader.peek().is_some() {
return if let Some(err) = parsed.errors.values().next() {
Err(err.clone())
@ -425,17 +406,13 @@ impl CommandDispatcher {
/// Only parses a given source with the specified source.
#[must_use]
pub fn parse_input(&self, command: &str, source: &CommandSource) -> ParsingResult<'_> {
pub fn parse_input(&self, command: &str, source: &S) -> ParsingResult<'_, S> {
let mut reader = StringReader::new(command);
self.parse(&mut reader, source)
}
/// Parses a command owned by a [`StringReader`] with the provided source.
pub fn parse(
&self,
reader: &mut StringReader<'_>,
source: &CommandSource,
) -> ParsingResult<'_> {
pub fn parse(&self, reader: &mut StringReader<'_>, source: &S) -> ParsingResult<'_, S> {
let context = CommandContextBuilder::new(
self,
Arc::new(source.clone()),
@ -449,11 +426,11 @@ impl CommandDispatcher {
&'a self,
node: NodeId,
original_reader: &mut StringReader<'_>,
context_so_far: &CommandContextBuilder<'a>,
) -> ParsingResult<'a> {
context_so_far: &CommandContextBuilder<'a, S>,
) -> ParsingResult<'a, S> {
let source = context_so_far.source.clone();
let mut errors: FxHashMap<NodeId, CommandSyntaxError> = FxHashMap::default();
let mut potentials: Vec<ParsingResult> = Vec::new();
let mut potentials: Vec<ParsingResult<'a, S>> = Vec::new();
let cursor = original_reader.cursor();
for child in self.tree.get_relevant_nodes(original_reader, node) {
@ -539,16 +516,7 @@ impl CommandDispatcher {
///
/// If the input starts with one slash (`/`), it is removed
/// inside the call itself.
///
/// # Panics
///
/// Panics if the source given to it is a dummy one.
pub fn handle_command<'a>(&'a self, source: &CommandSource, mut input: &'a str) {
assert!(
source.server.is_some(),
"Source provided to this command was a dummy source"
);
pub fn handle_command<'a>(&'a self, source: &S, mut input: &'a str) {
// If input starts with '/', but the command with that leading slash is NOT
// registered, while the stripped command IS registered (or if it's an unknown command
// starting with a single slash, e.g. from console input), strip one leading slash.
@ -583,7 +551,12 @@ impl CommandDispatcher {
/// Sends a command error to the provided source.
/// This also shows the contextual information
/// leading up to the error if necessary.
pub fn send_error_to_source(source: &CommandSource, error: CommandSyntaxError, command: &str) {
#[expect(deprecated)]
pub fn send_error_to_source(
source: &impl CommandSource,
error: CommandSyntaxError,
command: &str,
) {
source.send_message(error.message.color(Color::Named(NamedColor::Red)));
if let Some(context) = error.context {
@ -631,7 +604,7 @@ impl CommandDispatcher {
#[must_use]
pub fn get_completion_suggestions_at_end(
&self,
parsing_result: ParsingResult<'_>,
parsing_result: ParsingResult<'_, S>,
) -> Suggestions {
let length = parsing_result.reader.total_length();
self.get_completion_suggestions(parsing_result, length)
@ -644,7 +617,7 @@ impl CommandDispatcher {
#[must_use]
pub fn get_completion_suggestions(
&self,
parsing_result: ParsingResult<'_>,
parsing_result: ParsingResult<'_, S>,
cursor: usize,
) -> Suggestions {
let context = parsing_result.context;
@ -711,7 +684,7 @@ impl CommandDispatcher {
/// This function currently panics if the source provided was a dummy source.
/// This is subject to change in the future.
#[must_use]
pub fn suggest(&self, input: &str, source: &CommandSource) -> Vec<CommandSuggestion> {
pub fn suggest(&self, input: &str, source: &S) -> Vec<CommandSuggestion> {
self.suggest_with_range(input, source)
.suggestions
.into_iter()
@ -723,7 +696,7 @@ impl CommandDispatcher {
}
#[must_use]
pub fn suggest_with_range(&self, input: &str, source: &CommandSource) -> Suggestions {
pub fn suggest_with_range(&self, input: &str, source: &S) -> Suggestions {
// Never suggest arguments for a command that has been turned off.
if self.is_disabled(Self::command_name(input)) {
return Suggestions::empty();
@ -756,7 +729,7 @@ impl CommandDispatcher {
/// The map returned has the key as the command name
/// and the value as the command's description.
#[must_use]
pub fn get_all_permitted_commands(&self, source: &CommandSource) -> BTreeMap<&str, &str> {
pub fn get_all_permitted_commands(&self, source: &S) -> BTreeMap<&str, &str> {
let mut commands: BTreeMap<&str, &str> = BTreeMap::new();
for command in self.tree.get_root_children() {
@ -777,10 +750,7 @@ impl CommandDispatcher {
/// The key is the command identifier,
/// and the value is a tuple of `(description, usage)`.
#[must_use]
pub fn get_all_permitted_commands_usage(
&self,
source: &CommandSource,
) -> BTreeMap<&str, (&str, Box<str>)> {
pub fn get_all_permitted_commands_usage(&self, source: &S) -> BTreeMap<&str, (&str, Box<str>)> {
let mut commands: BTreeMap<&str, (&str, Box<str>)> = BTreeMap::new();
for (command_node_id, usage) in self.get_usage_of_commands(source) {
@ -801,7 +771,7 @@ impl CommandDispatcher {
#[must_use]
pub fn get_all_permitted_commands_usage_by_plugin(
&self,
source: &CommandSource,
source: &S,
plugin_name: &str,
) -> BTreeMap<&str, (&str, Box<str>)> {
let mut commands: BTreeMap<&str, (&str, Box<str>)> = BTreeMap::new();
@ -831,7 +801,7 @@ impl CommandDispatcher {
#[must_use]
pub fn get_permitted_command_usage(
&self,
source: &CommandSource,
source: &S,
command: &str,
) -> Option<(&str, Box<str>)> {
let command_node_id = self.tree.get(command)?;
@ -846,11 +816,7 @@ impl CommandDispatcher {
/// Returns the usage of the given command node.
#[must_use]
pub fn get_usage_of_command(
&self,
command_node: CommandNodeId,
source: &CommandSource,
) -> Option<String> {
pub fn get_usage_of_command(&self, command_node: CommandNodeId, source: &S) -> Option<String> {
// We know the root DOES NOT have an executor, so we pass false to `is_optional`.
self.get_usage_recursive(command_node.into(), source, false, false, None)
.map(|mut usage| {
@ -862,11 +828,7 @@ impl CommandDispatcher {
/// Returns the usage of each child of the given node (permitted for the given source).
#[must_use]
pub fn get_usage_of_children(
&self,
node: NodeId,
source: &CommandSource,
) -> FxHashMap<NodeId, String> {
pub fn get_usage_of_children(&self, node: NodeId, source: &S) -> FxHashMap<NodeId, String> {
let mut map = FxHashMap::default();
let is_optional = self.tree[node].command().is_some();
@ -881,10 +843,7 @@ impl CommandDispatcher {
/// Returns the usage of each command (permitted for the given source).
#[must_use]
pub fn get_usage_of_commands(
&self,
source: &CommandSource,
) -> FxHashMap<CommandNodeId, String> {
pub fn get_usage_of_commands(&self, source: &S) -> FxHashMap<CommandNodeId, String> {
self.get_usage_of_children(ROOT_NODE_ID, source)
.into_iter()
// This is safe because every child of the root child is a command node.
@ -900,7 +859,7 @@ impl CommandDispatcher {
fn get_usage_recursive(
&self,
node: NodeId,
source: &CommandSource,
source: &S,
is_optional: bool,
deep: bool,
redirector_usage_text: Option<String>,
@ -1007,15 +966,15 @@ impl CommandDispatcher {
#[cfg(test)]
mod test {
use crate::command::argument_builder::{
use crate::argument_builder::{
ArgumentBuilder, CommandArgumentBuilder, LiteralArgumentBuilder, RequiredArgumentBuilder,
};
use crate::command::argument_types::core::integer::IntegerArgumentType;
use crate::command::context::command_context::CommandContext;
use crate::command::context::command_source::CommandSource;
use crate::command::errors::error_types::DISPATCHER_UNKNOWN_COMMAND;
use crate::command::node::dispatcher::CommandDispatcher;
use crate::command::node::{CommandExecutor, CommandExecutorResult};
use crate::argument_types::core::integer::IntegerArgumentType;
use crate::context::command_context::CommandContext;
use crate::errors::error_types::DISPATCHER_UNKNOWN_COMMAND;
use crate::node::dispatcher::CommandDispatcher;
use crate::node::{CommandExecutor, CommandExecutorResult};
use crate::source::DummySource;
#[test]
fn unknown_command() {
@ -1023,7 +982,7 @@ mod test {
dispatcher.register(
CommandArgumentBuilder::new("unknown", "A command without an executor").build(),
);
let source = CommandSource::dummy();
let source = DummySource::dummy();
let result = dispatcher.execute_input("unknown", &source);
assert!(result.is_err_and(|error| error.error_type == &DISPATCHER_UNKNOWN_COMMAND));
}
@ -1034,7 +993,7 @@ mod test {
let executor: fn(&CommandContext) -> CommandExecutorResult = |_| Ok(1);
dispatcher
.register(CommandArgumentBuilder::new("simple", "A simple command").executes(executor));
let source = CommandSource::dummy();
let source = DummySource::dummy();
let result = dispatcher.execute_input("simple", &source);
assert_eq!(result, Ok(1));
}
@ -1050,7 +1009,7 @@ mod test {
.register(CommandArgumentBuilder::new("simple", "A simple command").executes(executor));
dispatcher.disable_command("simple");
let source = CommandSource::dummy();
let source = DummySource::dummy();
let result = dispatcher.execute_input("simple", &source);
assert!(result.is_err_and(|error| error.error_type == &DISPATCHER_UNKNOWN_COMMAND));
}
@ -1066,7 +1025,7 @@ mod test {
};
dispatcher.register_with_aliases(command(), &["Plugin-Alias"]);
let source = CommandSource::dummy();
let source = DummySource::dummy();
assert_eq!(dispatcher.execute_input("plugin-command", &source), Ok(1));
assert_eq!(dispatcher.execute_input("plugin-alias", &source), Ok(1));
@ -1170,7 +1129,7 @@ mod test {
),
),
);
let source = CommandSource::dummy();
let source = DummySource::dummy();
assert_eq!(
dispatcher.execute_input("arithmetic 3 + -7", &source),
Ok(-4)
@ -1194,7 +1153,7 @@ mod test {
// Note that we CANNOT use redirect here as node itself needs to execute the command,
// not its 'children'.
dispatcher.register(CommandArgumentBuilder::new("b", "An alias for /a").executes(executor));
let source = CommandSource::dummy();
let source = DummySource::dummy();
assert_eq!(dispatcher.execute_input("a", &source), Ok(1));
assert_eq!(dispatcher.execute_input("b", &source), Ok(1));
}
@ -1215,7 +1174,7 @@ mod test {
));
// Note that this time, we SHOULD use redirect - it is leading to another node having `command`.
dispatcher.register(CommandArgumentBuilder::new("b", "An alias for /a").redirect(a));
let source = CommandSource::dummy();
let source = DummySource::dummy();
assert_eq!(dispatcher.execute_input("a 5", &source), Ok(5));
assert_eq!(dispatcher.execute_input("b 7", &source), Ok(7));
}
@ -1246,7 +1205,7 @@ mod test {
dispatcher.register(builder);
let source = CommandSource::dummy();
let source = DummySource::dummy();
assert_eq!(dispatcher.execute_input("recurse", &source), Ok(1));
assert_eq!(dispatcher.execute_input("recurse 4", &source), Ok(1));
assert_eq!(dispatcher.execute_input("recurse 9 -1", &source), Ok(1));
@ -1269,7 +1228,7 @@ mod test {
CommandArgumentBuilder::new("//set", "WorldEdit set command").executes(executor),
);
let source = CommandSource::dummy();
let source = DummySource::dummy();
// Direct execution with //set
assert_eq!(dispatcher.execute_input("//set", &source), Ok(42));
// Execution via /set alias (as sent by Java client for //set)

View File

@ -0,0 +1,267 @@
pub mod attached;
pub mod detached;
pub mod dispatcher;
pub mod tree;
use crate::argument_types::argument_type::AnyArgumentType;
use crate::context::command_context::CommandContext;
use crate::errors::command_syntax_error::CommandSyntaxError;
use crate::node::attached::NodeId;
use crate::node::detached::GlobalNodeId;
use crate::source::{CommandSource, DummySource};
use crate::suggestion::provider::SuggestionProvider;
use std::borrow::Cow;
use std::sync::Arc;
/// Represents a [`CommandExecutor`]'s result.
///
/// If the command **ran successfully**, an [`Ok`] is returned containing an [`i32`].
/// This represents the 'output value' of the command, which is *homologous* to the
/// `int` that command executors in vanilla return **upon success**.
///
/// **You should choose the successful result as `1` if**:
/// - you don't know what value to use for a success for your
/// own commands, or
/// - you don't understand what this value means, or
/// - you just simply don't care about this value at all
///
/// If the command **fails**, an [`Err`] is returned, containing the [`CommandSyntaxError`]
/// that led to this result.
pub type CommandExecutorResult = Result<i32, CommandSyntaxError>;
/// A struct implementing this trait is able to run with a given context.
pub trait CommandExecutor<S: CommandSource = DummySource>: Sync + Send {
/// Executes this executor for a command.
fn execute(&self, context: &CommandContext<S>) -> CommandExecutorResult;
}
impl<F, S: CommandSource> CommandExecutor<S> for F
where
F: Fn(&CommandContext<S>) -> CommandExecutorResult + Send + Sync,
{
fn execute(&self, context: &CommandContext<S>) -> CommandExecutorResult {
self(context)
}
}
/// A function that takes a context and returns a command result.
pub type Command<S = DummySource> = Arc<dyn CommandExecutor<S>>;
/// Represents the result of [`Arc<S>`]s from a [`CommandContext`].
pub type RedirectModifierResult<S = DummySource> = Result<Vec<Arc<S>>, CommandSyntaxError>;
/// A function that performs the required modification.
pub type RedirectModifierExecutor<S = DummySource> =
dyn Fn(&CommandContext<S>) -> RedirectModifierResult<S> + Send + Sync;
/// A function that returns a new collection of sources from a given context.
#[derive(Clone)]
pub enum RedirectModifier<S: CommandSource = DummySource> {
/// Always returns only the source from the given context.
KeepSource,
/// Returns multiple [`CommandSource`]s from one context via
/// custom behavior.
Custom(Arc<RedirectModifierExecutor<S>>),
}
impl<S: CommandSource> RedirectModifier<S> {
/// Tries to provide a [`Vec`] of [`Arc<S>`] from a
/// given [`CommandContext`].
pub fn sources(&self, command_context: &CommandContext<S>) -> RedirectModifierResult<S> {
match self {
Self::KeepSource => Ok(vec![command_context.source.clone()]),
Self::Custom(function) => function(command_context),
}
}
}
/// Represents the result of a node requirement.
pub type RequirementResult = bool;
/// A predicate that returns if the provided source satisfies it.
#[derive(Clone)]
pub struct Requirement<S: CommandSource = DummySource>(
pub Arc<dyn Fn(&S) -> RequirementResult + Send + Sync>,
);
impl<S: CommandSource> Requirement<S> {
/// Evaluates the given condition, returning whether the
/// given [`CommandSource`] satisfies this requirement.
#[must_use]
pub fn evaluate(&self, command_source: &S) -> RequirementResult {
self.0(command_source)
}
}
impl<F, S: CommandSource> From<F> for Requirement<S>
where
F: Fn(&S) -> RequirementResult + Send + Sync + 'static,
{
fn from(value: F) -> Self {
Self(Arc::new(value))
}
}
// Permissions
impl<S: CommandSource> From<String> for Requirement<S> {
fn from(value: String) -> Self {
Self(Arc::new({
let permission = Arc::new(value);
move |source: &S| source.has_permission(&permission)
}))
}
}
impl<S: CommandSource> From<&'static str> for Requirement<S> {
fn from(value: &'static str) -> Self {
Self(Arc::new(move |source: &S| source.has_permission(value)))
}
}
/// A structure that returns if the source is qualified enough to run the command.
#[derive(Clone)]
pub struct Requirements<S: CommandSource = DummySource>(pub Vec<Requirement<S>>);
impl<S: CommandSource> Requirements<S> {
/// Creates a new `Requirements` with no requirements in it.
/// If used, this will always return `true` when evaluated.
#[must_use]
pub const fn new() -> Self {
Self(Vec::new())
}
/// Evaluates the given condition, returning whether the
/// given [`CommandSource`] satisfies all contained requirements.
#[must_use]
pub fn evaluate(&self, command_source: &S) -> RequirementResult {
for predicate in &self.0 {
if !predicate.evaluate(command_source) {
return false;
}
}
true
}
}
impl<S: CommandSource> Default for Requirements<S> {
fn default() -> Self {
Self::new()
}
}
/// Stores common owned data for a node.
#[derive(Clone)]
pub struct OwnedNodeData<S: CommandSource = DummySource> {
pub global_id: GlobalNodeId,
pub requirements: Requirements<S>,
pub modifier: RedirectModifier<S>,
pub forks: bool,
pub command: Option<Command<S>>,
}
/// Represents the extra metadata of a node storing a literal.
#[derive(Clone)]
pub struct LiteralNodeMetadata {
pub literal: Cow<'static, str>,
pub literal_lowercase: String,
}
impl LiteralNodeMetadata {
pub fn new(literal: impl Into<Cow<'static, str>>) -> Self {
let literal = literal.into();
Self {
literal: literal.clone(),
literal_lowercase: literal.to_lowercase(),
}
}
}
/// A special type of [`LiteralNodeMetadata`], containing
/// a description for the command as well.
#[derive(Clone)]
pub struct CommandNodeMetadata {
pub literal: Cow<'static, str>,
pub literal_lowercase: String,
pub description: Cow<'static, str>,
pub source: Option<String>,
}
impl CommandNodeMetadata {
pub fn new(
literal: impl Into<Cow<'static, str>>,
description: impl Into<Cow<'static, str>>,
) -> Self {
let literal = literal.into();
Self {
literal: literal.clone(),
literal_lowercase: literal.to_lowercase(),
description: description.into(),
source: None,
}
}
}
/// Represents the extra metadata of an argument of any type.
#[derive(Clone)]
pub struct ArgumentNodeMetadata<S: CommandSource = DummySource> {
pub name: Cow<'static, str>,
pub argument_type: Arc<dyn AnyArgumentType<S>>,
pub suggestion_provider: Option<Arc<dyn SuggestionProvider<S>>>,
}
impl<S: CommandSource> ArgumentNodeMetadata<S> {
pub fn new(
name: impl Into<Cow<'static, str>>,
argument_type: Arc<dyn AnyArgumentType<S>>,
suggestion_provider: Option<Arc<dyn SuggestionProvider<S>>>,
) -> Self {
Self {
name: name.into(),
argument_type,
suggestion_provider,
}
}
}
/// Represents the extra metadata for nodes of different types. Can be of the root, a literal, command or an argument.
pub enum NodeMetadata<S: CommandSource = DummySource> {
/// Metadata of the root node.
Root,
/// Metadata of a literal node that doesn't start a command.
Literal(LiteralNodeMetadata),
/// Metadata of a literal node that starts a command.
Command(CommandNodeMetadata),
/// Metadata of an argument node.
Argument(ArgumentNodeMetadata<S>),
}
/// Stores where this redirection would lead to.
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
pub enum Redirection {
/// Leads to the root of the tree.
Root,
/// Leads to a node in the tree from its tree-local ID.
Local(NodeId),
/// Leads to a node in the tree from its global ID.
Global(GlobalNodeId),
}
impl<T: Into<NodeId>> From<T> for Redirection {
fn from(value: T) -> Self {
Self::Local(value.into())
}
}
impl From<GlobalNodeId> for Redirection {
fn from(value: GlobalNodeId) -> Self {
Self::Global(value)
}
}

View File

@ -1,15 +1,15 @@
use crate::command::context::command_context::{CommandContextBuilder, ParsedArgument};
use crate::command::context::command_source::CommandSource;
use crate::command::context::string_range::StringRange;
use crate::command::errors::command_syntax_error::CommandSyntaxError;
use crate::command::errors::error_types::LITERAL_INCORRECT;
use crate::command::node::Redirection;
use crate::command::node::attached::{
use crate::context::command_context::{CommandContextBuilder, ParsedArgument};
use crate::context::string_range::StringRange;
use crate::errors::command_syntax_error::CommandSyntaxError;
use crate::errors::error_types::LITERAL_INCORRECT;
use crate::node::Redirection;
use crate::node::attached::{
ArgumentAttachedNode, ArgumentNodeId, AttachedNode, CommandAttachedNode, CommandNodeId,
LiteralAttachedNode, LiteralNodeId, NodeClassification, NodeId, RootAttachedNode,
};
use crate::command::node::detached::{CommandDetachedNode, DetachedNode, GlobalNodeId};
use crate::command::string_reader::StringReader;
use crate::node::detached::{CommandDetachedNode, DetachedNode, GlobalNodeId};
use crate::source::{CommandSource, DummySource};
use crate::string_reader::StringReader;
use pumpkin_util::text::TextComponent;
use rustc_hash::{FxBuildHasher, FxHashMap, FxHashSet};
use std::num::NonZero;
@ -20,10 +20,10 @@ use std::sync::Arc;
pub const ROOT_NODE_ID: NodeId = NodeId(NonZero::new(1).expect("1 is non-zero"));
/// A consumer which takes ambiguity of input (when two or more nodes are satisfied)
pub trait AmbiguityConsumer {
pub trait AmbiguityConsumer<S: CommandSource = DummySource> {
fn ambiguous(
&mut self,
tree: &Tree,
tree: &Tree<S>,
parent: NodeId,
child: NodeId,
sibling: NodeId,
@ -76,12 +76,12 @@ pub enum NodeIdClassification {
/// - **Argument**:
/// Parses and accepts a specific type of value. This is very dynamic.
#[derive(Clone)]
pub struct Tree {
pub struct Tree<S: CommandSource = DummySource> {
/// All the nodes stored in this tree.
///
/// In this vector, indices starting at 0 indicates the first node (ID = 1),
/// 1 indicates the second node (ID = 2) and so on.
nodes: Vec<AttachedNode>,
nodes: Vec<AttachedNode<S>>,
/// Keys linking [`GlobalNodeId`] to the [`NodeId`] for this tree.
/// Useful for redirecting.
@ -91,13 +91,13 @@ pub struct Tree {
command_node_mappings: FxHashMap<String, CommandNodeId>,
}
impl Default for Tree {
impl<S: CommandSource> Default for Tree<S> {
fn default() -> Self {
Self::new()
}
}
impl Tree {
impl<S: CommandSource> Tree<S> {
/// Constructs a new tree, containing a new root node without children.
#[must_use]
pub fn new() -> Self {
@ -130,7 +130,7 @@ impl Tree {
/// Helper to attach a given [`AttachedNode`], returning
/// its [`NodeId`].
fn add(&mut self, node: AttachedNode) -> NodeId {
fn add(&mut self, node: AttachedNode<S>) -> NodeId {
let global_id = node.global_id();
let local_id = self.alloc();
@ -144,7 +144,7 @@ impl Tree {
/// Helper to attach a [`DetachedNode`] irreversibly
/// into this [`Tree`], returning the ID of the now attached
/// node.
fn attach(&mut self, node: DetachedNode) -> NodeId {
fn attach(&mut self, node: DetachedNode<S>) -> NodeId {
// First, we decompose this node.
let node = node.decompose();
@ -177,7 +177,7 @@ impl Tree {
}
/// Adds a [`CommandDetachedNode`] to the root node of this tree.
pub fn add_child_to_root(&mut self, node: impl Into<CommandDetachedNode>) -> CommandNodeId {
pub fn add_child_to_root(&mut self, node: impl Into<CommandDetachedNode<S>>) -> CommandNodeId {
// First, attach the node to this tree.
let node = node.into();
let name = node.meta.literal.to_string();
@ -199,7 +199,7 @@ impl Tree {
///
/// Essentially, this means that a [`CommandDetachedNode`] must have the root node
/// of the tree *as its parent*, to be attached to the tree.
pub fn add_child(&mut self, parent: NodeId, node: impl Into<DetachedNode>) -> NodeId {
pub fn add_child(&mut self, parent: NodeId, node: impl Into<DetachedNode<S>>) -> NodeId {
let node = node.into();
assert!(
parent == ROOT_NODE_ID || !matches!(node, DetachedNode::Command(_)),
@ -269,12 +269,12 @@ impl Tree {
/// Returns whether the given node is able to be used by a given source.
#[must_use]
pub fn can_use(&self, node: NodeId, source: &CommandSource) -> bool {
pub fn can_use(&self, node: NodeId, source: &S) -> bool {
self[node].requirements().evaluate(source)
}
/// Finds ambiguities of input and gives them to the [`AmbiguityConsumer`].
pub fn find_ambiguities(&self, node: NodeId, consumer: &mut impl AmbiguityConsumer) {
pub fn find_ambiguities(&self, node: NodeId, consumer: &mut impl AmbiguityConsumer<S>) {
let mut matches: FxHashSet<String> = FxHashSet::default();
for child in self.get_children(node) {
@ -363,13 +363,13 @@ impl Tree {
&self,
node_id: NodeId,
reader: &'a mut StringReader<'_>,
command_context_builder: &'a mut CommandContextBuilder<'_>,
command_context_builder: &'a mut CommandContextBuilder<'_, S>,
) -> Result<(), CommandSyntaxError> {
match &self[node_id] {
AttachedNode::Root(_) => {}
AttachedNode::Literal(node) => {
let start = reader.cursor();
let Ok(end) = AttachedNode::parse_literal(reader, &node.meta.literal) else {
let Ok(end) = AttachedNode::<S>::parse_literal(reader, &node.meta.literal) else {
return Err(LITERAL_INCORRECT
.create(reader, TextComponent::text(node.meta.literal.to_string())));
};
@ -377,7 +377,7 @@ impl Tree {
}
AttachedNode::Command(node) => {
let start = reader.cursor();
let Ok(end) = AttachedNode::parse_literal(reader, &node.meta.literal) else {
let Ok(end) = AttachedNode::<S>::parse_literal(reader, &node.meta.literal) else {
return Err(LITERAL_INCORRECT
.create(reader, TextComponent::text(node.meta.literal.to_string())));
};
@ -419,20 +419,20 @@ impl Tree {
}
/// Returns an iterator to all the nodes of this tree.
pub fn iter(&self) -> std::slice::Iter<'_, AttachedNode> {
pub fn iter(&self) -> std::slice::Iter<'_, AttachedNode<S>> {
self.nodes.iter()
}
}
impl Index<NodeId> for Tree {
type Output = AttachedNode;
impl<S: CommandSource> Index<NodeId> for Tree<S> {
type Output = AttachedNode<S>;
fn index(&self, index: NodeId) -> &Self::Output {
&self.nodes[index.0.get() - 1]
}
}
impl IndexMut<NodeId> for Tree {
impl<S: CommandSource> IndexMut<NodeId> for Tree<S> {
fn index_mut(&mut self, index: NodeId) -> &mut Self::Output {
&mut self.nodes[index.0.get() - 1]
}
@ -441,8 +441,8 @@ impl IndexMut<NodeId> for Tree {
/// Macro helper to create [`Index`] and [`IndexMut`] for [`Tree`] with typed IDs.
macro_rules! impl_index_index_mut {
($node_id: ident -> AttachedNode::$attached_node_enum: ident($attached_node: ident)) => {
impl Index<$node_id> for Tree {
type Output = $attached_node;
impl<S: CommandSource> Index<$node_id> for Tree<S> {
type Output = $attached_node<S>;
fn index(&self, index: $node_id) -> &Self::Output {
if let AttachedNode::$attached_node_enum(node) = &self.nodes[index.0.get() - 1] {
@ -456,7 +456,7 @@ macro_rules! impl_index_index_mut {
}
}
impl IndexMut<$node_id> for Tree {
impl<S: CommandSource> IndexMut<$node_id> for Tree<S> {
fn index_mut(&mut self, index: $node_id) -> &mut Self::Output {
if let AttachedNode::$attached_node_enum(node) = &mut self.nodes[index.0.get() - 1]
{
@ -476,9 +476,9 @@ impl_index_index_mut!(LiteralNodeId -> AttachedNode::Literal(LiteralAttachedNode
impl_index_index_mut!(CommandNodeId -> AttachedNode::Command(CommandAttachedNode));
impl_index_index_mut!(ArgumentNodeId -> AttachedNode::Argument(ArgumentAttachedNode));
impl<'a> IntoIterator for &'a Tree {
type Item = &'a AttachedNode;
type IntoIter = std::slice::Iter<'a, AttachedNode>;
impl<'a, S: CommandSource> IntoIterator for &'a Tree<S> {
type Item = &'a AttachedNode<S>;
type IntoIter = std::slice::Iter<'a, AttachedNode<S>>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
@ -487,17 +487,18 @@ impl<'a> IntoIterator for &'a Tree {
#[cfg(test)]
mod test {
use crate::command::argument_builder::{
use crate::argument_builder::{
ArgumentBuilder, CommandArgumentBuilder, LiteralArgumentBuilder, RequiredArgumentBuilder,
};
use crate::command::argument_types::core::string::StringArgumentType;
use crate::command::node::attached::NodeId;
use crate::command::node::tree::{AmbiguityConsumer, Tree};
use crate::argument_types::core::string::StringArgumentType;
use crate::node::attached::NodeId;
use crate::node::tree::{AmbiguityConsumer, Tree};
use crate::source::DummySource;
#[test]
fn adding_nodes() {
// New tree (containing only one root node)
let mut tree = Tree::new();
let mut tree = Tree::<DummySource>::new();
assert_eq!(tree.size(), 1);
// Adding one node.
@ -516,7 +517,7 @@ mod test {
#[test]
fn adding_children_to_attached_node() {
let mut tree = Tree::new();
let mut tree = Tree::<DummySource>::new();
let parent: NodeId = tree
.add_child_to_root(CommandArgumentBuilder::new("foo", "A test command"))
@ -532,7 +533,7 @@ mod test {
#[test]
#[should_panic = "Cannot add a CommandDetachedNode as a child of a non-root node"]
fn adding_command_node_to_non_root_node() {
let mut tree = Tree::new();
let mut tree = Tree::<DummySource>::new();
let parent: NodeId = tree
.add_child_to_root(CommandArgumentBuilder::new("foo", "A test command"))

View File

@ -1,6 +1,6 @@
use std::borrow::Cow;
use crate::command::{
use crate::{
errors::error_types::{AnyCommandErrorType, CommandErrorType},
string_reader::StringReader,
};

View File

@ -1,4 +1,4 @@
use crate::command::{
use crate::{
errors::error_types::CommandErrorType,
snbt::rules::{EXPECTED_BINARY_NUMERAL, EXPECTED_DECIMAL_NUMERAL, EXPECTED_HEX_NUMERAL},
};

View File

@ -5,14 +5,12 @@ mod rules;
#[cfg(test)]
mod tests;
use crate::command::errors::command_syntax_error::{CommandSyntaxError, CommandSyntaxErrorContext};
use crate::command::errors::error_types::{CommandErrorType, LITERAL_INCORRECT};
use crate::command::parser::{Parser, ParserErrors};
use crate::command::snbt::markers::{
ArrayPrefix, Base, IntegerLiteral, Sign, SignedPrefix, TypeSuffix,
};
use crate::command::string_reader::StringReader;
use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
use crate::errors::command_syntax_error::{CommandSyntaxError, CommandSyntaxErrorContext};
use crate::errors::error_types::{CommandErrorType, LITERAL_INCORRECT};
use crate::parser::{Parser, ParserErrors};
use crate::snbt::markers::{ArrayPrefix, Base, IntegerLiteral, Sign, SignedPrefix, TypeSuffix};
use crate::string_reader::StringReader;
use crate::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
use pumpkin_codecs::Number;
use pumpkin_data::translation;
use pumpkin_nbt::tag::NbtTag;
@ -62,6 +60,7 @@ pub struct SnbtParser<'r, 's> {
//
impl SnbtParser<'_, '_> {
/// Parses SNBT with a given [`StringReader`], giving the result or error from parsing.
#[expect(deprecated)]
pub fn parse_for_commands(reader: &mut StringReader) -> Result<NbtTag, CommandSyntaxError> {
let (result, errors) = {
let mut parser = SnbtParser {

View File

@ -2,7 +2,7 @@ use pumpkin_data::translation;
use pumpkin_nbt::{nbt_ops::NbtOps, tag::NbtTag};
use pumpkin_util::uuid::parse_uuid_vec;
use crate::command::{errors::error_types::CommandErrorType, parser::Parser, snbt::SnbtParser};
use crate::{errors::error_types::CommandErrorType, parser::Parser, snbt::SnbtParser};
use pumpkin_codecs::DynamicOps;
pub const EXPECTED_NUMBER_OR_BOOLEAN: CommandErrorType<0> = CommandErrorType::new(

View File

@ -1,12 +1,12 @@
use std::collections::HashMap;
use crate::command::errors::error_types::{CommandErrorType, LITERAL_INCORRECT};
use crate::command::parser::Parser;
use crate::command::snbt::markers::{
use crate::errors::error_types::{CommandErrorType, LITERAL_INCORRECT};
use crate::parser::Parser;
use crate::snbt::markers::{
ArrayPrefix, Base, IntegerLiteral, IntegerSuffix, Sign, Signed, SignedPrefix, TypeSuffix,
};
use crate::command::snbt::operations::SnbtOperations;
use crate::command::snbt::{NUMBER_PARSE_FAILURE, SnbtParser};
use crate::snbt::operations::SnbtOperations;
use crate::snbt::{NUMBER_PARSE_FAILURE, SnbtParser};
use pumpkin_codecs::{DynamicOps, Number};
use pumpkin_data::translation;
use pumpkin_nbt::compound::NbtCompound;

View File

@ -2,7 +2,7 @@ use pumpkin_nbt::{compound::NbtCompound, nbt_compound_tag, tag::NbtTag};
const BUILT_IN_LIKE_SUGGESTIONS: &[&str] = &["(", "bool", "false", "true", "uuid"];
use crate::command::{
use crate::{
errors::command_syntax_error::CommandSyntaxError, snbt::SnbtParser,
string_reader::StringReader, suggestion::suggestions::SuggestionsBuilder,
};

View File

@ -0,0 +1,189 @@
use pumpkin_util::math::position::BlockPos;
use pumpkin_util::math::vector2::Vector2;
use pumpkin_util::math::vector3::Vector3;
use pumpkin_util::text::TextComponent;
use std::sync::Arc;
use crate::argument_types::entity_anchor::EntityAnchor;
use crate::errors::command_syntax_error::CommandSyntaxError;
pub trait ReturnValueCallable: Send + Sync {
fn call(&self, value: ReturnValue);
}
pub type ReturnValueCallback = Arc<dyn ReturnValueCallable>;
#[derive(Clone, Default)]
pub struct ResultValueTaker(pub Vec<ReturnValueCallback>);
impl ResultValueTaker {
#[must_use]
pub fn merge(taker_1: &Self, taker_2: &Self) -> Self {
let mut takers = Vec::with_capacity(taker_1.0.len() + taker_2.0.len());
for taker in &taker_1.0 {
takers.push(taker.clone());
}
for taker in &taker_2.0 {
takers.push(taker.clone());
}
Self(takers)
}
#[must_use]
pub fn new() -> Self {
Self(Vec::new())
}
pub fn call(&self, return_value: ReturnValue) {
for callback in &self.0 {
callback.call(return_value);
}
}
}
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum ReturnValue {
Success(i32),
Failure,
}
impl ReturnValue {
#[must_use]
pub const fn success_value(self) -> bool {
match self {
Self::Success(_) => true,
Self::Failure => false,
}
}
#[must_use]
pub const fn result_value(self) -> i32 {
match self {
Self::Success(value) => value,
Self::Failure => 0,
}
}
}
/// Abstract command source trait for command dispatch and execution.
pub trait CommandSource: Clone + Send + Sync + 'static {
/// Sends a feedback message to the command sender.
fn send_message(&self, message: TextComponent);
/// Sends an error message to the command sender.
fn send_error(&self, error: TextComponent) {
self.send_message(error);
}
/// Invokes callbacks for the command's return value.
fn call_result(&self, _result: ReturnValue) {}
/// Checks if this command source has the specified permission.
fn has_permission(&self, _permission: &str) -> bool {
true
}
/// Returns the position of this command source in the world.
fn position(&self) -> Vector3<f64> {
Vector3::default()
}
/// Returns the rotation (pitch, yaw) of this command source.
fn rotation(&self) -> Vector2<f32> {
Vector2::default()
}
/// Checks if the block at the given position is loaded.
fn check_block_loaded(&self, _pos: &BlockPos) -> Result<(), CommandSyntaxError> {
Ok(())
}
/// Returns the anchor position for the given entity anchor.
fn anchor_position(&self, _anchor: EntityAnchor) -> Vector3<f64> {
self.position()
}
/// Returns the current entity anchor of this command source.
fn entity_anchor(&self) -> EntityAnchor {
EntityAnchor::Feet
}
}
#[derive(Clone, Default)]
pub struct DummySource {
pub position: Vector3<f64>,
pub rotation: Vector2<f32>,
pub entity_anchor: EntityAnchor,
pub command_result_taker: ResultValueTaker,
}
impl DummySource {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn dummy() -> Self {
Self::default()
}
}
impl CommandSource for DummySource {
fn send_message(&self, _message: TextComponent) {}
fn send_error(&self, _error: TextComponent) {}
fn position(&self) -> Vector3<f64> {
self.position
}
fn rotation(&self) -> Vector2<f32> {
self.rotation
}
fn entity_anchor(&self) -> EntityAnchor {
self.entity_anchor
}
fn call_result(&self, result: ReturnValue) {
self.command_result_taker.call(result);
}
}
impl CommandSource for () {
fn send_message(&self, _message: TextComponent) {}
}
impl<S: CommandSource> CommandSource for Arc<S> {
fn send_message(&self, message: TextComponent) {
(**self).send_message(message);
}
fn send_error(&self, error: TextComponent) {
(**self).send_error(error);
}
fn has_permission(&self, permission: &str) -> bool {
(**self).has_permission(permission)
}
fn position(&self) -> Vector3<f64> {
(**self).position()
}
fn rotation(&self) -> Vector2<f32> {
(**self).rotation()
}
fn entity_anchor(&self) -> EntityAnchor {
(**self).entity_anchor()
}
fn anchor_position(&self, anchor: EntityAnchor) -> Vector3<f64> {
(**self).anchor_position(anchor)
}
fn call_result(&self, result: ReturnValue) {
(**self).call_result(result);
}
fn check_block_loaded(&self, pos: &BlockPos) -> Result<(), CommandSyntaxError> {
(**self).check_block_loaded(pos)
}
}

View File

@ -1,7 +1,7 @@
use crate::command::errors::command_syntax_error::{
use crate::errors::command_syntax_error::{
CommandSyntaxError, CommandSyntaxErrorContext, ContextProvider,
};
use crate::command::errors::error_types::{self, CommandErrorType};
use crate::errors::error_types::{self, CommandErrorType};
use pumpkin_util::text::TextComponent;
use std::borrow::Cow;
use std::str::FromStr;
@ -367,7 +367,7 @@ impl ContextProvider for StringReader<'_> {
#[cfg(test)]
mod test {
use crate::command::{errors::error_types, string_reader::StringReader};
use crate::{errors::error_types, string_reader::StringReader};
#[test]
fn non_parsing_methods() {

View File

@ -5,7 +5,7 @@ use pumpkin_util::text::TextComponent;
use std::fmt::Debug;
use std::hash::Hash;
use crate::command::context::string_range::StringRange;
use crate::context::string_range::StringRange;
/// A structure that describes the text of a suggestion.
/// It's actual value can either be a [`String`], or an [`i32`].
@ -21,7 +21,7 @@ use crate::command::context::string_range::StringRange;
/// be fully expressed as a [`SuggestionText::Integer`] (no leading zeros).
/// This is important to establish proper ordering.
/// ```
/// use pumpkin::command::suggestion::SuggestionText;
/// use pumpkin_command::suggestion::SuggestionText;
///
/// let five_suggestion_1 = SuggestionText::new(5);
/// let five_suggestion_2 = SuggestionText::new("5");
@ -228,8 +228,8 @@ impl Suggestion {
#[cfg(test)]
mod test {
use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
use crate::command::{context::string_range::StringRange, suggestion::Suggestion};
use crate::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
use crate::{context::string_range::StringRange, suggestion::Suggestion};
use std::slice;
#[test]

View File

@ -1,12 +1,13 @@
use crate::command::context::command_context::CommandContext;
use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
use crate::context::command_context::CommandContext;
use crate::source::{CommandSource, DummySource};
use crate::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
/// The [`Suggestions`] given by a [`SuggestionProvider`].
pub type SuggestionProviderResult = Suggestions;
/// A trait allowing an object to provide suggestions using a
/// [`CommandContext`] and [`SuggestionsBuilder`].
pub trait SuggestionProvider: Send + Sync {
pub trait SuggestionProvider<S: CommandSource = DummySource>: Send + Sync {
/// Uses a [`CommandContext`] and [`SuggestionsBuilder`] to suggest.
///
/// # Arguments
@ -17,7 +18,7 @@ pub trait SuggestionProvider: Send + Sync {
/// The [`Suggestions`] representing the suggested items.
fn suggest(
&self,
context: &CommandContext,
context: &CommandContext<S>,
builder: SuggestionsBuilder,
) -> SuggestionProviderResult;
}

View File

@ -1,5 +1,5 @@
use crate::command::context::string_range::StringRange;
use crate::command::suggestion::{Suggestion, SuggestionText};
use crate::context::string_range::StringRange;
use crate::suggestion::{Suggestion, SuggestionText};
use pumpkin_util::text::TextComponent;
use std::borrow::Borrow;
use std::cmp::Ordering;

View File

@ -24,6 +24,7 @@ rayon.workspace = true
pumpkin-util.workspace = true
pumpkin-nbt.workspace = true
pumpkin-config.workspace = true
pumpkin-command.workspace = true
pumpkin-inventory.workspace = true
pumpkin-world.workspace = true
pumpkin-data = { workspace = true, features = ["default"] }

View File

@ -1,165 +1,69 @@
use crate::command::argument_builder::private::Sealed;
use crate::command::argument_types::argument_type::AnyArgumentType;
use crate::command::node::detached::{
ArgumentDetachedNode, CommandDetachedNode, DetachedNode, GlobalNodeId, LiteralDetachedNode,
};
use crate::command::node::{
Command, CommandExecutor, RedirectModifier, Redirection, Requirement, Requirements,
};
use crate::command::suggestion::provider::SuggestionProvider;
use rustc_hash::FxHashMap;
use std::borrow::Cow;
use std::sync::Arc;
/// Represents an intermediate struct for
/// building arguments for commands.
///
/// # Note
///
/// This is an implementation detail.
struct CommonArgumentBuilder {
pub global_id: GlobalNodeId,
pub arguments: FxHashMap<String, DetachedNode>,
pub command: Option<Command>,
pub requirements: Requirements,
pub target: Option<Redirection>,
pub modifier: RedirectModifier,
pub forks: bool,
}
use crate::command::context::command_source::CommandSource;
use crate::command::node::detached::DetachedNode;
use crate::command::node::{
ArcCommandExecutorAdapter, Command, CommandExecutor, CommandExecutorAdapter, RedirectModifier,
Requirement,
};
use crate::command::suggestion::provider::{SuggestionProvider, SuggestionProviderAdapter};
impl CommonArgumentBuilder {
fn new() -> Self {
Self {
global_id: GlobalNodeId::new(),
arguments: FxHashMap::default(),
command: None,
requirements: Requirements::new(),
target: None,
modifier: RedirectModifier::KeepSource,
forks: false,
}
}
}
use pumpkin_command::argument_builder::ArgumentBuilder as CoreArgumentBuilder;
pub use pumpkin_command::node::Redirection;
impl Default for CommonArgumentBuilder {
fn default() -> Self {
Self::new()
}
}
pub struct CommandArgumentBuilder(
pub pumpkin_command::argument_builder::CommandArgumentBuilder<CommandSource>,
);
pub struct LiteralArgumentBuilder(
pub pumpkin_command::argument_builder::LiteralArgumentBuilder<CommandSource>,
);
pub struct RequiredArgumentBuilder(
pub pumpkin_command::argument_builder::RequiredArgumentBuilder<CommandSource>,
);
/// A short-form way to create a new [`CommandArgumentBuilder`]
/// from a literal and a command description.
///
/// This can be imported directly without a prefix, or imported with the `argument_builder::` prefix. Here's an example of usage:
/// ```
/// use pumpkin::command::argument_builder::command;
///
/// let builder = command("foo", "A test command");
/// ```
///
/// The builder returned will eventually construct a [`CommandDetachedNode`].
/// This node can then be registered into a dispatcher.
pub fn command(
literal: impl Into<Cow<'static, str>>,
description: impl Into<Cow<'static, str>>,
) -> CommandArgumentBuilder {
CommandArgumentBuilder::new(literal, description)
CommandArgumentBuilder(pumpkin_command::argument_builder::command(
literal,
description,
))
}
/// A short-form way to create a new [`LiteralArgumentBuilder`]
/// from a literal.
///
/// This can be imported directly without a prefix, or imported with the `argument_builder::` prefix. Here's an example of usage:
/// ```
/// use pumpkin::command::argument_builder::literal;
///
/// let builder = literal("bar");
/// ```
///
/// The builder returned will eventually construct a [`LiteralDetachedNode`].
pub fn literal(literal: impl Into<Cow<'static, str>>) -> LiteralArgumentBuilder {
LiteralArgumentBuilder::new(literal)
LiteralArgumentBuilder(pumpkin_command::argument_builder::literal(literal))
}
/// A short-form way to create a new [`RequiredArgumentBuilder`]
/// from an argument type and name.
///
/// This can be imported directly without a prefix, or imported with the `argument_builder::` prefix. Here's an example of usage:
/// ```
/// use pumpkin::command::{
/// argument_builder::argument,
/// argument_types::core::integer::IntegerArgumentType
/// };
///
/// let argument_builder = argument("bar", IntegerArgumentType::new(1, 10));
/// ```
///
/// The builder returned will eventually construct a [`ArgumentDetachedNode`].
pub fn argument(
name: impl Into<Cow<'static, str>>,
arg_type: impl AnyArgumentType + 'static,
arg_type: impl pumpkin_command::argument_types::argument_type::AnyArgumentType<CommandSource>
+ 'static,
) -> RequiredArgumentBuilder {
RequiredArgumentBuilder::new(name, arg_type)
RequiredArgumentBuilder(pumpkin_command::argument_builder::argument(name, arg_type))
}
/// A builder that builds a literal, non-command [`DetachedNode`].
pub struct LiteralArgumentBuilder {
common: CommonArgumentBuilder,
literal: Cow<'static, str>,
}
pub trait ArgumentBuilder: Sized {
type Node;
/// A builder that builds a command [`DetachedNode`].
pub struct CommandArgumentBuilder {
common: CommonArgumentBuilder,
literal: Cow<'static, str>,
description: Cow<'static, str>,
source: Option<String>,
}
/// A builder that builds an argument [`DetachedNode`].
pub struct RequiredArgumentBuilder {
common: CommonArgumentBuilder,
name: Cow<'static, str>,
argument_type: Arc<dyn AnyArgumentType>,
suggestion_provider: Option<Arc<dyn SuggestionProvider>>,
}
mod private {
// We want to make this trait private so that
// we can implement it for only our
// argument builders defined here.
pub trait Sealed {}
}
pub trait ArgumentBuilder<N: Into<DetachedNode>>: Sized + Sealed {
/// Puts an argument to be specified, right after this one is specified.
///
/// # Panics
///
/// Panics if this node is redirected to another node, or the child
/// provided is of the type [`CommandDetachedNode`].
#[must_use]
fn then(self, child: impl Into<DetachedNode>) -> Self;
/// Gets the command to execute for the node being built.
#[must_use]
fn command(&self) -> Option<Command>;
/// Sets the command to execute for the node being built.
#[must_use]
fn executes(self, command: impl CommandExecutor + 'static) -> Self {
self.executes_arc(Arc::new(command))
}
/// Sets the command to execute for the node being built.
#[must_use]
fn executes_arc(self, command: Arc<dyn CommandExecutor + 'static>) -> Self;
/// Sets the redirect target of the node being built to another, without a modifier.
#[must_use]
fn redirect(self, redirection: impl Into<Redirection>) -> Self;
/// Sets the redirect target of the node being built to another, with a given modifier.
#[must_use]
fn redirect_with_modifier(
self,
@ -167,392 +71,218 @@ pub trait ArgumentBuilder<N: Into<DetachedNode>>: Sized + Sealed {
redirect_modifier: RedirectModifier,
) -> Self;
/// Forks the given context, using multiple for later.
#[must_use]
fn fork(self, redirection: impl Into<Redirection>, redirect_modifier: RedirectModifier)
-> Self;
/// Forwards the given context, with the given `fork` flag.
#[must_use]
fn forward(
self,
redirection: impl Into<Redirection>,
redirect_modifier: RedirectModifier,
fork: bool,
) -> Self;
/// Gets a reference to the arguments of the node to be built.
#[must_use]
fn arguments(&self) -> &FxHashMap<String, DetachedNode>;
/// Gets the node to which the node being built by this [`ArgumentBuilder`] redirects.
#[must_use]
fn target(&self) -> Option<Redirection>;
/// Adds a given predicate to this list of requirements of the node being built.
///
/// This means that it is possible to chain multiple predicates together, which must all
/// be satisfied.
///
/// Permissions can also be inserted directly into this method as a `requirement`.
#[must_use]
fn requires(self, requirement: impl Into<Requirement>) -> Self;
/// Overwrites the current requirements of this node to a new value.
#[must_use]
fn overwrite_requirements(self, requirements: Requirements) -> Self;
/// Gets the redirect modifier of the node this [`ArgumentBuilder`] is building.
#[must_use]
fn redirect_modifier(&self) -> RedirectModifier;
/// Whether this builder forks.
#[must_use]
fn forks(&self) -> bool;
/// Returns the 'future [`GlobalNodeId`]' of the node that will be produced by this Builder.
/// Very useful for redirects.
#[must_use]
fn id(&self) -> GlobalNodeId;
/// Builds the node represented by this builder, consuming itself in the process.
#[must_use]
fn build(self) -> N;
}
// Implement the private trait for our builders!
impl Sealed for LiteralArgumentBuilder {}
impl Sealed for CommandArgumentBuilder {}
impl Sealed for RequiredArgumentBuilder {}
/// Helper macro to implement repeated code of `ArgumentBuilder` for our types.
macro_rules! impl_boilerplate_argument_builder {
() => {
fn then(mut self, argument: impl Into<DetachedNode>) -> Self {
assert!(
self.target().is_none(),
"Cannot add children to a redirected node"
);
let node = argument.into();
assert!(
!matches!(node, DetachedNode::Command(_)),
"Cannot add a CommandDetachedNode as a child of a builder"
);
self.common.arguments.insert(node.name(), node);
self
}
fn command(&self) -> Option<Command> {
self.common.command.clone()
}
fn executes_arc(mut self, command: Arc<dyn CommandExecutor + 'static>) -> Self {
self.common.command = Some(command);
self
}
fn requires(mut self, requirement: impl Into<Requirement>) -> Self {
self.common.requirements.0.push(requirement.into());
self
}
fn overwrite_requirements(mut self, requirements: Requirements) -> Self {
self.common.requirements = requirements;
self
}
fn redirect(self, redirection: impl Into<Redirection>) -> Self {
self.forward(redirection.into(), RedirectModifier::KeepSource, false)
}
fn redirect_with_modifier(self, redirection: impl Into<Redirection>, redirect_modifier: RedirectModifier) -> Self {
self.forward(redirection.into(), redirect_modifier, false)
}
fn fork(self, redirection: impl Into<Redirection>, redirect_modifier: RedirectModifier) -> Self {
self.forward(redirection.into(), redirect_modifier, true)
}
fn forward(mut self, redirection: impl Into<Redirection>, redirect_modifier: RedirectModifier, fork: bool) -> Self {
assert!(self.common.arguments.is_empty(), "Cannot forward a node with children. The node must have no children to redirect somewhere else");
self.common.target = Some(redirection.into());
self.common.modifier = redirect_modifier;
self.common.forks = fork;
self
}
fn arguments(&self) -> &FxHashMap<String, DetachedNode> {
&self.common.arguments
}
fn target(&self) -> Option<Redirection> {
self.common.target.clone()
}
fn redirect_modifier(&self) -> RedirectModifier {
self.common.modifier.clone()
}
fn forks(&self) -> bool {
self.common.forks
}
fn id(&self) -> GlobalNodeId {
self.common.global_id
}
};
}
/// Helper macro to generate `From` impl blocks for each builder.
macro_rules! impl_builder_from_impls {
($builder: ty => $detached_node: ty) => {
impl From<$builder> for $detached_node {
fn from(value: $builder) -> Self {
value.build()
}
}
impl From<$builder> for DetachedNode {
fn from(value: $builder) -> Self {
value.build().into()
}
}
};
}
impl_builder_from_impls!(LiteralArgumentBuilder => LiteralDetachedNode);
impl_builder_from_impls!(CommandArgumentBuilder => CommandDetachedNode);
impl_builder_from_impls!(RequiredArgumentBuilder => ArgumentDetachedNode);
impl LiteralArgumentBuilder {
/// Creates a new [`LiteralArgumentBuilder`] from a literal.
pub fn new(literal: impl Into<Cow<'static, str>>) -> Self {
Self {
common: CommonArgumentBuilder::new(),
literal: literal.into(),
}
}
}
impl CommandArgumentBuilder {
/// Creates a new [`CommandArgumentBuilder`] from a literal and a command description.
pub fn new(
literal: impl Into<Cow<'static, str>>,
description: impl Into<Cow<'static, str>>,
) -> Self {
Self {
common: CommonArgumentBuilder::new(),
literal: literal.into(),
description: description.into(),
source: None,
}
}
/// Sets the source (e.g. plugin name) that registered this command.
#[must_use]
pub fn with_source(mut self, source: impl Into<String>) -> Self {
self.source = Some(source.into());
self
}
fn build(self) -> Self::Node;
}
impl RequiredArgumentBuilder {
/// Creates a new [`RequiredArgumentBuilder`] from a name and an argument type.
pub fn new(
name: impl Into<Cow<'static, str>>,
arg_type: impl AnyArgumentType + 'static,
) -> Self {
Self {
common: CommonArgumentBuilder::new(),
name: name.into(),
argument_type: Arc::new(arg_type),
suggestion_provider: None,
}
}
/// Sets the [`SuggestionProvider`] of this builder for the `ArgumentDetachedNode`.
#[must_use]
pub fn suggests(self, provider: impl SuggestionProvider + 'static) -> Self {
self.suggests_arc(Arc::new(provider))
}
/// Sets the [`SuggestionProvider`] of this builder for the `ArgumentDetachedNode`.
#[must_use]
pub fn suggests_arc(mut self, provider: Arc<dyn SuggestionProvider>) -> Self {
self.suggestion_provider = Some(provider);
self
Self(self.0.suggests(SuggestionProviderAdapter(provider)))
}
}
impl ArgumentBuilder<LiteralDetachedNode> for LiteralArgumentBuilder {
impl_boilerplate_argument_builder!();
impl ArgumentBuilder for CommandArgumentBuilder {
type Node = pumpkin_command::node::detached::CommandDetachedNode<CommandSource>;
fn build(self) -> LiteralDetachedNode {
let mut node = LiteralDetachedNode::new(
self.common.global_id,
self.literal,
self.common.command,
self.common.requirements,
self.common.target,
self.common.modifier,
self.common.forks,
);
node.children = self.common.arguments;
node
fn then(self, child: impl Into<DetachedNode>) -> Self {
Self(self.0.then(child.into()))
}
fn command(&self) -> Option<Command> {
self.0.command()
}
fn executes(self, command: impl CommandExecutor + 'static) -> Self {
Self(self.0.executes(CommandExecutorAdapter(command)))
}
fn executes_arc(self, command: Arc<dyn CommandExecutor + 'static>) -> Self {
Self(
self.0
.executes_arc(Arc::new(ArcCommandExecutorAdapter(command))),
)
}
fn redirect(self, redirection: impl Into<Redirection>) -> Self {
Self(self.0.redirect(redirection))
}
fn redirect_with_modifier(
self,
redirection: impl Into<Redirection>,
redirect_modifier: RedirectModifier,
) -> Self {
Self(
self.0
.redirect_with_modifier(redirection, redirect_modifier),
)
}
fn fork(
self,
redirection: impl Into<Redirection>,
redirect_modifier: RedirectModifier,
) -> Self {
Self(self.0.fork(redirection, redirect_modifier))
}
fn requires(self, requirement: impl Into<Requirement>) -> Self {
Self(self.0.requires(requirement))
}
fn build(self) -> Self::Node {
self.0.build()
}
}
impl ArgumentBuilder<CommandDetachedNode> for CommandArgumentBuilder {
impl_boilerplate_argument_builder!();
impl ArgumentBuilder for LiteralArgumentBuilder {
type Node = pumpkin_command::node::detached::LiteralDetachedNode<CommandSource>;
fn build(self) -> CommandDetachedNode {
let mut node = CommandDetachedNode::new(
self.common.global_id,
self.literal,
self.description,
self.common.command,
self.common.requirements,
self.common.target,
self.common.modifier,
self.common.forks,
);
node.meta.source = self.source;
node.children = self.common.arguments;
node
fn then(self, child: impl Into<DetachedNode>) -> Self {
Self(self.0.then(child.into()))
}
fn command(&self) -> Option<Command> {
self.0.command()
}
fn executes(self, command: impl CommandExecutor + 'static) -> Self {
Self(self.0.executes(CommandExecutorAdapter(command)))
}
fn executes_arc(self, command: Arc<dyn CommandExecutor + 'static>) -> Self {
Self(
self.0
.executes_arc(Arc::new(ArcCommandExecutorAdapter(command))),
)
}
fn redirect(self, redirection: impl Into<Redirection>) -> Self {
Self(self.0.redirect(redirection))
}
fn redirect_with_modifier(
self,
redirection: impl Into<Redirection>,
redirect_modifier: RedirectModifier,
) -> Self {
Self(
self.0
.redirect_with_modifier(redirection, redirect_modifier),
)
}
fn fork(
self,
redirection: impl Into<Redirection>,
redirect_modifier: RedirectModifier,
) -> Self {
Self(self.0.fork(redirection, redirect_modifier))
}
fn requires(self, requirement: impl Into<Requirement>) -> Self {
Self(self.0.requires(requirement))
}
fn build(self) -> Self::Node {
self.0.build()
}
}
impl ArgumentBuilder<ArgumentDetachedNode> for RequiredArgumentBuilder {
impl_boilerplate_argument_builder!();
impl ArgumentBuilder for RequiredArgumentBuilder {
type Node = pumpkin_command::node::detached::ArgumentDetachedNode<CommandSource>;
fn build(self) -> ArgumentDetachedNode {
let mut node = ArgumentDetachedNode::new(
self.common.global_id,
self.name,
self.argument_type,
self.common.command,
self.common.requirements,
self.common.target,
self.common.modifier,
self.common.forks,
self.suggestion_provider,
);
node.children = self.common.arguments;
node
fn then(self, child: impl Into<DetachedNode>) -> Self {
Self(self.0.then(child.into()))
}
fn command(&self) -> Option<Command> {
self.0.command()
}
fn executes(self, command: impl CommandExecutor + 'static) -> Self {
Self(self.0.executes(CommandExecutorAdapter(command)))
}
fn executes_arc(self, command: Arc<dyn CommandExecutor + 'static>) -> Self {
Self(
self.0
.executes_arc(Arc::new(ArcCommandExecutorAdapter(command))),
)
}
fn redirect(self, redirection: impl Into<Redirection>) -> Self {
Self(self.0.redirect(redirection))
}
fn redirect_with_modifier(
self,
redirection: impl Into<Redirection>,
redirect_modifier: RedirectModifier,
) -> Self {
Self(
self.0
.redirect_with_modifier(redirection, redirect_modifier),
)
}
fn fork(
self,
redirection: impl Into<Redirection>,
redirect_modifier: RedirectModifier,
) -> Self {
Self(self.0.fork(redirection, redirect_modifier))
}
fn requires(self, requirement: impl Into<Requirement>) -> Self {
Self(self.0.requires(requirement))
}
fn build(self) -> Self::Node {
self.0.build()
}
}
#[cfg(test)]
mod test {
use crate::command::argument_builder::{ArgumentBuilder, argument, command, literal};
use crate::command::argument_types::core::double::DoubleArgumentType;
use crate::command::argument_types::core::integer::IntegerArgumentType;
use crate::command::argument_types::core::string::StringArgumentType;
use crate::command::errors::error_types;
use crate::command::node::Redirection;
use crate::command::node::attached::AttachedNode;
use crate::command::node::tree::Tree;
use crate::command::string_reader::StringReader;
#[test]
fn literal_one() {
let builder = literal("test");
let node = builder.build();
assert_eq!(node.meta.literal, "test");
}
#[test]
fn required_one() {
let builder = argument("test", IntegerArgumentType::new(1, 10));
let node = builder.build();
assert_eq!(node.meta.name, "test");
let mut reader1 = StringReader::new("5");
let mut reader2 = StringReader::new("11");
let boxed_result = node
.meta
.argument_type
.parse(&mut reader1)
.expect("The parsing should not have errored");
let result = boxed_result
.downcast::<i32>()
.expect("Downcasting shouldn't have failed");
assert_eq!(result, Box::new(5));
let error = node
.meta
.argument_type
.parse(&mut reader2)
.expect_err("The parsing should have errored as 11 is outside the range");
assert!(error.is(&error_types::INTEGER_TOO_HIGH));
}
#[test]
fn literal_multiple() {
let mut builder = command("letter", "A test command");
for letter in 'a'..='z' {
// Add a node per letter for the argument.
let letter_string = letter.to_string();
builder = builder.then(literal(letter_string));
}
let node = builder.build();
assert_eq!(node.children.len(), 26);
}
#[test]
fn required_multiple() {
let builder = command("test", "A test command")
.then(argument("number", DoubleArgumentType::any()))
.then(argument("word", StringArgumentType::SingleWord));
let node = builder.build();
assert_eq!(node.children.len(), 2);
}
#[test]
fn redirect() {
let builder = command("test", "A test command").redirect(Redirection::Root);
let mut tree = Tree::new();
let node_id = tree.add_child_to_root(builder);
let node = &tree[node_id];
let redirect = node
.redirect
.expect("Redirection should exist as it was added before");
let target_id = tree
.resolve(redirect)
.expect("Target should have been resolved properly");
let target = &tree[target_id];
assert!(matches!(target, AttachedNode::Root(_)));
}
#[test]
#[should_panic = "Cannot forward a node with children. The node must have no children to redirect somewhere else"]
fn redirect_after_child() {
let _ = command("test", "A test command")
.then(literal("child"))
.redirect(Redirection::Root);
}
#[test]
#[should_panic = "Cannot add children to a redirected node"]
fn redirect_before_child() {
let _ = command("test", "A test command")
.redirect(Redirection::Root)
.then(literal("child"));
}
#[test]
#[should_panic = "Cannot add a CommandDetachedNode as a child of a builder"]
fn add_command_as_child() {
let _ = command("foo", "A test command").then(command("bar", "Another test command"));
impl From<CommandArgumentBuilder>
for pumpkin_command::node::detached::CommandDetachedNode<CommandSource>
{
fn from(b: CommandArgumentBuilder) -> Self {
b.0.build()
}
}
impl From<LiteralArgumentBuilder> for DetachedNode {
fn from(b: LiteralArgumentBuilder) -> Self {
Self::Literal(b.0.build())
}
}
impl From<RequiredArgumentBuilder> for DetachedNode {
fn from(b: RequiredArgumentBuilder) -> Self {
Self::Argument(b.0.build())
}
}
impl From<LiteralArgumentBuilder>
for pumpkin_command::node::detached::LiteralDetachedNode<CommandSource>
{
fn from(b: LiteralArgumentBuilder) -> Self {
b.0.build()
}
}
impl From<RequiredArgumentBuilder>
for pumpkin_command::node::detached::ArgumentDetachedNode<CommandSource>
{
fn from(b: RequiredArgumentBuilder) -> Self {
b.0.build()
}
}

View File

@ -79,7 +79,7 @@ impl EntityArgumentType {
}
}
impl ArgumentType for EntityArgumentType {
impl ArgumentType<CommandSource> for EntityArgumentType {
type Item = EntitySelector;
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
@ -126,7 +126,7 @@ impl EntityArgumentType {
self,
reader: &mut StringReader,
allow_selectors: bool,
) -> Result<<Self as ArgumentType>::Item, CommandSyntaxError> {
) -> Result<<Self as ArgumentType<CommandSource>>::Item, CommandSyntaxError> {
let selector = {
let parser = EntitySelectorParser::new(reader, allow_selectors);
parser.parse_and_consume()?

View File

@ -1,82 +1,13 @@
use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
use crate::command::context::command_context::CommandContext;
use crate::command::context::command_source::CommandSource;
use crate::command::errors::command_syntax_error::CommandSyntaxError;
use crate::command::errors::error_types::CommandErrorType;
use crate::command::string_reader::StringReader;
use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
use crate::entity::Entity;
use pumpkin_data::translation;
pub use pumpkin_command::argument_types::entity_anchor::*;
use pumpkin_util::math::vector3::Vector3;
use pumpkin_util::text::TextComponent;
pub const INVALID_ERROR_TYPE: CommandErrorType<1> = CommandErrorType::new(
translation::java::ARGUMENT_ANCHOR_INVALID,
translation::java::ARGUMENT_ANCHOR_INVALID,
);
pub struct EntityAnchorArgumentType;
impl ArgumentType for EntityAnchorArgumentType {
type Item = EntityAnchor;
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
let i = reader.cursor();
let anchor = reader.read_unquoted_string();
EntityAnchor::from_id(anchor.as_str()).map_or_else(
|| {
reader.set_cursor(i);
Err(INVALID_ERROR_TYPE.create(reader, TextComponent::text(anchor)))
},
Ok,
)
}
fn client_side_parser(&'_ self) -> JavaClientArgumentType {
JavaClientArgumentType::EntityAnchor
}
fn list_suggestions(
&self,
_context: &CommandContext,
builder: SuggestionsBuilder,
) -> Suggestions {
builder.filter_and_suggest(&["eyes", "feet"]).build()
}
fn examples(&self) -> Vec<String> {
examples!("eyes", "feet")
}
pub trait EntityAnchorExt {
fn transform_position(self, position: Vector3<f64>, entity: &Entity) -> Vector3<f64>;
fn position_at_entity(self, entity: &Entity) -> Vector3<f64>;
}
impl_copy_get!(EntityAnchorArgumentType, EntityAnchor);
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum EntityAnchor {
Feet,
Eyes,
}
impl EntityAnchor {
/// Gets the [`EntityAnchor`] whose identity is the ID provided.
#[must_use]
pub fn from_id(id: &str) -> Option<Self> {
match id {
"feet" => Some(Self::Feet),
"eyes" => Some(Self::Eyes),
_ => None,
}
}
/// Gets the ID of this [`EntityAnchor`]
#[must_use]
pub const fn id(self) -> &'static str {
match self {
Self::Feet => "feet",
Self::Eyes => "eyes",
}
}
impl EntityAnchorExt for EntityAnchor {
fn transform_position(self, position: Vector3<f64>, entity: &Entity) -> Vector3<f64> {
match self {
Self::Feet => position,
@ -84,18 +15,7 @@ impl EntityAnchor {
}
}
/// Gets the position of an entity with respect to this anchor.
pub fn position_at_entity(self, entity: &Entity) -> Vector3<f64> {
fn position_at_entity(self, entity: &Entity) -> Vector3<f64> {
self.transform_position(entity.pos.load(), entity)
}
/// Gets the position of a source with respect to this anchor.
#[must_use]
pub fn position_at_source(self, command_source: &CommandSource) -> Vector3<f64> {
let pos = command_source.position;
command_source
.entity
.as_ref()
.map_or_else(|| pos, |e| self.position_at_entity(e.get_entity()))
}
}

View File

@ -228,7 +228,7 @@ impl GameProfileResult {
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct GameProfileArgumentType;
impl ArgumentType for GameProfileArgumentType {
impl ArgumentType<CommandSource> for GameProfileArgumentType {
type Item = GameProfileResult;
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
@ -256,7 +256,7 @@ impl GameProfileArgumentType {
fn parse_with_allow_selectors(
reader: &mut StringReader,
allow_selectors: bool,
) -> Result<<Self as ArgumentType>::Item, CommandSyntaxError> {
) -> Result<<Self as ArgumentType<CommandSource>>::Item, CommandSyntaxError> {
if reader.peek() == Some('@') {
// We read a selector variable.
let parser = EntitySelectorParser::new(reader, allow_selectors);

View File

@ -1,16 +1,6 @@
use crate::command::errors::command_syntax_error::CommandSyntaxError;
use crate::command::errors::error_types::{
CommandErrorType, READER_INVALID_DOUBLE, READER_INVALID_FLOAT, READER_INVALID_INT,
};
use crate::command::string_reader::StringReader;
use pumpkin_data::translation;
use pumpkin_util::identifier::Identifier;
use pumpkin_util::math::bounds::{Bounds, DoubleBounds, FloatDegreeBounds, IntBounds};
use pumpkin_util::text::TextComponent;
use std::str::FromStr;
/// Creates a [`Vec<String>`] of examples from
/// the given string literals.
#[macro_export]
macro_rules! examples {
( $( $example:literal ),* ) => {
vec! [
@ -19,235 +9,12 @@ macro_rules! examples {
};
}
// Helper methods for assertion with a `StringReader`:
pub use pumpkin_command::argument_types::*;
/// Asserts that the result read by `reader` with the argument
/// type `$argument_type` used to parse is equal to `Ok($value)`.
/// Also resets the reader's cursor back to the start.
#[cfg(test)]
macro_rules! assert_parse_ok_reset {
($reader: expr, $argument_type: expr, $value: expr) => {{
assert_eq!($argument_type.parse(&mut $reader), Ok($value));
$reader.set_cursor(0)
}};
($reader: expr, $argument_type: expr) => {{
assert!($argument_type.parse(&mut $reader).is_ok());
$reader.set_cursor(0)
}};
}
/// Asserts that the result read by `reader` with the argument
/// type `$argument_type` used to parse is an `Err` containing the type of error as `$error_type`.
/// Also resets the reader's cursor back to the start.
#[cfg(test)]
macro_rules! assert_parse_err_reset {
($reader: expr, $argument_type: expr, $error_type: expr) => {
let error_type_dyn: &'static dyn crate::command::errors::error_types::AnyCommandErrorType =
$error_type;
assert_eq!(
$argument_type.parse(&mut $reader).map_err(|e| e.error_type),
Err(error_type_dyn)
);
$reader.set_cursor(0)
};
}
/// 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::java::ARGUMENT_RANGE_EMPTY,
translation::java::ARGUMENT_RANGE_EMPTY,
);
const SWAPPED_BOUNDS_ERROR_TYPE: CommandErrorType<0> = CommandErrorType::new(
translation::java::ARGUMENT_RANGE_SWAPPED,
translation::java::ARGUMENT_RANGE_SWAPPED,
);
const INVALID_IDENTIFIER_ERROR_TYPE: CommandErrorType<0> = CommandErrorType::new(
translation::java::ARGUMENT_ID_INVALID,
translation::java::ARGUMENT_ID_INVALID,
);
/// A trait to try getting a value from a [`StringReader`].
pub trait FromStringReader: Sized {
/// Tries to parse a value of this type from a [`StringReader`].
///
/// If this fails, a [`CommandSyntaxError`] is returned with the erroring details.
fn from_reader(reader: &mut StringReader) -> Result<Self, CommandSyntaxError>;
}
fn bounds_from_reader<T: Copy + FromStr + PartialOrd>(
reader: &mut StringReader,
error_type: &'static CommandErrorType<1>,
) -> Result<Bounds<T>, CommandSyntaxError> {
if reader.can_read_char() {
let i = reader.cursor();
try_bounds_from_reader(reader, error_type).map_err(|e| {
// On catching any error, set the reader's cursor position to be at where the range starts.
if let Some(mut context) = e.context {
context.cursor = i;
CommandSyntaxError::create(e.error_type, e.message, &context)
} else {
CommandSyntaxError::create_without_context(e.error_type, e.message)
}
})
} else {
Err(EMPTY_BOUNDS_ERROR_TYPE.create(reader))
}
}
fn try_bounds_from_reader<T: Copy + FromStr + PartialOrd>(
reader: &mut StringReader,
error_type: &'static CommandErrorType<1>,
) -> Result<Bounds<T>, CommandSyntaxError> {
let min = read_number_from_reader(reader, error_type)?;
let max = if reader.peek() == Some('.') && reader.peek_with_offset(1) == Some('.') {
reader.skip();
reader.skip();
read_number_from_reader(reader, error_type)?
} else {
min
};
if min.is_none() && max.is_none() {
Err(EMPTY_BOUNDS_ERROR_TYPE.create(reader))
} else {
Ok(Bounds::<T>::new(min, max))
}
}
/// Tries to read a number of a type from a reader.
fn read_number_from_reader<T: FromStr>(
reader: &mut StringReader,
error_type: &'static CommandErrorType<1>,
) -> Result<Option<T>, CommandSyntaxError> {
let i = reader.cursor();
while has_allowed_peaked_character(reader) {
reader.skip();
}
let string = &reader.string()[i..reader.cursor()].to_string();
if string.is_empty() {
Ok(None)
} else {
string.parse::<T>().map_or_else(
|_| Err(error_type.create(reader, TextComponent::text(string.clone()))),
|t| Ok(Some(t)),
)
}
}
fn has_allowed_peaked_character(reader: &StringReader) -> bool {
let c = reader.peek();
if matches!(c, Some('0'..='9' | '-')) {
true
} else {
c.is_some_and(|c| {
c == '.' && (!reader.can_read_chars(2) || reader.peek_with_offset(1) != Some('.'))
})
}
}
impl FromStringReader for IntBounds {
fn from_reader(reader: &mut StringReader) -> Result<Self, CommandSyntaxError> {
let i = reader.cursor();
let bounds = bounds_from_reader(reader, &READER_INVALID_INT)?;
if bounds.are_swapped() {
reader.set_cursor(i);
Err(SWAPPED_BOUNDS_ERROR_TYPE.create(reader))
} else {
Ok(Self::from_bounds(bounds))
}
}
}
impl FromStringReader for DoubleBounds {
fn from_reader(reader: &mut StringReader) -> Result<Self, CommandSyntaxError> {
let i = reader.cursor();
let bounds = bounds_from_reader(reader, &READER_INVALID_DOUBLE)?;
if bounds.are_swapped() {
reader.set_cursor(i);
Err(SWAPPED_BOUNDS_ERROR_TYPE.create(reader))
} else {
Ok(Self::from_bounds(bounds))
}
}
}
impl FromStringReader for FloatDegreeBounds {
fn from_reader(reader: &mut StringReader) -> Result<Self, CommandSyntaxError> {
let i = reader.cursor();
let bounds = bounds_from_reader(reader, &READER_INVALID_FLOAT)?;
if bounds.are_swapped() {
reader.set_cursor(i);
Err(SWAPPED_BOUNDS_ERROR_TYPE.create(reader))
} else {
Ok(Self::from_bounds(bounds))
}
}
}
impl FromStringReader for Identifier {
fn from_reader(reader: &mut StringReader) -> Result<Self, CommandSyntaxError> {
let start = reader.cursor();
while let Some(c) = reader.peek()
&& Self::is_valid_char(c)
{
reader.skip();
}
let raw_identifier = &reader.string()[start..reader.cursor()];
let identifier_result = Self::parse(raw_identifier);
identifier_result.map_or_else(
|_| {
reader.set_cursor(start);
Err(INVALID_IDENTIFIER_ERROR_TYPE.create(reader))
},
Ok,
)
}
}
pub mod argument_type;
pub mod attribute;
pub mod block;
pub mod block_predicate;
pub mod component;
pub mod coordinates;
pub mod core;
pub mod dialog;
pub mod entity;
pub mod entity_anchor;
pub mod entity_selector;
pub mod game_profile;
pub mod gamemode;
pub mod hex_color;
pub mod identifier;
pub mod item;
pub mod item_predicate;
pub mod nbt;
pub mod nbt_path;
pub mod objective;
pub mod particle;
pub mod placed_feature;
pub mod pool;
pub mod range;
pub mod resource;
pub mod resource_key;
pub mod resource_or_tag;
pub mod slot;
pub mod sound_category;
pub mod structure;
pub mod team;
pub mod team_color;
pub mod template;
pub mod time;
pub mod uuid;

View File

@ -1,4 +1,5 @@
use crate::command::{
CommandSource,
argument_types::argument_type::{ArgumentType, JavaClientArgumentType},
context::command_context::CommandContext,
errors::command_syntax_error::CommandSyntaxError,
@ -10,7 +11,7 @@ use crate::command::{
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct ObjectiveArgumentType;
impl ArgumentType for ObjectiveArgumentType {
impl ArgumentType<CommandSource> for ObjectiveArgumentType {
type Item = String;
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {

View File

@ -1,3 +1,4 @@
use crate::command::CommandSource;
use crate::command::argument_types::FromStringReader;
use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
use crate::command::context::command_context::CommandContext;
@ -42,7 +43,7 @@ pub static ERROR_INVALID: CommandErrorType<0> = CommandErrorType::new(
translation::java::ARGUMENT_ID_INVALID,
);
impl ArgumentType for ResourceKeyArgument {
impl ArgumentType<CommandSource> for ResourceKeyArgument {
type Item = ResourceKey;
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {

View File

@ -1,4 +1,5 @@
use crate::command::{
CommandSource,
argument_types::argument_type::{ArgumentType, JavaClientArgumentType},
context::command_context::CommandContext,
errors::command_syntax_error::CommandSyntaxError,
@ -10,7 +11,7 @@ use crate::command::{
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct TeamArgumentType;
impl ArgumentType for TeamArgumentType {
impl ArgumentType<CommandSource> for TeamArgumentType {
type Item = String;
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {

View File

@ -6,6 +6,7 @@ use pumpkin_util::permission::{Permission, PermissionDefault, PermissionRegistry
use pumpkin_util::text::TextComponent;
use pumpkin_util::text::hover::HoverEvent;
use crate::command::CommandSource;
use crate::command::argument_builder::{ArgumentBuilder, argument, command, literal};
use crate::command::argument_types::FromStringReader;
use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
@ -50,7 +51,7 @@ const ERROR_CREATE_FAILED: CommandErrorType<1> = CommandErrorType::new(
#[derive(Clone, Copy)]
pub struct BossbarIdArgumentType;
impl ArgumentType for BossbarIdArgumentType {
impl ArgumentType<CommandSource> for BossbarIdArgumentType {
type Item = String;
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
@ -85,7 +86,7 @@ pub struct BossbarColorArgumentType;
const COLORS: [&str; 7] = ["blue", "green", "pink", "purple", "red", "white", "yellow"];
impl ArgumentType for BossbarColorArgumentType {
impl ArgumentType<CommandSource> for BossbarColorArgumentType {
type Item = BossbarColor;
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
@ -138,7 +139,7 @@ const STYLES: [&str; 5] = [
"progress",
];
impl ArgumentType for BossbarStyleArgumentType {
impl ArgumentType<CommandSource> for BossbarStyleArgumentType {
type Item = BossbarDivisions;
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {

View File

@ -21,7 +21,7 @@ use crate::command::argument_types::coordinates::swizzle::SwizzleArgumentType;
use crate::command::argument_types::coordinates::vec3::Vec3ArgumentType;
use crate::command::argument_types::core::string::StringArgumentType;
use crate::command::argument_types::entity::EntityArgumentType;
use crate::command::argument_types::entity_anchor::EntityAnchorArgumentType;
use crate::command::argument_types::entity_anchor::{EntityAnchorArgumentType, EntityAnchorExt};
use crate::command::argument_types::identifier::IdentifierArgumentType;
use crate::command::argument_types::nbt_path::{NbtPath, NbtPathArgumentType};
use crate::command::argument_types::objective::ObjectiveArgumentType;

View File

@ -1,3 +1,4 @@
use crate::command::CommandSource;
use crate::command::argument_builder::{ArgumentBuilder, argument, command};
use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
use crate::command::argument_types::core::string::StringArgumentType;
@ -35,7 +36,7 @@ enum HelpArgument {
}
struct HelpArgumentType;
impl ArgumentType for HelpArgumentType {
impl ArgumentType<CommandSource> for HelpArgumentType {
type Item = HelpArgument;
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
@ -68,7 +69,10 @@ impl ArgumentType for HelpArgumentType {
// We use greedy phrases for now
// as the `?` command as the argument
// doesn't work for the unquoted word one.
let mut text = StringArgumentType::GreedyPhrase.parse(reader)?;
let mut text = ArgumentType::<CommandSource>::parse(
&StringArgumentType::GreedyPhrase,
reader,
)?;
{
let mut integer_text = text.as_str();

View File

@ -10,7 +10,9 @@ use crate::command::argument_builder::{ArgumentBuilder, argument, command, liter
use crate::command::argument_types::coordinates::rotation::RotationArgumentType;
use crate::command::argument_types::coordinates::vec3::Vec3ArgumentType;
use crate::command::argument_types::entity::EntityArgumentType;
use crate::command::argument_types::entity_anchor::{EntityAnchor, EntityAnchorArgumentType};
use crate::command::argument_types::entity_anchor::{
EntityAnchor, EntityAnchorArgumentType, EntityAnchorExt,
};
use crate::command::context::command_context::CommandContext;
use crate::command::node::dispatcher::CommandDispatcher;
use crate::command::node::{CommandExecutor, CommandExecutorResult};

View File

@ -11,7 +11,9 @@ use crate::command::argument_builder::{ArgumentBuilder, argument, command, liter
use crate::command::argument_types::coordinates::rotation::RotationArgumentType;
use crate::command::argument_types::coordinates::vec3::Vec3ArgumentType;
use crate::command::argument_types::entity::EntityArgumentType;
use crate::command::argument_types::entity_anchor::{EntityAnchor, EntityAnchorArgumentType};
use crate::command::argument_types::entity_anchor::{
EntityAnchor, EntityAnchorArgumentType, EntityAnchorExt,
};
use crate::command::context::command_context::CommandContext;
use crate::command::errors::error_types::CommandErrorType;
use crate::command::node::dispatcher::CommandDispatcher;

View File

@ -11,6 +11,7 @@ use pumpkin_util::text::TextComponent;
use tracing::info;
use crate::command::CommandSender;
use crate::command::CommandSource;
use crate::command::argument_builder::{ArgumentBuilder, argument, command, literal};
use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
use crate::command::argument_types::core::bool::BoolArgumentType;
@ -59,7 +60,7 @@ impl GameTestReporter for CommandTestReporter {
/// matching the behavior of the old command implementation.
struct TestInstanceArgumentType;
impl ArgumentType for TestInstanceArgumentType {
impl ArgumentType<CommandSource> for TestInstanceArgumentType {
type Item = String;
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {

View File

@ -1,12 +1,16 @@
use crate::command::CommandSender;
use crate::command::argument_types::entity_anchor::EntityAnchor;
use crate::command::errors::command_syntax_error::CommandSyntaxError;
use crate::command::errors::error_types::CommandErrorType;
use crate::command::argument_types::entity_anchor::{EntityAnchor, EntityAnchorExt};
use crate::entity::EntityBase;
use crate::entity::player::Player;
use crate::server::Server;
use crate::world::World;
use pumpkin_command::errors::command_syntax_error::CommandSyntaxError;
use pumpkin_command::errors::error_types::CommandErrorType;
pub use pumpkin_command::source::{
ResultValueTaker, ReturnValue, ReturnValueCallable, ReturnValueCallback,
};
use pumpkin_data::translation;
use pumpkin_util::math::position::BlockPos;
use pumpkin_util::math::vector2::Vector2;
use pumpkin_util::math::vector3::Vector3;
use pumpkin_util::math::wrap_degrees;
@ -23,50 +27,6 @@ pub const REQUIRES_ENTITY: CommandErrorType<0> = CommandErrorType::new(
translation::java::PERMISSIONS_REQUIRES_ENTITY,
);
pub trait ReturnValueCallable: Send + Sync {
fn call(&self, value: ReturnValue);
}
pub type ReturnValueCallback = Arc<dyn ReturnValueCallable>;
/// Represents a collection of 'return value callbacks'.
#[derive(Clone)]
pub struct ResultValueTaker(pub Vec<ReturnValueCallback>);
impl ResultValueTaker {
/// Merges two takers, returning one.
#[must_use]
pub fn merge(taker_1: &Self, taker_2: &Self) -> Self {
let mut takers = Vec::with_capacity(taker_1.0.len() + taker_2.0.len());
for taker in &taker_1.0 {
takers.push(taker.clone());
}
for taker in &taker_2.0 {
takers.push(taker.clone());
}
Self(takers)
}
/// Constructs a new, empty result value taker.
#[must_use]
pub fn new() -> Self {
Self(Vec::new())
}
/// Calls all the contained callbacks of this taker with the returned result.
pub fn call(&self, return_value: ReturnValue) {
for callback in &self.0 {
callback.call(return_value);
}
}
}
impl Default for ResultValueTaker {
fn default() -> Self {
Self::new()
}
}
/// Represents a source of a command, which
/// contains its own state, which could keep track of its:
/// - position
@ -505,28 +465,60 @@ impl CommandSource {
}
}
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum ReturnValue {
Success(i32),
Failure,
}
impl pumpkin_command::source::CommandSource for CommandSource {
fn send_message(&self, message: TextComponent) {
self.send_message(message);
}
impl ReturnValue {
/// Get the success value of this return value.
#[must_use]
pub const fn success_value(self) -> bool {
match self {
Self::Success(_) => true,
Self::Failure => false,
fn send_error(&self, error: TextComponent) {
self.send_error(error);
}
fn call_result(&self, result: ReturnValue) {
self.command_result_taker.call(result);
}
fn has_permission(&self, permission: &str) -> bool {
self.has_permission(permission)
}
fn position(&self) -> Vector3<f64> {
self.position
}
fn rotation(&self) -> Vector2<f32> {
self.rotation
}
fn check_block_loaded(&self, pos: &BlockPos) -> Result<(), CommandSyntaxError> {
let world = self.world();
if world
.level
.read_chunk_sync(&pos.chunk_position(), |_| ())
.is_none()
{
Err(
pumpkin_command::argument_types::coordinates::block_pos::NOT_LOADED_ERROR_TYPE
.create_without_context(),
)
} else if !world.is_in_build_limit(*pos) {
Err(
pumpkin_command::argument_types::coordinates::block_pos::OUT_OF_WORLD_ERROR_TYPE
.create_without_context(),
)
} else {
Ok(())
}
}
/// Get the result integral value of this return value.
#[must_use]
pub const fn result_value(self) -> i32 {
match self {
Self::Success(value) => value,
Self::Failure => 0,
}
fn entity_anchor(&self) -> EntityAnchor {
self.entity_anchor
}
fn anchor_position(&self, anchor: EntityAnchor) -> Vector3<f64> {
let pos = self.position;
self.entity
.as_ref()
.map_or_else(|| pos, |e| anchor.position_at_entity(e.get_entity()))
}
}

View File

@ -1,3 +1,11 @@
pub mod command_context;
pub mod command_context {
pub use pumpkin_command::context::command_context::*;
pub type CommandContext<'a> =
pumpkin_command::context::CommandContext<'a, crate::command::CommandSource>;
}
pub mod command_source;
pub mod string_range;
pub mod string_range {
pub use pumpkin_command::context::string_range::*;
}
pub use command_context::CommandContext;
pub use pumpkin_command::context::*;

View File

@ -1 +0,0 @@
pub use crate::command::node::dispatcher::*;

View File

@ -7,7 +7,7 @@ use std::sync::atomic::AtomicBool;
use crate::block::entities::BlockEntity;
use crate::block::entities::command_block::CommandBlockEntity;
use crate::command::context::command_source::CommandSource;
pub use crate::command::context::command_source::CommandSource;
use crate::entity::EntityBase;
use crate::entity::player::Player;
use crate::server::Server;
@ -23,18 +23,69 @@ use pumpkin_util::permission::{PermissionDefault, PermissionLvl};
use pumpkin_util::text::TextComponent;
use pumpkin_util::translation::Locale;
pub use pumpkin_command::*;
pub mod argument_builder;
pub mod argument_types;
pub mod client_suggestions;
pub mod commands;
pub mod context;
pub mod dispatcher;
pub mod errors;
pub mod node;
pub mod parser;
pub mod snbt;
pub mod string_reader;
pub mod suggestion;
pub mod dispatcher {
pub use pumpkin_command::dispatcher::*;
pub type CommandDispatcher =
pumpkin_command::dispatcher::CommandDispatcher<crate::command::CommandSource>;
}
pub mod errors {
pub use pumpkin_command::errors::*;
}
pub mod parser {
pub use pumpkin_command::parser::*;
}
pub mod snbt {
pub use pumpkin_command::snbt::*;
}
pub mod string_reader {
pub use pumpkin_command::string_reader::*;
}
pub mod suggestion {
pub use pumpkin_command::suggestion::*;
pub mod provider {
use crate::command::context::command_context::CommandContext;
use crate::command::context::command_source::CommandSource;
use pumpkin_command::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
pub type SuggestionProviderResult = Suggestions;
pub trait SuggestionProvider: Send + Sync {
fn suggest(
&self,
context: &CommandContext,
builder: SuggestionsBuilder,
) -> SuggestionProviderResult;
}
pub struct SuggestionProviderAdapter<T>(pub T);
impl<T: SuggestionProvider>
pumpkin_command::suggestion::provider::SuggestionProvider<CommandSource>
for SuggestionProviderAdapter<T>
{
fn suggest(
&self,
context: &pumpkin_command::context::command_context::CommandContext<
'_,
CommandSource,
>,
builder: SuggestionsBuilder,
) -> SuggestionProviderResult {
self.0.suggest(context, builder)
}
}
}
}
/// Whether console and RCON command output is broadcast to online operators.
///
@ -397,5 +448,6 @@ const fn command_block_y_rot(facing: Facing) -> f32 {
}
}
pub use context::command_context::CommandContext;
pub use node::dispatcher::CommandDispatcher;
pub use node::{Command, CommandExecutor, CommandExecutorResult};

View File

@ -1,263 +1,78 @@
pub mod attached;
pub mod detached;
pub mod dispatcher;
pub mod tree;
use crate::command::argument_types::argument_type::AnyArgumentType;
use crate::command::context::command_context::CommandContext;
use crate::command::context::command_source::CommandSource;
use crate::command::errors::command_syntax_error::CommandSyntaxError;
use crate::command::node::attached::NodeId;
use crate::command::node::detached::GlobalNodeId;
use crate::command::suggestion::provider::SuggestionProvider;
use std::borrow::Cow;
use std::sync::Arc;
/// Represents a [`CommandExecutor`]'s result.
///
/// If the command **ran successfully**, an [`Ok`] is returned containing an [`i32`].
/// This represents the 'output value' of the command, which is *homologous* to the
/// `int` that command executors in vanilla return **upon success**.
///
/// **You should choose the successful result as `1` if**:
/// - you don't know what value to use for a success for your
/// own commands, or
/// - you don't understand what this value means, or
/// - you just simply don't care about this value at all
///
/// If the command **fails**, an [`Err`] is returned, containing the [`CommandSyntaxError`]
/// that led to this result.
pub type CommandExecutorResult = Result<i32, CommandSyntaxError>;
pub use pumpkin_command::node::*;
pub mod attached {
pub use pumpkin_command::node::attached::*;
pub type AttachedNode =
pumpkin_command::node::attached::AttachedNode<crate::command::CommandSource>;
pub type RootAttachedNode =
pumpkin_command::node::attached::RootAttachedNode<crate::command::CommandSource>;
pub type LiteralAttachedNode =
pumpkin_command::node::attached::LiteralAttachedNode<crate::command::CommandSource>;
pub type CommandAttachedNode =
pumpkin_command::node::attached::CommandAttachedNode<crate::command::CommandSource>;
pub type ArgumentAttachedNode =
pumpkin_command::node::attached::ArgumentAttachedNode<crate::command::CommandSource>;
}
pub mod detached {
pub use pumpkin_command::node::detached::*;
pub type DetachedNode =
pumpkin_command::node::detached::DetachedNode<crate::command::CommandSource>;
pub type LiteralDetachedNode =
pumpkin_command::node::detached::LiteralDetachedNode<crate::command::CommandSource>;
pub type CommandDetachedNode =
pumpkin_command::node::detached::CommandDetachedNode<crate::command::CommandSource>;
pub type ArgumentDetachedNode =
pumpkin_command::node::detached::ArgumentDetachedNode<crate::command::CommandSource>;
}
pub mod tree {
pub use pumpkin_command::node::tree::*;
pub type Tree = pumpkin_command::node::tree::Tree<crate::command::CommandSource>;
}
pub mod dispatcher {
pub use pumpkin_command::dispatcher::*;
pub type CommandDispatcher =
pumpkin_command::dispatcher::CommandDispatcher<crate::command::CommandSource>;
}
pub type Command = pumpkin_command::node::Command<CommandSource>;
pub type Requirement = pumpkin_command::node::Requirement<CommandSource>;
pub type Requirements = pumpkin_command::node::Requirements<CommandSource>;
pub type RedirectModifier = pumpkin_command::node::RedirectModifier<CommandSource>;
pub type RedirectModifierResult = pumpkin_command::node::RedirectModifierResult<CommandSource>;
pub type RedirectModifierExecutor = pumpkin_command::node::RedirectModifierExecutor<CommandSource>;
/// A struct implementing this trait is able to run with a given context.
pub trait CommandExecutor: Sync + Send {
/// Executes this executor for a command.
fn execute(&self, context: &CommandContext) -> CommandExecutorResult;
fn execute(&self, context: &CommandContext) -> pumpkin_command::node::CommandExecutorResult;
}
impl<F> CommandExecutor for F
where
F: Fn(&CommandContext) -> CommandExecutorResult + Send + Sync,
pub struct CommandExecutorAdapter<T>(pub T);
impl<T: CommandExecutor> pumpkin_command::node::CommandExecutor<CommandSource>
for CommandExecutorAdapter<T>
{
fn execute(&self, context: &CommandContext) -> CommandExecutorResult {
self(context)
fn execute(
&self,
context: &pumpkin_command::context::command_context::CommandContext<'_, CommandSource>,
) -> pumpkin_command::node::CommandExecutorResult {
self.0.execute(context)
}
}
/// A function that takes a context and returns a command result.
pub type Command = Arc<dyn CommandExecutor>;
pub struct ArcCommandExecutorAdapter(pub std::sync::Arc<dyn CommandExecutor>);
/// Represents the result of [`Arc<CommandSource>`]s from a [`CommandContext`].
pub type RedirectModifierResult = Result<Vec<Arc<CommandSource>>, CommandSyntaxError>;
/// A function that performs the required modification.
pub type RedirectModifierExecutor = dyn Fn(&CommandContext) -> RedirectModifierResult + Send + Sync;
/// A function that returns a new collection of sources from a given context.
#[derive(Clone)]
pub enum RedirectModifier {
/// Always returns only the source from the given context.
KeepSource,
/// Returns multiple [`CommandSource`]s from one context via
/// custom behavior.
Custom(Arc<RedirectModifierExecutor>),
}
impl RedirectModifier {
/// Tries to provide a [`Vec`] of [`Arc<CommandSource>`] from a
/// given [`CommandContext`].
pub fn sources(&self, command_context: &CommandContext) -> RedirectModifierResult {
match self {
Self::KeepSource => Ok(vec![command_context.source.clone()]),
Self::Custom(function) => function(command_context),
}
}
}
/// Represents the result of a node requirement.
pub type RequirementResult = bool;
/// A predicate that returns if the provided source satisfies it.
#[derive(Clone)]
pub struct Requirement(pub Arc<dyn Fn(&CommandSource) -> RequirementResult + Send + Sync>);
impl Requirement {
/// Evaluates the given condition, returning whether the
/// given [`CommandSource`] satisfies this requirement.
#[must_use]
pub fn evaluate(&self, command_source: &CommandSource) -> RequirementResult {
self.0(command_source)
}
}
impl<F> From<F> for Requirement
where
F: Fn(&CommandSource) -> RequirementResult + Send + Sync + 'static,
{
fn from(value: F) -> Self {
Self(Arc::new(value))
}
}
// Permissions
impl From<String> for Requirement {
fn from(value: String) -> Self {
Self(Arc::new({
let permission = Arc::new(value);
move |source| source.has_permission(&permission)
}))
}
}
impl From<&'static str> for Requirement {
fn from(value: &'static str) -> Self {
Self(Arc::new(move |source| source.has_permission(value)))
}
}
/// A structure that returns if the source is qualified enough to run the command.
#[derive(Clone)]
pub struct Requirements(pub Vec<Requirement>);
impl Requirements {
/// Creates a new `Requirements` with no requirements in it.
/// If used, this will always return `true` when evaluated.
#[must_use]
pub const fn new() -> Self {
Self(Vec::new())
}
/// Evaluates the given condition, returning whether the
/// given [`CommandSource`] satisfies all contained requirements.
#[must_use]
pub fn evaluate(&self, command_source: &CommandSource) -> RequirementResult {
for predicate in &self.0 {
if !predicate.evaluate(command_source) {
return false;
}
}
true
}
}
impl Default for Requirements {
fn default() -> Self {
Self::new()
}
}
/// Stores common owned data for a node.
#[derive(Clone)]
pub struct OwnedNodeData {
pub global_id: GlobalNodeId,
pub requirements: Requirements,
pub modifier: RedirectModifier,
pub forks: bool,
pub command: Option<Command>,
}
/// Represents the extra metadata of a node storing a literal.
#[derive(Clone)]
pub struct LiteralNodeMetadata {
pub literal: Cow<'static, str>,
pub literal_lowercase: String,
}
impl LiteralNodeMetadata {
pub fn new(literal: impl Into<Cow<'static, str>>) -> Self {
let literal = literal.into();
Self {
literal: literal.clone(),
literal_lowercase: literal.to_lowercase(),
}
}
}
/// A special type of [`LiteralNodeMetadata`], containing
/// a description for the command as well.
#[derive(Clone)]
pub struct CommandNodeMetadata {
pub literal: Cow<'static, str>,
pub literal_lowercase: String,
pub description: Cow<'static, str>,
pub source: Option<String>,
}
impl CommandNodeMetadata {
pub fn new(
literal: impl Into<Cow<'static, str>>,
description: impl Into<Cow<'static, str>>,
) -> Self {
let literal = literal.into();
Self {
literal: literal.clone(),
literal_lowercase: literal.to_lowercase(),
description: description.into(),
source: None,
}
}
}
/// Represents the extra metadata of an argument of any type.
#[derive(Clone)]
pub struct ArgumentNodeMetadata {
pub name: Cow<'static, str>,
pub argument_type: Arc<dyn AnyArgumentType>,
pub suggestion_provider: Option<Arc<dyn SuggestionProvider>>,
}
impl ArgumentNodeMetadata {
pub fn new(
name: impl Into<Cow<'static, str>>,
argument_type: Arc<dyn AnyArgumentType>,
suggestion_provider: Option<Arc<dyn SuggestionProvider>>,
) -> Self {
Self {
name: name.into(),
argument_type,
suggestion_provider,
}
}
}
/// Represents the extra metadata for nodes of different types. Can be of the root, a literal, command or an argument.
pub enum NodeMetadata {
/// Metadata of the root node.
Root,
/// Metadata of a literal node that doesn't start a command.
Literal(LiteralNodeMetadata),
/// Metadata of a literal node that starts a command.
Command(CommandNodeMetadata),
/// Metadata of an argument node.
Argument(ArgumentNodeMetadata),
}
/// Stores where this redirection would lead to.
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
pub enum Redirection {
/// Leads to the root of the tree.
Root,
/// Leads to a node in the tree from its tree-local ID.
Local(NodeId),
/// Leads to a node in the tree from its global ID.
Global(GlobalNodeId),
}
impl<T: Into<NodeId>> From<T> for Redirection {
fn from(value: T) -> Self {
Self::Local(value.into())
}
}
impl From<GlobalNodeId> for Redirection {
fn from(value: GlobalNodeId) -> Self {
Self::Global(value)
impl pumpkin_command::node::CommandExecutor<CommandSource> for ArcCommandExecutorAdapter {
fn execute(
&self,
context: &pumpkin_command::context::command_context::CommandContext<'_, CommandSource>,
) -> pumpkin_command::node::CommandExecutorResult {
self.0.execute(context)
}
}

View File

@ -8,7 +8,7 @@ use std::{
net::SocketAddr,
sync::{
Arc,
atomic::{AtomicBool, AtomicU32, Ordering},
atomic::{AtomicBool, AtomicU32, AtomicUsize, Ordering},
},
};
@ -47,7 +47,7 @@ use pumpkin_protocol::{
serial::{PacketRead, PacketReadSlice},
};
use tokio::{
sync::mpsc::{Receiver, Sender},
sync::mpsc::{Receiver, Sender, UnboundedReceiver, UnboundedSender, error::TryRecvError},
sync::{Mutex, RwLock, oneshot},
task::JoinHandle,
};
@ -58,7 +58,10 @@ pub mod login;
use self::nethernet::NetherNetSession;
use crate::{
entity::player::Player,
net::{DisconnectReason, PacketHandlerResult, PacketRateLimiter},
net::{
DisconnectReason, MAX_PENDING_BYTES, PacketHandlerResult, PacketRateLimiter,
decrement_pending_bytes,
},
plugin::api::events::world::chunk_send::ChunkSend,
server::Server,
};
@ -101,12 +104,15 @@ pub struct BedrockClient {
tasks: TaskTracker,
rt_handle: tokio::runtime::Handle,
outgoing_packet_queue_send: Sender<OutgoingPacket>,
outgoing_packet_queue_send: UnboundedSender<OutgoingPacket>,
/// A queue of serialized packets to send to the network
outgoing_packet_queue_recv: Mutex<Option<Receiver<OutgoingPacket>>>,
outgoing_packet_queue_recv: Mutex<Option<UnboundedReceiver<OutgoingPacket>>>,
outgoing_packet_priority_send: Sender<OutgoingPacket>,
outgoing_packet_priority_recv: Mutex<Option<Receiver<OutgoingPacket>>>,
outgoing_packet_priority_send: UnboundedSender<OutgoingPacket>,
outgoing_packet_priority_recv: Mutex<Option<UnboundedReceiver<OutgoingPacket>>>,
/// Tracks total buffered payload bytes in the outgoing queues.
pub pending_bytes: Arc<AtomicUsize>,
/// The packet encoder for outgoing packets.
network_writer: Arc<RwLock<BedrockBatchEncoder>>,
@ -135,8 +141,8 @@ impl BedrockClient {
be_clients: Arc<Mutex<HashMap<SocketAddr, Arc<Self>>>>,
packet_limiter: PacketRateLimiter,
) -> Self {
let (send, recv) = tokio::sync::mpsc::channel(4096);
let (priority_send, priority_recv) = tokio::sync::mpsc::channel(4096);
let (send, recv) = tokio::sync::mpsc::unbounded_channel();
let (priority_send, priority_recv) = tokio::sync::mpsc::unbounded_channel();
let (incoming_send, incoming_recv) = tokio::sync::mpsc::channel(4096);
let rt_handle = tokio::runtime::Handle::current();
Self {
@ -154,6 +160,7 @@ impl BedrockClient {
outgoing_packet_queue_recv: Mutex::new(Some(recv)),
outgoing_packet_priority_send: priority_send,
outgoing_packet_priority_recv: Mutex::new(Some(priority_recv)),
pending_bytes: Arc::new(AtomicUsize::new(0)),
next_form_id: AtomicU32::new(0),
inventory_opened: AtomicBool::new(false),
client_cache_supported: AtomicBool::new(false),
@ -176,6 +183,8 @@ impl BedrockClient {
}
pub fn start_outgoing_packet_task(self: &Arc<Self>) {
const MAX_BATCH_SIZE: usize = 64;
let client = self.clone();
self.spawn_task(async move {
let Some(mut packet_receiver) = client.outgoing_packet_queue_recv.lock().await.take()
@ -189,41 +198,70 @@ impl BedrockClient {
};
let mut interval = tokio::time::interval(std::time::Duration::from_millis(100));
while !client.close_token.is_cancelled() {
let packet = tokio::select! {
loop {
let recv_result = tokio::select! {
biased;
() = client.close_token.cancelled() => break,
res = priority_packet_receiver.recv() => match res {
Some(p) => p,
None => break,
},
res = priority_packet_receiver.recv() => res,
res = packet_receiver.recv() => res,
_ = interval.tick() => {
if !client.tick_connection().await {
break;
}
continue;
}
res = packet_receiver.recv() => match res {
Some(p) => p,
None => break,
},
() = client.close_token.cancelled() => {
priority_packet_receiver
.try_recv()
.ok()
.or_else(|| packet_receiver.try_recv().ok())
}
};
let data = packet.data.strip_prefix(&[BEDROCK_GAME_PACKET]);
let Some(data) = data else {
warn!("Refusing to send a non-game packet over NetherNet");
continue;
let Some(packet) = recv_result else {
break;
};
if let Err(error) = client.session.send(Bytes::copy_from_slice(data)).await {
warn!(
"Failed to send NetherNet packet to {}: {error}",
client.address
);
client.close().await;
let mut packet_batch = Vec::with_capacity(MAX_BATCH_SIZE);
packet_batch.push(packet);
while packet_batch.len() < MAX_BATCH_SIZE {
match priority_packet_receiver.try_recv() {
Ok(packet) => {
packet_batch.push(packet);
continue;
}
Err(TryRecvError::Disconnected | TryRecvError::Empty) => {}
}
match packet_receiver.try_recv() {
Ok(packet) => packet_batch.push(packet),
Err(TryRecvError::Disconnected | TryRecvError::Empty) => break,
}
}
if let Some(completion) = packet.completion {
let _ = completion.send(());
for packet in packet_batch {
let packet_len = packet.data.len();
let data = packet.data.strip_prefix(&[BEDROCK_GAME_PACKET]);
let Some(data) = data else {
warn!("Refusing to send a non-game packet over NetherNet");
decrement_pending_bytes(&client.pending_bytes, packet_len);
continue;
};
if let Err(error) = client.session.send(Bytes::copy_from_slice(data)).await {
warn!(
"Failed to send NetherNet packet to {}: {error}",
client.address
);
decrement_pending_bytes(&client.pending_bytes, packet_len);
client.close().await;
return;
}
decrement_pending_bytes(&client.pending_bytes, packet_len);
if let Some(completion) = packet.completion {
let _ = completion.send(());
}
}
}
});
@ -270,9 +308,14 @@ impl BedrockClient {
}
pub fn try_kick(&self, reason: DisconnectReason, message: String) {
warn!("Closing connection for {}: {message}", self.address);
let packet = CDisconnect::new(reason as i32, message);
if let Ok(data) = self.serialize_packet(&packet) {
self.try_enqueue_packet(data);
let packet_len = data.len();
let _ = self.pending_bytes.fetch_add(packet_len, Ordering::AcqRel);
let _ = self
.outgoing_packet_priority_send
.send(OutgoingPacket::normal(data));
}
if !self.close_token.is_cancelled() {
self.close_token.cancel();
@ -280,6 +323,7 @@ impl BedrockClient {
}
pub async fn kick(&self, reason: DisconnectReason, message: String) {
warn!("Closing connection for {}: {message}", self.address);
self.send_packet(&CDisconnect::new(reason as i32, message))
.await;
self.close().await;
@ -293,6 +337,7 @@ impl BedrockClient {
filtered_message: String,
send_packet: bool,
) {
warn!("Closing connection for {}: {message}", self.address);
if send_packet {
self.send_packet(&CDisconnect {
reason: pumpkin_protocol::codec::var_int::VarInt(reason as i32),
@ -406,8 +451,9 @@ impl BedrockClient {
self.player.store(Arc::new(Some(player)));
}
#[allow(clippy::unused_async)]
pub async fn enqueue_packet(&self, packet_data: Bytes) {
self.enqueue_packet_data(packet_data).await;
self.try_enqueue_packet_data(packet_data);
}
pub fn try_enqueue_packet(&self, packet_data: Bytes) {
@ -420,37 +466,40 @@ impl BedrockClient {
/// # Arguments
///
/// * `packet_data`: A `Bytes` payload representing the encoded packet.
#[allow(clippy::unused_async)]
pub async fn enqueue_packet_data(&self, packet_data: Bytes) {
if let Err(err) = self
.outgoing_packet_queue_send
.send(OutgoingPacket::normal(packet_data))
.await
{
// This is expected to fail if we are closed
if !self.is_closed() {
error!("Failed to add packet to the outgoing packet queue for client: {err}");
}
}
self.try_enqueue_packet_data(packet_data);
}
pub fn try_enqueue_packet_data(&self, packet_data: Bytes) {
if self.is_closed() {
return;
}
let packet_len = packet_data.len();
let prev_bytes = self.pending_bytes.fetch_add(packet_len, Ordering::AcqRel);
let new_bytes = prev_bytes.saturating_add(packet_len);
if new_bytes > MAX_PENDING_BYTES {
decrement_pending_bytes(&self.pending_bytes, packet_len);
if !self.is_closed() {
warn!(
"Bedrock client {} outbound packet buffer overflow ({} bytes > {} bytes). Closing connection.",
self.address, new_bytes, MAX_PENDING_BYTES
);
self.close_token.cancel();
}
return;
}
if let Err(err) = self
.outgoing_packet_queue_send
.try_send(OutgoingPacket::normal(packet_data))
.send(OutgoingPacket::normal(packet_data))
{
match err {
tokio::sync::mpsc::error::TrySendError::Full(_) => {
debug!(
"Failed to add packet to the outgoing packet queue for client: channel full"
);
}
tokio::sync::mpsc::error::TrySendError::Closed(_) => {
if !self.is_closed() {
error!(
"Failed to add packet to the outgoing packet queue for client: channel closed"
);
}
}
decrement_pending_bytes(&self.pending_bytes, packet_len);
// This is expected to fail if we are closed
if !self.is_closed() {
error!("Failed to add packet to the outgoing packet queue for client: {err}");
}
}
}
@ -512,12 +561,32 @@ impl BedrockClient {
}
pub async fn send_game_packet(&self, packet_data: Bytes) {
if self.is_closed() {
return;
}
let packet_len = packet_data.len();
let prev_bytes = self.pending_bytes.fetch_add(packet_len, Ordering::AcqRel);
let new_bytes = prev_bytes.saturating_add(packet_len);
if new_bytes > MAX_PENDING_BYTES {
decrement_pending_bytes(&self.pending_bytes, packet_len);
if !self.is_closed() {
warn!(
"Bedrock client {} outbound packet buffer overflow ({} bytes > {} bytes). Closing connection.",
self.address, new_bytes, MAX_PENDING_BYTES
);
self.close_token.cancel();
}
return;
}
let (tx, rx) = oneshot::channel();
if let Err(err) = self
.outgoing_packet_priority_send
.send(OutgoingPacket::priority(packet_data, tx))
.await
{
decrement_pending_bytes(&self.pending_bytes, packet_len);
if !self.is_closed() {
error!("Failed to add priority packet to the outgoing packet queue: {err}");
}

View File

@ -3,7 +3,7 @@ use pumpkin_protocol::java::client::play::{
};
use pumpkin_world::level::SyncChunk;
use std::net::SocketAddr;
use std::sync::atomic::{AtomicBool, AtomicI32, Ordering};
use std::sync::atomic::{AtomicBool, AtomicI32, AtomicUsize, Ordering};
use std::time::{Instant, SystemTime, UNIX_EPOCH};
use std::{collections::VecDeque, io::Write, sync::Arc};
@ -46,7 +46,7 @@ use tokio::{
sync::oneshot,
};
use tokio::{
sync::mpsc::{Receiver, Sender, error::TryRecvError},
sync::mpsc::{UnboundedReceiver, UnboundedSender, error::TryRecvError},
task::JoinHandle,
};
use tokio_util::sync::CancellationToken;
@ -66,7 +66,8 @@ use pending::PendingConnection;
use crate::entity::player::Player;
use crate::net::{
ClientPlatform, GameProfile, PacketHandlerResult, PacketRateLimiter, PlayerConfig,
ClientPlatform, GameProfile, MAX_PENDING_BYTES, PacketHandlerResult, PacketRateLimiter,
PlayerConfig, decrement_pending_bytes,
};
use crate::plugin::api::events::world::chunk_send::ChunkSend;
use crate::plugin::player::player_custom_payload::PlayerCustomPayloadEvent;
@ -95,13 +96,15 @@ pub struct JavaClient {
/// An notifier that is triggered when this client is closed.
close_token: CancellationToken,
/// A normal-priority queue of serialized packets to send to the network.
outgoing_packet_queue_send: Sender<OutgoingPacket>,
outgoing_packet_queue_send: UnboundedSender<OutgoingPacket>,
/// A normal-priority queue of serialized packets to send to the network.
outgoing_packet_queue_recv: Option<Receiver<OutgoingPacket>>,
outgoing_packet_queue_recv: Option<UnboundedReceiver<OutgoingPacket>>,
/// A high-priority queue of serialized packets to send to the network.
outgoing_packet_priority_send: Sender<OutgoingPacket>,
outgoing_packet_priority_send: UnboundedSender<OutgoingPacket>,
/// A high-priority queue of serialized packets to send to the network.
outgoing_packet_priority_recv: Option<Receiver<OutgoingPacket>>,
outgoing_packet_priority_recv: Option<UnboundedReceiver<OutgoingPacket>>,
/// Tracks total buffered payload bytes in the outgoing queues.
pub pending_bytes: Arc<AtomicUsize>,
/// The packet encoder for outgoing packets.
network_writer: std::sync::Mutex<Option<TCPNetworkEncoder<BufWriter<OwnedWriteHalf>>>>,
/// The packet decoder for incoming packets.
@ -224,8 +227,8 @@ impl JavaClient {
gameprofile: GameProfile,
config: PlayerConfig,
) -> Self {
let (send, recv) = tokio::sync::mpsc::channel(4096);
let (priority_send, priority_recv) = tokio::sync::mpsc::channel(4096);
let (send, recv) = tokio::sync::mpsc::unbounded_channel();
let (priority_send, priority_recv) = tokio::sync::mpsc::unbounded_channel();
Self {
id: pending.id,
@ -241,6 +244,7 @@ impl JavaClient {
outgoing_packet_queue_recv: Some(recv),
outgoing_packet_priority_send: priority_send,
outgoing_packet_priority_recv: Some(priority_recv),
pending_bytes: Arc::new(AtomicUsize::new(0)),
version: pending.version,
network_writer: std::sync::Mutex::new(Some(pending.network_writer)),
network_reader: std::sync::Mutex::new(Some(pending.network_reader)),
@ -480,16 +484,46 @@ impl JavaClient {
}
}
#[allow(clippy::unused_async)]
pub async fn enqueue_packet(&self, packet_data: Bytes) {
self.enqueue_packet_data(packet_data).await;
self.try_enqueue_packet_data(packet_data);
}
#[allow(clippy::unused_async)]
pub async fn enqueue_packet_data(&self, packet_data: Bytes) {
self.try_enqueue_packet_data(packet_data);
}
pub fn try_enqueue_packet(&self, packet_data: Bytes) {
self.try_enqueue_packet_data(packet_data);
}
pub fn try_enqueue_packet_data(&self, packet_data: Bytes) {
if self.close_token.is_cancelled() {
return;
}
let packet_len = packet_data.len();
let prev_bytes = self.pending_bytes.fetch_add(packet_len, Ordering::AcqRel);
let new_bytes = prev_bytes.saturating_add(packet_len);
if new_bytes > MAX_PENDING_BYTES {
decrement_pending_bytes(&self.pending_bytes, packet_len);
if !self.close_token.is_cancelled() {
warn!(
"Client {} outbound packet buffer overflow ({} bytes > {} bytes). Closing connection.",
self.id, new_bytes, MAX_PENDING_BYTES
);
self.close();
}
return;
}
if let Err(err) = self
.outgoing_packet_queue_send
.send(OutgoingPacket::normal(packet_data))
.await
{
decrement_pending_bytes(&self.pending_bytes, packet_len);
// This is expected to fail if we are closed
if !self.close_token.is_cancelled() {
warn!(
@ -503,35 +537,6 @@ impl JavaClient {
}
}
pub fn try_enqueue_packet(&self, packet_data: Bytes) {
self.try_enqueue_packet_data(packet_data);
}
pub fn try_enqueue_packet_data(&self, packet_data: Bytes) {
if let Err(err) = self
.outgoing_packet_queue_send
.try_send(OutgoingPacket::normal(packet_data))
{
match err {
tokio::sync::mpsc::error::TrySendError::Full(_) => {
debug!(
"Failed to add packet to the outgoing packet queue for client {}: channel full",
self.id
);
}
tokio::sync::mpsc::error::TrySendError::Closed(_) => {
if !self.close_token.is_cancelled() {
warn!(
"Failed to add packet to the outgoing packet queue for client {}: channel closed",
self.id
);
self.close();
}
}
}
}
}
pub async fn await_close_interrupt(&self) {
self.close_token.cancelled().await;
}
@ -562,31 +567,34 @@ impl JavaClient {
}
pub fn try_kick(&self, reason: &TextComponent) {
match self.connection_state.load() {
let serialized = match self.connection_state.load() {
ConnectionState::Login => {
let packet = CLoginDisconnect::new(
serde_json::to_string(&reason.0).unwrap_or_else(|_| String::new()),
);
if let Ok(data) = self.serialize_packet(&packet) {
self.try_enqueue_packet(data);
}
self.serialize_packet(&packet).ok()
}
ConnectionState::Config => {
let reason_text = reason.clone().get_text();
let packet = CConfigDisconnect::new(&reason_text);
if let Ok(data) = self.serialize_packet(&packet) {
self.try_enqueue_packet(data);
}
self.serialize_packet(&packet).ok()
}
ConnectionState::Play => {
let packet = CPlayDisconnect::new(reason);
if let Ok(data) = self.serialize_packet(&packet) {
self.try_enqueue_packet(data);
}
self.serialize_packet(&packet).ok()
}
_ => {}
_ => None,
};
if let Some(data) = serialized {
let packet_len = data.len();
let _ = self.pending_bytes.fetch_add(packet_len, Ordering::AcqRel);
let _ = self
.outgoing_packet_priority_send
.send(OutgoingPacket::normal(data));
}
debug!("Closing connection for {}", self.id);
let reason_text = reason.clone().get_text();
warn!("Closing connection for {}: {reason_text}", self.id);
self.close();
}
@ -612,7 +620,8 @@ impl JavaClient {
_ => {}
}
}
debug!("Closing connection for {}", self.id);
let reason_text = reason.clone().get_text();
warn!("Closing connection for {}: {reason_text}", self.id);
self.close();
}
@ -621,13 +630,33 @@ impl JavaClient {
}
pub async fn send_packet_now_data(&self, packet: Bytes) {
if self.close_token.is_cancelled() {
return;
}
let packet_len = packet.len();
let prev_bytes = self.pending_bytes.fetch_add(packet_len, Ordering::AcqRel);
let new_bytes = prev_bytes.saturating_add(packet_len);
if new_bytes > MAX_PENDING_BYTES {
decrement_pending_bytes(&self.pending_bytes, packet_len);
if !self.close_token.is_cancelled() {
warn!(
"Client {} outbound packet buffer overflow ({} bytes > {} bytes). Closing connection.",
self.id, new_bytes, MAX_PENDING_BYTES
);
self.close();
}
return;
}
let (completion_tx, completion_rx) = oneshot::channel();
if let Err(err) = self
.outgoing_packet_priority_send
.send(OutgoingPacket::high_priority(packet, completion_tx))
.await
{
decrement_pending_bytes(&self.pending_bytes, packet_len);
// It is expected that the packet will fail if we are closed
if !self.close_token.is_cancelled() {
warn!(
@ -701,6 +730,7 @@ impl JavaClient {
/// - **Status:** Handles status request and ping packets.
/// - **Login/Transfer:** Handles login and transfer packets.
/// - **Config:** Handles configuration packets.
#[expect(clippy::too_many_lines)]
pub fn start_outgoing_packet_task(&mut self) {
const MAX_BATCH_SIZE: usize = 64;
@ -711,6 +741,7 @@ impl JavaClient {
return;
};
let close_token = self.close_token.clone();
let pending_bytes = self.pending_bytes.clone();
let Some(mut writer) = self
.network_writer
.lock()
@ -721,12 +752,17 @@ impl JavaClient {
};
let id = self.id;
self.spawn_task(async move {
while !close_token.is_cancelled() {
loop {
let recv_result = tokio::select! {
biased;
() = close_token.cancelled() => None,
res = priority_packet_receiver.recv() => res,
res = packet_receiver.recv() => res,
() = close_token.cancelled() => {
priority_packet_receiver
.try_recv()
.ok()
.or_else(|| packet_receiver.try_recv().ok())
}
};
let Some(packet_data) = recv_result else {
@ -796,6 +832,9 @@ impl JavaClient {
send_failed = true;
}
let flushed_bytes: usize = written_packets.iter().map(|p| p.data.len()).sum();
decrement_pending_bytes(&pending_bytes, flushed_bytes);
if send_failed {
// We now need to close the connection to the client since the stream is in an unknown state.
close_token.cancel();

View File

@ -9,7 +9,10 @@ use pumpkin_world::level::SyncChunk;
use std::{
net::SocketAddr,
num::NonZero,
sync::{Arc, atomic::Ordering},
sync::{
Arc,
atomic::{AtomicUsize, Ordering},
},
};
use pumpkin_data::translation;
@ -119,6 +122,17 @@ pub enum PacketHandlerResult {
ReadyToPlay(GameProfile, PlayerConfig),
}
/// Maximum payload bytes that may be queued for a client before it is considered stalled/overflowing and disconnected.
pub const MAX_PENDING_BYTES: usize = 64 * 1024 * 1024; // 64 MB
/// Defensively decrement an atomic pending byte counter without underflowing.
#[inline]
pub fn decrement_pending_bytes(pending_bytes: &AtomicUsize, bytes: usize) {
let _ = pending_bytes.fetch_update(Ordering::Release, Ordering::Relaxed, |val| {
Some(val.saturating_sub(bytes))
});
}
/// This is just a Wrapper for both Java & Bedrock connections
#[expect(clippy::large_enum_variant)]
pub enum ClientPlatform {
@ -176,6 +190,14 @@ impl ClientPlatform {
}
}
#[must_use]
pub fn pending_bytes(&self) -> usize {
match self {
Self::Java(java) => java.pending_bytes.load(Ordering::Relaxed),
Self::Bedrock(bedrock) => bedrock.pending_bytes.load(Ordering::Relaxed),
}
}
pub fn try_enqueue_packet_data(&self, packet_data: Bytes) {
match self {
Self::Java(java) => java.try_enqueue_packet_data(packet_data),
@ -694,4 +716,18 @@ mod tests {
"Name containing DEL (127) should be invalid"
);
}
#[test]
fn decrement_pending_bytes_saturating() {
use super::decrement_pending_bytes;
use std::sync::atomic::{AtomicUsize, Ordering};
let counter = AtomicUsize::new(100);
decrement_pending_bytes(&counter, 40);
assert_eq!(counter.load(Ordering::Relaxed), 60);
// Underflow protection: decrementing more than current value should clamp to 0
decrement_pending_bytes(&counter, 100);
assert_eq!(counter.load(Ordering::Relaxed), 0);
}
}