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

20
Cargo.lock generated
View File

@ -3356,6 +3356,7 @@ dependencies = [
"ordered-float", "ordered-float",
"postcard", "postcard",
"pumpkin-codecs", "pumpkin-codecs",
"pumpkin-command",
"pumpkin-config", "pumpkin-config",
"pumpkin-data", "pumpkin-data",
"pumpkin-gametest", "pumpkin-gametest",
@ -3435,6 +3436,25 @@ dependencies = [
"wit-encoder", "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]] [[package]]
name = "pumpkin-config" name = "pumpkin-config"
version = "0.1.0+26.2-26.45" version = "0.1.0+26.2-26.45"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,11 +1,10 @@
use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType}; use crate::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
use crate::command::argument_types::coordinates::Coordinates; use crate::argument_types::coordinates::Coordinates;
use crate::command::context::command_context::CommandContext; use crate::context::command_context::CommandContext;
use crate::command::errors::command_syntax_error::CommandSyntaxError; use crate::errors::command_syntax_error::CommandSyntaxError;
use crate::command::errors::error_types::CommandErrorType; use crate::errors::error_types::CommandErrorType;
use crate::command::string_reader::StringReader; use crate::string_reader::StringReader;
use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder, TextCoordinates}; use crate::suggestion::suggestions::{Suggestions, SuggestionsBuilder, TextCoordinates};
use crate::world::World;
use pumpkin_data::translation; use pumpkin_data::translation;
use pumpkin_util::math::position::BlockPos; 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. /// a player can spawn.
pub struct BlockPosArgumentType; pub struct BlockPosArgumentType;
impl ArgumentType for BlockPosArgumentType { impl<S: crate::source::CommandSource> ArgumentType<S> for BlockPosArgumentType {
type Item = Coordinates; type Item = Coordinates;
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> { fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
@ -56,7 +55,7 @@ impl ArgumentType for BlockPosArgumentType {
fn list_suggestions( fn list_suggestions(
&self, &self,
_context: &CommandContext, _context: &CommandContext<S>,
builder: SuggestionsBuilder, builder: SuggestionsBuilder,
) -> Suggestions { ) -> Suggestions {
let remainder = builder.remaining(); let remainder = builder.remaining();
@ -68,7 +67,7 @@ impl ArgumentType for BlockPosArgumentType {
}; };
builder.suggest_3d_coordinates(suggested_coordinates, |value| { 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 /// # Returns
/// The loaded `BlockPos` containing the position represented by the parsed argument, wrapped in an `Ok`, /// 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. /// or an `Err` with the appropriate [`CommandSyntaxError`] if it could not be resolved.
pub fn get_block_pos( pub fn get_block_pos<S: crate::source::CommandSource>(
context: &CommandContext, context: &CommandContext<S>,
name: &str, name: &str,
) -> Result<BlockPos, CommandSyntaxError> { ) -> Result<BlockPos, CommandSyntaxError> {
Ok(BlockPos::floored_v( Ok(BlockPos::floored_v(
@ -104,41 +103,13 @@ impl BlockPosArgumentType {
/// # Returns /// # Returns
/// The loaded `BlockPos` containing the position represented by the parsed argument, wrapped in an `Ok`, /// 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. /// or an `Err` with the appropriate [`CommandSyntaxError`] if it could not be resolved.
pub fn get_loaded_block_pos( pub fn get_loaded_block_pos<S: crate::source::CommandSource>(
context: &CommandContext, context: &CommandContext<S>,
name: &str, 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> { ) -> Result<BlockPos, CommandSyntaxError> {
let pos = Self::get_block_pos(context, name)?; let pos = Self::get_block_pos(context, name)?;
if world context.source.check_block_loaded(&pos)?;
.level Ok(pos)
.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)
}
} }
/// Returns a [`CommandContext`]'s parsed coordinate argument in the form of a [`BlockPos`] /// Returns a [`CommandContext`]'s parsed coordinate argument in the form of a [`BlockPos`]
@ -151,15 +122,22 @@ impl BlockPosArgumentType {
/// # Returns /// # Returns
/// The loaded `BlockPos` containing the position represented by the parsed argument, wrapped in an `Ok`, /// 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. /// or an `Err` with the appropriate [`CommandSyntaxError`] if it could not be resolved.
pub fn get_spawnable_pos( pub fn get_spawnable_pos<S: crate::source::CommandSource>(
context: &CommandContext, context: &CommandContext<S>,
name: &str, name: &str,
) -> Result<BlockPos, CommandSyntaxError> { ) -> Result<BlockPos, CommandSyntaxError> {
let pos = Self::get_block_pos(context, name)?; let pos = Self::get_block_pos(context, name)?;
if World::is_valid(pos) { if is_valid_block_pos(pos) {
Ok(pos) Ok(pos)
} else { } else {
Err(OUT_OF_BOUNDS_ERROR_TYPE.create_without_context()) 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::vector2::Vector2;
use pumpkin_util::math::vector3::Vector3; use pumpkin_util::math::vector3::Vector3;
use crate::command::suggestion::suggestions::TextCoordinates; use crate::suggestion::suggestions::TextCoordinates;
use crate::command::{ use crate::{
argument_types::{ argument_types::{
argument_type::{ArgumentType, JavaClientArgumentType}, argument_type::{ArgumentType, JavaClientArgumentType},
coordinates::{Coordinates, WorldCoordinate}, coordinates::{Coordinates, WorldCoordinate},
@ -22,7 +22,7 @@ pub const INCOMPLETE_ERROR_TYPE: CommandErrorType<0> = CommandErrorType::new(
pub struct ColumnPosArgumentType; pub struct ColumnPosArgumentType;
impl ArgumentType for ColumnPosArgumentType { impl<S: crate::source::CommandSource> ArgumentType<S> for ColumnPosArgumentType {
type Item = Coordinates; type Item = Coordinates;
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> { fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
@ -48,7 +48,7 @@ impl ArgumentType for ColumnPosArgumentType {
fn list_suggestions( fn list_suggestions(
&self, &self,
_context: &CommandContext, _context: &CommandContext<S>,
builder: SuggestionsBuilder, builder: SuggestionsBuilder,
) -> Suggestions { ) -> Suggestions {
let remainder = builder.remaining(); let remainder = builder.remaining();
@ -60,7 +60,7 @@ impl ArgumentType for ColumnPosArgumentType {
}; };
builder.suggest_2d_coordinates(suggested_coordinates, |value| { 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 /// # Returns
/// The `ColumnPos` containing the position represented by the parsed argument, wrapped in an `Ok`, /// 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. /// or an `Err` with the appropriate [`CommandSyntaxError`] if it could not be resolved.
pub fn get_column_pos( pub fn get_column_pos<S: crate::source::CommandSource>(
context: &CommandContext, context: &CommandContext<S>,
name: &str, name: &str,
) -> Result<ColumnPos, CommandSyntaxError> { ) -> Result<ColumnPos, CommandSyntaxError> {
let block_pos = BlockPos::floored_v( let block_pos = BlockPos::floored_v(

View File

@ -1,9 +1,7 @@
use crate::command::context::command_source::CommandSource; use crate::errors::command_syntax_error::CommandSyntaxError;
use crate::command::errors::command_syntax_error::CommandSyntaxError; use crate::errors::error_types::{CommandErrorType, READER_EXPECTED_DOUBLE, READER_EXPECTED_INT};
use crate::command::errors::error_types::{ use crate::source::CommandSource;
CommandErrorType, READER_EXPECTED_DOUBLE, READER_EXPECTED_INT, use crate::string_reader::StringReader;
};
use crate::command::string_reader::StringReader;
use pumpkin_data::translation; use pumpkin_data::translation;
use pumpkin_util::math::vector2::Vector2; use pumpkin_util::math::vector2::Vector2;
use pumpkin_util::math::vector3::{Axis, Vector3}; use pumpkin_util::math::vector3::{Axis, Vector3};
@ -179,10 +177,10 @@ impl Coordinates {
/// Returns the physical position that these [`Coordinates`] represent. /// Returns the physical position that these [`Coordinates`] represent.
#[must_use] #[must_use]
pub fn resolve(&self, source: &CommandSource) -> Vector3<f64> { pub fn resolve(&self, source: &impl CommandSource) -> Vector3<f64> {
match self { match self {
Self::World(vector) => { Self::World(vector) => {
let pos = source.position; let pos = source.position();
Vector3::new( Vector3::new(
vector.x.resolve(pos.x), vector.x.resolve(pos.x),
vector.y.resolve(pos.y), vector.y.resolve(pos.y),
@ -190,8 +188,8 @@ impl Coordinates {
) )
} }
Self::Local { left, up, forward } => { Self::Local { left, up, forward } => {
let start = source.entity_anchor.position_at_source(source); let start = source.entity_anchor().position_at_source(source);
convert_local_coordinates(*left, *up, *forward, source.rotation).add(&start) 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 *x*-component of the vector is the **pitch**.
/// - The returned *y*-component of the vector is the **yaw**. /// - The returned *y*-component of the vector is the **yaw**.
#[must_use] #[must_use]
pub const fn rotation(&self, source: &CommandSource) -> Vector2<f32> { pub fn rotation(&self, source: &impl CommandSource) -> Vector2<f32> {
match self { match self {
Self::World(coords) => { Self::World(coords) => {
let rotation = source.rotation; let rotation = source.rotation();
Vector2::new( Vector2::new(
coords.x.resolve(rotation.x as f64) as f32, coords.x.resolve(rotation.x as f64) as f32,
coords.y.resolve(rotation.y 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::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
use crate::command::argument_types::coordinates::{Coordinates, WorldCoordinate}; use crate::argument_types::coordinates::{Coordinates, WorldCoordinate};
use crate::command::context::command_context::CommandContext; use crate::context::command_context::CommandContext;
use crate::command::errors::command_syntax_error::CommandSyntaxError; use crate::errors::command_syntax_error::CommandSyntaxError;
use crate::command::errors::error_types::CommandErrorType; use crate::errors::error_types::CommandErrorType;
use crate::command::string_reader::StringReader; use crate::string_reader::StringReader;
use pumpkin_data::translation; use pumpkin_data::translation;
use pumpkin_util::math::vector3::Vector3; use pumpkin_util::math::vector3::Vector3;
@ -14,7 +14,7 @@ pub const NOT_COMPLETE_ERROR_TYPE: CommandErrorType<0> = CommandErrorType::new(
pub struct RotationArgumentType; pub struct RotationArgumentType;
impl ArgumentType for RotationArgumentType { impl<S: crate::source::CommandSource> ArgumentType<S> for RotationArgumentType {
type Item = Coordinates; type Item = Coordinates;
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> { 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 *x*-coordinate of the coordinates is the absolute/relative **pitch**.
/// - The returned *y*-coordinate of the coordinates is the absolute/relative **yaw**. /// - The returned *y*-coordinate of the coordinates is the absolute/relative **yaw**.
/// - The returned *z*-coordinate of the coordinates is always 0. /// - 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() context.get_argument::<Coordinates>(name).copied()
} }
} }

View File

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

View File

@ -1,10 +1,10 @@
use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType}; use crate::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
use crate::command::argument_types::coordinates::{Coordinates, WorldCoordinate}; use crate::argument_types::coordinates::{Coordinates, WorldCoordinate};
use crate::command::context::command_context::CommandContext; use crate::context::command_context::CommandContext;
use crate::command::errors::command_syntax_error::CommandSyntaxError; use crate::errors::command_syntax_error::CommandSyntaxError;
use crate::command::errors::error_types::CommandErrorType; use crate::errors::error_types::CommandErrorType;
use crate::command::string_reader::StringReader; use crate::string_reader::StringReader;
use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder, TextCoordinates}; use crate::suggestion::suggestions::{Suggestions, SuggestionsBuilder, TextCoordinates};
use pumpkin_data::translation; use pumpkin_data::translation;
use pumpkin_util::math::vector2::Vector2; use pumpkin_util::math::vector2::Vector2;
use pumpkin_util::math::vector3::Vector3; 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; type Item = Coordinates;
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> { fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
@ -76,7 +76,7 @@ impl ArgumentType for Vec2ArgumentType {
fn list_suggestions( fn list_suggestions(
&self, &self,
_context: &CommandContext, _context: &CommandContext<S>,
builder: SuggestionsBuilder, builder: SuggestionsBuilder,
) -> Suggestions { ) -> Suggestions {
let remainder = builder.remaining(); let remainder = builder.remaining();
@ -88,21 +88,21 @@ impl ArgumentType for Vec2ArgumentType {
}; };
builder.suggest_2d_coordinates(suggested_coordinates, |value| { 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 { impl Vec2ArgumentType {
pub fn get_coordinates( pub fn get_coordinates<S: crate::source::CommandSource>(
context: &CommandContext, context: &CommandContext<S>,
name: &str, name: &str,
) -> Result<Coordinates, CommandSyntaxError> { ) -> Result<Coordinates, CommandSyntaxError> {
Ok(*context.get_argument(name)?) Ok(*context.get_argument(name)?)
} }
pub fn get_vector2( pub fn get_vector2<S: crate::source::CommandSource>(
context: &CommandContext, context: &CommandContext<S>,
name: &str, name: &str,
) -> Result<Vector2<f64>, CommandSyntaxError> { ) -> Result<Vector2<f64>, CommandSyntaxError> {
let resolved = Self::get_coordinates(context, name)?.resolve(context.source.as_ref()); 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::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
use crate::command::argument_types::coordinates::{Coordinates, MIXED_TYPE_ERROR_TYPE}; use crate::argument_types::coordinates::{Coordinates, MIXED_TYPE_ERROR_TYPE};
use crate::command::context::command_context::CommandContext; use crate::context::command_context::CommandContext;
use crate::command::errors::command_syntax_error::CommandSyntaxError; use crate::errors::command_syntax_error::CommandSyntaxError;
use crate::command::errors::error_types::CommandErrorType; use crate::errors::error_types::CommandErrorType;
use crate::command::string_reader::StringReader; use crate::string_reader::StringReader;
use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder, TextCoordinates}; use crate::suggestion::suggestions::{Suggestions, SuggestionsBuilder, TextCoordinates};
use pumpkin_data::translation; use pumpkin_data::translation;
use pumpkin_util::math::vector3::Vector3; 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; type Item = Coordinates;
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> { fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
@ -79,7 +79,7 @@ impl ArgumentType for Vec3ArgumentType {
fn list_suggestions( fn list_suggestions(
&self, &self,
_context: &CommandContext, _context: &CommandContext<S>,
builder: SuggestionsBuilder, builder: SuggestionsBuilder,
) -> Suggestions { ) -> Suggestions {
let remainder = builder.remaining(); let remainder = builder.remaining();
@ -91,23 +91,23 @@ impl ArgumentType for Vec3ArgumentType {
}; };
builder.suggest_3d_coordinates(suggestioned_coordinates, |value| { 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 { impl Vec3ArgumentType {
/// Returns a [`CommandContext`]'s parsed three-dimensional vector as a set of [`Coordinates`]. /// Returns a [`CommandContext`]'s parsed three-dimensional vector as a set of [`Coordinates`].
pub fn get_coordinates( pub fn get_coordinates<S: crate::source::CommandSource>(
context: &CommandContext, context: &CommandContext<S>,
name: &str, name: &str,
) -> Result<Coordinates, CommandSyntaxError> { ) -> Result<Coordinates, CommandSyntaxError> {
Ok(*context.get_argument(name)?) Ok(*context.get_argument(name)?)
} }
/// Returns a [`CommandContext`]'s parsed three-dimensional vector and resolves it to a [`Vector3`]. /// Returns a [`CommandContext`]'s parsed three-dimensional vector and resolves it to a [`Vector3`].
pub fn get_vector3( pub fn get_vector3<S: crate::source::CommandSource>(
context: &CommandContext, context: &CommandContext<S>,
name: &str, name: &str,
) -> Result<Vector3<f64>, CommandSyntaxError> { ) -> Result<Vector3<f64>, CommandSyntaxError> {
Ok(Self::get_coordinates(context, name)?.resolve(context.source.as_ref())) 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}, argument_types::argument_type::{ArgumentType, JavaClientArgumentType},
errors::command_syntax_error::CommandSyntaxError, errors::command_syntax_error::CommandSyntaxError,
string_reader::StringReader, string_reader::StringReader,
@ -8,7 +8,7 @@ use crate::command::{
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct BoolArgumentType; pub struct BoolArgumentType;
impl ArgumentType for BoolArgumentType { impl<S: crate::source::CommandSource> ArgumentType<S> for BoolArgumentType {
type Item = bool; type Item = bool;
fn parse(&self, reader: &mut StringReader) -> Result<bool, CommandSyntaxError> { fn parse(&self, reader: &mut StringReader) -> Result<bool, CommandSyntaxError> {
@ -28,9 +28,8 @@ impl_copy_get!(BoolArgumentType, bool);
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use crate::command::{ use crate::{
argument_types::{argument_type::ArgumentType, core::bool::BoolArgumentType}, argument_types::core::bool::BoolArgumentType, errors::error_types,
errors::error_types,
string_reader::StringReader, string_reader::StringReader,
}; };

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,7 +1,7 @@
use pumpkin_nbt::compound::NbtCompound; use pumpkin_nbt::compound::NbtCompound;
use pumpkin_util::identifier::Identifier; use pumpkin_util::identifier::Identifier;
use crate::command::{ use crate::{
argument_types::{ argument_types::{
FromStringReader, FromStringReader,
argument_type::{ArgumentType, JavaClientArgumentType}, argument_type::{ArgumentType, JavaClientArgumentType},
@ -20,12 +20,12 @@ pub enum DialogArg {
pub struct DialogArgumentType; pub struct DialogArgumentType;
impl ArgumentType for DialogArgumentType { impl<S: crate::source::CommandSource> ArgumentType<S> for DialogArgumentType {
type Item = DialogArg; type Item = DialogArg;
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> { fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
if reader.peek() == Some('{') { if reader.peek() == Some('{') {
let compound = NbtCompoundArgumentType.parse(reader)?; let compound = ArgumentType::<S>::parse(&NbtCompoundArgumentType, reader)?;
Ok(DialogArg::Nbt(compound)) Ok(DialogArg::Nbt(compound))
} else { } else {
let id = Identifier::from_reader(reader)?; let id = Identifier::from_reader(reader)?;
@ -48,8 +48,8 @@ impl ArgumentType for DialogArgumentType {
impl DialogArgumentType { impl DialogArgumentType {
/// Returns the parsed [`DialogArg`] from the name of the argument. /// Returns the parsed [`DialogArg`] from the name of the argument.
pub fn get<'a>( pub fn get<'a, S: crate::source::CommandSource>(
context: &'a CommandContext, context: &'a CommandContext<S>,
name: &'_ str, name: &'_ str,
) -> Result<&'a DialogArg, CommandSyntaxError> { ) -> Result<&'a DialogArg, CommandSyntaxError> {
context.get_argument(name) 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::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
use crate::command::context::command_context::CommandContext; use crate::context::command_context::CommandContext;
use crate::command::errors::command_syntax_error::CommandSyntaxError; use crate::errors::command_syntax_error::CommandSyntaxError;
use crate::command::errors::error_types::CommandErrorType; use crate::errors::error_types::CommandErrorType;
use crate::command::string_reader::StringReader; use crate::string_reader::StringReader;
use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder}; use crate::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
use pumpkin_data::translation; use pumpkin_data::translation;
use pumpkin_util::GameMode; use pumpkin_util::GameMode;
use pumpkin_util::text::TextComponent; use pumpkin_util::text::TextComponent;
@ -16,7 +16,7 @@ pub const INVALID_ERROR_TYPE: CommandErrorType<1> = CommandErrorType::new(
pub struct GameModeArgumentType; pub struct GameModeArgumentType;
impl ArgumentType for GameModeArgumentType { impl<S: crate::source::CommandSource> ArgumentType<S> for GameModeArgumentType {
type Item = GameMode; type Item = GameMode;
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> { fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
@ -31,7 +31,7 @@ impl ArgumentType for GameModeArgumentType {
fn list_suggestions( fn list_suggestions(
&self, &self,
_context: &CommandContext, _context: &CommandContext<S>,
mut builder: SuggestionsBuilder, mut builder: SuggestionsBuilder,
) -> Suggestions { ) -> Suggestions {
for gamemode in GameMode::VALUES { for gamemode in GameMode::VALUES {

View File

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

View File

@ -1,6 +1,6 @@
use pumpkin_util::identifier::Identifier; use pumpkin_util::identifier::Identifier;
use crate::command::{ use crate::{
argument_types::{ argument_types::{
FromStringReader, FromStringReader,
argument_type::{ArgumentType, JavaClientArgumentType}, argument_type::{ArgumentType, JavaClientArgumentType},
@ -12,7 +12,7 @@ use crate::command::{
/// An argument type that parses a generic [`Identifier`] with a namespace and path. /// An argument type that parses a generic [`Identifier`] with a namespace and path.
pub struct IdentifierArgumentType; pub struct IdentifierArgumentType;
impl ArgumentType for IdentifierArgumentType { impl<S: crate::source::CommandSource> ArgumentType<S> for IdentifierArgumentType {
type Item = Identifier; type Item = Identifier;
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> { fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
@ -29,8 +29,8 @@ impl ArgumentType for IdentifierArgumentType {
} }
impl IdentifierArgumentType { impl IdentifierArgumentType {
pub fn get( pub fn get<S: crate::source::CommandSource>(
context: &crate::command::context::command_context::CommandContext, context: &crate::context::command_context::CommandContext<S>,
name: &str, name: &str,
) -> Result<Identifier, CommandSyntaxError> { ) -> Result<Identifier, CommandSyntaxError> {
Ok(context.get_argument::<Identifier>(name)?.clone()) 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_data::translation;
use pumpkin_util::text::TextComponent; use pumpkin_util::text::TextComponent;
use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType}; use crate::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
use crate::command::context::command_context::CommandContext; use crate::context::command_context::CommandContext;
use crate::command::errors::command_syntax_error::CommandSyntaxError; use crate::errors::command_syntax_error::CommandSyntaxError;
use crate::command::errors::error_types::CommandErrorType; use crate::errors::error_types::CommandErrorType;
use crate::command::snbt::SnbtParser; use crate::snbt::SnbtParser;
use crate::command::string_reader::StringReader; use crate::string_reader::StringReader;
use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder}; use crate::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
pub const ERROR_UNKNOWN_ITEM: CommandErrorType<1> = CommandErrorType::new( pub const ERROR_UNKNOWN_ITEM: CommandErrorType<1> = CommandErrorType::new(
translation::java::ARGUMENT_ITEM_ID_INVALID, translation::java::ARGUMENT_ITEM_ID_INVALID,
@ -20,7 +20,7 @@ pub const ERROR_UNKNOWN_ITEM: CommandErrorType<1> = CommandErrorType::new(
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub struct ItemStackArgumentType; pub struct ItemStackArgumentType;
impl ArgumentType for ItemStackArgumentType { impl<S: crate::source::CommandSource> ArgumentType<S> for ItemStackArgumentType {
type Item = ItemStack; type Item = ItemStack;
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> { fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
@ -100,7 +100,7 @@ impl ArgumentType for ItemStackArgumentType {
fn list_suggestions( fn list_suggestions(
&self, &self,
_context: &CommandContext, _context: &CommandContext<S>,
builder: SuggestionsBuilder, builder: SuggestionsBuilder,
) -> Suggestions { ) -> Suggestions {
builder.build() builder.build()
@ -108,7 +108,10 @@ impl ArgumentType for ItemStackArgumentType {
} }
impl 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() 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_data::translation;
use pumpkin_util::text::TextComponent; use pumpkin_util::text::TextComponent;
use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType}; use crate::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
use crate::command::context::command_context::CommandContext; use crate::context::command_context::CommandContext;
use crate::command::errors::command_syntax_error::CommandSyntaxError; use crate::errors::command_syntax_error::CommandSyntaxError;
use crate::command::errors::error_types::CommandErrorType; use crate::errors::error_types::CommandErrorType;
use crate::command::string_reader::StringReader; use crate::string_reader::StringReader;
use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder}; use crate::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
pub const ERROR_UNKNOWN_ITEM: CommandErrorType<1> = CommandErrorType::new( pub const ERROR_UNKNOWN_ITEM: CommandErrorType<1> = CommandErrorType::new(
translation::java::ARGUMENT_ITEM_ID_INVALID, translation::java::ARGUMENT_ITEM_ID_INVALID,
@ -41,7 +41,7 @@ impl ItemPredicate {
pub struct ItemPredicateArgumentType; pub struct ItemPredicateArgumentType;
impl ArgumentType for ItemPredicateArgumentType { impl<S: crate::source::CommandSource> ArgumentType<S> for ItemPredicateArgumentType {
type Item = ItemPredicate; type Item = ItemPredicate;
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> { fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
@ -131,7 +131,7 @@ impl ArgumentType for ItemPredicateArgumentType {
fn list_suggestions( fn list_suggestions(
&self, &self,
_context: &CommandContext, _context: &CommandContext<S>,
builder: SuggestionsBuilder, builder: SuggestionsBuilder,
) -> Suggestions { ) -> Suggestions {
builder.build() builder.build()
@ -164,7 +164,10 @@ fn skip_components_and_nbt(reader: &mut StringReader) {
} }
impl ItemPredicateArgumentType { 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()) 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 pumpkin_nbt::tag::NbtTag;
use crate::command::{ use crate::{
argument_types::argument_type::{ArgumentType, JavaClientArgumentType}, argument_types::argument_type::{ArgumentType, JavaClientArgumentType},
context::command_context::CommandContext, context::command_context::CommandContext,
errors::{command_syntax_error::CommandSyntaxError, error_types::CommandErrorType}, 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. /// Parses any type of NBT tag from SNBT.
pub struct NbtTagArgumentType; pub struct NbtTagArgumentType;
impl ArgumentType for NbtTagArgumentType { impl<S: crate::source::CommandSource> ArgumentType<S> for NbtTagArgumentType {
type Item = NbtTag; type Item = NbtTag;
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> { fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
@ -28,7 +28,7 @@ impl ArgumentType for NbtTagArgumentType {
fn list_suggestions( fn list_suggestions(
&self, &self,
_context: &CommandContext, _context: &CommandContext<S>,
builder: SuggestionsBuilder, builder: SuggestionsBuilder,
) -> Suggestions { ) -> Suggestions {
SnbtParser::parse_for_suggestions(builder) SnbtParser::parse_for_suggestions(builder)
@ -41,8 +41,8 @@ impl ArgumentType for NbtTagArgumentType {
impl NbtTagArgumentType { impl NbtTagArgumentType {
/// Returns the parsed [`NbtTag`] from the name of the argument. /// Returns the parsed [`NbtTag`] from the name of the argument.
pub fn get<'a>( pub fn get<'a, S: crate::source::CommandSource>(
context: &'a CommandContext, context: &'a CommandContext<S>,
name: &'_ str, name: &'_ str,
) -> Result<&'a NbtTag, CommandSyntaxError> { ) -> Result<&'a NbtTag, CommandSyntaxError> {
context.get_argument(name) context.get_argument(name)
@ -57,7 +57,7 @@ pub const EXPECTED_COMPOUND_ERROR_TYPE: CommandErrorType<0> = CommandErrorType::
/// Parses **only** compound NBT tags from SNBT. /// Parses **only** compound NBT tags from SNBT.
pub struct NbtCompoundArgumentType; pub struct NbtCompoundArgumentType;
impl ArgumentType for NbtCompoundArgumentType { impl<S: crate::source::CommandSource> ArgumentType<S> for NbtCompoundArgumentType {
type Item = NbtCompound; type Item = NbtCompound;
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> { fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
@ -81,8 +81,8 @@ impl ArgumentType for NbtCompoundArgumentType {
impl NbtCompoundArgumentType { impl NbtCompoundArgumentType {
/// Returns the parsed [`NbtCompound`] from the name of the argument. /// Returns the parsed [`NbtCompound`] from the name of the argument.
pub fn get<'a>( pub fn get<'a, S: crate::source::CommandSource>(
context: &'a CommandContext, context: &'a CommandContext<S>,
name: &'_ str, name: &'_ str,
) -> Result<&'a NbtCompound, CommandSyntaxError> { ) -> Result<&'a NbtCompound, CommandSyntaxError> {
context.get_argument(name) context.get_argument(name)

View File

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

View File

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

View File

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

View File

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

View File

@ -1,11 +1,11 @@
use crate::command::argument_types::FromStringReader; use crate::argument_types::FromStringReader;
use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType}; use crate::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
use crate::command::context::command_context::CommandContext; use crate::context::command_context::CommandContext;
use crate::command::errors::command_syntax_error::CommandSyntaxError; use crate::errors::command_syntax_error::CommandSyntaxError;
use crate::command::errors::error_types::{CommandErrorType, DISPATCHER_PARSE_EXCEPTION}; use crate::errors::error_types::{CommandErrorType, DISPATCHER_PARSE_EXCEPTION};
use crate::command::node::attached::AttachedNode; use crate::node::attached::AttachedNode;
use crate::command::string_reader::StringReader; use crate::string_reader::StringReader;
use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder}; use crate::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
use pumpkin_data::entity::EntityType; use pumpkin_data::entity::EntityType;
use pumpkin_data::translation; use pumpkin_data::translation;
use pumpkin_protocol::java::client::play::SuggestionProviders; 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), 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); type Item = &'static (dyn Any + Send + Sync);
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> { fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
@ -139,7 +139,7 @@ impl ArgumentType for ResourceArgument {
fn list_suggestions( fn list_suggestions(
&self, &self,
_context: &CommandContext, _context: &CommandContext<S>,
suggestions_builder: SuggestionsBuilder, suggestions_builder: SuggestionsBuilder,
) -> Suggestions { ) -> Suggestions {
if self.0 == ENTITY_TYPE_REGISTRY { if self.0 == ENTITY_TYPE_REGISTRY {
@ -225,8 +225,8 @@ impl ArgumentType for ResourceArgument {
} }
impl ResourceArgument { impl ResourceArgument {
pub fn get_resource<T: 'static>( pub fn get_resource<T: 'static, S: crate::source::CommandSource>(
context: &CommandContext, context: &CommandContext<S>,
name: &str, name: &str,
registry_key: &Identifier, registry_key: &Identifier,
) -> Result<&'static T, CommandSyntaxError> { ) -> Result<&'static T, CommandSyntaxError> {
@ -291,36 +291,36 @@ impl ResourceArgument {
} }
} }
pub fn get_entity_type( pub fn get_entity_type<S: crate::source::CommandSource>(
context: &CommandContext, context: &CommandContext<S>,
name: &str, name: &str,
) -> Result<&'static EntityType, CommandSyntaxError> { ) -> Result<&'static EntityType, CommandSyntaxError> {
Self::get_resource(context, name, ENTITY_TYPE_REGISTRY) Self::get_resource(context, name, ENTITY_TYPE_REGISTRY)
} }
pub fn get_enchantment( pub fn get_enchantment<S: crate::source::CommandSource>(
context: &CommandContext, context: &CommandContext<S>,
name: &str, name: &str,
) -> Result<&'static pumpkin_data::Enchantment, CommandSyntaxError> { ) -> Result<&'static pumpkin_data::Enchantment, CommandSyntaxError> {
Self::get_resource(context, name, ENCHANTMENT_REGISTRY) Self::get_resource(context, name, ENCHANTMENT_REGISTRY)
} }
pub fn get_mob_effect( pub fn get_mob_effect<S: crate::source::CommandSource>(
context: &CommandContext, context: &CommandContext<S>,
name: &str, name: &str,
) -> Result<&'static pumpkin_data::effect::StatusEffect, CommandSyntaxError> { ) -> Result<&'static pumpkin_data::effect::StatusEffect, CommandSyntaxError> {
Self::get_resource(context, name, MOB_EFFECT_REGISTRY) Self::get_resource(context, name, MOB_EFFECT_REGISTRY)
} }
pub fn get_damage_type( pub fn get_damage_type<S: crate::source::CommandSource>(
context: &CommandContext, context: &CommandContext<S>,
name: &str, name: &str,
) -> Result<&'static pumpkin_data::damage::DamageType, CommandSyntaxError> { ) -> Result<&'static pumpkin_data::damage::DamageType, CommandSyntaxError> {
Self::get_resource(context, name, DAMAGE_TYPE_REGISTRY) Self::get_resource(context, name, DAMAGE_TYPE_REGISTRY)
} }
pub fn get_summonable_entity_type( pub fn get_summonable_entity_type<S: crate::source::CommandSource>(
context: &CommandContext, context: &CommandContext<S>,
name: &str, name: &str,
) -> Result<&'static EntityType, CommandSyntaxError> { ) -> Result<&'static EntityType, CommandSyntaxError> {
let val: &'static EntityType = Self::get_resource(context, name, ENTITY_TYPE_REGISTRY)?; 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::structures::StructureSet;
use pumpkin_data::tag::{self, RegistryKey}; use pumpkin_data::tag::{self, RegistryKey};
use pumpkin_data::translation; use pumpkin_data::translation;
use pumpkin_util::identifier::Identifier; use pumpkin_util::identifier::Identifier;
use pumpkin_util::text::TextComponent; use pumpkin_util::text::TextComponent;
use pumpkin_world::poi::POI_TYPE_NETHER_PORTAL;
use crate::command::argument_types::FromStringReader; use crate::argument_types::FromStringReader;
use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType}; use crate::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
use crate::command::argument_types::resource_key::BIOME_REGISTRY; use crate::context::command_context::CommandContext;
use crate::command::context::command_context::CommandContext; use crate::errors::command_syntax_error::CommandSyntaxError;
use crate::command::errors::command_syntax_error::CommandSyntaxError; use crate::errors::error_types::CommandErrorType;
use crate::command::errors::error_types::CommandErrorType; use crate::string_reader::StringReader;
use crate::command::string_reader::StringReader; use crate::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
use crate::command::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 STRUCTURE_REGISTRY: Identifier = Identifier::vanilla_static("worldgen/structure");
pub static POI_REGISTRY: Identifier = Identifier::vanilla_static("point_of_interest_type"); 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}")), .map(|name| format!("minecraft:{name}")),
) )
.build() .build()
} else if *registry == *BIOME_REGISTRY { } else if *registry == BIOME_REGISTRY {
let biomes = pumpkin_data::biome::Biome::ALL let biomes = pumpkin_data::biome::Biome::ALL
.iter() .iter()
.map(|biome| format!("minecraft:{}", biome.registry_id)); .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 { } else if *registry == POI_REGISTRY {
// There is no generated POI type registry (yet), so offer the types // There is no generated POI type registry (yet), so offer the types
// known from tag data plus the ones the server actually creates. // known from tag data plus the ones the server actually creates.
let mut names: BTreeSet<String> = tag::get_latest_map(RegistryKey::PointOfInterestType) let poi_types = [
.into_iter() "minecraft:armorer",
.flat_map(|map| map.values().flat_map(|tag| tag.0.iter())) "minecraft:butcher",
.map(|name| format!("minecraft:{name}")) "minecraft:cartographer",
.collect(); "minecraft:cleric",
names.insert(POI_TYPE_NETHER_PORTAL.to_string()); "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 builder
.filter_and_suggest_iter( .filter_and_suggest_iter(
names poi_types
.into_iter() .iter()
.map(|&s| s.to_string())
.chain(tag_names(RegistryKey::PointOfInterestType)), .chain(tag_names(RegistryKey::PointOfInterestType)),
) )
.build() .build()
} else { } else {
Suggestions::empty() builder.build()
} }
} }
@ -113,7 +127,7 @@ fn suggest_for_registry(registry: &Identifier, builder: SuggestionsBuilder) -> S
/// the command. /// the command.
pub struct ResourceOrTagKeyArgument(pub Identifier); pub struct ResourceOrTagKeyArgument(pub Identifier);
impl ArgumentType for ResourceOrTagKeyArgument { impl<S: crate::source::CommandSource> ArgumentType<S> for ResourceOrTagKeyArgument {
type Item = ResourceOrTag; type Item = ResourceOrTag;
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> { fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
@ -122,13 +136,13 @@ impl ArgumentType for ResourceOrTagKeyArgument {
fn list_suggestions( fn list_suggestions(
&self, &self,
_context: &CommandContext, _context: &CommandContext<S>,
builder: SuggestionsBuilder, builder: SuggestionsBuilder,
) -> Suggestions { ) -> Suggestions {
suggest_for_registry(&self.0, builder) suggest_for_registry(&self.0, builder)
} }
fn client_side_parser(&'_ self) -> JavaClientArgumentType { fn client_side_parser(&self) -> JavaClientArgumentType {
JavaClientArgumentType::ResourceOrTagKey { JavaClientArgumentType::ResourceOrTagKey {
identifier: self.0.clone(), identifier: self.0.clone(),
} }
@ -148,7 +162,7 @@ impl ArgumentType for ResourceOrTagKeyArgument {
/// generated entry data (such as POI types) are accepted as-is. /// generated entry data (such as POI types) are accepted as-is.
pub struct ResourceOrTagArgument(pub Identifier); pub struct ResourceOrTagArgument(pub Identifier);
impl ArgumentType for ResourceOrTagArgument { impl<S: crate::source::CommandSource> ArgumentType<S> for ResourceOrTagArgument {
type Item = ResourceOrTag; type Item = ResourceOrTag;
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> { fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
@ -156,7 +170,7 @@ impl ArgumentType for ResourceOrTagArgument {
match &value { match &value {
ResourceOrTag::Resource(id) => { ResourceOrTag::Resource(id) => {
if self.0 == *BIOME_REGISTRY if self.0 == BIOME_REGISTRY
&& !(id.is_vanilla() && !(id.is_vanilla()
&& pumpkin_data::biome::Biome::from_name(id.path()).is_some()) && pumpkin_data::biome::Biome::from_name(id.path()).is_some())
{ {
@ -167,7 +181,7 @@ impl ArgumentType for ResourceOrTagArgument {
} }
} }
ResourceOrTag::Tag(id) => { ResourceOrTag::Tag(id) => {
let registry_key = if self.0 == *BIOME_REGISTRY { let registry_key = if self.0 == BIOME_REGISTRY {
Some(RegistryKey::WorldgenBiome) Some(RegistryKey::WorldgenBiome)
} else if self.0 == POI_REGISTRY { } else if self.0 == POI_REGISTRY {
Some(RegistryKey::PointOfInterestType) Some(RegistryKey::PointOfInterestType)
@ -191,7 +205,7 @@ impl ArgumentType for ResourceOrTagArgument {
fn list_suggestions( fn list_suggestions(
&self, &self,
_context: &CommandContext, _context: &CommandContext<S>,
builder: SuggestionsBuilder, builder: SuggestionsBuilder,
) -> Suggestions { ) -> Suggestions {
suggest_for_registry(&self.0, builder) suggest_for_registry(&self.0, builder)

View File

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

View File

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

View File

@ -1,15 +1,15 @@
use crate::command::argument_types::FromStringReader; use crate::argument_types::FromStringReader;
use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType}; use crate::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
use crate::command::context::command_context::CommandContext; use crate::context::command_context::CommandContext;
use crate::command::errors::command_syntax_error::CommandSyntaxError; use crate::errors::command_syntax_error::CommandSyntaxError;
use crate::command::string_reader::StringReader; use crate::string_reader::StringReader;
use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder}; use crate::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
use pumpkin_protocol::java::client::play::SuggestionProviders; use pumpkin_protocol::java::client::play::SuggestionProviders;
use pumpkin_util::identifier::Identifier; use pumpkin_util::identifier::Identifier;
pub struct TemplateNameArgumentType; pub struct TemplateNameArgumentType;
impl ArgumentType for TemplateNameArgumentType { impl<S: crate::source::CommandSource> ArgumentType<S> for TemplateNameArgumentType {
type Item = String; type Item = String;
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> { fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
@ -19,7 +19,7 @@ impl ArgumentType for TemplateNameArgumentType {
fn list_suggestions( fn list_suggestions(
&self, &self,
_context: &CommandContext, _context: &CommandContext<S>,
builder: SuggestionsBuilder, builder: SuggestionsBuilder,
) -> Suggestions { ) -> Suggestions {
let names = pumpkin_world::generation::structure::template::all_template_names(); 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::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
use crate::command::context::command_context::CommandContext; use crate::context::command_context::CommandContext;
use crate::command::errors::command_syntax_error::CommandSyntaxError; use crate::errors::command_syntax_error::CommandSyntaxError;
use crate::command::errors::error_types::CommandErrorType; use crate::errors::error_types::CommandErrorType;
use crate::command::string_reader::StringReader; use crate::string_reader::StringReader;
use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder}; use crate::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
use pumpkin_data::translation; use pumpkin_data::translation;
use pumpkin_util::text::TextComponent; 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; type Item = i32;
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> { fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
@ -74,7 +74,7 @@ impl ArgumentType for TimeArgumentType {
fn list_suggestions( fn list_suggestions(
&self, &self,
_context: &CommandContext, _context: &CommandContext<S>,
suggestions_builder: SuggestionsBuilder, suggestions_builder: SuggestionsBuilder,
) -> Suggestions { ) -> Suggestions {
let mut reader = StringReader::new(suggestions_builder.remaining()); let mut reader = StringReader::new(suggestions_builder.remaining());
@ -95,15 +95,18 @@ impl ArgumentType for TimeArgumentType {
impl TimeArgumentType { impl TimeArgumentType {
/// Returns a [`CommandContext`]'s parsed time argument in the form /// Returns a [`CommandContext`]'s parsed time argument in the form
/// of its duration, in ticks. /// 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)?) Ok(*context.get_argument(name)?)
} }
} }
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use crate::command::{ use crate::{
argument_types::{argument_type::ArgumentType, time, time::TimeArgumentType}, argument_types::{time, time::TimeArgumentType},
string_reader::StringReader, string_reader::StringReader,
}; };

View File

@ -2,7 +2,7 @@ use pumpkin_data::translation::java::ARGUMENT_UUID_INVALID;
use pumpkin_util::uuid::parse_uuid; use pumpkin_util::uuid::parse_uuid;
use uuid::Uuid; use uuid::Uuid;
use crate::command::{ use crate::{
argument_types::argument_type::{ArgumentType, JavaClientArgumentType}, argument_types::argument_type::{ArgumentType, JavaClientArgumentType},
errors::{command_syntax_error::CommandSyntaxError, error_types::CommandErrorType}, errors::{command_syntax_error::CommandSyntaxError, error_types::CommandErrorType},
string_reader::StringReader, string_reader::StringReader,
@ -15,7 +15,7 @@ pub const INVALID_UUID_ERROR_TYPE: CommandErrorType<0> =
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct UuidArgumentType; pub struct UuidArgumentType;
impl ArgumentType for UuidArgumentType { impl<S: crate::source::CommandSource> ArgumentType<S> for UuidArgumentType {
type Item = Uuid; type Item = Uuid;
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> { 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::context::string_range::StringRange;
use crate::command::context::string_range::StringRange; use crate::errors::command_syntax_error::CommandSyntaxError;
use crate::command::errors::command_syntax_error::CommandSyntaxError; use crate::errors::error_types::DISPATCHER_PARSE_EXCEPTION;
use crate::command::errors::error_types::DISPATCHER_PARSE_EXCEPTION; use crate::node::attached::NodeId;
use crate::command::node::attached::NodeId; use crate::node::dispatcher::{CommandDispatcher, ResultConsumer};
use crate::command::node::dispatcher::{CommandDispatcher, ResultConsumer}; use crate::node::tree::Tree;
use crate::command::node::tree::Tree; use crate::node::{Command, RedirectModifier};
use crate::command::node::{Command, RedirectModifier}; use crate::source::{CommandSource, DummySource, ReturnValue};
use crate::server::Server;
use crate::world::World;
use pumpkin_util::text::TextComponent; use pumpkin_util::text::TextComponent;
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use std::any::Any; use std::any::Any;
@ -53,9 +51,9 @@ impl ParsedArgument {
/// Represents the context used when commands are run. /// Represents the context used when commands are run.
#[derive(Clone)] #[derive(Clone)]
pub struct CommandContext<'a> { pub struct CommandContext<'a, S: CommandSource = DummySource> {
/// The source running the commands. /// The source running the commands.
pub source: Arc<CommandSource>, pub source: Arc<S>,
/// The input string ran as the command. /// The input string ran as the command.
pub input: String, pub input: String,
@ -65,7 +63,7 @@ pub struct CommandContext<'a> {
pub arguments: FxHashMap<String, Arc<ParsedArgument>>, pub arguments: FxHashMap<String, Arc<ParsedArgument>>,
/// The tree this context is related to. /// 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. /// The root that this context will use, bound to the tree.
/// Not necessarily the root node of the tree, however. /// Not necessarily the root node of the tree, however.
@ -81,20 +79,28 @@ pub struct CommandContext<'a> {
pub child: Option<Arc<Self>>, pub child: Option<Arc<Self>>,
/// The redirect modifier of this context. /// The redirect modifier of this context.
pub modifier: RedirectModifier, pub modifier: RedirectModifier<S>,
/// Whether this context forks or not. /// Whether this context forks or not.
pub forks: bool, pub forks: bool,
/// The command stored in this context which /// The command stored in this context which
/// is run to get a command result. /// 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. /// Copies this context with the source provided.
#[must_use] #[must_use]
pub fn with_source(&self, source: Arc<CommandSource>) -> Self { pub fn with_source(&self, source: Arc<S>) -> Self {
Self { Self {
source, source,
input: self.input.clone(), input: self.input.clone(),
@ -136,8 +142,8 @@ impl CommandContext<'_> {
/// A simple example that takes two arguments specified from the node /// A simple example that takes two arguments specified from the node
/// and returns their sum as the status output of the `Executor`: /// and returns their sum as the status output of the `Executor`:
/// ``` /// ```
/// use pumpkin::command::context::command_context::CommandContext; /// use pumpkin_command::context::command_context::CommandContext;
/// use pumpkin::command::node::{CommandExecutor, CommandExecutorResult}; /// use pumpkin_command::node::{CommandExecutor, CommandExecutorResult};
/// ///
/// struct Executor; /// struct Executor;
/// impl CommandExecutor for 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. /// Represents a linked chain of [`CommandContext`]s, where the previous links to the next as a child.
#[derive(Clone)] #[derive(Clone)]
pub struct ContextChain<'a> { pub struct ContextChain<'a, S: CommandSource = DummySource> {
/// The modifiers of this context chain. /// The modifiers of this context chain.
modifiers: Vec<Arc<CommandContext<'a>>>, modifiers: Vec<Arc<CommandContext<'a, S>>>,
/// That specific [`CommandContext`] to execute. /// 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. /// Creates a new chain of contexts from a vector of them and one to execute.
/// ///
/// # Panics /// # Panics
/// ///
/// Panics if the `execute` given is non-executable. /// Panics if the `execute` given is non-executable.
#[must_use] #[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!( assert!(
execute.command.is_some(), execute.command.is_some(),
"Expected last command in chain to be executable" "Expected last command in chain to be executable"
@ -212,7 +209,7 @@ impl<'a> ContextChain<'a> {
/// Tries to flatten a [`CommandContext`]. If no command /// Tries to flatten a [`CommandContext`]. If no command
/// is available at the end of chain, [`None`] is returned. /// is available at the end of chain, [`None`] is returned.
#[must_use] #[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 modifiers = Vec::new();
let mut current = Arc::new(root.clone()); let mut current = Arc::new(root.clone());
loop { loop {
@ -231,11 +228,11 @@ impl<'a> ContextChain<'a> {
/// Runs the given modifier with provided details. /// Runs the given modifier with provided details.
pub fn run_modifier( pub fn run_modifier(
modifier: &CommandContext<'a>, modifier: &CommandContext<'a, S>,
source: &Arc<CommandSource>, source: &Arc<S>,
result_consumer: &dyn ResultConsumer, result_consumer: &dyn ResultConsumer<S>,
forked_mode: bool, forked_mode: bool,
) -> Result<Vec<Arc<CommandSource>>, CommandSyntaxError> { ) -> Result<Vec<Arc<S>>, CommandSyntaxError> {
let source_modifier = &modifier.modifier; let source_modifier = &modifier.modifier;
if matches!(source_modifier, RedirectModifier::KeepSource) { if matches!(source_modifier, RedirectModifier::KeepSource) {
@ -261,9 +258,9 @@ impl<'a> ContextChain<'a> {
/// ///
/// Panics if the `executable` provided cannot be executed. /// Panics if the `executable` provided cannot be executed.
pub fn run_executable( pub fn run_executable(
executable: &CommandContext<'a>, executable: &CommandContext<'a, S>,
source: &Arc<CommandSource>, source: &Arc<S>,
result_consumer: &dyn ResultConsumer, result_consumer: &dyn ResultConsumer<S>,
forked_mode: bool, forked_mode: bool,
) -> Result<i32, CommandSyntaxError> { ) -> Result<i32, CommandSyntaxError> {
let context_to_use = executable.with_source(source.clone()); 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. /// Executes all contexts in the chain, returning the ultimate result.
pub fn execute_all( pub fn execute_all(
&self, &self,
source: &Arc<CommandSource>, source: &Arc<S>,
result_consumer: &dyn ResultConsumer, result_consumer: &dyn ResultConsumer<S>,
) -> Result<i32, CommandSyntaxError> { ) -> Result<i32, CommandSyntaxError> {
if self.modifiers.is_empty() { if self.modifiers.is_empty() {
return Self::run_executable(&self.execute, source, result_consumer, false); return Self::run_executable(&self.execute, source, result_consumer, false);
} }
let mut forked_mode = 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 { for modifier in &self.modifiers {
forked_mode |= modifier.forks; forked_mode |= modifier.forks;
@ -338,7 +335,7 @@ impl<'a> ContextChain<'a> {
/// Gets a reference to the top context of this chain. /// Gets a reference to the top context of this chain.
#[must_use] #[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() { if self.modifiers.is_empty() {
&self.execute &self.execute
} else { } else {
@ -347,7 +344,7 @@ impl<'a> ContextChain<'a> {
} }
/// Gets a mutable reference to the top context of this chain. /// 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() { if self.modifiers.is_empty() {
&mut self.execute &mut self.execute
} else { } else {
@ -373,12 +370,12 @@ impl<'a> ContextChain<'a> {
/// ///
/// This builder's lifetime is bound to the dispatcher provided to it. /// This builder's lifetime is bound to the dispatcher provided to it.
#[derive(Clone)] #[derive(Clone)]
pub struct CommandContextBuilder<'a> { pub struct CommandContextBuilder<'a, S: CommandSource = DummySource> {
/// The dispatcher this builder is related to. /// The dispatcher this builder is related to.
pub dispatcher: &'a CommandDispatcher, pub dispatcher: &'a CommandDispatcher<S>,
/// The source running the commands. /// The source running the commands.
pub source: Arc<CommandSource>, pub source: Arc<S>,
/// Arguments which have been parsed and /// Arguments which have been parsed and
/// can be fetched for command execution. /// can be fetched for command execution.
@ -398,24 +395,24 @@ pub struct CommandContextBuilder<'a> {
pub child: Option<Box<Self>>, pub child: Option<Box<Self>>,
/// The redirect modifier of this context. /// The redirect modifier of this context.
pub modifier: RedirectModifier, pub modifier: RedirectModifier<S>,
/// Whether this context forks or not. /// Whether this context forks or not.
pub forks: bool, pub forks: bool,
/// The command stored in this context which /// The command stored in this context which
/// is run to get a command result. /// 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. /// Creates a new [`CommandContextBuilder`] from the properties required to initialize one.
/// ///
/// Note that builder's lifetime is bound to the dispatcher provided to it. /// Note that builder's lifetime is bound to the dispatcher provided to it.
#[must_use] #[must_use]
pub fn new( pub fn new(
dispatcher: &'a CommandDispatcher, dispatcher: &'a CommandDispatcher<S>,
source: Arc<CommandSource>, source: Arc<S>,
root: NodeId, root: NodeId,
start: usize, start: usize,
) -> Self { ) -> Self {
@ -435,7 +432,7 @@ impl<'a> CommandContextBuilder<'a> {
/// Builds the required [`CommandContext`], consuming itself in the process. /// Builds the required [`CommandContext`], consuming itself in the process.
#[must_use] #[must_use]
pub fn build(self, input: &str) -> CommandContext<'a> { pub fn build(self, input: &str) -> CommandContext<'a, S> {
CommandContext { CommandContext {
source: self.source, source: self.source,
input: input.to_string(), input: input.to_string(),
@ -452,7 +449,7 @@ impl<'a> CommandContextBuilder<'a> {
} }
/// Mutates itself with the new source set. /// 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; self.source = source;
} }
@ -462,7 +459,7 @@ impl<'a> CommandContextBuilder<'a> {
} }
/// Mutates itself with the new command set. /// 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; self.command = command;
} }
@ -538,18 +535,16 @@ impl<'a> CommandContextBuilder<'a> {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use crate::command::argument_builder::{ArgumentBuilder, CommandArgumentBuilder}; use crate::argument_builder::{ArgumentBuilder, CommandArgumentBuilder};
use crate::command::context::command_context::{ use crate::context::command_context::{
CommandContext, CommandContextBuilder, ContextChain, ParsedArgument, Stage, CommandContext, CommandContextBuilder, ContextChain, ParsedArgument, Stage,
}; };
use crate::command::context::command_source::CommandSource; use crate::context::string_range::StringRange;
use crate::command::context::string_range::StringRange; use crate::errors::command_syntax_error::CommandSyntaxError;
use crate::command::errors::command_syntax_error::CommandSyntaxError; use crate::node::dispatcher::{CommandDispatcher, EmptyResultConsumer};
use crate::command::node::dispatcher::{CommandDispatcher, EmptyResultConsumer}; use crate::node::tree::ROOT_NODE_ID;
use crate::command::node::tree::ROOT_NODE_ID; use crate::node::{CommandExecutor, CommandExecutorResult, RedirectModifier, Redirection};
use crate::command::node::{ use crate::source::DummySource;
CommandExecutor, CommandExecutorResult, RedirectModifier, Redirection,
};
use pumpkin_util::math::vector3::Vector3; use pumpkin_util::math::vector3::Vector3;
use std::sync::Arc; use std::sync::Arc;
@ -562,12 +557,8 @@ mod test {
// For testing purposes // For testing purposes
fn builder(dispatcher: &'_ CommandDispatcher) -> CommandContextBuilder<'_> { fn builder(dispatcher: &'_ CommandDispatcher) -> CommandContextBuilder<'_> {
let mut builder = CommandContextBuilder::new( let mut builder =
dispatcher, CommandContextBuilder::new(dispatcher, Arc::new(DummySource::dummy()), ROOT_NODE_ID, 0);
Arc::new(CommandSource::dummy()),
ROOT_NODE_ID,
0,
);
let parsed_argument = ParsedArgument::new(StringRange::between(0, 1), Box::new(6789i32)); let parsed_argument = ParsedArgument::new(StringRange::between(0, 1), Box::new(6789i32));
@ -611,7 +602,7 @@ mod test {
dispatcher dispatcher
.register(CommandArgumentBuilder::new("foo", "A test command").executes(TenExecutor)); .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 result = dispatcher.parse_input("foo", &source);
let top_context = result.context.build("foo"); let top_context = result.context.build("foo");
let chain = ContextChain::try_flatten(&top_context) let chain = ContextChain::try_flatten(&top_context)
@ -629,7 +620,7 @@ mod test {
CommandArgumentBuilder::new("bar", "Another test command").redirect(Redirection::Root), 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 result = dispatcher.parse_input("bar foo", &source);
let top_context = result.context.build("bar foo"); let top_context = result.context.build("bar foo");
let chain = ContextChain::try_flatten(&top_context) let chain = ContextChain::try_flatten(&top_context)
@ -644,7 +635,7 @@ mod test {
dispatcher dispatcher
.register(CommandArgumentBuilder::new("foo", "A test command").executes(TenExecutor)); .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 result = dispatcher.parse_input("foo", &source);
let top_context = result.context.build("foo"); let top_context = result.context.build("foo");
let chain = ContextChain::try_flatten(&top_context) let chain = ContextChain::try_flatten(&top_context)
@ -667,7 +658,7 @@ mod test {
.redirect(Redirection::Root), .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 result = dispatcher.parse_input("bar qux foo", &source);
let top_context = result.context.build("bar qux foo"); let top_context = result.context.build("bar qux foo");
let chain = ContextChain::try_flatten(&top_context) let chain = ContextChain::try_flatten(&top_context)
@ -691,7 +682,7 @@ mod test {
let mut dispatcher = CommandDispatcher::new(); let mut dispatcher = CommandDispatcher::new();
dispatcher.register(CommandArgumentBuilder::new("foo", "A test command")); 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 result = dispatcher.parse_input("foo", &source);
let top_context = result.context.build("foo"); let top_context = result.context.build("foo");
@ -716,15 +707,15 @@ mod test {
dispatcher.register( dispatcher.register(
CommandArgumentBuilder::new("bar", "Another test command").redirect_with_modifier( CommandArgumentBuilder::new("bar", "Another test command").redirect_with_modifier(
Redirection::Root, Redirection::Root,
RedirectModifier::Custom(Arc::new(|context| { RedirectModifier::Custom(Arc::new(|context: &CommandContext<DummySource>| {
let mut new_source = context.source.as_ref().clone(); let mut new_source = context.source.as_ref().clone();
new_source.position = Vector3::new(0f64, 10f64, 0f64); new_source.position = Vector3::new(0f64, 10f64, 0f64);
Ok(vec![Arc::new(new_source)]) Ok(vec![Arc::new(new_source)])
})), })),
), ),
); );
let source = Arc::new(CommandSource::dummy()); let source = Arc::new(DummySource::dummy());
let result = dispatcher.parse_input("bar foo", &source); let result = dispatcher.parse_input("bar foo", source.as_ref());
let top_context = result.context.build("bar foo"); let top_context = result.context.build("bar foo");
let chain = ContextChain::try_flatten(&top_context) let chain = ContextChain::try_flatten(&top_context)
.expect("The context should have properly flattened, as it has a command to execute"); .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 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. /// A struct detailing the context of a syntax error, including where it happened.
#[derive(Debug, Clone, PartialEq, Eq)] #[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, translation::bedrock::COMMANDS_GENERIC_SYNTAX,
); );
use crate::command::errors::{ use crate::errors::{
command_syntax_error::{CommandSyntaxError, ContextProvider}, command_syntax_error::{CommandSyntaxError, ContextProvider},
error_types::sealed::Sealed, 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. /// Creates an error without context from itself and from a slice of `N` arguments.
#[must_use] #[must_use]
#[expect(deprecated)]
pub fn create_without_context_args_slice( pub fn create_without_context_args_slice(
&'static self, &'static self,
args: &[TextComponent; N], 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. /// Creates an error with context from itself and from a slice of `N` arguments.
#[expect(deprecated)]
pub fn create_args_slice<C>( pub fn create_args_slice<C>(
&'static self, &'static self,
context_provider: &C, 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)] #[cfg(test)]
mod test { mod test {
use crate::command::errors::error_types::{CommandErrorType, LiteralCommandErrorType}; use crate::errors::error_types::{CommandErrorType, LiteralCommandErrorType};
use crate::command::string_reader::StringReader; use crate::string_reader::StringReader;
use pumpkin_util::text::TextComponent; use pumpkin_util::text::TextComponent;
const TEST_LITERAL_ERROR_TYPE: LiteralCommandErrorType = const TEST_LITERAL_ERROR_TYPE: LiteralCommandErrorType =
@ -382,6 +384,7 @@ mod test {
} }
#[test] #[test]
#[expect(deprecated)]
fn create_translatable_error() { fn create_translatable_error() {
let mut reader = StringReader::new("foo bar"); let mut reader = StringReader::new("foo bar");
reader.set_cursor(4); 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::context::string_range::StringRange;
use crate::command::errors::command_syntax_error::CommandSyntaxError; use crate::errors::command_syntax_error::CommandSyntaxError;
use crate::command::errors::error_types::LITERAL_INCORRECT; use crate::errors::error_types::LITERAL_INCORRECT;
use crate::command::node::detached::GlobalNodeId; use crate::node::detached::GlobalNodeId;
use crate::command::node::tree::ROOT_NODE_ID; use crate::node::tree::ROOT_NODE_ID;
use crate::command::node::{ use crate::node::{
ArgumentNodeMetadata, Command, CommandNodeMetadata, LiteralNodeMetadata, NodeMetadata, ArgumentNodeMetadata, Command, CommandNodeMetadata, LiteralNodeMetadata, NodeMetadata,
OwnedNodeData, RedirectModifier, Redirection, Requirements, 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 pumpkin_util::text::TextComponent;
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use std::num::NonZero; 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`. /// Represents a node which has been attached as the root of a `Tree`.
#[derive(Clone)] #[derive(Clone)]
pub struct RootAttachedNode { pub struct RootAttachedNode<S: CommandSource = DummySource> {
pub owned: OwnedNodeData, pub owned: OwnedNodeData<S>,
pub children: FxHashMap<String, NodeId>, pub children: FxHashMap<String, NodeId>,
} }
impl Default for RootAttachedNode { impl<S: CommandSource> Default for RootAttachedNode<S> {
fn default() -> Self { fn default() -> Self {
Self::new() Self::new()
} }
} }
impl RootAttachedNode { impl<S: CommandSource> RootAttachedNode<S> {
#[must_use] #[must_use]
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
@ -100,8 +101,8 @@ impl RootAttachedNode {
/// Represents a literal, non-command node that has already been attached /// Represents a literal, non-command node that has already been attached
/// to a `Tree`. /// to a `Tree`.
#[derive(Clone)] #[derive(Clone)]
pub struct LiteralAttachedNode { pub struct LiteralAttachedNode<S: CommandSource = DummySource> {
pub owned: OwnedNodeData, pub owned: OwnedNodeData<S>,
pub children: FxHashMap<String, NodeId>, pub children: FxHashMap<String, NodeId>,
pub redirect: Option<Redirection>, pub redirect: Option<Redirection>,
pub meta: LiteralNodeMetadata, pub meta: LiteralNodeMetadata,
@ -110,8 +111,8 @@ pub struct LiteralAttachedNode {
/// Represents a literal, command node that has already been attached /// Represents a literal, command node that has already been attached
/// to a `Tree`. /// to a `Tree`.
#[derive(Clone)] #[derive(Clone)]
pub struct CommandAttachedNode { pub struct CommandAttachedNode<S: CommandSource = DummySource> {
pub owned: OwnedNodeData, pub owned: OwnedNodeData<S>,
pub children: FxHashMap<String, NodeId>, pub children: FxHashMap<String, NodeId>,
pub redirect: Option<Redirection>, pub redirect: Option<Redirection>,
pub meta: CommandNodeMetadata, 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 /// Represents a node that accepts a specific type of argument that has already been attached
/// to a `Tree`. /// to a `Tree`.
#[derive(Clone)] #[derive(Clone)]
pub struct ArgumentAttachedNode { pub struct ArgumentAttachedNode<S: CommandSource = DummySource> {
pub owned: OwnedNodeData, pub owned: OwnedNodeData<S>,
pub children: FxHashMap<String, NodeId>, pub children: FxHashMap<String, NodeId>,
pub redirect: Option<Redirection>, pub redirect: Option<Redirection>,
pub meta: ArgumentNodeMetadata, pub meta: ArgumentNodeMetadata<S>,
} }
/// Allows a way to store the kind of node /// 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. /// Represents a node not attached to a `Tree` yet.
#[derive(Clone)] #[derive(Clone)]
pub enum AttachedNode { pub enum AttachedNode<S: CommandSource = DummySource> {
Root(RootAttachedNode), Root(RootAttachedNode<S>),
Literal(LiteralAttachedNode), Literal(LiteralAttachedNode<S>),
Command(CommandAttachedNode), Command(CommandAttachedNode<S>),
Argument(ArgumentAttachedNode), Argument(ArgumentAttachedNode<S>),
} }
/// Returned when a literal could not be parsed. /// Returned when a literal could not be parsed.
pub struct CouldNotParseLiteral; pub struct CouldNotParseLiteral;
impl AttachedNode { impl<S: CommandSource> AttachedNode<S> {
/// Creates an [`AttachedNode`] from its properties allowing any [`NodeMetadata`]. /// Creates an [`AttachedNode`] from its properties allowing any [`NodeMetadata`].
#[must_use] #[must_use]
pub fn from_parts( pub fn from_parts(
owned: OwnedNodeData, owned: OwnedNodeData<S>,
children: FxHashMap<String, NodeId>, children: FxHashMap<String, NodeId>,
redirect: Option<Redirection>, redirect: Option<Redirection>,
meta: NodeMetadata, meta: NodeMetadata<S>,
) -> Self { ) -> Self {
match meta { match meta {
NodeMetadata::Root => Self::Root(RootAttachedNode { owned, children }), NodeMetadata::Root => Self::Root(RootAttachedNode { owned, children }),
@ -201,7 +202,7 @@ impl AttachedNode {
/// Gets a reference to the owned data of this node. /// Gets a reference to the owned data of this node.
#[must_use] #[must_use]
pub const fn owned_node_data_ref(&self) -> &OwnedNodeData { pub const fn owned_node_data_ref(&self) -> &OwnedNodeData<S> {
match self { match self {
Self::Root(node) => &node.owned, Self::Root(node) => &node.owned,
Self::Literal(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. /// 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 { match self {
Self::Root(node) => &mut node.owned, Self::Root(node) => &mut node.owned,
Self::Literal(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. /// Gets all the requirements required for this node to be run.
#[must_use] #[must_use]
pub const fn requirements(&self) -> &Requirements { pub const fn requirements(&self) -> &Requirements<S> {
&self.owned_node_data_ref().requirements &self.owned_node_data_ref().requirements
} }
/// Overrides all the requirements for this node to be run to a value. /// 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; self.owned_node_data_mut_ref().requirements = requirements;
} }
/// Gets the modifier for this node to be run. /// Gets the modifier for this node to be run.
#[must_use] #[must_use]
pub const fn modifier(&self) -> &RedirectModifier { pub const fn modifier(&self) -> &RedirectModifier<S> {
&self.owned_node_data_ref().modifier &self.owned_node_data_ref().modifier
} }
/// Sets the modifier for this node to a value. /// 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; self.owned_node_data_mut_ref().modifier = modifier;
} }
@ -318,12 +319,12 @@ impl AttachedNode {
/// Gets the executable command for this node. /// Gets the executable command for this node.
#[must_use] #[must_use]
pub fn command(&self) -> &Option<Command> { pub fn command(&self) -> &Option<Command<S>> {
&self.owned_node_data_ref().command &self.owned_node_data_ref().command
} }
/// Sets the executable command for this node. /// 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; self.owned_node_data_mut_ref().command = command;
} }

View File

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

View File

@ -1,19 +1,17 @@
use crate::command::argument_builder::{ArgumentBuilder, CommandArgumentBuilder}; use crate::argument_builder::{ArgumentBuilder, CommandArgumentBuilder};
use crate::command::context::command_context::{ use crate::context::command_context::{CommandContext, CommandContextBuilder, ContextChain};
CommandContext, CommandContextBuilder, ContextChain, use crate::errors::command_syntax_error::CommandSyntaxError;
}; use crate::errors::error_types::{
use crate::command::context::command_source::{CommandSource, ReturnValue};
use crate::command::errors::command_syntax_error::CommandSyntaxError;
use crate::command::errors::error_types::{
DISPATCHER_EXPECTED_ARGUMENT_SEPARATOR, DISPATCHER_UNKNOWN_ARGUMENT, DISPATCHER_EXPECTED_ARGUMENT_SEPARATOR, DISPATCHER_UNKNOWN_ARGUMENT,
DISPATCHER_UNKNOWN_COMMAND, LiteralCommandErrorType, DISPATCHER_UNKNOWN_COMMAND, LiteralCommandErrorType,
}; };
use crate::command::node::Redirection; use crate::node::Redirection;
use crate::command::node::attached::{CommandNodeId, NodeId}; use crate::node::attached::{CommandNodeId, NodeId};
use crate::command::node::detached::CommandDetachedNode; use crate::node::detached::CommandDetachedNode;
use crate::command::node::tree::{NodeIdClassification, ROOT_NODE_ID, Tree}; use crate::node::tree::{NodeIdClassification, ROOT_NODE_ID, Tree};
use crate::command::string_reader::StringReader; use crate::source::{CommandSource, DummySource, ReturnValue};
use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder}; use crate::string_reader::StringReader;
use crate::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
use pumpkin_data::translation::java::COMMAND_CONTEXT_HERE; use pumpkin_data::translation::java::COMMAND_CONTEXT_HERE;
use pumpkin_protocol::java::client::play::CommandSuggestion; use pumpkin_protocol::java::client::play::CommandSuggestion;
use pumpkin_util::text::TextComponent; use pumpkin_util::text::TextComponent;
@ -21,7 +19,7 @@ use pumpkin_util::text::click::ClickEvent;
use pumpkin_util::text::color::{Color, NamedColor}; use pumpkin_util::text::color::{Color, NamedColor};
use rustc_hash::{FxHashMap, FxHashSet}; use rustc_hash::{FxHashMap, FxHashSet};
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::sync::{Arc, LazyLock}; use std::sync::Arc;
pub const ARG_SEPARATOR: &str = " "; pub const ARG_SEPARATOR: &str = " ";
pub const ARG_SEPARATOR_CHAR: char = ' '; pub const ARG_SEPARATOR_CHAR: char = ' ';
@ -38,47 +36,41 @@ pub const UNRESOLVED_REDIRECT: LiteralCommandErrorType =
LiteralCommandErrorType::new("Could not resolve redirect to node"); LiteralCommandErrorType::new("Could not resolve redirect to node");
/// Represents the result of parsing. /// Represents the result of parsing.
pub struct ParsingResult<'a> { pub struct ParsingResult<'a, S: CommandSource = DummySource> {
pub context: CommandContextBuilder<'a>, pub context: CommandContextBuilder<'a, S>,
pub errors: FxHashMap<NodeId, CommandSyntaxError>, pub errors: FxHashMap<NodeId, CommandSyntaxError>,
pub reader: StringReader<'static>, pub reader: StringReader<'static>,
} }
/// Structs implementing this trait are able to execute upon command completion. /// Structs implementing this trait are able to execute upon command completion.
pub trait ResultConsumer: Sync + Send { pub trait ResultConsumer<S: CommandSource = DummySource>: Sync + Send {
fn on_command_completion(&self, context: &CommandContext, result: ReturnValue); fn on_command_completion(&self, context: &CommandContext<S>, result: ReturnValue);
} }
/// A [`ResultConsumer`] which does nothing. /// A [`ResultConsumer`] which does nothing.
pub struct EmptyResultConsumer; pub struct EmptyResultConsumer;
impl ResultConsumer for EmptyResultConsumer { impl<S: CommandSource> ResultConsumer<S> for EmptyResultConsumer {
fn on_command_completion(&self, _context: &CommandContext, _result: ReturnValue) {} 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. /// A [`ResultConsumer`] which defers the given result to the source provided.
pub struct ResultDeferrer; pub struct ResultDeferrer;
impl ResultConsumer for ResultDeferrer { impl<S: CommandSource> ResultConsumer<S> for ResultDeferrer {
fn on_command_completion(&self, context: &CommandContext, result: ReturnValue) { fn on_command_completion(&self, context: &CommandContext<S>, result: ReturnValue) {
context.source.command_result_taker.call(result); 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. /// The core command dispatcher, used to register, parse and execute commands.
/// ///
/// Internally, this dispatcher stores a [`Tree`]. Refer to its documentation /// Internally, this dispatcher stores a [`Tree`]. Refer to its documentation
/// for more information about nodes. /// for more information about nodes.
#[derive(Clone)] #[derive(Clone)]
pub struct CommandDispatcher { pub struct CommandDispatcher<S: CommandSource = DummySource> {
pub tree: Tree, pub tree: Tree<S>,
pub consumer: Arc<dyn ResultConsumer>, pub consumer: Arc<dyn ResultConsumer<S>>,
/// Primary names of commands that have been turned off through the server /// Primary names of commands that have been turned off through the server
/// configuration. A disabled command behaves as if it does not exist: it /// configuration. A disabled command behaves as if it does not exist: it
@ -91,13 +83,13 @@ pub struct CommandDispatcher {
inactive_plugin_commands: FxHashSet<String>, inactive_plugin_commands: FxHashSet<String>,
} }
impl Default for CommandDispatcher { impl<S: CommandSource> Default for CommandDispatcher<S> {
fn default() -> Self { fn default() -> Self {
Self::new() Self::new()
} }
} }
impl CommandDispatcher { impl<S: CommandSource> CommandDispatcher<S> {
/// Creates a new [`CommandDispatcher`] with a new [`Tree`]. /// Creates a new [`CommandDispatcher`] with a new [`Tree`].
#[must_use] #[must_use]
pub fn new() -> Self { pub fn new() -> Self {
@ -105,10 +97,11 @@ impl CommandDispatcher {
} }
/// Creates this [`CommandDispatcher`] from a pre-existing tree. /// 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 { Self {
tree, tree,
consumer: RESULT_DEFERRER.clone(), consumer: Arc::new(ResultDeferrer),
disabled: FxHashSet::default(), disabled: FxHashSet::default(),
inactive_plugin_commands: FxHashSet::default(), inactive_plugin_commands: FxHashSet::default(),
} }
@ -133,7 +126,7 @@ impl CommandDispatcher {
} }
/// Makes a plugin command and its aliases unavailable until registration. /// 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); let primary_name = self.primary_command_name(name);
for alias in self.tree_alias_names(&primary_name) { for alias in self.tree_alias_names(&primary_name) {
self.deactivate_plugin_command(alias); self.deactivate_plugin_command(alias);
@ -142,7 +135,7 @@ impl CommandDispatcher {
} }
/// Makes every root command registered by a plugin source unavailable. /// 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 let primary_names = self
.tree .tree
.get_root_children() .get_root_children()
@ -253,7 +246,7 @@ impl CommandDispatcher {
/// Note that, at least for now with this system, there is no way to /// Note that, at least for now with this system, there is no way to
/// unregister a command. This is due to redirection to /// unregister a command. This is due to redirection to
/// potentially unregistered (freed) nodes. /// 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 mut node = command_node.into();
let name = Self::normalize_command_name(&node.meta.literal); let name = Self::normalize_command_name(&node.meta.literal);
node.meta.literal = name.clone().into(); node.meta.literal = name.clone().into();
@ -268,17 +261,13 @@ impl CommandDispatcher {
if self.tree.get(&single_slash).is_none() { if self.tree.get(&single_slash).is_none() {
let main_node = &self.tree[main_node_id]; let main_node = &self.tree[main_node_id];
let description = main_node.meta.description.clone(); let description = main_node.meta.description.clone();
let mut alias = crate::command::argument_builder::CommandArgumentBuilder::new( let mut alias =
single_slash, crate::argument_builder::CommandArgumentBuilder::new(single_slash, description);
description,
);
if let Some(executor) = &main_node.owned.command { if let Some(executor) = &main_node.owned.command {
alias = alias.executes_arc(executor.clone()); alias = alias.executes_arc(executor.clone());
alias = alias.overwrite_requirements(main_node.owned.requirements.clone()); alias = alias.overwrite_requirements(main_node.owned.requirements.clone());
} }
alias = alias.redirect(crate::command::node::Redirection::Local( alias = alias.redirect(crate::node::Redirection::Local(main_node_id.into()));
main_node_id.into(),
));
self.tree.add_child_to_root(alias.build()); self.tree.add_child_to_root(alias.build());
} }
} else if let Some(stripped) = name.strip_prefix('/') { } else if let Some(stripped) = name.strip_prefix('/') {
@ -286,17 +275,13 @@ impl CommandDispatcher {
if self.tree.get(&double_slash).is_none() { if self.tree.get(&double_slash).is_none() {
let main_node = &self.tree[main_node_id]; let main_node = &self.tree[main_node_id];
let description = main_node.meta.description.clone(); let description = main_node.meta.description.clone();
let mut alias = crate::command::argument_builder::CommandArgumentBuilder::new( let mut alias =
double_slash, crate::argument_builder::CommandArgumentBuilder::new(double_slash, description);
description,
);
if let Some(executor) = &main_node.owned.command { if let Some(executor) = &main_node.owned.command {
alias = alias.executes_arc(executor.clone()); alias = alias.executes_arc(executor.clone());
alias = alias.overwrite_requirements(main_node.owned.requirements.clone()); alias = alias.overwrite_requirements(main_node.owned.requirements.clone());
} }
alias = alias.redirect(crate::command::node::Redirection::Local( alias = alias.redirect(crate::node::Redirection::Local(main_node_id.into()));
main_node_id.into(),
));
self.tree.add_child_to_root(alias.build()); 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 /// Note that, at least for now with this system, there is no way to
/// unregister a command. This is due to redirection to /// unregister a command. This is due to redirection to
/// potentially unregistered (freed) nodes. /// potentially unregistered (freed) nodes.
pub fn register_with_aliases<S: AsRef<str>>( pub fn register_with_aliases<Str: AsRef<str>>(
&mut self, &mut self,
command_node: impl Into<CommandDetachedNode>, command_node: impl Into<CommandDetachedNode<S>>,
aliases: &[S], aliases: &[Str],
) -> CommandNodeId { ) -> CommandNodeId {
let main_node_id = self.register(command_node); let main_node_id = self.register(command_node);
@ -365,11 +350,7 @@ impl CommandDispatcher {
/// ///
/// # Note /// # Note
/// This does not cache parsed input. /// This does not cache parsed input.
pub fn execute_input( pub fn execute_input(&self, input: &str, source: &S) -> Result<i32, CommandSyntaxError> {
&self,
input: &str,
source: &CommandSource,
) -> Result<i32, CommandSyntaxError> {
let mut reader = StringReader::new(input); let mut reader = StringReader::new(input);
// A disabled command must behave as if it does not exist from every // A disabled command must behave as if it does not exist from every
@ -390,14 +371,14 @@ impl CommandDispatcher {
pub fn execute_reader( pub fn execute_reader(
&self, &self,
reader: &mut StringReader<'_>, reader: &mut StringReader<'_>,
source: &CommandSource, source: &S,
) -> Result<i32, CommandSyntaxError> { ) -> Result<i32, CommandSyntaxError> {
let parsed = self.parse(reader, source); let parsed = self.parse(reader, source);
self.execute(parsed) self.execute(parsed)
} }
/// Executes a given result that has already been parsed from an input. /// 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() { if parsed.reader.peek().is_some() {
return if let Some(err) = parsed.errors.values().next() { return if let Some(err) = parsed.errors.values().next() {
Err(err.clone()) Err(err.clone())
@ -425,17 +406,13 @@ impl CommandDispatcher {
/// Only parses a given source with the specified source. /// Only parses a given source with the specified source.
#[must_use] #[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); let mut reader = StringReader::new(command);
self.parse(&mut reader, source) self.parse(&mut reader, source)
} }
/// Parses a command owned by a [`StringReader`] with the provided source. /// Parses a command owned by a [`StringReader`] with the provided source.
pub fn parse( pub fn parse(&self, reader: &mut StringReader<'_>, source: &S) -> ParsingResult<'_, S> {
&self,
reader: &mut StringReader<'_>,
source: &CommandSource,
) -> ParsingResult<'_> {
let context = CommandContextBuilder::new( let context = CommandContextBuilder::new(
self, self,
Arc::new(source.clone()), Arc::new(source.clone()),
@ -449,11 +426,11 @@ impl CommandDispatcher {
&'a self, &'a self,
node: NodeId, node: NodeId,
original_reader: &mut StringReader<'_>, original_reader: &mut StringReader<'_>,
context_so_far: &CommandContextBuilder<'a>, context_so_far: &CommandContextBuilder<'a, S>,
) -> ParsingResult<'a> { ) -> ParsingResult<'a, S> {
let source = context_so_far.source.clone(); let source = context_so_far.source.clone();
let mut errors: FxHashMap<NodeId, CommandSyntaxError> = FxHashMap::default(); 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(); let cursor = original_reader.cursor();
for child in self.tree.get_relevant_nodes(original_reader, node) { 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 /// If the input starts with one slash (`/`), it is removed
/// inside the call itself. /// inside the call itself.
/// pub fn handle_command<'a>(&'a self, source: &S, mut input: &'a str) {
/// # 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"
);
// If input starts with '/', but the command with that leading slash is NOT // 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 // 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. // 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. /// Sends a command error to the provided source.
/// This also shows the contextual information /// This also shows the contextual information
/// leading up to the error if necessary. /// 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))); source.send_message(error.message.color(Color::Named(NamedColor::Red)));
if let Some(context) = error.context { if let Some(context) = error.context {
@ -631,7 +604,7 @@ impl CommandDispatcher {
#[must_use] #[must_use]
pub fn get_completion_suggestions_at_end( pub fn get_completion_suggestions_at_end(
&self, &self,
parsing_result: ParsingResult<'_>, parsing_result: ParsingResult<'_, S>,
) -> Suggestions { ) -> Suggestions {
let length = parsing_result.reader.total_length(); let length = parsing_result.reader.total_length();
self.get_completion_suggestions(parsing_result, length) self.get_completion_suggestions(parsing_result, length)
@ -644,7 +617,7 @@ impl CommandDispatcher {
#[must_use] #[must_use]
pub fn get_completion_suggestions( pub fn get_completion_suggestions(
&self, &self,
parsing_result: ParsingResult<'_>, parsing_result: ParsingResult<'_, S>,
cursor: usize, cursor: usize,
) -> Suggestions { ) -> Suggestions {
let context = parsing_result.context; let context = parsing_result.context;
@ -711,7 +684,7 @@ impl CommandDispatcher {
/// This function currently panics if the source provided was a dummy source. /// This function currently panics if the source provided was a dummy source.
/// This is subject to change in the future. /// This is subject to change in the future.
#[must_use] #[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) self.suggest_with_range(input, source)
.suggestions .suggestions
.into_iter() .into_iter()
@ -723,7 +696,7 @@ impl CommandDispatcher {
} }
#[must_use] #[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. // Never suggest arguments for a command that has been turned off.
if self.is_disabled(Self::command_name(input)) { if self.is_disabled(Self::command_name(input)) {
return Suggestions::empty(); return Suggestions::empty();
@ -756,7 +729,7 @@ impl CommandDispatcher {
/// The map returned has the key as the command name /// The map returned has the key as the command name
/// and the value as the command's description. /// and the value as the command's description.
#[must_use] #[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(); let mut commands: BTreeMap<&str, &str> = BTreeMap::new();
for command in self.tree.get_root_children() { for command in self.tree.get_root_children() {
@ -777,10 +750,7 @@ impl CommandDispatcher {
/// The key is the command identifier, /// The key is the command identifier,
/// and the value is a tuple of `(description, usage)`. /// and the value is a tuple of `(description, usage)`.
#[must_use] #[must_use]
pub fn get_all_permitted_commands_usage( pub fn get_all_permitted_commands_usage(&self, source: &S) -> BTreeMap<&str, (&str, Box<str>)> {
&self,
source: &CommandSource,
) -> BTreeMap<&str, (&str, Box<str>)> {
let mut commands: BTreeMap<&str, (&str, Box<str>)> = BTreeMap::new(); let mut commands: BTreeMap<&str, (&str, Box<str>)> = BTreeMap::new();
for (command_node_id, usage) in self.get_usage_of_commands(source) { for (command_node_id, usage) in self.get_usage_of_commands(source) {
@ -801,7 +771,7 @@ impl CommandDispatcher {
#[must_use] #[must_use]
pub fn get_all_permitted_commands_usage_by_plugin( pub fn get_all_permitted_commands_usage_by_plugin(
&self, &self,
source: &CommandSource, source: &S,
plugin_name: &str, plugin_name: &str,
) -> BTreeMap<&str, (&str, Box<str>)> { ) -> BTreeMap<&str, (&str, Box<str>)> {
let mut commands: BTreeMap<&str, (&str, Box<str>)> = BTreeMap::new(); let mut commands: BTreeMap<&str, (&str, Box<str>)> = BTreeMap::new();
@ -831,7 +801,7 @@ impl CommandDispatcher {
#[must_use] #[must_use]
pub fn get_permitted_command_usage( pub fn get_permitted_command_usage(
&self, &self,
source: &CommandSource, source: &S,
command: &str, command: &str,
) -> Option<(&str, Box<str>)> { ) -> Option<(&str, Box<str>)> {
let command_node_id = self.tree.get(command)?; let command_node_id = self.tree.get(command)?;
@ -846,11 +816,7 @@ impl CommandDispatcher {
/// Returns the usage of the given command node. /// Returns the usage of the given command node.
#[must_use] #[must_use]
pub fn get_usage_of_command( pub fn get_usage_of_command(&self, command_node: CommandNodeId, source: &S) -> Option<String> {
&self,
command_node: CommandNodeId,
source: &CommandSource,
) -> Option<String> {
// We know the root DOES NOT have an executor, so we pass false to `is_optional`. // 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) self.get_usage_recursive(command_node.into(), source, false, false, None)
.map(|mut usage| { .map(|mut usage| {
@ -862,11 +828,7 @@ impl CommandDispatcher {
/// Returns the usage of each child of the given node (permitted for the given source). /// Returns the usage of each child of the given node (permitted for the given source).
#[must_use] #[must_use]
pub fn get_usage_of_children( pub fn get_usage_of_children(&self, node: NodeId, source: &S) -> FxHashMap<NodeId, String> {
&self,
node: NodeId,
source: &CommandSource,
) -> FxHashMap<NodeId, String> {
let mut map = FxHashMap::default(); let mut map = FxHashMap::default();
let is_optional = self.tree[node].command().is_some(); 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). /// Returns the usage of each command (permitted for the given source).
#[must_use] #[must_use]
pub fn get_usage_of_commands( pub fn get_usage_of_commands(&self, source: &S) -> FxHashMap<CommandNodeId, String> {
&self,
source: &CommandSource,
) -> FxHashMap<CommandNodeId, String> {
self.get_usage_of_children(ROOT_NODE_ID, source) self.get_usage_of_children(ROOT_NODE_ID, source)
.into_iter() .into_iter()
// This is safe because every child of the root child is a command node. // This is safe because every child of the root child is a command node.
@ -900,7 +859,7 @@ impl CommandDispatcher {
fn get_usage_recursive( fn get_usage_recursive(
&self, &self,
node: NodeId, node: NodeId,
source: &CommandSource, source: &S,
is_optional: bool, is_optional: bool,
deep: bool, deep: bool,
redirector_usage_text: Option<String>, redirector_usage_text: Option<String>,
@ -1007,15 +966,15 @@ impl CommandDispatcher {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use crate::command::argument_builder::{ use crate::argument_builder::{
ArgumentBuilder, CommandArgumentBuilder, LiteralArgumentBuilder, RequiredArgumentBuilder, ArgumentBuilder, CommandArgumentBuilder, LiteralArgumentBuilder, RequiredArgumentBuilder,
}; };
use crate::command::argument_types::core::integer::IntegerArgumentType; use crate::argument_types::core::integer::IntegerArgumentType;
use crate::command::context::command_context::CommandContext; use crate::context::command_context::CommandContext;
use crate::command::context::command_source::CommandSource; use crate::errors::error_types::DISPATCHER_UNKNOWN_COMMAND;
use crate::command::errors::error_types::DISPATCHER_UNKNOWN_COMMAND; use crate::node::dispatcher::CommandDispatcher;
use crate::command::node::dispatcher::CommandDispatcher; use crate::node::{CommandExecutor, CommandExecutorResult};
use crate::command::node::{CommandExecutor, CommandExecutorResult}; use crate::source::DummySource;
#[test] #[test]
fn unknown_command() { fn unknown_command() {
@ -1023,7 +982,7 @@ mod test {
dispatcher.register( dispatcher.register(
CommandArgumentBuilder::new("unknown", "A command without an executor").build(), CommandArgumentBuilder::new("unknown", "A command without an executor").build(),
); );
let source = CommandSource::dummy(); let source = DummySource::dummy();
let result = dispatcher.execute_input("unknown", &source); let result = dispatcher.execute_input("unknown", &source);
assert!(result.is_err_and(|error| error.error_type == &DISPATCHER_UNKNOWN_COMMAND)); 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); let executor: fn(&CommandContext) -> CommandExecutorResult = |_| Ok(1);
dispatcher dispatcher
.register(CommandArgumentBuilder::new("simple", "A simple command").executes(executor)); .register(CommandArgumentBuilder::new("simple", "A simple command").executes(executor));
let source = CommandSource::dummy(); let source = DummySource::dummy();
let result = dispatcher.execute_input("simple", &source); let result = dispatcher.execute_input("simple", &source);
assert_eq!(result, Ok(1)); assert_eq!(result, Ok(1));
} }
@ -1050,7 +1009,7 @@ mod test {
.register(CommandArgumentBuilder::new("simple", "A simple command").executes(executor)); .register(CommandArgumentBuilder::new("simple", "A simple command").executes(executor));
dispatcher.disable_command("simple"); dispatcher.disable_command("simple");
let source = CommandSource::dummy(); let source = DummySource::dummy();
let result = dispatcher.execute_input("simple", &source); let result = dispatcher.execute_input("simple", &source);
assert!(result.is_err_and(|error| error.error_type == &DISPATCHER_UNKNOWN_COMMAND)); 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"]); 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-command", &source), Ok(1));
assert_eq!(dispatcher.execute_input("plugin-alias", &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!( assert_eq!(
dispatcher.execute_input("arithmetic 3 + -7", &source), dispatcher.execute_input("arithmetic 3 + -7", &source),
Ok(-4) Ok(-4)
@ -1194,7 +1153,7 @@ mod test {
// Note that we CANNOT use redirect here as node itself needs to execute the command, // Note that we CANNOT use redirect here as node itself needs to execute the command,
// not its 'children'. // not its 'children'.
dispatcher.register(CommandArgumentBuilder::new("b", "An alias for /a").executes(executor)); 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("a", &source), Ok(1));
assert_eq!(dispatcher.execute_input("b", &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`. // 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)); 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("a 5", &source), Ok(5));
assert_eq!(dispatcher.execute_input("b 7", &source), Ok(7)); assert_eq!(dispatcher.execute_input("b 7", &source), Ok(7));
} }
@ -1246,7 +1205,7 @@ mod test {
dispatcher.register(builder); 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", &source), Ok(1));
assert_eq!(dispatcher.execute_input("recurse 4", &source), Ok(1)); assert_eq!(dispatcher.execute_input("recurse 4", &source), Ok(1));
assert_eq!(dispatcher.execute_input("recurse 9 -1", &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), CommandArgumentBuilder::new("//set", "WorldEdit set command").executes(executor),
); );
let source = CommandSource::dummy(); let source = DummySource::dummy();
// Direct execution with //set // Direct execution with //set
assert_eq!(dispatcher.execute_input("//set", &source), Ok(42)); assert_eq!(dispatcher.execute_input("//set", &source), Ok(42));
// Execution via /set alias (as sent by Java client for //set) // 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::context::command_context::{CommandContextBuilder, ParsedArgument};
use crate::command::context::command_source::CommandSource; use crate::context::string_range::StringRange;
use crate::command::context::string_range::StringRange; use crate::errors::command_syntax_error::CommandSyntaxError;
use crate::command::errors::command_syntax_error::CommandSyntaxError; use crate::errors::error_types::LITERAL_INCORRECT;
use crate::command::errors::error_types::LITERAL_INCORRECT; use crate::node::Redirection;
use crate::command::node::Redirection; use crate::node::attached::{
use crate::command::node::attached::{
ArgumentAttachedNode, ArgumentNodeId, AttachedNode, CommandAttachedNode, CommandNodeId, ArgumentAttachedNode, ArgumentNodeId, AttachedNode, CommandAttachedNode, CommandNodeId,
LiteralAttachedNode, LiteralNodeId, NodeClassification, NodeId, RootAttachedNode, LiteralAttachedNode, LiteralNodeId, NodeClassification, NodeId, RootAttachedNode,
}; };
use crate::command::node::detached::{CommandDetachedNode, DetachedNode, GlobalNodeId}; use crate::node::detached::{CommandDetachedNode, DetachedNode, GlobalNodeId};
use crate::command::string_reader::StringReader; use crate::source::{CommandSource, DummySource};
use crate::string_reader::StringReader;
use pumpkin_util::text::TextComponent; use pumpkin_util::text::TextComponent;
use rustc_hash::{FxBuildHasher, FxHashMap, FxHashSet}; use rustc_hash::{FxBuildHasher, FxHashMap, FxHashSet};
use std::num::NonZero; 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")); 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) /// 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( fn ambiguous(
&mut self, &mut self,
tree: &Tree, tree: &Tree<S>,
parent: NodeId, parent: NodeId,
child: NodeId, child: NodeId,
sibling: NodeId, sibling: NodeId,
@ -76,12 +76,12 @@ pub enum NodeIdClassification {
/// - **Argument**: /// - **Argument**:
/// Parses and accepts a specific type of value. This is very dynamic. /// Parses and accepts a specific type of value. This is very dynamic.
#[derive(Clone)] #[derive(Clone)]
pub struct Tree { pub struct Tree<S: CommandSource = DummySource> {
/// All the nodes stored in this tree. /// All the nodes stored in this tree.
/// ///
/// In this vector, indices starting at 0 indicates the first node (ID = 1), /// In this vector, indices starting at 0 indicates the first node (ID = 1),
/// 1 indicates the second node (ID = 2) and so on. /// 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. /// Keys linking [`GlobalNodeId`] to the [`NodeId`] for this tree.
/// Useful for redirecting. /// Useful for redirecting.
@ -91,13 +91,13 @@ pub struct Tree {
command_node_mappings: FxHashMap<String, CommandNodeId>, command_node_mappings: FxHashMap<String, CommandNodeId>,
} }
impl Default for Tree { impl<S: CommandSource> Default for Tree<S> {
fn default() -> Self { fn default() -> Self {
Self::new() Self::new()
} }
} }
impl Tree { impl<S: CommandSource> Tree<S> {
/// Constructs a new tree, containing a new root node without children. /// Constructs a new tree, containing a new root node without children.
#[must_use] #[must_use]
pub fn new() -> Self { pub fn new() -> Self {
@ -130,7 +130,7 @@ impl Tree {
/// Helper to attach a given [`AttachedNode`], returning /// Helper to attach a given [`AttachedNode`], returning
/// its [`NodeId`]. /// its [`NodeId`].
fn add(&mut self, node: AttachedNode) -> NodeId { fn add(&mut self, node: AttachedNode<S>) -> NodeId {
let global_id = node.global_id(); let global_id = node.global_id();
let local_id = self.alloc(); let local_id = self.alloc();
@ -144,7 +144,7 @@ impl Tree {
/// Helper to attach a [`DetachedNode`] irreversibly /// Helper to attach a [`DetachedNode`] irreversibly
/// into this [`Tree`], returning the ID of the now attached /// into this [`Tree`], returning the ID of the now attached
/// node. /// node.
fn attach(&mut self, node: DetachedNode) -> NodeId { fn attach(&mut self, node: DetachedNode<S>) -> NodeId {
// First, we decompose this node. // First, we decompose this node.
let node = node.decompose(); let node = node.decompose();
@ -177,7 +177,7 @@ impl Tree {
} }
/// Adds a [`CommandDetachedNode`] to the root node of this 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. // First, attach the node to this tree.
let node = node.into(); let node = node.into();
let name = node.meta.literal.to_string(); let name = node.meta.literal.to_string();
@ -199,7 +199,7 @@ impl Tree {
/// ///
/// Essentially, this means that a [`CommandDetachedNode`] must have the root node /// Essentially, this means that a [`CommandDetachedNode`] must have the root node
/// of the tree *as its parent*, to be attached to the tree. /// 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(); let node = node.into();
assert!( assert!(
parent == ROOT_NODE_ID || !matches!(node, DetachedNode::Command(_)), 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. /// Returns whether the given node is able to be used by a given source.
#[must_use] #[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) self[node].requirements().evaluate(source)
} }
/// Finds ambiguities of input and gives them to the [`AmbiguityConsumer`]. /// 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(); let mut matches: FxHashSet<String> = FxHashSet::default();
for child in self.get_children(node) { for child in self.get_children(node) {
@ -363,13 +363,13 @@ impl Tree {
&self, &self,
node_id: NodeId, node_id: NodeId,
reader: &'a mut StringReader<'_>, reader: &'a mut StringReader<'_>,
command_context_builder: &'a mut CommandContextBuilder<'_>, command_context_builder: &'a mut CommandContextBuilder<'_, S>,
) -> Result<(), CommandSyntaxError> { ) -> Result<(), CommandSyntaxError> {
match &self[node_id] { match &self[node_id] {
AttachedNode::Root(_) => {} AttachedNode::Root(_) => {}
AttachedNode::Literal(node) => { AttachedNode::Literal(node) => {
let start = reader.cursor(); 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 return Err(LITERAL_INCORRECT
.create(reader, TextComponent::text(node.meta.literal.to_string()))); .create(reader, TextComponent::text(node.meta.literal.to_string())));
}; };
@ -377,7 +377,7 @@ impl Tree {
} }
AttachedNode::Command(node) => { AttachedNode::Command(node) => {
let start = reader.cursor(); 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 return Err(LITERAL_INCORRECT
.create(reader, TextComponent::text(node.meta.literal.to_string()))); .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. /// 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() self.nodes.iter()
} }
} }
impl Index<NodeId> for Tree { impl<S: CommandSource> Index<NodeId> for Tree<S> {
type Output = AttachedNode; type Output = AttachedNode<S>;
fn index(&self, index: NodeId) -> &Self::Output { fn index(&self, index: NodeId) -> &Self::Output {
&self.nodes[index.0.get() - 1] &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 { fn index_mut(&mut self, index: NodeId) -> &mut Self::Output {
&mut self.nodes[index.0.get() - 1] &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 helper to create [`Index`] and [`IndexMut`] for [`Tree`] with typed IDs.
macro_rules! impl_index_index_mut { macro_rules! impl_index_index_mut {
($node_id: ident -> AttachedNode::$attached_node_enum: ident($attached_node: ident)) => { ($node_id: ident -> AttachedNode::$attached_node_enum: ident($attached_node: ident)) => {
impl Index<$node_id> for Tree { impl<S: CommandSource> Index<$node_id> for Tree<S> {
type Output = $attached_node; type Output = $attached_node<S>;
fn index(&self, index: $node_id) -> &Self::Output { fn index(&self, index: $node_id) -> &Self::Output {
if let AttachedNode::$attached_node_enum(node) = &self.nodes[index.0.get() - 1] { 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 { 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] 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!(CommandNodeId -> AttachedNode::Command(CommandAttachedNode));
impl_index_index_mut!(ArgumentNodeId -> AttachedNode::Argument(ArgumentAttachedNode)); impl_index_index_mut!(ArgumentNodeId -> AttachedNode::Argument(ArgumentAttachedNode));
impl<'a> IntoIterator for &'a Tree { impl<'a, S: CommandSource> IntoIterator for &'a Tree<S> {
type Item = &'a AttachedNode; type Item = &'a AttachedNode<S>;
type IntoIter = std::slice::Iter<'a, AttachedNode>; type IntoIter = std::slice::Iter<'a, AttachedNode<S>>;
fn into_iter(self) -> Self::IntoIter { fn into_iter(self) -> Self::IntoIter {
self.iter() self.iter()
@ -487,17 +487,18 @@ impl<'a> IntoIterator for &'a Tree {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use crate::command::argument_builder::{ use crate::argument_builder::{
ArgumentBuilder, CommandArgumentBuilder, LiteralArgumentBuilder, RequiredArgumentBuilder, ArgumentBuilder, CommandArgumentBuilder, LiteralArgumentBuilder, RequiredArgumentBuilder,
}; };
use crate::command::argument_types::core::string::StringArgumentType; use crate::argument_types::core::string::StringArgumentType;
use crate::command::node::attached::NodeId; use crate::node::attached::NodeId;
use crate::command::node::tree::{AmbiguityConsumer, Tree}; use crate::node::tree::{AmbiguityConsumer, Tree};
use crate::source::DummySource;
#[test] #[test]
fn adding_nodes() { fn adding_nodes() {
// New tree (containing only one root node) // New tree (containing only one root node)
let mut tree = Tree::new(); let mut tree = Tree::<DummySource>::new();
assert_eq!(tree.size(), 1); assert_eq!(tree.size(), 1);
// Adding one node. // Adding one node.
@ -516,7 +517,7 @@ mod test {
#[test] #[test]
fn adding_children_to_attached_node() { fn adding_children_to_attached_node() {
let mut tree = Tree::new(); let mut tree = Tree::<DummySource>::new();
let parent: NodeId = tree let parent: NodeId = tree
.add_child_to_root(CommandArgumentBuilder::new("foo", "A test command")) .add_child_to_root(CommandArgumentBuilder::new("foo", "A test command"))
@ -532,7 +533,7 @@ mod test {
#[test] #[test]
#[should_panic = "Cannot add a CommandDetachedNode as a child of a non-root node"] #[should_panic = "Cannot add a CommandDetachedNode as a child of a non-root node"]
fn adding_command_node_to_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 let parent: NodeId = tree
.add_child_to_root(CommandArgumentBuilder::new("foo", "A test command")) .add_child_to_root(CommandArgumentBuilder::new("foo", "A test command"))

View File

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

View File

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

View File

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

View File

@ -2,7 +2,7 @@ use pumpkin_data::translation;
use pumpkin_nbt::{nbt_ops::NbtOps, tag::NbtTag}; use pumpkin_nbt::{nbt_ops::NbtOps, tag::NbtTag};
use pumpkin_util::uuid::parse_uuid_vec; 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; use pumpkin_codecs::DynamicOps;
pub const EXPECTED_NUMBER_OR_BOOLEAN: CommandErrorType<0> = CommandErrorType::new( pub const EXPECTED_NUMBER_OR_BOOLEAN: CommandErrorType<0> = CommandErrorType::new(

View File

@ -1,12 +1,12 @@
use std::collections::HashMap; use std::collections::HashMap;
use crate::command::errors::error_types::{CommandErrorType, LITERAL_INCORRECT}; use crate::errors::error_types::{CommandErrorType, LITERAL_INCORRECT};
use crate::command::parser::Parser; use crate::parser::Parser;
use crate::command::snbt::markers::{ use crate::snbt::markers::{
ArrayPrefix, Base, IntegerLiteral, IntegerSuffix, Sign, Signed, SignedPrefix, TypeSuffix, ArrayPrefix, Base, IntegerLiteral, IntegerSuffix, Sign, Signed, SignedPrefix, TypeSuffix,
}; };
use crate::command::snbt::operations::SnbtOperations; use crate::snbt::operations::SnbtOperations;
use crate::command::snbt::{NUMBER_PARSE_FAILURE, SnbtParser}; use crate::snbt::{NUMBER_PARSE_FAILURE, SnbtParser};
use pumpkin_codecs::{DynamicOps, Number}; use pumpkin_codecs::{DynamicOps, Number};
use pumpkin_data::translation; use pumpkin_data::translation;
use pumpkin_nbt::compound::NbtCompound; 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"]; const BUILT_IN_LIKE_SUGGESTIONS: &[&str] = &["(", "bool", "false", "true", "uuid"];
use crate::command::{ use crate::{
errors::command_syntax_error::CommandSyntaxError, snbt::SnbtParser, errors::command_syntax_error::CommandSyntaxError, snbt::SnbtParser,
string_reader::StringReader, suggestion::suggestions::SuggestionsBuilder, 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, CommandSyntaxError, CommandSyntaxErrorContext, ContextProvider,
}; };
use crate::command::errors::error_types::{self, CommandErrorType}; use crate::errors::error_types::{self, CommandErrorType};
use pumpkin_util::text::TextComponent; use pumpkin_util::text::TextComponent;
use std::borrow::Cow; use std::borrow::Cow;
use std::str::FromStr; use std::str::FromStr;
@ -367,7 +367,7 @@ impl ContextProvider for StringReader<'_> {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use crate::command::{errors::error_types, string_reader::StringReader}; use crate::{errors::error_types, string_reader::StringReader};
#[test] #[test]
fn non_parsing_methods() { fn non_parsing_methods() {

View File

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

View File

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

View File

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

View File

@ -24,6 +24,7 @@ rayon.workspace = true
pumpkin-util.workspace = true pumpkin-util.workspace = true
pumpkin-nbt.workspace = true pumpkin-nbt.workspace = true
pumpkin-config.workspace = true pumpkin-config.workspace = true
pumpkin-command.workspace = true
pumpkin-inventory.workspace = true pumpkin-inventory.workspace = true
pumpkin-world.workspace = true pumpkin-world.workspace = true
pumpkin-data = { workspace = true, features = ["default"] } 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::borrow::Cow;
use std::sync::Arc; use std::sync::Arc;
/// Represents an intermediate struct for use crate::command::context::command_source::CommandSource;
/// building arguments for commands. use crate::command::node::detached::DetachedNode;
/// use crate::command::node::{
/// # Note ArcCommandExecutorAdapter, Command, CommandExecutor, CommandExecutorAdapter, RedirectModifier,
/// Requirement,
/// This is an implementation detail. };
struct CommonArgumentBuilder { use crate::command::suggestion::provider::{SuggestionProvider, SuggestionProviderAdapter};
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,
}
impl CommonArgumentBuilder { use pumpkin_command::argument_builder::ArgumentBuilder as CoreArgumentBuilder;
fn new() -> Self { pub use pumpkin_command::node::Redirection;
Self {
global_id: GlobalNodeId::new(),
arguments: FxHashMap::default(),
command: None,
requirements: Requirements::new(),
target: None,
modifier: RedirectModifier::KeepSource,
forks: false,
}
}
}
impl Default for CommonArgumentBuilder { pub struct CommandArgumentBuilder(
fn default() -> Self { pub pumpkin_command::argument_builder::CommandArgumentBuilder<CommandSource>,
Self::new() );
} 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( pub fn command(
literal: impl Into<Cow<'static, str>>, literal: impl Into<Cow<'static, str>>,
description: impl Into<Cow<'static, str>>, description: impl Into<Cow<'static, str>>,
) -> CommandArgumentBuilder { ) -> 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 { 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( pub fn argument(
name: impl Into<Cow<'static, str>>, 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 {
RequiredArgumentBuilder::new(name, arg_type) RequiredArgumentBuilder(pumpkin_command::argument_builder::argument(name, arg_type))
} }
/// A builder that builds a literal, non-command [`DetachedNode`]. pub trait ArgumentBuilder: Sized {
pub struct LiteralArgumentBuilder { type Node;
common: CommonArgumentBuilder,
literal: Cow<'static, str>,
}
/// 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] #[must_use]
fn then(self, child: impl Into<DetachedNode>) -> Self; fn then(self, child: impl Into<DetachedNode>) -> Self;
/// Gets the command to execute for the node being built.
#[must_use] #[must_use]
fn command(&self) -> Option<Command>; fn command(&self) -> Option<Command>;
/// Sets the command to execute for the node being built.
#[must_use] #[must_use]
fn executes(self, command: impl CommandExecutor + 'static) -> Self { fn executes(self, command: impl CommandExecutor + 'static) -> Self {
self.executes_arc(Arc::new(command)) self.executes_arc(Arc::new(command))
} }
/// Sets the command to execute for the node being built.
#[must_use] #[must_use]
fn executes_arc(self, command: Arc<dyn CommandExecutor + 'static>) -> Self; 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] #[must_use]
fn redirect(self, redirection: impl Into<Redirection>) -> Self; 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] #[must_use]
fn redirect_with_modifier( fn redirect_with_modifier(
self, self,
@ -167,392 +71,218 @@ pub trait ArgumentBuilder<N: Into<DetachedNode>>: Sized + Sealed {
redirect_modifier: RedirectModifier, redirect_modifier: RedirectModifier,
) -> Self; ) -> Self;
/// Forks the given context, using multiple for later.
#[must_use] #[must_use]
fn fork(self, redirection: impl Into<Redirection>, redirect_modifier: RedirectModifier) fn fork(self, redirection: impl Into<Redirection>, redirect_modifier: RedirectModifier)
-> Self; -> 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] #[must_use]
fn requires(self, requirement: impl Into<Requirement>) -> Self; fn requires(self, requirement: impl Into<Requirement>) -> Self;
/// Overwrites the current requirements of this node to a new value.
#[must_use] #[must_use]
fn overwrite_requirements(self, requirements: Requirements) -> Self; fn build(self) -> Self::Node;
/// 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
}
} }
impl RequiredArgumentBuilder { 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] #[must_use]
pub fn suggests(self, provider: impl SuggestionProvider + 'static) -> Self { pub fn suggests(self, provider: impl SuggestionProvider + 'static) -> Self {
self.suggests_arc(Arc::new(provider)) Self(self.0.suggests(SuggestionProviderAdapter(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
} }
} }
impl ArgumentBuilder<LiteralDetachedNode> for LiteralArgumentBuilder { impl ArgumentBuilder for CommandArgumentBuilder {
impl_boilerplate_argument_builder!(); type Node = pumpkin_command::node::detached::CommandDetachedNode<CommandSource>;
fn build(self) -> LiteralDetachedNode { fn then(self, child: impl Into<DetachedNode>) -> Self {
let mut node = LiteralDetachedNode::new( Self(self.0.then(child.into()))
self.common.global_id, }
self.literal,
self.common.command, fn command(&self) -> Option<Command> {
self.common.requirements, self.0.command()
self.common.target, }
self.common.modifier,
self.common.forks, fn executes(self, command: impl CommandExecutor + 'static) -> Self {
); Self(self.0.executes(CommandExecutorAdapter(command)))
node.children = self.common.arguments; }
node
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 ArgumentBuilder for LiteralArgumentBuilder {
impl_boilerplate_argument_builder!(); type Node = pumpkin_command::node::detached::LiteralDetachedNode<CommandSource>;
fn build(self) -> CommandDetachedNode { fn then(self, child: impl Into<DetachedNode>) -> Self {
let mut node = CommandDetachedNode::new( Self(self.0.then(child.into()))
self.common.global_id, }
self.literal,
self.description, fn command(&self) -> Option<Command> {
self.common.command, self.0.command()
self.common.requirements, }
self.common.target,
self.common.modifier, fn executes(self, command: impl CommandExecutor + 'static) -> Self {
self.common.forks, Self(self.0.executes(CommandExecutorAdapter(command)))
); }
node.meta.source = self.source;
node.children = self.common.arguments; fn executes_arc(self, command: Arc<dyn CommandExecutor + 'static>) -> Self {
node 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 ArgumentBuilder for RequiredArgumentBuilder {
impl_boilerplate_argument_builder!(); type Node = pumpkin_command::node::detached::ArgumentDetachedNode<CommandSource>;
fn build(self) -> ArgumentDetachedNode { fn then(self, child: impl Into<DetachedNode>) -> Self {
let mut node = ArgumentDetachedNode::new( Self(self.0.then(child.into()))
self.common.global_id, }
self.name,
self.argument_type, fn command(&self) -> Option<Command> {
self.common.command, self.0.command()
self.common.requirements, }
self.common.target,
self.common.modifier, fn executes(self, command: impl CommandExecutor + 'static) -> Self {
self.common.forks, Self(self.0.executes(CommandExecutorAdapter(command)))
self.suggestion_provider, }
);
node.children = self.common.arguments; fn executes_arc(self, command: Arc<dyn CommandExecutor + 'static>) -> Self {
node 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)] impl From<CommandArgumentBuilder>
mod test { for pumpkin_command::node::detached::CommandDetachedNode<CommandSource>
use crate::command::argument_builder::{ArgumentBuilder, argument, command, literal}; {
use crate::command::argument_types::core::double::DoubleArgumentType; fn from(b: CommandArgumentBuilder) -> Self {
use crate::command::argument_types::core::integer::IntegerArgumentType; b.0.build()
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; impl From<LiteralArgumentBuilder> for DetachedNode {
use crate::command::node::tree::Tree; fn from(b: LiteralArgumentBuilder) -> Self {
use crate::command::string_reader::StringReader; Self::Literal(b.0.build())
}
#[test] }
fn literal_one() {
let builder = literal("test"); impl From<RequiredArgumentBuilder> for DetachedNode {
let node = builder.build(); fn from(b: RequiredArgumentBuilder) -> Self {
Self::Argument(b.0.build())
assert_eq!(node.meta.literal, "test"); }
} }
#[test] impl From<LiteralArgumentBuilder>
fn required_one() { for pumpkin_command::node::detached::LiteralDetachedNode<CommandSource>
let builder = argument("test", IntegerArgumentType::new(1, 10)); {
let node = builder.build(); fn from(b: LiteralArgumentBuilder) -> Self {
b.0.build()
assert_eq!(node.meta.name, "test"); }
}
let mut reader1 = StringReader::new("5");
let mut reader2 = StringReader::new("11"); impl From<RequiredArgumentBuilder>
for pumpkin_command::node::detached::ArgumentDetachedNode<CommandSource>
let boxed_result = node {
.meta fn from(b: RequiredArgumentBuilder) -> Self {
.argument_type b.0.build()
.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"));
} }
} }

View File

@ -79,7 +79,7 @@ impl EntityArgumentType {
} }
} }
impl ArgumentType for EntityArgumentType { impl ArgumentType<CommandSource> for EntityArgumentType {
type Item = EntitySelector; type Item = EntitySelector;
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> { fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
@ -126,7 +126,7 @@ impl EntityArgumentType {
self, self,
reader: &mut StringReader, reader: &mut StringReader,
allow_selectors: bool, allow_selectors: bool,
) -> Result<<Self as ArgumentType>::Item, CommandSyntaxError> { ) -> Result<<Self as ArgumentType<CommandSource>>::Item, CommandSyntaxError> {
let selector = { let selector = {
let parser = EntitySelectorParser::new(reader, allow_selectors); let parser = EntitySelectorParser::new(reader, allow_selectors);
parser.parse_and_consume()? 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 crate::entity::Entity;
use pumpkin_data::translation; pub use pumpkin_command::argument_types::entity_anchor::*;
use pumpkin_util::math::vector3::Vector3; use pumpkin_util::math::vector3::Vector3;
use pumpkin_util::text::TextComponent;
pub const INVALID_ERROR_TYPE: CommandErrorType<1> = CommandErrorType::new( pub trait EntityAnchorExt {
translation::java::ARGUMENT_ANCHOR_INVALID, fn transform_position(self, position: Vector3<f64>, entity: &Entity) -> Vector3<f64>;
translation::java::ARGUMENT_ANCHOR_INVALID, fn position_at_entity(self, entity: &Entity) -> Vector3<f64>;
);
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")
}
} }
impl_copy_get!(EntityAnchorArgumentType, EntityAnchor); impl EntityAnchorExt for 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",
}
}
fn transform_position(self, position: Vector3<f64>, entity: &Entity) -> Vector3<f64> { fn transform_position(self, position: Vector3<f64>, entity: &Entity) -> Vector3<f64> {
match self { match self {
Self::Feet => position, Self::Feet => position,
@ -84,18 +15,7 @@ impl EntityAnchor {
} }
} }
/// Gets the position of an entity with respect to this anchor. fn position_at_entity(self, entity: &Entity) -> Vector3<f64> {
pub fn position_at_entity(self, entity: &Entity) -> Vector3<f64> {
self.transform_position(entity.pos.load(), entity) 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)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct GameProfileArgumentType; pub struct GameProfileArgumentType;
impl ArgumentType for GameProfileArgumentType { impl ArgumentType<CommandSource> for GameProfileArgumentType {
type Item = GameProfileResult; type Item = GameProfileResult;
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> { fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
@ -256,7 +256,7 @@ impl GameProfileArgumentType {
fn parse_with_allow_selectors( fn parse_with_allow_selectors(
reader: &mut StringReader, reader: &mut StringReader,
allow_selectors: bool, allow_selectors: bool,
) -> Result<<Self as ArgumentType>::Item, CommandSyntaxError> { ) -> Result<<Self as ArgumentType<CommandSource>>::Item, CommandSyntaxError> {
if reader.peek() == Some('@') { if reader.peek() == Some('@') {
// We read a selector variable. // We read a selector variable.
let parser = EntitySelectorParser::new(reader, allow_selectors); 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 /// Creates a [`Vec<String>`] of examples from
/// the given string literals. /// the given string literals.
#[macro_export]
macro_rules! examples { macro_rules! examples {
( $( $example:literal ),* ) => { ( $( $example:literal ),* ) => {
vec! [ 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;
pub mod entity_anchor; pub mod entity_anchor;
pub mod entity_selector; pub mod entity_selector;
pub mod game_profile; 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 objective;
pub mod particle;
pub mod placed_feature;
pub mod pool;
pub mod range;
pub mod resource;
pub mod resource_key; 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;
pub mod team_color;
pub mod template;
pub mod time;
pub mod uuid;

View File

@ -1,4 +1,5 @@
use crate::command::{ use crate::command::{
CommandSource,
argument_types::argument_type::{ArgumentType, JavaClientArgumentType}, argument_types::argument_type::{ArgumentType, JavaClientArgumentType},
context::command_context::CommandContext, context::command_context::CommandContext,
errors::command_syntax_error::CommandSyntaxError, errors::command_syntax_error::CommandSyntaxError,
@ -10,7 +11,7 @@ use crate::command::{
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct ObjectiveArgumentType; pub struct ObjectiveArgumentType;
impl ArgumentType for ObjectiveArgumentType { impl ArgumentType<CommandSource> for ObjectiveArgumentType {
type Item = String; type Item = String;
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> { 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::FromStringReader;
use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType}; use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
use crate::command::context::command_context::CommandContext; use crate::command::context::command_context::CommandContext;
@ -42,7 +43,7 @@ pub static ERROR_INVALID: CommandErrorType<0> = CommandErrorType::new(
translation::java::ARGUMENT_ID_INVALID, translation::java::ARGUMENT_ID_INVALID,
); );
impl ArgumentType for ResourceKeyArgument { impl ArgumentType<CommandSource> for ResourceKeyArgument {
type Item = ResourceKey; type Item = ResourceKey;
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> { fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {

View File

@ -1,4 +1,5 @@
use crate::command::{ use crate::command::{
CommandSource,
argument_types::argument_type::{ArgumentType, JavaClientArgumentType}, argument_types::argument_type::{ArgumentType, JavaClientArgumentType},
context::command_context::CommandContext, context::command_context::CommandContext,
errors::command_syntax_error::CommandSyntaxError, errors::command_syntax_error::CommandSyntaxError,
@ -10,7 +11,7 @@ use crate::command::{
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct TeamArgumentType; pub struct TeamArgumentType;
impl ArgumentType for TeamArgumentType { impl ArgumentType<CommandSource> for TeamArgumentType {
type Item = String; type Item = String;
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> { 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::TextComponent;
use pumpkin_util::text::hover::HoverEvent; use pumpkin_util::text::hover::HoverEvent;
use crate::command::CommandSource;
use crate::command::argument_builder::{ArgumentBuilder, argument, command, literal}; use crate::command::argument_builder::{ArgumentBuilder, argument, command, literal};
use crate::command::argument_types::FromStringReader; use crate::command::argument_types::FromStringReader;
use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType}; use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
@ -50,7 +51,7 @@ const ERROR_CREATE_FAILED: CommandErrorType<1> = CommandErrorType::new(
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub struct BossbarIdArgumentType; pub struct BossbarIdArgumentType;
impl ArgumentType for BossbarIdArgumentType { impl ArgumentType<CommandSource> for BossbarIdArgumentType {
type Item = String; type Item = String;
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> { 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"]; const COLORS: [&str; 7] = ["blue", "green", "pink", "purple", "red", "white", "yellow"];
impl ArgumentType for BossbarColorArgumentType { impl ArgumentType<CommandSource> for BossbarColorArgumentType {
type Item = BossbarColor; type Item = BossbarColor;
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> { fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
@ -138,7 +139,7 @@ const STYLES: [&str; 5] = [
"progress", "progress",
]; ];
impl ArgumentType for BossbarStyleArgumentType { impl ArgumentType<CommandSource> for BossbarStyleArgumentType {
type Item = BossbarDivisions; type Item = BossbarDivisions;
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> { 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::coordinates::vec3::Vec3ArgumentType;
use crate::command::argument_types::core::string::StringArgumentType; use crate::command::argument_types::core::string::StringArgumentType;
use crate::command::argument_types::entity::EntityArgumentType; 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::identifier::IdentifierArgumentType;
use crate::command::argument_types::nbt_path::{NbtPath, NbtPathArgumentType}; use crate::command::argument_types::nbt_path::{NbtPath, NbtPathArgumentType};
use crate::command::argument_types::objective::ObjectiveArgumentType; 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_builder::{ArgumentBuilder, argument, command};
use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType}; use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
use crate::command::argument_types::core::string::StringArgumentType; use crate::command::argument_types::core::string::StringArgumentType;
@ -35,7 +36,7 @@ enum HelpArgument {
} }
struct HelpArgumentType; struct HelpArgumentType;
impl ArgumentType for HelpArgumentType { impl ArgumentType<CommandSource> for HelpArgumentType {
type Item = HelpArgument; type Item = HelpArgument;
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> { fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
@ -68,7 +69,10 @@ impl ArgumentType for HelpArgumentType {
// We use greedy phrases for now // We use greedy phrases for now
// as the `?` command as the argument // as the `?` command as the argument
// doesn't work for the unquoted word one. // 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(); 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::rotation::RotationArgumentType;
use crate::command::argument_types::coordinates::vec3::Vec3ArgumentType; use crate::command::argument_types::coordinates::vec3::Vec3ArgumentType;
use crate::command::argument_types::entity::EntityArgumentType; 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::context::command_context::CommandContext;
use crate::command::node::dispatcher::CommandDispatcher; use crate::command::node::dispatcher::CommandDispatcher;
use crate::command::node::{CommandExecutor, CommandExecutorResult}; 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::rotation::RotationArgumentType;
use crate::command::argument_types::coordinates::vec3::Vec3ArgumentType; use crate::command::argument_types::coordinates::vec3::Vec3ArgumentType;
use crate::command::argument_types::entity::EntityArgumentType; 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::context::command_context::CommandContext;
use crate::command::errors::error_types::CommandErrorType; use crate::command::errors::error_types::CommandErrorType;
use crate::command::node::dispatcher::CommandDispatcher; use crate::command::node::dispatcher::CommandDispatcher;

View File

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

View File

@ -1,12 +1,16 @@
use crate::command::CommandSender; use crate::command::CommandSender;
use crate::command::argument_types::entity_anchor::EntityAnchor; use crate::command::argument_types::entity_anchor::{EntityAnchor, EntityAnchorExt};
use crate::command::errors::command_syntax_error::CommandSyntaxError;
use crate::command::errors::error_types::CommandErrorType;
use crate::entity::EntityBase; use crate::entity::EntityBase;
use crate::entity::player::Player; use crate::entity::player::Player;
use crate::server::Server; use crate::server::Server;
use crate::world::World; 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_data::translation;
use pumpkin_util::math::position::BlockPos;
use pumpkin_util::math::vector2::Vector2; use pumpkin_util::math::vector2::Vector2;
use pumpkin_util::math::vector3::Vector3; use pumpkin_util::math::vector3::Vector3;
use pumpkin_util::math::wrap_degrees; use pumpkin_util::math::wrap_degrees;
@ -23,50 +27,6 @@ pub const REQUIRES_ENTITY: CommandErrorType<0> = CommandErrorType::new(
translation::java::PERMISSIONS_REQUIRES_ENTITY, 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 /// Represents a source of a command, which
/// contains its own state, which could keep track of its: /// contains its own state, which could keep track of its:
/// - position /// - position
@ -505,28 +465,60 @@ impl CommandSource {
} }
} }
#[derive(Copy, Clone, PartialEq, Eq, Debug)] impl pumpkin_command::source::CommandSource for CommandSource {
pub enum ReturnValue { fn send_message(&self, message: TextComponent) {
Success(i32), self.send_message(message);
Failure, }
}
impl ReturnValue { fn send_error(&self, error: TextComponent) {
/// Get the success value of this return value. self.send_error(error);
#[must_use] }
pub const fn success_value(self) -> bool {
match self { fn call_result(&self, result: ReturnValue) {
Self::Success(_) => true, self.command_result_taker.call(result);
Self::Failure => false, }
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. fn entity_anchor(&self) -> EntityAnchor {
#[must_use] self.entity_anchor
pub const fn result_value(self) -> i32 { }
match self {
Self::Success(value) => value, fn anchor_position(&self, anchor: EntityAnchor) -> Vector3<f64> {
Self::Failure => 0, 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 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::BlockEntity;
use crate::block::entities::command_block::CommandBlockEntity; 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::EntityBase;
use crate::entity::player::Player; use crate::entity::player::Player;
use crate::server::Server; use crate::server::Server;
@ -23,18 +23,69 @@ use pumpkin_util::permission::{PermissionDefault, PermissionLvl};
use pumpkin_util::text::TextComponent; use pumpkin_util::text::TextComponent;
use pumpkin_util::translation::Locale; use pumpkin_util::translation::Locale;
pub use pumpkin_command::*;
pub mod argument_builder; pub mod argument_builder;
pub mod argument_types; pub mod argument_types;
pub mod client_suggestions; pub mod client_suggestions;
pub mod commands; pub mod commands;
pub mod context; pub mod context;
pub mod dispatcher;
pub mod errors;
pub mod node; pub mod node;
pub mod parser;
pub mod snbt; pub mod dispatcher {
pub mod string_reader; pub use pumpkin_command::dispatcher::*;
pub mod suggestion; 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. /// 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::dispatcher::CommandDispatcher;
pub use node::{Command, CommandExecutor, CommandExecutorResult}; 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_context::CommandContext;
use crate::command::context::command_source::CommandSource; 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. pub use pumpkin_command::node::*;
///
/// If the command **ran successfully**, an [`Ok`] is returned containing an [`i32`]. pub mod attached {
/// This represents the 'output value' of the command, which is *homologous* to the pub use pumpkin_command::node::attached::*;
/// `int` that command executors in vanilla return **upon success**. pub type AttachedNode =
/// pumpkin_command::node::attached::AttachedNode<crate::command::CommandSource>;
/// **You should choose the successful result as `1` if**: pub type RootAttachedNode =
/// - you don't know what value to use for a success for your pumpkin_command::node::attached::RootAttachedNode<crate::command::CommandSource>;
/// own commands, or pub type LiteralAttachedNode =
/// - you don't understand what this value means, or pumpkin_command::node::attached::LiteralAttachedNode<crate::command::CommandSource>;
/// - you just simply don't care about this value at all pub type CommandAttachedNode =
/// pumpkin_command::node::attached::CommandAttachedNode<crate::command::CommandSource>;
/// If the command **fails**, an [`Err`] is returned, containing the [`CommandSyntaxError`] pub type ArgumentAttachedNode =
/// that led to this result. pumpkin_command::node::attached::ArgumentAttachedNode<crate::command::CommandSource>;
pub type CommandExecutorResult = Result<i32, CommandSyntaxError>; }
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. /// A struct implementing this trait is able to run with a given context.
pub trait CommandExecutor: Sync + Send { pub trait CommandExecutor: Sync + Send {
/// Executes this executor for a command. /// 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 pub struct CommandExecutorAdapter<T>(pub T);
where
F: Fn(&CommandContext) -> CommandExecutorResult + Send + Sync, impl<T: CommandExecutor> pumpkin_command::node::CommandExecutor<CommandSource>
for CommandExecutorAdapter<T>
{ {
fn execute(&self, context: &CommandContext) -> CommandExecutorResult { fn execute(
self(context) &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 struct ArcCommandExecutorAdapter(pub std::sync::Arc<dyn CommandExecutor>);
pub type Command = Arc<dyn CommandExecutor>;
/// Represents the result of [`Arc<CommandSource>`]s from a [`CommandContext`]. impl pumpkin_command::node::CommandExecutor<CommandSource> for ArcCommandExecutorAdapter {
pub type RedirectModifierResult = Result<Vec<Arc<CommandSource>>, CommandSyntaxError>; fn execute(
&self,
/// A function that performs the required modification. context: &pumpkin_command::context::command_context::CommandContext<'_, CommandSource>,
pub type RedirectModifierExecutor = dyn Fn(&CommandContext) -> RedirectModifierResult + Send + Sync; ) -> pumpkin_command::node::CommandExecutorResult {
self.0.execute(context)
/// 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)
} }
} }

View File

@ -8,7 +8,7 @@ use std::{
net::SocketAddr, net::SocketAddr,
sync::{ sync::{
Arc, Arc,
atomic::{AtomicBool, AtomicU32, Ordering}, atomic::{AtomicBool, AtomicU32, AtomicUsize, Ordering},
}, },
}; };
@ -47,7 +47,7 @@ use pumpkin_protocol::{
serial::{PacketRead, PacketReadSlice}, serial::{PacketRead, PacketReadSlice},
}; };
use tokio::{ use tokio::{
sync::mpsc::{Receiver, Sender}, sync::mpsc::{Receiver, Sender, UnboundedReceiver, UnboundedSender, error::TryRecvError},
sync::{Mutex, RwLock, oneshot}, sync::{Mutex, RwLock, oneshot},
task::JoinHandle, task::JoinHandle,
}; };
@ -58,7 +58,10 @@ pub mod login;
use self::nethernet::NetherNetSession; use self::nethernet::NetherNetSession;
use crate::{ use crate::{
entity::player::Player, entity::player::Player,
net::{DisconnectReason, PacketHandlerResult, PacketRateLimiter}, net::{
DisconnectReason, MAX_PENDING_BYTES, PacketHandlerResult, PacketRateLimiter,
decrement_pending_bytes,
},
plugin::api::events::world::chunk_send::ChunkSend, plugin::api::events::world::chunk_send::ChunkSend,
server::Server, server::Server,
}; };
@ -101,12 +104,15 @@ pub struct BedrockClient {
tasks: TaskTracker, tasks: TaskTracker,
rt_handle: tokio::runtime::Handle, 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 /// 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_send: UnboundedSender<OutgoingPacket>,
outgoing_packet_priority_recv: Mutex<Option<Receiver<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. /// The packet encoder for outgoing packets.
network_writer: Arc<RwLock<BedrockBatchEncoder>>, network_writer: Arc<RwLock<BedrockBatchEncoder>>,
@ -135,8 +141,8 @@ impl BedrockClient {
be_clients: Arc<Mutex<HashMap<SocketAddr, Arc<Self>>>>, be_clients: Arc<Mutex<HashMap<SocketAddr, Arc<Self>>>>,
packet_limiter: PacketRateLimiter, packet_limiter: PacketRateLimiter,
) -> Self { ) -> Self {
let (send, recv) = tokio::sync::mpsc::channel(4096); let (send, recv) = tokio::sync::mpsc::unbounded_channel();
let (priority_send, priority_recv) = tokio::sync::mpsc::channel(4096); let (priority_send, priority_recv) = tokio::sync::mpsc::unbounded_channel();
let (incoming_send, incoming_recv) = tokio::sync::mpsc::channel(4096); let (incoming_send, incoming_recv) = tokio::sync::mpsc::channel(4096);
let rt_handle = tokio::runtime::Handle::current(); let rt_handle = tokio::runtime::Handle::current();
Self { Self {
@ -154,6 +160,7 @@ impl BedrockClient {
outgoing_packet_queue_recv: Mutex::new(Some(recv)), outgoing_packet_queue_recv: Mutex::new(Some(recv)),
outgoing_packet_priority_send: priority_send, outgoing_packet_priority_send: priority_send,
outgoing_packet_priority_recv: Mutex::new(Some(priority_recv)), outgoing_packet_priority_recv: Mutex::new(Some(priority_recv)),
pending_bytes: Arc::new(AtomicUsize::new(0)),
next_form_id: AtomicU32::new(0), next_form_id: AtomicU32::new(0),
inventory_opened: AtomicBool::new(false), inventory_opened: AtomicBool::new(false),
client_cache_supported: AtomicBool::new(false), client_cache_supported: AtomicBool::new(false),
@ -176,6 +183,8 @@ impl BedrockClient {
} }
pub fn start_outgoing_packet_task(self: &Arc<Self>) { pub fn start_outgoing_packet_task(self: &Arc<Self>) {
const MAX_BATCH_SIZE: usize = 64;
let client = self.clone(); let client = self.clone();
self.spawn_task(async move { self.spawn_task(async move {
let Some(mut packet_receiver) = client.outgoing_packet_queue_recv.lock().await.take() 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)); let mut interval = tokio::time::interval(std::time::Duration::from_millis(100));
while !client.close_token.is_cancelled() { loop {
let packet = tokio::select! { let recv_result = tokio::select! {
biased; biased;
() = client.close_token.cancelled() => break, res = priority_packet_receiver.recv() => res,
res = priority_packet_receiver.recv() => match res { res = packet_receiver.recv() => res,
Some(p) => p,
None => break,
},
_ = interval.tick() => { _ = interval.tick() => {
if !client.tick_connection().await { if !client.tick_connection().await {
break; break;
} }
continue; continue;
} }
res = packet_receiver.recv() => match res { () = client.close_token.cancelled() => {
Some(p) => p, priority_packet_receiver
None => break, .try_recv()
}, .ok()
.or_else(|| packet_receiver.try_recv().ok())
}
}; };
let data = packet.data.strip_prefix(&[BEDROCK_GAME_PACKET]); let Some(packet) = recv_result else {
let Some(data) = data else { break;
warn!("Refusing to send a non-game packet over NetherNet");
continue;
}; };
if let Err(error) = client.session.send(Bytes::copy_from_slice(data)).await {
warn!( let mut packet_batch = Vec::with_capacity(MAX_BATCH_SIZE);
"Failed to send NetherNet packet to {}: {error}", packet_batch.push(packet);
client.address
); while packet_batch.len() < MAX_BATCH_SIZE {
client.close().await; 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 { for packet in packet_batch {
let _ = completion.send(()); 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) { pub fn try_kick(&self, reason: DisconnectReason, message: String) {
warn!("Closing connection for {}: {message}", self.address);
let packet = CDisconnect::new(reason as i32, message); let packet = CDisconnect::new(reason as i32, message);
if let Ok(data) = self.serialize_packet(&packet) { 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() { if !self.close_token.is_cancelled() {
self.close_token.cancel(); self.close_token.cancel();
@ -280,6 +323,7 @@ impl BedrockClient {
} }
pub async fn kick(&self, reason: DisconnectReason, message: String) { 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)) self.send_packet(&CDisconnect::new(reason as i32, message))
.await; .await;
self.close().await; self.close().await;
@ -293,6 +337,7 @@ impl BedrockClient {
filtered_message: String, filtered_message: String,
send_packet: bool, send_packet: bool,
) { ) {
warn!("Closing connection for {}: {message}", self.address);
if send_packet { if send_packet {
self.send_packet(&CDisconnect { self.send_packet(&CDisconnect {
reason: pumpkin_protocol::codec::var_int::VarInt(reason as i32), reason: pumpkin_protocol::codec::var_int::VarInt(reason as i32),
@ -406,8 +451,9 @@ impl BedrockClient {
self.player.store(Arc::new(Some(player))); self.player.store(Arc::new(Some(player)));
} }
#[allow(clippy::unused_async)]
pub async fn enqueue_packet(&self, packet_data: Bytes) { 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) { pub fn try_enqueue_packet(&self, packet_data: Bytes) {
@ -420,37 +466,40 @@ impl BedrockClient {
/// # Arguments /// # Arguments
/// ///
/// * `packet_data`: A `Bytes` payload representing the encoded packet. /// * `packet_data`: A `Bytes` payload representing the encoded packet.
#[allow(clippy::unused_async)]
pub async fn enqueue_packet_data(&self, packet_data: Bytes) { pub async fn enqueue_packet_data(&self, packet_data: Bytes) {
if let Err(err) = self self.try_enqueue_packet_data(packet_data);
.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}");
}
}
} }
pub fn try_enqueue_packet_data(&self, packet_data: Bytes) { 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 if let Err(err) = self
.outgoing_packet_queue_send .outgoing_packet_queue_send
.try_send(OutgoingPacket::normal(packet_data)) .send(OutgoingPacket::normal(packet_data))
{ {
match err { decrement_pending_bytes(&self.pending_bytes, packet_len);
tokio::sync::mpsc::error::TrySendError::Full(_) => { // This is expected to fail if we are closed
debug!( if !self.is_closed() {
"Failed to add packet to the outgoing packet queue for client: channel full" error!("Failed to add packet to the outgoing packet queue for client: {err}");
);
}
tokio::sync::mpsc::error::TrySendError::Closed(_) => {
if !self.is_closed() {
error!(
"Failed to add packet to the outgoing packet queue for client: channel closed"
);
}
}
} }
} }
} }
@ -512,12 +561,32 @@ impl BedrockClient {
} }
pub async fn send_game_packet(&self, packet_data: Bytes) { 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(); let (tx, rx) = oneshot::channel();
if let Err(err) = self if let Err(err) = self
.outgoing_packet_priority_send .outgoing_packet_priority_send
.send(OutgoingPacket::priority(packet_data, tx)) .send(OutgoingPacket::priority(packet_data, tx))
.await
{ {
decrement_pending_bytes(&self.pending_bytes, packet_len);
if !self.is_closed() { if !self.is_closed() {
error!("Failed to add priority packet to the outgoing packet queue: {err}"); 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 pumpkin_world::level::SyncChunk;
use std::net::SocketAddr; 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::time::{Instant, SystemTime, UNIX_EPOCH};
use std::{collections::VecDeque, io::Write, sync::Arc}; use std::{collections::VecDeque, io::Write, sync::Arc};
@ -46,7 +46,7 @@ use tokio::{
sync::oneshot, sync::oneshot,
}; };
use tokio::{ use tokio::{
sync::mpsc::{Receiver, Sender, error::TryRecvError}, sync::mpsc::{UnboundedReceiver, UnboundedSender, error::TryRecvError},
task::JoinHandle, task::JoinHandle,
}; };
use tokio_util::sync::CancellationToken; use tokio_util::sync::CancellationToken;
@ -66,7 +66,8 @@ use pending::PendingConnection;
use crate::entity::player::Player; use crate::entity::player::Player;
use crate::net::{ 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::api::events::world::chunk_send::ChunkSend;
use crate::plugin::player::player_custom_payload::PlayerCustomPayloadEvent; 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. /// An notifier that is triggered when this client is closed.
close_token: CancellationToken, close_token: CancellationToken,
/// A normal-priority queue of serialized packets to send to the network. /// 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. /// 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. /// 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. /// 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. /// The packet encoder for outgoing packets.
network_writer: std::sync::Mutex<Option<TCPNetworkEncoder<BufWriter<OwnedWriteHalf>>>>, network_writer: std::sync::Mutex<Option<TCPNetworkEncoder<BufWriter<OwnedWriteHalf>>>>,
/// The packet decoder for incoming packets. /// The packet decoder for incoming packets.
@ -224,8 +227,8 @@ impl JavaClient {
gameprofile: GameProfile, gameprofile: GameProfile,
config: PlayerConfig, config: PlayerConfig,
) -> Self { ) -> Self {
let (send, recv) = tokio::sync::mpsc::channel(4096); let (send, recv) = tokio::sync::mpsc::unbounded_channel();
let (priority_send, priority_recv) = tokio::sync::mpsc::channel(4096); let (priority_send, priority_recv) = tokio::sync::mpsc::unbounded_channel();
Self { Self {
id: pending.id, id: pending.id,
@ -241,6 +244,7 @@ impl JavaClient {
outgoing_packet_queue_recv: Some(recv), outgoing_packet_queue_recv: Some(recv),
outgoing_packet_priority_send: priority_send, outgoing_packet_priority_send: priority_send,
outgoing_packet_priority_recv: Some(priority_recv), outgoing_packet_priority_recv: Some(priority_recv),
pending_bytes: Arc::new(AtomicUsize::new(0)),
version: pending.version, version: pending.version,
network_writer: std::sync::Mutex::new(Some(pending.network_writer)), network_writer: std::sync::Mutex::new(Some(pending.network_writer)),
network_reader: std::sync::Mutex::new(Some(pending.network_reader)), 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) { 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) { 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 if let Err(err) = self
.outgoing_packet_queue_send .outgoing_packet_queue_send
.send(OutgoingPacket::normal(packet_data)) .send(OutgoingPacket::normal(packet_data))
.await
{ {
decrement_pending_bytes(&self.pending_bytes, packet_len);
// This is expected to fail if we are closed // This is expected to fail if we are closed
if !self.close_token.is_cancelled() { if !self.close_token.is_cancelled() {
warn!( 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) { pub async fn await_close_interrupt(&self) {
self.close_token.cancelled().await; self.close_token.cancelled().await;
} }
@ -562,31 +567,34 @@ impl JavaClient {
} }
pub fn try_kick(&self, reason: &TextComponent) { pub fn try_kick(&self, reason: &TextComponent) {
match self.connection_state.load() { let serialized = match self.connection_state.load() {
ConnectionState::Login => { ConnectionState::Login => {
let packet = CLoginDisconnect::new( let packet = CLoginDisconnect::new(
serde_json::to_string(&reason.0).unwrap_or_else(|_| String::new()), serde_json::to_string(&reason.0).unwrap_or_else(|_| String::new()),
); );
if let Ok(data) = self.serialize_packet(&packet) { self.serialize_packet(&packet).ok()
self.try_enqueue_packet(data);
}
} }
ConnectionState::Config => { ConnectionState::Config => {
let reason_text = reason.clone().get_text(); let reason_text = reason.clone().get_text();
let packet = CConfigDisconnect::new(&reason_text); let packet = CConfigDisconnect::new(&reason_text);
if let Ok(data) = self.serialize_packet(&packet) { self.serialize_packet(&packet).ok()
self.try_enqueue_packet(data);
}
} }
ConnectionState::Play => { ConnectionState::Play => {
let packet = CPlayDisconnect::new(reason); let packet = CPlayDisconnect::new(reason);
if let Ok(data) = self.serialize_packet(&packet) { self.serialize_packet(&packet).ok()
self.try_enqueue_packet(data);
}
} }
_ => {} _ => 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(); 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(); self.close();
} }
@ -621,13 +630,33 @@ impl JavaClient {
} }
pub async fn send_packet_now_data(&self, packet: Bytes) { 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(); let (completion_tx, completion_rx) = oneshot::channel();
if let Err(err) = self if let Err(err) = self
.outgoing_packet_priority_send .outgoing_packet_priority_send
.send(OutgoingPacket::high_priority(packet, completion_tx)) .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 // It is expected that the packet will fail if we are closed
if !self.close_token.is_cancelled() { if !self.close_token.is_cancelled() {
warn!( warn!(
@ -701,6 +730,7 @@ impl JavaClient {
/// - **Status:** Handles status request and ping packets. /// - **Status:** Handles status request and ping packets.
/// - **Login/Transfer:** Handles login and transfer packets. /// - **Login/Transfer:** Handles login and transfer packets.
/// - **Config:** Handles configuration packets. /// - **Config:** Handles configuration packets.
#[expect(clippy::too_many_lines)]
pub fn start_outgoing_packet_task(&mut self) { pub fn start_outgoing_packet_task(&mut self) {
const MAX_BATCH_SIZE: usize = 64; const MAX_BATCH_SIZE: usize = 64;
@ -711,6 +741,7 @@ impl JavaClient {
return; return;
}; };
let close_token = self.close_token.clone(); let close_token = self.close_token.clone();
let pending_bytes = self.pending_bytes.clone();
let Some(mut writer) = self let Some(mut writer) = self
.network_writer .network_writer
.lock() .lock()
@ -721,12 +752,17 @@ impl JavaClient {
}; };
let id = self.id; let id = self.id;
self.spawn_task(async move { self.spawn_task(async move {
while !close_token.is_cancelled() { loop {
let recv_result = tokio::select! { let recv_result = tokio::select! {
biased; biased;
() = close_token.cancelled() => None,
res = priority_packet_receiver.recv() => res, res = priority_packet_receiver.recv() => res,
res = 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 { let Some(packet_data) = recv_result else {
@ -796,6 +832,9 @@ impl JavaClient {
send_failed = true; 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 { if send_failed {
// We now need to close the connection to the client since the stream is in an unknown state. // We now need to close the connection to the client since the stream is in an unknown state.
close_token.cancel(); close_token.cancel();

View File

@ -9,7 +9,10 @@ use pumpkin_world::level::SyncChunk;
use std::{ use std::{
net::SocketAddr, net::SocketAddr,
num::NonZero, num::NonZero,
sync::{Arc, atomic::Ordering}, sync::{
Arc,
atomic::{AtomicUsize, Ordering},
},
}; };
use pumpkin_data::translation; use pumpkin_data::translation;
@ -119,6 +122,17 @@ pub enum PacketHandlerResult {
ReadyToPlay(GameProfile, PlayerConfig), 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 /// This is just a Wrapper for both Java & Bedrock connections
#[expect(clippy::large_enum_variant)] #[expect(clippy::large_enum_variant)]
pub enum ClientPlatform { 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) { pub fn try_enqueue_packet_data(&self, packet_data: Bytes) {
match self { match self {
Self::Java(java) => java.try_enqueue_packet_data(packet_data), Self::Java(java) => java.try_enqueue_packet_data(packet_data),
@ -694,4 +716,18 @@ mod tests {
"Name containing DEL (127) should be invalid" "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);
}
} }