From 4f88d77ab1c33b5af8fd1c3ea18b70f7f03b7b68 Mon Sep 17 00:00:00 2001 From: Alexander Medvedev Date: Sun, 6 Sep 2026 20:55:19 +0200 Subject: [PATCH] chore(refactor): add pumpkin-command crate --- .gitignore | 2 + Cargo.lock | 20 + Cargo.toml | 2 + crates/pumpkin-command/Cargo.toml | 25 + .../pumpkin-command/src/argument_builder.rs | 589 +++++++++++++++ .../src}/argument_types/argument_type.rs | 46 +- .../src}/argument_types/attribute.rs | 11 +- .../src}/argument_types/block.rs | 11 +- .../src}/argument_types/block_predicate.rs | 28 +- .../src}/argument_types/component.rs | 21 +- .../src}/argument_types/coordinates/angle.rs | 23 +- .../argument_types/coordinates/block_pos.rs | 74 +- .../argument_types/coordinates/column_pos.rs | 14 +- .../src}/argument_types/coordinates/mod.rs | 22 +- .../argument_types/coordinates/rotation.rs | 19 +- .../argument_types/coordinates/swizzle.rs | 17 +- .../src}/argument_types/coordinates/vec2.rs | 28 +- .../src}/argument_types/coordinates/vec3.rs | 28 +- .../src}/argument_types/core/bool.rs | 9 +- .../src}/argument_types/core/double.rs | 9 +- .../src}/argument_types/core/float.rs | 9 +- .../src}/argument_types/core/integer.rs | 9 +- .../src}/argument_types/core/long.rs | 9 +- .../src}/argument_types/core/mod.rs | 2 +- .../src}/argument_types/core/string.rs | 16 +- .../src}/argument_types/dialog.rs | 10 +- .../src/argument_types/entity_anchor.rs | 85 +++ .../src}/argument_types/gamemode.rs | 16 +- .../src}/argument_types/hex_color.rs | 16 +- .../src}/argument_types/identifier.rs | 8 +- .../src}/argument_types/item.rs | 23 +- .../src}/argument_types/item_predicate.rs | 21 +- .../pumpkin-command/src/argument_types/mod.rs | 263 +++++++ .../src}/argument_types/nbt.rs | 16 +- .../src}/argument_types/nbt_path.rs | 18 +- .../src}/argument_types/particle.rs | 23 +- .../src}/argument_types/placed_feature.rs | 16 +- .../src}/argument_types/pool.rs | 16 +- .../src}/argument_types/range.rs | 9 +- .../src}/argument_types/resource.rs | 44 +- .../src}/argument_types/resource_or_tag.rs | 70 +- .../src}/argument_types/slot.rs | 20 +- .../src}/argument_types/sound_category.rs | 21 +- .../src}/argument_types/structure.rs | 16 +- .../src}/argument_types/team_color.rs | 16 +- .../src}/argument_types/template.rs | 16 +- .../src}/argument_types/time.rs | 25 +- .../src}/argument_types/uuid.rs | 4 +- .../src}/context/command_context.rs | 159 ++-- crates/pumpkin-command/src/context/mod.rs | 5 + .../src/context/string_range.rs | 76 ++ crates/pumpkin-command/src/dispatcher.rs | 1 + .../src}/errors/command_syntax_error.rs | 2 +- .../src}/errors/error_types.rs | 9 +- .../src}/errors/mod.rs | 0 crates/pumpkin-command/src/lib.rs | 20 + .../src}/node/attached.rs | 69 +- .../src}/node/detached.rs | 93 +-- .../src}/node/dispatcher.rs | 209 +++--- crates/pumpkin-command/src/node/mod.rs | 267 +++++++ .../src}/node/tree.rs | 83 +-- .../command => pumpkin-command/src}/parser.rs | 2 +- .../src}/snbt/markers.rs | 2 +- .../src}/snbt/mod.rs | 15 +- .../src}/snbt/operations.rs | 2 +- .../src}/snbt/rules.rs | 10 +- .../src}/snbt/tests.rs | 2 +- crates/pumpkin-command/src/source.rs | 189 +++++ .../src}/string_reader.rs | 6 +- .../src}/suggestion/mod.rs | 8 +- .../src}/suggestion/provider.rs | 9 +- .../src}/suggestion/suggestions.rs | 4 +- crates/pumpkin/Cargo.toml | 1 + .../pumpkin/src/command/argument_builder.rs | 696 ++++++------------ .../src/command/argument_types/entity.rs | 4 +- .../command/argument_types/entity_anchor.rs | 92 +-- .../command/argument_types/game_profile.rs | 4 +- .../pumpkin/src/command/argument_types/mod.rs | 237 +----- .../src/command/argument_types/objective.rs | 3 +- .../command/argument_types/resource_key.rs | 3 +- .../src/command/argument_types/team.rs | 3 +- .../pumpkin/src/command/commands/bossbar.rs | 7 +- .../pumpkin/src/command/commands/execute.rs | 2 +- crates/pumpkin/src/command/commands/help.rs | 8 +- crates/pumpkin/src/command/commands/rotate.rs | 4 +- .../pumpkin/src/command/commands/teleport.rs | 4 +- crates/pumpkin/src/command/commands/test.rs | 3 +- .../src/command/context/command_source.rs | 124 ++-- crates/pumpkin/src/command/context/mod.rs | 12 +- crates/pumpkin/src/command/dispatcher.rs | 1 - crates/pumpkin/src/command/mod.rs | 66 +- crates/pumpkin/src/command/node/mod.rs | 309 ++------ crates/pumpkin/src/net/bedrock/mod.rs | 187 +++-- crates/pumpkin/src/net/java/mod.rs | 151 ++-- crates/pumpkin/src/net/mod.rs | 38 +- 95 files changed, 2986 insertions(+), 2030 deletions(-) create mode 100644 crates/pumpkin-command/Cargo.toml create mode 100644 crates/pumpkin-command/src/argument_builder.rs rename crates/{pumpkin/src/command => pumpkin-command/src}/argument_types/argument_type.rs (80%) rename crates/{pumpkin/src/command => pumpkin-command/src}/argument_types/attribute.rs (86%) rename crates/{pumpkin/src/command => pumpkin-command/src}/argument_types/block.rs (86%) rename crates/{pumpkin/src/command => pumpkin-command/src}/argument_types/block_predicate.rs (88%) rename crates/{pumpkin/src/command => pumpkin-command/src}/argument_types/component.rs (65%) rename crates/{pumpkin/src/command => pumpkin-command/src}/argument_types/coordinates/angle.rs (74%) rename crates/{pumpkin/src/command => pumpkin-command/src}/argument_types/coordinates/block_pos.rs (68%) rename crates/{pumpkin/src/command => pumpkin-command/src}/argument_types/coordinates/column_pos.rs (89%) rename crates/{pumpkin/src/command => pumpkin-command/src}/argument_types/coordinates/mod.rs (95%) rename crates/{pumpkin/src/command => pumpkin-command/src}/argument_types/coordinates/rotation.rs (76%) rename crates/{pumpkin/src/command => pumpkin-command/src}/argument_types/coordinates/swizzle.rs (81%) rename crates/{pumpkin/src/command => pumpkin-command/src}/argument_types/coordinates/vec2.rs (75%) rename crates/{pumpkin/src/command => pumpkin-command/src}/argument_types/coordinates/vec3.rs (78%) rename crates/{pumpkin/src/command => pumpkin-command/src}/argument_types/core/bool.rs (85%) rename crates/{pumpkin/src/command => pumpkin-command/src}/argument_types/core/double.rs (93%) rename crates/{pumpkin/src/command => pumpkin-command/src}/argument_types/core/float.rs (93%) rename crates/{pumpkin/src/command => pumpkin-command/src}/argument_types/core/integer.rs (94%) rename crates/{pumpkin/src/command => pumpkin-command/src}/argument_types/core/long.rs (94%) rename crates/{pumpkin/src/command => pumpkin-command/src}/argument_types/core/mod.rs (98%) rename crates/{pumpkin/src/command => pumpkin-command/src}/argument_types/core/string.rs (91%) rename crates/{pumpkin/src/command => pumpkin-command/src}/argument_types/dialog.rs (83%) create mode 100644 crates/pumpkin-command/src/argument_types/entity_anchor.rs rename crates/{pumpkin/src/command => pumpkin-command/src}/argument_types/gamemode.rs (70%) rename crates/{pumpkin/src/command => pumpkin-command/src}/argument_types/hex_color.rs (86%) rename crates/{pumpkin/src/command => pumpkin-command/src}/argument_types/identifier.rs (80%) rename crates/{pumpkin/src/command => pumpkin-command/src}/argument_types/item.rs (84%) rename crates/{pumpkin/src/command => pumpkin-command/src}/argument_types/item_predicate.rs (88%) create mode 100644 crates/pumpkin-command/src/argument_types/mod.rs rename crates/{pumpkin/src/command => pumpkin-command/src}/argument_types/nbt.rs (85%) rename crates/{pumpkin/src/command => pumpkin-command/src}/argument_types/nbt_path.rs (98%) rename crates/{pumpkin/src/command => pumpkin-command/src}/argument_types/particle.rs (69%) rename crates/{pumpkin/src/command => pumpkin-command/src}/argument_types/placed_feature.rs (67%) rename crates/{pumpkin/src/command => pumpkin-command/src}/argument_types/pool.rs (66%) rename crates/{pumpkin/src/command => pumpkin-command/src}/argument_types/range.rs (96%) rename crates/{pumpkin/src/command => pumpkin-command/src}/argument_types/resource.rs (91%) rename crates/{pumpkin/src/command => pumpkin-command/src}/argument_types/resource_or_tag.rs (77%) rename crates/{pumpkin/src/command => pumpkin-command/src}/argument_types/slot.rs (81%) rename crates/{pumpkin/src/command => pumpkin-command/src}/argument_types/sound_category.rs (74%) rename crates/{pumpkin/src/command => pumpkin-command/src}/argument_types/structure.rs (67%) rename crates/{pumpkin/src/command => pumpkin-command/src}/argument_types/team_color.rs (75%) rename crates/{pumpkin/src/command => pumpkin-command/src}/argument_types/template.rs (67%) rename crates/{pumpkin/src/command => pumpkin-command/src}/argument_types/time.rs (88%) rename crates/{pumpkin/src/command => pumpkin-command/src}/argument_types/uuid.rs (93%) rename crates/{pumpkin/src/command => pumpkin-command/src}/context/command_context.rs (85%) create mode 100644 crates/pumpkin-command/src/context/mod.rs create mode 100644 crates/pumpkin-command/src/context/string_range.rs create mode 100644 crates/pumpkin-command/src/dispatcher.rs rename crates/{pumpkin/src/command => pumpkin-command/src}/errors/command_syntax_error.rs (97%) rename crates/{pumpkin/src/command => pumpkin-command/src}/errors/error_types.rs (98%) rename crates/{pumpkin/src/command => pumpkin-command/src}/errors/mod.rs (100%) create mode 100644 crates/pumpkin-command/src/lib.rs rename crates/{pumpkin/src/command => pumpkin-command/src}/node/attached.rs (88%) rename crates/{pumpkin/src/command => pumpkin-command/src}/node/detached.rs (69%) rename crates/{pumpkin/src/command => pumpkin-command/src}/node/dispatcher.rs (88%) create mode 100644 crates/pumpkin-command/src/node/mod.rs rename crates/{pumpkin/src/command => pumpkin-command/src}/node/tree.rs (90%) rename crates/{pumpkin/src/command => pumpkin-command/src}/parser.rs (99%) rename crates/{pumpkin/src/command => pumpkin-command/src}/snbt/markers.rs (99%) rename crates/{pumpkin/src/command => pumpkin-command/src}/snbt/mod.rs (97%) rename crates/{pumpkin/src/command => pumpkin-command/src}/snbt/operations.rs (95%) rename crates/{pumpkin/src/command => pumpkin-command/src}/snbt/rules.rs (99%) rename crates/{pumpkin/src/command => pumpkin-command/src}/snbt/tests.rs (99%) create mode 100644 crates/pumpkin-command/src/source.rs rename crates/{pumpkin/src/command => pumpkin-command/src}/string_reader.rs (98%) rename crates/{pumpkin/src/command => pumpkin-command/src}/suggestion/mod.rs (98%) rename crates/{pumpkin/src/command => pumpkin-command/src}/suggestion/provider.rs (69%) rename crates/{pumpkin/src/command => pumpkin-command/src}/suggestion/suggestions.rs (99%) delete mode 100644 crates/pumpkin/src/command/dispatcher.rs diff --git a/.gitignore b/.gitignore index 8eb9a1b20..1ec704f03 100644 --- a/.gitignore +++ b/.gitignore @@ -111,6 +111,7 @@ data/* # project's configurations pumpkin.toml nethernet-key.der +.pumpkin-server-id *.local.nbt @@ -150,6 +151,7 @@ whitelist.json .console_history bukkit.yml commands.yml +/config/ help.yml permissions.yml spigot.yml diff --git a/Cargo.lock b/Cargo.lock index 6b8903371..ea3b66478 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3356,6 +3356,7 @@ dependencies = [ "ordered-float", "postcard", "pumpkin-codecs", + "pumpkin-command", "pumpkin-config", "pumpkin-data", "pumpkin-gametest", @@ -3435,6 +3436,25 @@ dependencies = [ "wit-encoder", ] +[[package]] +name = "pumpkin-command" +version = "0.1.0+26.2-26.45" +dependencies = [ + "itertools 0.15.0", + "pumpkin-codecs", + "pumpkin-data", + "pumpkin-nbt", + "pumpkin-protocol", + "pumpkin-util", + "pumpkin-world", + "rustc-hash", + "serde", + "serde_json", + "thiserror 2.0.20", + "tracing", + "uuid", +] + [[package]] name = "pumpkin-config" version = "0.1.0+26.2-26.45" diff --git a/Cargo.toml b/Cargo.toml index e18ef8a99..5cfdc8b2e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ members = [ "crates/pumpkin", "crates/pumpkin-api-macros", "crates/pumpkin-codecs", + "crates/pumpkin-command", "crates/pumpkin-config", "crates/pumpkin-data", "crates/pumpkin-gametest", @@ -176,6 +177,7 @@ p384 = { version = "0.14.0", default-features = false, features = ["std", "arith phf = { version = "0.14.0", default-features = false, features = ["std"] } proc-macro2 = { version = "1.0", default-features = false, features = ["proc-macro"] } pumpkin-codecs = { path = "crates/pumpkin-codecs", default-features = false } +pumpkin-command = { path = "crates/pumpkin-command", default-features = false } pumpkin-config = { path = "crates/pumpkin-config", default-features = false } pumpkin-data = { path = "crates/pumpkin-data", default-features = false } pumpkin-inventory = { path = "crates/pumpkin-inventory", default-features = false } diff --git a/crates/pumpkin-command/Cargo.toml b/crates/pumpkin-command/Cargo.toml new file mode 100644 index 000000000..a8dc46258 --- /dev/null +++ b/crates/pumpkin-command/Cargo.toml @@ -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 diff --git a/crates/pumpkin-command/src/argument_builder.rs b/crates/pumpkin-command/src/argument_builder.rs new file mode 100644 index 000000000..a63e35c12 --- /dev/null +++ b/crates/pumpkin-command/src/argument_builder.rs @@ -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 { + pub global_id: GlobalNodeId, + pub arguments: FxHashMap>, + pub command: Option>, + pub requirements: Requirements, + pub target: Option, + pub modifier: RedirectModifier, + pub forks: bool, +} + +impl CommonArgumentBuilder { + fn new() -> Self { + Self { + global_id: GlobalNodeId::new(), + arguments: FxHashMap::default(), + command: None, + requirements: Requirements::new(), + target: None, + modifier: RedirectModifier::KeepSource, + forks: false, + } + } +} + +impl Default for CommonArgumentBuilder { + 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::("foo", "A test command"); +/// ``` +/// +/// The builder returned will eventually construct a [`CommandDetachedNode`]. +/// This node can then be registered into a dispatcher. +pub fn command( + literal: impl Into>, + description: impl Into>, +) -> CommandArgumentBuilder { + 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::("bar"); +/// ``` +/// +/// The builder returned will eventually construct a [`LiteralDetachedNode`]. +pub fn literal( + literal: impl Into>, +) -> LiteralArgumentBuilder { + 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::("bar", IntegerArgumentType::new(1, 10)); +/// ``` +/// +/// The builder returned will eventually construct a [`ArgumentDetachedNode`]. +pub fn argument( + name: impl Into>, + arg_type: impl AnyArgumentType + 'static, +) -> RequiredArgumentBuilder { + RequiredArgumentBuilder::new(name, arg_type) +} + +/// A builder that builds a literal, non-command [`DetachedNode`]. +pub struct LiteralArgumentBuilder { + 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, +} + +/// A builder that builds an argument [`DetachedNode`]. +pub struct RequiredArgumentBuilder { + common: CommonArgumentBuilder, + name: Cow<'static, str>, + argument_type: Arc>, + suggestion_provider: Option>>, +} + +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, +>: 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>) -> Self; + + /// Gets the command to execute for the node being built. + #[must_use] + fn command(&self) -> Option>; + + /// Sets the command to execute for the node being built. + #[must_use] + fn executes(self, command: impl CommandExecutor + 'static) -> Self { + self.executes_arc(Arc::new(command)) + } + + /// Sets the command to execute for the node being built. + #[must_use] + fn executes_arc(self, command: Arc + 'static>) -> Self; + + /// Sets the redirect target of the node being built to another, without a modifier. + #[must_use] + fn redirect(self, redirection: impl Into) -> 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, + redirect_modifier: RedirectModifier, + ) -> Self; + + /// Forks the given context, using multiple for later. + #[must_use] + fn fork( + self, + redirection: impl Into, + redirect_modifier: RedirectModifier, + ) -> Self; + + /// Forwards the given context, with the given `fork` flag. + #[must_use] + fn forward( + self, + redirection: impl Into, + redirect_modifier: RedirectModifier, + fork: bool, + ) -> Self; + + /// Gets a reference to the arguments of the node to be built. + #[must_use] + fn arguments(&self) -> &FxHashMap>; + + /// Gets the node to which the node being built by this [`ArgumentBuilder`] redirects. + #[must_use] + fn target(&self) -> Option; + + /// 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>) -> Self; + + /// Overwrites the current requirements of this node to a new value. + #[must_use] + fn overwrite_requirements(self, requirements: Requirements) -> Self; + + /// Gets the redirect modifier of the node this [`ArgumentBuilder`] is building. + #[must_use] + fn redirect_modifier(&self) -> RedirectModifier; + + /// Whether this builder forks. + #[must_use] + fn forks(&self) -> bool; + + /// Returns the 'future [`GlobalNodeId`]' of the node that will be produced by this Builder. + /// Very useful for redirects. + #[must_use] + fn id(&self) -> GlobalNodeId; + + /// Builds the node represented by this builder, consuming itself in the process. + #[must_use] + fn build(self) -> N; +} + +// Implement the private trait for our builders! +impl Sealed for LiteralArgumentBuilder {} +impl Sealed for CommandArgumentBuilder {} +impl Sealed for RequiredArgumentBuilder {} + +/// Helper macro to implement repeated code of `ArgumentBuilder` for our types. +macro_rules! impl_boilerplate_argument_builder { + ($S:ident) => { + fn then(mut self, argument: impl Into>) -> 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> { + self.common.command.clone() + } + + fn executes_arc(mut self, command: Arc + 'static>) -> Self { + self.common.command = Some(command); + self + } + + fn requires(mut self, requirement: impl Into>) -> 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) -> Self { + self.forward(redirection.into(), RedirectModifier::KeepSource, false) + } + + fn redirect_with_modifier( + self, + redirection: impl Into, + redirect_modifier: RedirectModifier<$S>, + ) -> Self { + self.forward(redirection.into(), redirect_modifier, false) + } + + fn fork( + self, + redirection: impl Into, + redirect_modifier: RedirectModifier<$S>, + ) -> Self { + self.forward(redirection.into(), redirect_modifier, true) + } + + fn forward( + mut self, + redirection: impl Into, + 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> { + &self.common.arguments + } + + fn target(&self) -> Option { + 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 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>) -> 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>, + description: impl Into>, + ) -> 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) -> Self { + self.source = Some(source.into()); + self + } +} + +impl RequiredArgumentBuilder { + /// Creates a new [`RequiredArgumentBuilder`] from a name and an argument type. + pub fn new( + name: impl Into>, + arg_type: impl AnyArgumentType + 'static, + ) -> Self { + Self { + common: CommonArgumentBuilder::new(), + name: name.into(), + argument_type: Arc::new(arg_type), + suggestion_provider: None, + } + } + + /// Sets the [`SuggestionProvider`] of this builder for the `ArgumentDetachedNode`. + #[must_use] + pub fn suggests(self, provider: impl SuggestionProvider + 'static) -> Self { + self.suggests_arc(Arc::new(provider)) + } + + /// Sets the [`SuggestionProvider`] of this builder for the `ArgumentDetachedNode`. + #[must_use] + pub fn suggests_arc(mut self, provider: Arc>) -> Self { + self.suggestion_provider = Some(provider); + self + } +} + +impl ArgumentBuilder> for LiteralArgumentBuilder { + impl_boilerplate_argument_builder!(S); + + fn build(self) -> LiteralDetachedNode { + let mut node = LiteralDetachedNode::new( + self.common.global_id, + self.literal, + self.common.command, + self.common.requirements, + self.common.target, + self.common.modifier, + self.common.forks, + ); + node.children = self.common.arguments; + node + } +} + +impl ArgumentBuilder> for CommandArgumentBuilder { + impl_boilerplate_argument_builder!(S); + + fn build(self) -> CommandDetachedNode { + let mut node = CommandDetachedNode::new( + self.common.global_id, + self.literal, + self.description, + self.common.command, + self.common.requirements, + self.common.target, + self.common.modifier, + self.common.forks, + ); + node.meta.source = self.source; + node.children = self.common.arguments; + node + } +} + +impl ArgumentBuilder> for RequiredArgumentBuilder { + impl_boilerplate_argument_builder!(S); + + fn build(self) -> ArgumentDetachedNode { + let mut node = ArgumentDetachedNode::new( + self.common.global_id, + self.name, + self.argument_type, + self.common.command, + self.common.requirements, + self.common.target, + self.common.modifier, + self.common.forks, + self.suggestion_provider, + ); + node.children = self.common.arguments; + node + } +} + +#[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::("test"); + let node = builder.build(); + + assert_eq!(node.meta.literal, "test"); + } + + #[test] + fn required_one() { + let builder = argument::("test", IntegerArgumentType::new(1, 10)); + let node = builder.build(); + + assert_eq!(node.meta.name, "test"); + + let mut reader1 = StringReader::new("5"); + let mut reader2 = StringReader::new("11"); + + let boxed_result = node + .meta + .argument_type + .parse(&mut reader1) + .expect("The parsing should not have errored"); + let result = boxed_result + .downcast::() + .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")); + } +} diff --git a/crates/pumpkin/src/command/argument_types/argument_type.rs b/crates/pumpkin-command/src/argument_types/argument_type.rs similarity index 80% rename from crates/pumpkin/src/command/argument_types/argument_type.rs rename to crates/pumpkin-command/src/argument_types/argument_type.rs index 9ce858f32..3a1bb6dc8 100644 --- a/crates/pumpkin/src/command/argument_types/argument_type.rs +++ b/crates/pumpkin-command/src/argument_types/argument_type.rs @@ -1,20 +1,17 @@ use pumpkin_protocol::java::client::play::SuggestionProviders; -use crate::command::argument_types::argument_type::sealed::Sealed; -use crate::command::context::command_context::CommandContext; -use crate::command::context::command_source::CommandSource; -use crate::command::suggestion::suggestions::Suggestions; -use crate::command::suggestion::suggestions::SuggestionsBuilder; -use crate::command::{ - errors::command_syntax_error::CommandSyntaxError, string_reader::StringReader, -}; +use crate::context::command_context::CommandContext; +use crate::source::{CommandSource, DummySource}; +use crate::suggestion::suggestions::Suggestions; +use crate::suggestion::suggestions::SuggestionsBuilder; +use crate::{errors::command_syntax_error::CommandSyntaxError, string_reader::StringReader}; use std::any::Any; pub type JavaClientArgumentType = pumpkin_protocol::java::client::play::ArgumentType; pub type ParseWithSourceAnyResult = Result, CommandSyntaxError>; /// Represents an argument type that parses a particular type `Item`. -pub trait ArgumentType: Send + Sync { +pub trait ArgumentType: Send + Sync { /// The data type that this argument type parses. type Item: Send + Sync; @@ -32,7 +29,7 @@ pub trait ArgumentType: Send + Sync { fn parse_with_source( &self, reader: &mut StringReader, - _source: &CommandSource, + _source: &S, ) -> Result { self.parse(reader) } @@ -41,7 +38,7 @@ pub trait ArgumentType: Send + Sync { #[must_use] fn list_suggestions( &self, - _context: &CommandContext, + _context: &CommandContext, _builder: SuggestionsBuilder, ) -> Suggestions { Suggestions::empty() @@ -73,11 +70,11 @@ pub trait ArgumentType: Send + Sync { // only for `AnyArgumentType`. mod sealed { /// Private trait to ensure only types implementing `ArgumentType` can implement `AnyArgumentType`. - pub trait Sealed {} + pub trait Sealed {} } /// Represents an argument type with any parsable type. -pub trait AnyArgumentType: Sealed + Send + Sync { +pub trait AnyArgumentType: sealed::Sealed + Send + Sync { /// Parses a value by using a [`StringReader`]. Call this only if you have no source. /// /// Errors should be propagated using the `?` operator, which will @@ -91,17 +88,13 @@ pub trait AnyArgumentType: Sealed + Send + Sync { /// /// Errors should be propagated using the `?` operator, which will /// replicate Brigadier's behavior of exceptions. - fn parse_with_source( - &self, - reader: &mut StringReader, - source: &CommandSource, - ) -> ParseWithSourceAnyResult; + fn parse_with_source(&self, reader: &mut StringReader, source: &S) -> ParseWithSourceAnyResult; /// Provides a list of suggestions from this argument type. #[must_use] fn list_suggestions( &self, - context: &CommandContext, + context: &CommandContext, builder: SuggestionsBuilder, ) -> Suggestions; @@ -129,9 +122,12 @@ pub trait AnyArgumentType: Sealed + Send + Sync { } // Implement our private trait for all argument types. -impl, T: Send + Sync + 'static> Sealed for U {} +impl, S: CommandSource> sealed::Sealed for U {} -impl + 'static, T: Send + Sync + 'static> AnyArgumentType for U { +impl + 'static, S: CommandSource + 'static> AnyArgumentType for U +where + U::Item: 'static, +{ fn parse( &self, reader: &mut StringReader, @@ -142,11 +138,7 @@ impl + 'static, T: Send + Sync + 'static> AnyArgumentT } } - fn parse_with_source( - &self, - reader: &mut StringReader, - source: &CommandSource, - ) -> ParseWithSourceAnyResult { + fn parse_with_source(&self, reader: &mut StringReader, source: &S) -> ParseWithSourceAnyResult { match self.parse_with_source(reader, source) { Ok(value) => { let value: Box = Box::new(value); @@ -158,7 +150,7 @@ impl + 'static, T: Send + Sync + 'static> AnyArgumentT fn list_suggestions( &self, - context: &CommandContext, + context: &CommandContext, builder: SuggestionsBuilder, ) -> Suggestions { self.list_suggestions(context, builder) diff --git a/crates/pumpkin/src/command/argument_types/attribute.rs b/crates/pumpkin-command/src/argument_types/attribute.rs similarity index 86% rename from crates/pumpkin/src/command/argument_types/attribute.rs rename to crates/pumpkin-command/src/argument_types/attribute.rs index 4bcf87304..f28e81d78 100644 --- a/crates/pumpkin/src/command/argument_types/attribute.rs +++ b/crates/pumpkin-command/src/argument_types/attribute.rs @@ -1,4 +1,4 @@ -use crate::command::{ +use crate::{ argument_types::FromStringReader, argument_types::argument_type::{ArgumentType, JavaClientArgumentType}, context::command_context::CommandContext, @@ -20,7 +20,7 @@ pub const INVALID_ATTRIBUTE_ERROR_TYPE: CommandErrorType<1> = CommandErrorType:: #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct AttributeArgumentType; -impl ArgumentType for AttributeArgumentType { +impl ArgumentType for AttributeArgumentType { type Item = Attributes; fn parse(&self, reader: &mut StringReader) -> Result { @@ -46,7 +46,7 @@ impl ArgumentType for AttributeArgumentType { fn list_suggestions( &self, - _context: &CommandContext, + _context: &CommandContext, builder: SuggestionsBuilder, ) -> Suggestions { builder @@ -56,7 +56,10 @@ impl ArgumentType for AttributeArgumentType { } impl AttributeArgumentType { - pub fn get(context: &CommandContext, name: &str) -> Result { + pub fn get( + context: &CommandContext, + name: &str, + ) -> Result { context.get_argument::(name).cloned() } } diff --git a/crates/pumpkin/src/command/argument_types/block.rs b/crates/pumpkin-command/src/argument_types/block.rs similarity index 86% rename from crates/pumpkin/src/command/argument_types/block.rs rename to crates/pumpkin-command/src/argument_types/block.rs index 9eb58a17a..61526e1f7 100644 --- a/crates/pumpkin/src/command/argument_types/block.rs +++ b/crates/pumpkin-command/src/argument_types/block.rs @@ -1,4 +1,4 @@ -use crate::command::{ +use crate::{ argument_types::argument_type::{ArgumentType, JavaClientArgumentType}, context::command_context::CommandContext, errors::command_syntax_error::CommandSyntaxError, @@ -17,7 +17,7 @@ pub const INVALID_BLOCK_ERROR_TYPE: CommandErrorType<1> = CommandErrorType::new( #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct BlockArgumentType; -impl ArgumentType for BlockArgumentType { +impl ArgumentType for BlockArgumentType { type Item = &'static Block; fn parse(&self, reader: &mut StringReader) -> Result { @@ -46,7 +46,7 @@ impl ArgumentType for BlockArgumentType { fn list_suggestions( &self, - _context: &CommandContext, + _context: &CommandContext, builder: SuggestionsBuilder, ) -> Suggestions { builder.build() @@ -54,7 +54,10 @@ impl ArgumentType for BlockArgumentType { } impl BlockArgumentType { - pub fn get(context: &CommandContext, name: &str) -> Result<&'static Block, CommandSyntaxError> { + pub fn get( + context: &CommandContext, + name: &str, + ) -> Result<&'static Block, CommandSyntaxError> { context.get_argument::<&'static Block>(name).copied() } } diff --git a/crates/pumpkin/src/command/argument_types/block_predicate.rs b/crates/pumpkin-command/src/argument_types/block_predicate.rs similarity index 88% rename from crates/pumpkin/src/command/argument_types/block_predicate.rs rename to crates/pumpkin-command/src/argument_types/block_predicate.rs index 81ab2e91b..2cfff7efa 100644 --- a/crates/pumpkin/src/command/argument_types/block_predicate.rs +++ b/crates/pumpkin-command/src/argument_types/block_predicate.rs @@ -5,14 +5,14 @@ use pumpkin_data::{Block, translation}; use pumpkin_nbt::compound::NbtCompound; use pumpkin_util::text::TextComponent; -use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType}; -use crate::command::argument_types::block::INVALID_BLOCK_ERROR_TYPE; -use crate::command::argument_types::nbt::NbtCompoundArgumentType; -use crate::command::context::command_context::CommandContext; -use crate::command::errors::command_syntax_error::CommandSyntaxError; -use crate::command::errors::error_types::CommandErrorType; -use crate::command::string_reader::StringReader; -use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder}; +use crate::argument_types::argument_type::{ArgumentType, JavaClientArgumentType}; +use crate::argument_types::block::INVALID_BLOCK_ERROR_TYPE; +use crate::argument_types::nbt::NbtCompoundArgumentType; +use crate::context::command_context::CommandContext; +use crate::errors::command_syntax_error::CommandSyntaxError; +use crate::errors::error_types::CommandErrorType; +use crate::string_reader::StringReader; +use crate::suggestion::suggestions::{Suggestions, SuggestionsBuilder}; pub const ERROR_UNKNOWN_TAG: CommandErrorType<1> = CommandErrorType::new( translation::java::ARGUMENTS_BLOCK_TAG_UNKNOWN, @@ -117,7 +117,8 @@ fn parse_properties( fn parse_nbt(reader: &mut StringReader) -> Result, CommandSyntaxError> { if reader.peek() == Some('{') { - let tag = NbtCompoundArgumentType.parse(reader)?; + let tag = + ArgumentType::::parse(&NbtCompoundArgumentType, reader)?; Ok(Some(tag)) } else { Ok(None) @@ -126,7 +127,7 @@ fn parse_nbt(reader: &mut StringReader) -> Result, CommandSy pub struct BlockPredicateArgumentType; -impl ArgumentType for BlockPredicateArgumentType { +impl ArgumentType for BlockPredicateArgumentType { type Item = BlockPredicate; fn parse(&self, reader: &mut StringReader) -> Result { @@ -214,7 +215,7 @@ impl ArgumentType for BlockPredicateArgumentType { fn list_suggestions( &self, - _context: &CommandContext, + _context: &CommandContext, builder: SuggestionsBuilder, ) -> Suggestions { builder.build() @@ -222,7 +223,10 @@ impl ArgumentType for BlockPredicateArgumentType { } impl BlockPredicateArgumentType { - pub fn get(context: &CommandContext, name: &str) -> Result { + pub fn get( + context: &CommandContext, + name: &str, + ) -> Result { Ok(context.get_argument::(name)?.clone()) } } diff --git a/crates/pumpkin/src/command/argument_types/component.rs b/crates/pumpkin-command/src/argument_types/component.rs similarity index 65% rename from crates/pumpkin/src/command/argument_types/component.rs rename to crates/pumpkin-command/src/argument_types/component.rs index 17cb4430a..899d768a1 100644 --- a/crates/pumpkin/src/command/argument_types/component.rs +++ b/crates/pumpkin-command/src/argument_types/component.rs @@ -1,12 +1,12 @@ use pumpkin_data::translation; use pumpkin_util::text::TextComponent; -use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType}; -use crate::command::context::command_context::CommandContext; -use crate::command::errors::command_syntax_error::CommandSyntaxError; -use crate::command::errors::error_types::CommandErrorType; -use crate::command::string_reader::StringReader; -use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder}; +use crate::argument_types::argument_type::{ArgumentType, JavaClientArgumentType}; +use crate::context::command_context::CommandContext; +use crate::errors::command_syntax_error::CommandSyntaxError; +use crate::errors::error_types::CommandErrorType; +use crate::string_reader::StringReader; +use crate::suggestion::suggestions::{Suggestions, SuggestionsBuilder}; pub const ERROR_INVALID_JSON: CommandErrorType<1> = CommandErrorType::new( translation::java::ARGUMENT_COMPONENT_INVALID, @@ -16,7 +16,7 @@ pub const ERROR_INVALID_JSON: CommandErrorType<1> = CommandErrorType::new( #[derive(Clone, Copy)] pub struct ComponentArgumentType; -impl ArgumentType for ComponentArgumentType { +impl ArgumentType for ComponentArgumentType { type Item = TextComponent; fn parse(&self, reader: &mut StringReader) -> Result { @@ -40,7 +40,7 @@ impl ArgumentType for ComponentArgumentType { fn list_suggestions( &self, - _context: &CommandContext, + _context: &CommandContext, builder: SuggestionsBuilder, ) -> Suggestions { builder.build() @@ -48,7 +48,10 @@ impl ArgumentType for ComponentArgumentType { } impl ComponentArgumentType { - pub fn get(context: &CommandContext, name: &str) -> Result { + pub fn get( + context: &CommandContext, + name: &str, + ) -> Result { context.get_argument::(name).cloned() } } diff --git a/crates/pumpkin/src/command/argument_types/coordinates/angle.rs b/crates/pumpkin-command/src/argument_types/coordinates/angle.rs similarity index 74% rename from crates/pumpkin/src/command/argument_types/coordinates/angle.rs rename to crates/pumpkin-command/src/argument_types/coordinates/angle.rs index 3c386dec1..60b7f5719 100644 --- a/crates/pumpkin/src/command/argument_types/coordinates/angle.rs +++ b/crates/pumpkin-command/src/argument_types/coordinates/angle.rs @@ -1,12 +1,12 @@ use pumpkin_data::translation; use pumpkin_util::math::wrap_degrees; -use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType}; -use crate::command::context::command_context::CommandContext; -use crate::command::context::command_source::CommandSource; -use crate::command::errors::command_syntax_error::CommandSyntaxError; -use crate::command::errors::error_types::CommandErrorType; -use crate::command::string_reader::StringReader; +use crate::argument_types::argument_type::{ArgumentType, JavaClientArgumentType}; +use crate::context::command_context::CommandContext; +use crate::errors::command_syntax_error::CommandSyntaxError; +use crate::errors::error_types::CommandErrorType; +use crate::source::CommandSource; +use crate::string_reader::StringReader; pub const ERROR_NOT_COMPLETE: CommandErrorType<0> = CommandErrorType::new( translation::java::ARGUMENT_ANGLE_INCOMPLETE, @@ -31,9 +31,9 @@ impl Angle { } #[must_use] - pub fn get_angle(&self, source: &CommandSource) -> f32 { + pub fn get_angle(&self, source: &impl CommandSource) -> f32 { let base = if self.is_relative { - source.rotation.y + source.rotation().y } else { 0.0 }; @@ -43,7 +43,7 @@ impl Angle { pub struct AngleArgumentType; -impl ArgumentType for AngleArgumentType { +impl ArgumentType for AngleArgumentType { type Item = Angle; fn parse(&self, reader: &mut StringReader) -> Result { @@ -82,7 +82,10 @@ impl ArgumentType for AngleArgumentType { } impl AngleArgumentType { - pub fn get(context: &CommandContext, name: &str) -> Result { + pub fn get( + context: &CommandContext, + name: &str, + ) -> Result { context.get_argument::(name).copied() } } diff --git a/crates/pumpkin/src/command/argument_types/coordinates/block_pos.rs b/crates/pumpkin-command/src/argument_types/coordinates/block_pos.rs similarity index 68% rename from crates/pumpkin/src/command/argument_types/coordinates/block_pos.rs rename to crates/pumpkin-command/src/argument_types/coordinates/block_pos.rs index 48fbe4559..2da018216 100644 --- a/crates/pumpkin/src/command/argument_types/coordinates/block_pos.rs +++ b/crates/pumpkin-command/src/argument_types/coordinates/block_pos.rs @@ -1,11 +1,10 @@ -use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType}; -use crate::command::argument_types::coordinates::Coordinates; -use crate::command::context::command_context::CommandContext; -use crate::command::errors::command_syntax_error::CommandSyntaxError; -use crate::command::errors::error_types::CommandErrorType; -use crate::command::string_reader::StringReader; -use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder, TextCoordinates}; -use crate::world::World; +use crate::argument_types::argument_type::{ArgumentType, JavaClientArgumentType}; +use crate::argument_types::coordinates::Coordinates; +use crate::context::command_context::CommandContext; +use crate::errors::command_syntax_error::CommandSyntaxError; +use crate::errors::error_types::CommandErrorType; +use crate::string_reader::StringReader; +use crate::suggestion::suggestions::{Suggestions, SuggestionsBuilder, TextCoordinates}; use pumpkin_data::translation; use pumpkin_util::math::position::BlockPos; @@ -35,7 +34,7 @@ pub const OUT_OF_BOUNDS_ERROR_TYPE: CommandErrorType<0> = CommandErrorType::new( /// a player can spawn. pub struct BlockPosArgumentType; -impl ArgumentType for BlockPosArgumentType { +impl ArgumentType for BlockPosArgumentType { type Item = Coordinates; fn parse(&self, reader: &mut StringReader) -> Result { @@ -56,7 +55,7 @@ impl ArgumentType for BlockPosArgumentType { fn list_suggestions( &self, - _context: &CommandContext, + _context: &CommandContext, builder: SuggestionsBuilder, ) -> Suggestions { let remainder = builder.remaining(); @@ -68,7 +67,7 @@ impl ArgumentType for BlockPosArgumentType { }; builder.suggest_3d_coordinates(suggested_coordinates, |value| { - self.parse(&mut StringReader::new(value)).is_ok() + ArgumentType::::parse(self, &mut StringReader::new(value)).is_ok() }) } } @@ -83,8 +82,8 @@ impl BlockPosArgumentType { /// # Returns /// The loaded `BlockPos` containing the position represented by the parsed argument, wrapped in an `Ok`, /// or an `Err` with the appropriate [`CommandSyntaxError`] if it could not be resolved. - pub fn get_block_pos( - context: &CommandContext, + pub fn get_block_pos( + context: &CommandContext, name: &str, ) -> Result { Ok(BlockPos::floored_v( @@ -104,41 +103,13 @@ impl BlockPosArgumentType { /// # Returns /// The loaded `BlockPos` containing the position represented by the parsed argument, wrapped in an `Ok`, /// or an `Err` with the appropriate [`CommandSyntaxError`] if it could not be resolved. - pub fn get_loaded_block_pos( - context: &CommandContext, + pub fn get_loaded_block_pos( + context: &CommandContext, name: &str, - ) -> Result { - 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 { let pos = Self::get_block_pos(context, name)?; - if world - .level - .read_chunk_sync(&pos.chunk_position(), |_| ()) - .is_none() - { - Err(NOT_LOADED_ERROR_TYPE.create_without_context()) - } else if !world.is_in_build_limit(pos) { - Err(OUT_OF_WORLD_ERROR_TYPE.create_without_context()) - } else { - Ok(pos) - } + context.source.check_block_loaded(&pos)?; + Ok(pos) } /// Returns a [`CommandContext`]'s parsed coordinate argument in the form of a [`BlockPos`] @@ -151,15 +122,22 @@ impl BlockPosArgumentType { /// # Returns /// The loaded `BlockPos` containing the position represented by the parsed argument, wrapped in an `Ok`, /// or an `Err` with the appropriate [`CommandSyntaxError`] if it could not be resolved. - pub fn get_spawnable_pos( - context: &CommandContext, + pub fn get_spawnable_pos( + context: &CommandContext, name: &str, ) -> Result { let pos = Self::get_block_pos(context, name)?; - if World::is_valid(pos) { + if is_valid_block_pos(pos) { Ok(pos) } else { Err(OUT_OF_BOUNDS_ERROR_TYPE.create_without_context()) } } } + +#[must_use] +pub fn is_valid_block_pos(dest: BlockPos) -> bool { + (-30_000_000..30_000_000).contains(&dest.0.x) + && (-30_000_000..30_000_000).contains(&dest.0.z) + && (-20_000_000..20_000_000).contains(&dest.0.y) +} diff --git a/crates/pumpkin/src/command/argument_types/coordinates/column_pos.rs b/crates/pumpkin-command/src/argument_types/coordinates/column_pos.rs similarity index 89% rename from crates/pumpkin/src/command/argument_types/coordinates/column_pos.rs rename to crates/pumpkin-command/src/argument_types/coordinates/column_pos.rs index ee7294f13..fa538f7fd 100644 --- a/crates/pumpkin/src/command/argument_types/coordinates/column_pos.rs +++ b/crates/pumpkin-command/src/argument_types/coordinates/column_pos.rs @@ -3,8 +3,8 @@ use pumpkin_util::math::position::{BlockPos, ColumnPos}; use pumpkin_util::math::vector2::Vector2; use pumpkin_util::math::vector3::Vector3; -use crate::command::suggestion::suggestions::TextCoordinates; -use crate::command::{ +use crate::suggestion::suggestions::TextCoordinates; +use crate::{ argument_types::{ argument_type::{ArgumentType, JavaClientArgumentType}, coordinates::{Coordinates, WorldCoordinate}, @@ -22,7 +22,7 @@ pub const INCOMPLETE_ERROR_TYPE: CommandErrorType<0> = CommandErrorType::new( pub struct ColumnPosArgumentType; -impl ArgumentType for ColumnPosArgumentType { +impl ArgumentType for ColumnPosArgumentType { type Item = Coordinates; fn parse(&self, reader: &mut StringReader) -> Result { @@ -48,7 +48,7 @@ impl ArgumentType for ColumnPosArgumentType { fn list_suggestions( &self, - _context: &CommandContext, + _context: &CommandContext, builder: SuggestionsBuilder, ) -> Suggestions { let remainder = builder.remaining(); @@ -60,7 +60,7 @@ impl ArgumentType for ColumnPosArgumentType { }; builder.suggest_2d_coordinates(suggested_coordinates, |value| { - self.parse(&mut StringReader::new(value)).is_ok() + ArgumentType::::parse(self, &mut StringReader::new(value)).is_ok() }) } @@ -83,8 +83,8 @@ impl ColumnPosArgumentType { /// # Returns /// The `ColumnPos` containing the position represented by the parsed argument, wrapped in an `Ok`, /// or an `Err` with the appropriate [`CommandSyntaxError`] if it could not be resolved. - pub fn get_column_pos( - context: &CommandContext, + pub fn get_column_pos( + context: &CommandContext, name: &str, ) -> Result { let block_pos = BlockPos::floored_v( diff --git a/crates/pumpkin/src/command/argument_types/coordinates/mod.rs b/crates/pumpkin-command/src/argument_types/coordinates/mod.rs similarity index 95% rename from crates/pumpkin/src/command/argument_types/coordinates/mod.rs rename to crates/pumpkin-command/src/argument_types/coordinates/mod.rs index b34b10a1c..07a86f13a 100644 --- a/crates/pumpkin/src/command/argument_types/coordinates/mod.rs +++ b/crates/pumpkin-command/src/argument_types/coordinates/mod.rs @@ -1,9 +1,7 @@ -use crate::command::context::command_source::CommandSource; -use crate::command::errors::command_syntax_error::CommandSyntaxError; -use crate::command::errors::error_types::{ - CommandErrorType, READER_EXPECTED_DOUBLE, READER_EXPECTED_INT, -}; -use crate::command::string_reader::StringReader; +use crate::errors::command_syntax_error::CommandSyntaxError; +use crate::errors::error_types::{CommandErrorType, READER_EXPECTED_DOUBLE, READER_EXPECTED_INT}; +use crate::source::CommandSource; +use crate::string_reader::StringReader; use pumpkin_data::translation; use pumpkin_util::math::vector2::Vector2; use pumpkin_util::math::vector3::{Axis, Vector3}; @@ -179,10 +177,10 @@ impl Coordinates { /// Returns the physical position that these [`Coordinates`] represent. #[must_use] - pub fn resolve(&self, source: &CommandSource) -> Vector3 { + pub fn resolve(&self, source: &impl CommandSource) -> Vector3 { match self { Self::World(vector) => { - let pos = source.position; + let pos = source.position(); Vector3::new( vector.x.resolve(pos.x), vector.y.resolve(pos.y), @@ -190,8 +188,8 @@ impl Coordinates { ) } Self::Local { left, up, forward } => { - let start = source.entity_anchor.position_at_source(source); - convert_local_coordinates(*left, *up, *forward, source.rotation).add(&start) + let start = source.entity_anchor().position_at_source(source); + convert_local_coordinates(*left, *up, *forward, source.rotation()).add(&start) } } } @@ -201,10 +199,10 @@ impl Coordinates { /// - The returned *x*-component of the vector is the **pitch**. /// - The returned *y*-component of the vector is the **yaw**. #[must_use] - pub const fn rotation(&self, source: &CommandSource) -> Vector2 { + pub fn rotation(&self, source: &impl CommandSource) -> Vector2 { match self { Self::World(coords) => { - let rotation = source.rotation; + let rotation = source.rotation(); Vector2::new( coords.x.resolve(rotation.x as f64) as f32, coords.y.resolve(rotation.y as f64) as f32, diff --git a/crates/pumpkin/src/command/argument_types/coordinates/rotation.rs b/crates/pumpkin-command/src/argument_types/coordinates/rotation.rs similarity index 76% rename from crates/pumpkin/src/command/argument_types/coordinates/rotation.rs rename to crates/pumpkin-command/src/argument_types/coordinates/rotation.rs index 584de0364..1b2fefa53 100644 --- a/crates/pumpkin/src/command/argument_types/coordinates/rotation.rs +++ b/crates/pumpkin-command/src/argument_types/coordinates/rotation.rs @@ -1,9 +1,9 @@ -use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType}; -use crate::command::argument_types::coordinates::{Coordinates, WorldCoordinate}; -use crate::command::context::command_context::CommandContext; -use crate::command::errors::command_syntax_error::CommandSyntaxError; -use crate::command::errors::error_types::CommandErrorType; -use crate::command::string_reader::StringReader; +use crate::argument_types::argument_type::{ArgumentType, JavaClientArgumentType}; +use crate::argument_types::coordinates::{Coordinates, WorldCoordinate}; +use crate::context::command_context::CommandContext; +use crate::errors::command_syntax_error::CommandSyntaxError; +use crate::errors::error_types::CommandErrorType; +use crate::string_reader::StringReader; use pumpkin_data::translation; use pumpkin_util::math::vector3::Vector3; @@ -14,7 +14,7 @@ pub const NOT_COMPLETE_ERROR_TYPE: CommandErrorType<0> = CommandErrorType::new( pub struct RotationArgumentType; -impl ArgumentType for RotationArgumentType { +impl ArgumentType for RotationArgumentType { type Item = Coordinates; fn parse(&self, reader: &mut StringReader) -> Result { @@ -60,7 +60,10 @@ impl RotationArgumentType { /// - The returned *x*-coordinate of the coordinates is the absolute/relative **pitch**. /// - The returned *y*-coordinate of the coordinates is the absolute/relative **yaw**. /// - The returned *z*-coordinate of the coordinates is always 0. - pub fn get(context: &CommandContext, name: &str) -> Result { + pub fn get( + context: &CommandContext, + name: &str, + ) -> Result { context.get_argument::(name).copied() } } diff --git a/crates/pumpkin/src/command/argument_types/coordinates/swizzle.rs b/crates/pumpkin-command/src/argument_types/coordinates/swizzle.rs similarity index 81% rename from crates/pumpkin/src/command/argument_types/coordinates/swizzle.rs rename to crates/pumpkin-command/src/argument_types/coordinates/swizzle.rs index 29b679838..1abfed647 100644 --- a/crates/pumpkin/src/command/argument_types/coordinates/swizzle.rs +++ b/crates/pumpkin-command/src/argument_types/coordinates/swizzle.rs @@ -1,11 +1,11 @@ use pumpkin_data::translation; use pumpkin_util::math::vector3::Vector3; -use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType}; -use crate::command::context::command_context::CommandContext; -use crate::command::errors::command_syntax_error::CommandSyntaxError; -use crate::command::errors::error_types::CommandErrorType; -use crate::command::string_reader::StringReader; +use crate::argument_types::argument_type::{ArgumentType, JavaClientArgumentType}; +use crate::context::command_context::CommandContext; +use crate::errors::command_syntax_error::CommandSyntaxError; +use crate::errors::error_types::CommandErrorType; +use crate::string_reader::StringReader; pub const ERROR_INVALID_SWIZZLE: CommandErrorType<0> = CommandErrorType::new( translation::java::ARGUMENTS_SWIZZLE_INVALID, @@ -32,7 +32,7 @@ impl Swizzle { pub struct SwizzleArgumentType; -impl ArgumentType for SwizzleArgumentType { +impl ArgumentType for SwizzleArgumentType { type Item = Swizzle; fn parse(&self, reader: &mut StringReader) -> Result { @@ -82,7 +82,10 @@ impl ArgumentType for SwizzleArgumentType { } impl SwizzleArgumentType { - pub fn get(context: &CommandContext, name: &str) -> Result { + pub fn get( + context: &CommandContext, + name: &str, + ) -> Result { Ok(*context.get_argument::(name)?) } } diff --git a/crates/pumpkin/src/command/argument_types/coordinates/vec2.rs b/crates/pumpkin-command/src/argument_types/coordinates/vec2.rs similarity index 75% rename from crates/pumpkin/src/command/argument_types/coordinates/vec2.rs rename to crates/pumpkin-command/src/argument_types/coordinates/vec2.rs index 32fdf9a19..429c1f605 100644 --- a/crates/pumpkin/src/command/argument_types/coordinates/vec2.rs +++ b/crates/pumpkin-command/src/argument_types/coordinates/vec2.rs @@ -1,10 +1,10 @@ -use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType}; -use crate::command::argument_types::coordinates::{Coordinates, WorldCoordinate}; -use crate::command::context::command_context::CommandContext; -use crate::command::errors::command_syntax_error::CommandSyntaxError; -use crate::command::errors::error_types::CommandErrorType; -use crate::command::string_reader::StringReader; -use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder, TextCoordinates}; +use crate::argument_types::argument_type::{ArgumentType, JavaClientArgumentType}; +use crate::argument_types::coordinates::{Coordinates, WorldCoordinate}; +use crate::context::command_context::CommandContext; +use crate::errors::command_syntax_error::CommandSyntaxError; +use crate::errors::error_types::CommandErrorType; +use crate::string_reader::StringReader; +use crate::suggestion::suggestions::{Suggestions, SuggestionsBuilder, TextCoordinates}; use pumpkin_data::translation; use pumpkin_util::math::vector2::Vector2; use pumpkin_util::math::vector3::Vector3; @@ -37,7 +37,7 @@ impl Vec2ArgumentType { } } -impl ArgumentType for Vec2ArgumentType { +impl ArgumentType for Vec2ArgumentType { type Item = Coordinates; fn parse(&self, reader: &mut StringReader) -> Result { @@ -76,7 +76,7 @@ impl ArgumentType for Vec2ArgumentType { fn list_suggestions( &self, - _context: &CommandContext, + _context: &CommandContext, builder: SuggestionsBuilder, ) -> Suggestions { let remainder = builder.remaining(); @@ -88,21 +88,21 @@ impl ArgumentType for Vec2ArgumentType { }; builder.suggest_2d_coordinates(suggested_coordinates, |value| { - self.parse(&mut StringReader::new(value)).is_ok() + ArgumentType::::parse(self, &mut StringReader::new(value)).is_ok() }) } } impl Vec2ArgumentType { - pub fn get_coordinates( - context: &CommandContext, + pub fn get_coordinates( + context: &CommandContext, name: &str, ) -> Result { Ok(*context.get_argument(name)?) } - pub fn get_vector2( - context: &CommandContext, + pub fn get_vector2( + context: &CommandContext, name: &str, ) -> Result, CommandSyntaxError> { let resolved = Self::get_coordinates(context, name)?.resolve(context.source.as_ref()); diff --git a/crates/pumpkin/src/command/argument_types/coordinates/vec3.rs b/crates/pumpkin-command/src/argument_types/coordinates/vec3.rs similarity index 78% rename from crates/pumpkin/src/command/argument_types/coordinates/vec3.rs rename to crates/pumpkin-command/src/argument_types/coordinates/vec3.rs index 7d5dc477f..0edc533c6 100644 --- a/crates/pumpkin/src/command/argument_types/coordinates/vec3.rs +++ b/crates/pumpkin-command/src/argument_types/coordinates/vec3.rs @@ -1,10 +1,10 @@ -use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType}; -use crate::command::argument_types::coordinates::{Coordinates, MIXED_TYPE_ERROR_TYPE}; -use crate::command::context::command_context::CommandContext; -use crate::command::errors::command_syntax_error::CommandSyntaxError; -use crate::command::errors::error_types::CommandErrorType; -use crate::command::string_reader::StringReader; -use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder, TextCoordinates}; +use crate::argument_types::argument_type::{ArgumentType, JavaClientArgumentType}; +use crate::argument_types::coordinates::{Coordinates, MIXED_TYPE_ERROR_TYPE}; +use crate::context::command_context::CommandContext; +use crate::errors::command_syntax_error::CommandSyntaxError; +use crate::errors::error_types::CommandErrorType; +use crate::string_reader::StringReader; +use crate::suggestion::suggestions::{Suggestions, SuggestionsBuilder, TextCoordinates}; use pumpkin_data::translation; use pumpkin_util::math::vector3::Vector3; @@ -51,7 +51,7 @@ impl Vec3ArgumentType { } } -impl ArgumentType for Vec3ArgumentType { +impl ArgumentType for Vec3ArgumentType { type Item = Coordinates; fn parse(&self, reader: &mut StringReader) -> Result { @@ -79,7 +79,7 @@ impl ArgumentType for Vec3ArgumentType { fn list_suggestions( &self, - _context: &CommandContext, + _context: &CommandContext, builder: SuggestionsBuilder, ) -> Suggestions { let remainder = builder.remaining(); @@ -91,23 +91,23 @@ impl ArgumentType for Vec3ArgumentType { }; builder.suggest_3d_coordinates(suggestioned_coordinates, |value| { - self.parse(&mut StringReader::new(value)).is_ok() + ArgumentType::::parse(self, &mut StringReader::new(value)).is_ok() }) } } impl Vec3ArgumentType { /// Returns a [`CommandContext`]'s parsed three-dimensional vector as a set of [`Coordinates`]. - pub fn get_coordinates( - context: &CommandContext, + pub fn get_coordinates( + context: &CommandContext, name: &str, ) -> Result { Ok(*context.get_argument(name)?) } /// Returns a [`CommandContext`]'s parsed three-dimensional vector and resolves it to a [`Vector3`]. - pub fn get_vector3( - context: &CommandContext, + pub fn get_vector3( + context: &CommandContext, name: &str, ) -> Result, CommandSyntaxError> { Ok(Self::get_coordinates(context, name)?.resolve(context.source.as_ref())) diff --git a/crates/pumpkin/src/command/argument_types/core/bool.rs b/crates/pumpkin-command/src/argument_types/core/bool.rs similarity index 85% rename from crates/pumpkin/src/command/argument_types/core/bool.rs rename to crates/pumpkin-command/src/argument_types/core/bool.rs index 5cef951b0..3196d4eca 100644 --- a/crates/pumpkin/src/command/argument_types/core/bool.rs +++ b/crates/pumpkin-command/src/argument_types/core/bool.rs @@ -1,4 +1,4 @@ -use crate::command::{ +use crate::{ argument_types::argument_type::{ArgumentType, JavaClientArgumentType}, errors::command_syntax_error::CommandSyntaxError, string_reader::StringReader, @@ -8,7 +8,7 @@ use crate::command::{ #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct BoolArgumentType; -impl ArgumentType for BoolArgumentType { +impl ArgumentType for BoolArgumentType { type Item = bool; fn parse(&self, reader: &mut StringReader) -> Result { @@ -28,9 +28,8 @@ impl_copy_get!(BoolArgumentType, bool); #[cfg(test)] mod test { - use crate::command::{ - argument_types::{argument_type::ArgumentType, core::bool::BoolArgumentType}, - errors::error_types, + use crate::{ + argument_types::core::bool::BoolArgumentType, errors::error_types, string_reader::StringReader, }; diff --git a/crates/pumpkin/src/command/argument_types/core/double.rs b/crates/pumpkin-command/src/argument_types/core/double.rs similarity index 93% rename from crates/pumpkin/src/command/argument_types/core/double.rs rename to crates/pumpkin-command/src/argument_types/core/double.rs index b38435474..7cfe9fbe1 100644 --- a/crates/pumpkin/src/command/argument_types/core/double.rs +++ b/crates/pumpkin-command/src/argument_types/core/double.rs @@ -1,4 +1,4 @@ -use crate::command::{ +use crate::{ argument_types::{ argument_type::{ArgumentType, JavaClientArgumentType}, core::within_or_err, @@ -14,7 +14,7 @@ pub struct DoubleArgumentType { pub max: f64, } -impl ArgumentType for DoubleArgumentType { +impl ArgumentType for DoubleArgumentType { type Item = f64; fn parse(&self, reader: &mut StringReader) -> Result { @@ -76,9 +76,8 @@ impl DoubleArgumentType { #[cfg(test)] mod test { - use crate::command::{ - argument_types::{argument_type::ArgumentType, core::double::DoubleArgumentType}, - errors::error_types, + use crate::{ + argument_types::core::double::DoubleArgumentType, errors::error_types, string_reader::StringReader, }; diff --git a/crates/pumpkin/src/command/argument_types/core/float.rs b/crates/pumpkin-command/src/argument_types/core/float.rs similarity index 93% rename from crates/pumpkin/src/command/argument_types/core/float.rs rename to crates/pumpkin-command/src/argument_types/core/float.rs index 857ebbe48..299e1272e 100644 --- a/crates/pumpkin/src/command/argument_types/core/float.rs +++ b/crates/pumpkin-command/src/argument_types/core/float.rs @@ -1,4 +1,4 @@ -use crate::command::{ +use crate::{ argument_types::{ argument_type::{ArgumentType, JavaClientArgumentType}, core::within_or_err, @@ -14,7 +14,7 @@ pub struct FloatArgumentType { pub max: f32, } -impl ArgumentType for FloatArgumentType { +impl ArgumentType for FloatArgumentType { type Item = f32; fn parse(&self, reader: &mut StringReader) -> Result { @@ -76,9 +76,8 @@ impl FloatArgumentType { #[cfg(test)] mod test { - use crate::command::{ - argument_types::{argument_type::ArgumentType, core::float::FloatArgumentType}, - errors::error_types, + use crate::{ + argument_types::core::float::FloatArgumentType, errors::error_types, string_reader::StringReader, }; diff --git a/crates/pumpkin/src/command/argument_types/core/integer.rs b/crates/pumpkin-command/src/argument_types/core/integer.rs similarity index 94% rename from crates/pumpkin/src/command/argument_types/core/integer.rs rename to crates/pumpkin-command/src/argument_types/core/integer.rs index cefe35919..db44ffd8e 100644 --- a/crates/pumpkin/src/command/argument_types/core/integer.rs +++ b/crates/pumpkin-command/src/argument_types/core/integer.rs @@ -1,4 +1,4 @@ -use crate::command::{ +use crate::{ argument_types::{ argument_type::{ArgumentType, JavaClientArgumentType}, core::within_or_err, @@ -14,7 +14,7 @@ pub struct IntegerArgumentType { pub max: i32, } -impl ArgumentType for IntegerArgumentType { +impl ArgumentType for IntegerArgumentType { type Item = i32; fn parse(&self, reader: &mut StringReader) -> Result { @@ -76,9 +76,8 @@ impl IntegerArgumentType { #[cfg(test)] mod test { - use crate::command::{ - argument_types::{argument_type::ArgumentType, core::integer::IntegerArgumentType}, - errors::error_types, + use crate::{ + argument_types::core::integer::IntegerArgumentType, errors::error_types, string_reader::StringReader, }; diff --git a/crates/pumpkin/src/command/argument_types/core/long.rs b/crates/pumpkin-command/src/argument_types/core/long.rs similarity index 94% rename from crates/pumpkin/src/command/argument_types/core/long.rs rename to crates/pumpkin-command/src/argument_types/core/long.rs index cbb6dd6ab..ea5b1553e 100644 --- a/crates/pumpkin/src/command/argument_types/core/long.rs +++ b/crates/pumpkin-command/src/argument_types/core/long.rs @@ -1,4 +1,4 @@ -use crate::command::{ +use crate::{ argument_types::{ argument_type::{ArgumentType, JavaClientArgumentType}, core::within_or_err, @@ -14,7 +14,7 @@ pub struct LongArgumentType { pub max: i64, } -impl ArgumentType for LongArgumentType { +impl ArgumentType for LongArgumentType { type Item = i64; fn parse(&self, reader: &mut StringReader) -> Result { @@ -76,9 +76,8 @@ impl LongArgumentType { #[cfg(test)] mod test { - use crate::command::{ - argument_types::{argument_type::ArgumentType, core::long::LongArgumentType}, - errors::error_types, + use crate::{ + argument_types::core::long::LongArgumentType, errors::error_types, string_reader::StringReader, }; diff --git a/crates/pumpkin/src/command/argument_types/core/mod.rs b/crates/pumpkin-command/src/argument_types/core/mod.rs similarity index 98% rename from crates/pumpkin/src/command/argument_types/core/mod.rs rename to crates/pumpkin-command/src/argument_types/core/mod.rs index 4e39b6fec..2366b7c91 100644 --- a/crates/pumpkin/src/command/argument_types/core/mod.rs +++ b/crates/pumpkin-command/src/argument_types/core/mod.rs @@ -1,6 +1,6 @@ use pumpkin_util::text::TextComponent; -use crate::command::{ +use crate::{ errors::{command_syntax_error::CommandSyntaxError, error_types::CommandErrorType}, string_reader::StringReader, }; diff --git a/crates/pumpkin/src/command/argument_types/core/string.rs b/crates/pumpkin-command/src/argument_types/core/string.rs similarity index 91% rename from crates/pumpkin/src/command/argument_types/core/string.rs rename to crates/pumpkin-command/src/argument_types/core/string.rs index b97388f05..705808727 100644 --- a/crates/pumpkin/src/command/argument_types/core/string.rs +++ b/crates/pumpkin-command/src/argument_types/core/string.rs @@ -1,7 +1,7 @@ use pumpkin_protocol::java::client::play::StringProtoArgBehavior; -use crate::command::context::command_context::CommandContext; -use crate::command::{ +use crate::context::command_context::CommandContext; +use crate::{ argument_types::argument_type::{ArgumentType, JavaClientArgumentType}, errors::command_syntax_error::CommandSyntaxError, string_reader::StringReader, @@ -18,7 +18,7 @@ pub enum StringArgumentType { GreedyPhrase, } -impl ArgumentType for StringArgumentType { +impl ArgumentType for StringArgumentType { type Item = String; fn parse(&self, reader: &mut StringReader) -> Result { @@ -52,16 +52,18 @@ impl ArgumentType for StringArgumentType { impl StringArgumentType { /// Returns a [`CommandContext`]'s parsed `String` argument as a string slice. - pub fn get<'a>(context: &'a CommandContext, name: &str) -> Result<&'a str, CommandSyntaxError> { + pub fn get<'a, S: crate::source::CommandSource>( + context: &'a CommandContext, + name: &str, + ) -> Result<&'a str, CommandSyntaxError> { Ok(context.get_argument::(name)?.as_str()) } } #[cfg(test)] mod test { - use crate::command::{ - argument_types::{argument_type::ArgumentType, core::string::StringArgumentType}, - errors::error_types, + use crate::{ + argument_types::core::string::StringArgumentType, errors::error_types, string_reader::StringReader, }; diff --git a/crates/pumpkin/src/command/argument_types/dialog.rs b/crates/pumpkin-command/src/argument_types/dialog.rs similarity index 83% rename from crates/pumpkin/src/command/argument_types/dialog.rs rename to crates/pumpkin-command/src/argument_types/dialog.rs index 3822ab610..58905d3a3 100644 --- a/crates/pumpkin/src/command/argument_types/dialog.rs +++ b/crates/pumpkin-command/src/argument_types/dialog.rs @@ -1,7 +1,7 @@ use pumpkin_nbt::compound::NbtCompound; use pumpkin_util::identifier::Identifier; -use crate::command::{ +use crate::{ argument_types::{ FromStringReader, argument_type::{ArgumentType, JavaClientArgumentType}, @@ -20,12 +20,12 @@ pub enum DialogArg { pub struct DialogArgumentType; -impl ArgumentType for DialogArgumentType { +impl ArgumentType for DialogArgumentType { type Item = DialogArg; fn parse(&self, reader: &mut StringReader) -> Result { if reader.peek() == Some('{') { - let compound = NbtCompoundArgumentType.parse(reader)?; + let compound = ArgumentType::::parse(&NbtCompoundArgumentType, reader)?; Ok(DialogArg::Nbt(compound)) } else { let id = Identifier::from_reader(reader)?; @@ -48,8 +48,8 @@ impl ArgumentType for DialogArgumentType { impl DialogArgumentType { /// Returns the parsed [`DialogArg`] from the name of the argument. - pub fn get<'a>( - context: &'a CommandContext, + pub fn get<'a, S: crate::source::CommandSource>( + context: &'a CommandContext, name: &'_ str, ) -> Result<&'a DialogArg, CommandSyntaxError> { context.get_argument(name) diff --git a/crates/pumpkin-command/src/argument_types/entity_anchor.rs b/crates/pumpkin-command/src/argument_types/entity_anchor.rs new file mode 100644 index 000000000..b797092ab --- /dev/null +++ b/crates/pumpkin-command/src/argument_types/entity_anchor.rs @@ -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 ArgumentType for EntityAnchorArgumentType { + type Item = EntityAnchor; + + fn parse(&self, reader: &mut StringReader) -> Result { + 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 { + 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 { + 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 { + command_source.anchor_position(self) + } +} diff --git a/crates/pumpkin/src/command/argument_types/gamemode.rs b/crates/pumpkin-command/src/argument_types/gamemode.rs similarity index 70% rename from crates/pumpkin/src/command/argument_types/gamemode.rs rename to crates/pumpkin-command/src/argument_types/gamemode.rs index 06b538dfc..976429c78 100644 --- a/crates/pumpkin/src/command/argument_types/gamemode.rs +++ b/crates/pumpkin-command/src/argument_types/gamemode.rs @@ -1,9 +1,9 @@ -use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType}; -use crate::command::context::command_context::CommandContext; -use crate::command::errors::command_syntax_error::CommandSyntaxError; -use crate::command::errors::error_types::CommandErrorType; -use crate::command::string_reader::StringReader; -use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder}; +use crate::argument_types::argument_type::{ArgumentType, JavaClientArgumentType}; +use crate::context::command_context::CommandContext; +use crate::errors::command_syntax_error::CommandSyntaxError; +use crate::errors::error_types::CommandErrorType; +use crate::string_reader::StringReader; +use crate::suggestion::suggestions::{Suggestions, SuggestionsBuilder}; use pumpkin_data::translation; use pumpkin_util::GameMode; use pumpkin_util::text::TextComponent; @@ -16,7 +16,7 @@ pub const INVALID_ERROR_TYPE: CommandErrorType<1> = CommandErrorType::new( pub struct GameModeArgumentType; -impl ArgumentType for GameModeArgumentType { +impl ArgumentType for GameModeArgumentType { type Item = GameMode; fn parse(&self, reader: &mut StringReader) -> Result { @@ -31,7 +31,7 @@ impl ArgumentType for GameModeArgumentType { fn list_suggestions( &self, - _context: &CommandContext, + _context: &CommandContext, mut builder: SuggestionsBuilder, ) -> Suggestions { for gamemode in GameMode::VALUES { diff --git a/crates/pumpkin/src/command/argument_types/hex_color.rs b/crates/pumpkin-command/src/argument_types/hex_color.rs similarity index 86% rename from crates/pumpkin/src/command/argument_types/hex_color.rs rename to crates/pumpkin-command/src/argument_types/hex_color.rs index 9e80df83d..25820369c 100644 --- a/crates/pumpkin/src/command/argument_types/hex_color.rs +++ b/crates/pumpkin-command/src/argument_types/hex_color.rs @@ -1,9 +1,9 @@ -use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType}; -use crate::command::context::command_context::CommandContext; -use crate::command::errors::command_syntax_error::CommandSyntaxError; -use crate::command::errors::error_types::{CommandErrorType, DISPATCHER_PARSE_EXCEPTION}; -use crate::command::string_reader::StringReader; -use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder}; +use crate::argument_types::argument_type::{ArgumentType, JavaClientArgumentType}; +use crate::context::command_context::CommandContext; +use crate::errors::command_syntax_error::CommandSyntaxError; +use crate::errors::error_types::{CommandErrorType, DISPATCHER_PARSE_EXCEPTION}; +use crate::string_reader::StringReader; +use crate::suggestion::suggestions::{Suggestions, SuggestionsBuilder}; use pumpkin_data::translation::java::ARGUMENT_HEXCOLOR_INVALID; use pumpkin_util::text::TextComponent; use pumpkin_util::text::color::RGBColor; @@ -13,7 +13,7 @@ pub const INVALID_HEX_ERROR_TYPE: CommandErrorType<1> = pub struct HexColorArgumentType; -impl ArgumentType for HexColorArgumentType { +impl ArgumentType for HexColorArgumentType { type Item = RGBColor; fn parse(&self, reader: &mut StringReader) -> Result { @@ -39,7 +39,7 @@ impl ArgumentType for HexColorArgumentType { fn list_suggestions( &self, - _context: &CommandContext, + _context: &CommandContext, builder: SuggestionsBuilder, ) -> Suggestions { builder.filter_and_suggest(&["F00", "FF0000"]).build() diff --git a/crates/pumpkin/src/command/argument_types/identifier.rs b/crates/pumpkin-command/src/argument_types/identifier.rs similarity index 80% rename from crates/pumpkin/src/command/argument_types/identifier.rs rename to crates/pumpkin-command/src/argument_types/identifier.rs index bb09d7e95..e3829d4f8 100644 --- a/crates/pumpkin/src/command/argument_types/identifier.rs +++ b/crates/pumpkin-command/src/argument_types/identifier.rs @@ -1,6 +1,6 @@ use pumpkin_util::identifier::Identifier; -use crate::command::{ +use crate::{ argument_types::{ FromStringReader, argument_type::{ArgumentType, JavaClientArgumentType}, @@ -12,7 +12,7 @@ use crate::command::{ /// An argument type that parses a generic [`Identifier`] with a namespace and path. pub struct IdentifierArgumentType; -impl ArgumentType for IdentifierArgumentType { +impl ArgumentType for IdentifierArgumentType { type Item = Identifier; fn parse(&self, reader: &mut StringReader) -> Result { @@ -29,8 +29,8 @@ impl ArgumentType for IdentifierArgumentType { } impl IdentifierArgumentType { - pub fn get( - context: &crate::command::context::command_context::CommandContext, + pub fn get( + context: &crate::context::command_context::CommandContext, name: &str, ) -> Result { Ok(context.get_argument::(name)?.clone()) diff --git a/crates/pumpkin/src/command/argument_types/item.rs b/crates/pumpkin-command/src/argument_types/item.rs similarity index 84% rename from crates/pumpkin/src/command/argument_types/item.rs rename to crates/pumpkin-command/src/argument_types/item.rs index 565515dae..4d6709b90 100644 --- a/crates/pumpkin/src/command/argument_types/item.rs +++ b/crates/pumpkin-command/src/argument_types/item.rs @@ -4,13 +4,13 @@ use pumpkin_data::item_stack::ItemStack; use pumpkin_data::translation; use pumpkin_util::text::TextComponent; -use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType}; -use crate::command::context::command_context::CommandContext; -use crate::command::errors::command_syntax_error::CommandSyntaxError; -use crate::command::errors::error_types::CommandErrorType; -use crate::command::snbt::SnbtParser; -use crate::command::string_reader::StringReader; -use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder}; +use crate::argument_types::argument_type::{ArgumentType, JavaClientArgumentType}; +use crate::context::command_context::CommandContext; +use crate::errors::command_syntax_error::CommandSyntaxError; +use crate::errors::error_types::CommandErrorType; +use crate::snbt::SnbtParser; +use crate::string_reader::StringReader; +use crate::suggestion::suggestions::{Suggestions, SuggestionsBuilder}; pub const ERROR_UNKNOWN_ITEM: CommandErrorType<1> = CommandErrorType::new( translation::java::ARGUMENT_ITEM_ID_INVALID, @@ -20,7 +20,7 @@ pub const ERROR_UNKNOWN_ITEM: CommandErrorType<1> = CommandErrorType::new( #[derive(Clone, Copy)] pub struct ItemStackArgumentType; -impl ArgumentType for ItemStackArgumentType { +impl ArgumentType for ItemStackArgumentType { type Item = ItemStack; fn parse(&self, reader: &mut StringReader) -> Result { @@ -100,7 +100,7 @@ impl ArgumentType for ItemStackArgumentType { fn list_suggestions( &self, - _context: &CommandContext, + _context: &CommandContext, builder: SuggestionsBuilder, ) -> Suggestions { builder.build() @@ -108,7 +108,10 @@ impl ArgumentType for ItemStackArgumentType { } impl ItemStackArgumentType { - pub fn get(context: &CommandContext, name: &str) -> Result { + pub fn get( + context: &CommandContext, + name: &str, + ) -> Result { context.get_argument::(name).cloned() } } diff --git a/crates/pumpkin/src/command/argument_types/item_predicate.rs b/crates/pumpkin-command/src/argument_types/item_predicate.rs similarity index 88% rename from crates/pumpkin/src/command/argument_types/item_predicate.rs rename to crates/pumpkin-command/src/argument_types/item_predicate.rs index b972f202e..993cc0d5d 100644 --- a/crates/pumpkin/src/command/argument_types/item_predicate.rs +++ b/crates/pumpkin-command/src/argument_types/item_predicate.rs @@ -4,12 +4,12 @@ use pumpkin_data::tag::{RegistryKey, get_tag_ids}; use pumpkin_data::translation; use pumpkin_util::text::TextComponent; -use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType}; -use crate::command::context::command_context::CommandContext; -use crate::command::errors::command_syntax_error::CommandSyntaxError; -use crate::command::errors::error_types::CommandErrorType; -use crate::command::string_reader::StringReader; -use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder}; +use crate::argument_types::argument_type::{ArgumentType, JavaClientArgumentType}; +use crate::context::command_context::CommandContext; +use crate::errors::command_syntax_error::CommandSyntaxError; +use crate::errors::error_types::CommandErrorType; +use crate::string_reader::StringReader; +use crate::suggestion::suggestions::{Suggestions, SuggestionsBuilder}; pub const ERROR_UNKNOWN_ITEM: CommandErrorType<1> = CommandErrorType::new( translation::java::ARGUMENT_ITEM_ID_INVALID, @@ -41,7 +41,7 @@ impl ItemPredicate { pub struct ItemPredicateArgumentType; -impl ArgumentType for ItemPredicateArgumentType { +impl ArgumentType for ItemPredicateArgumentType { type Item = ItemPredicate; fn parse(&self, reader: &mut StringReader) -> Result { @@ -131,7 +131,7 @@ impl ArgumentType for ItemPredicateArgumentType { fn list_suggestions( &self, - _context: &CommandContext, + _context: &CommandContext, builder: SuggestionsBuilder, ) -> Suggestions { builder.build() @@ -164,7 +164,10 @@ fn skip_components_and_nbt(reader: &mut StringReader) { } impl ItemPredicateArgumentType { - pub fn get(context: &CommandContext, name: &str) -> Result { + pub fn get( + context: &CommandContext, + name: &str, + ) -> Result { Ok(context.get_argument::(name)?.clone()) } } diff --git a/crates/pumpkin-command/src/argument_types/mod.rs b/crates/pumpkin-command/src/argument_types/mod.rs new file mode 100644 index 000000000..0812b136e --- /dev/null +++ b/crates/pumpkin-command/src/argument_types/mod.rs @@ -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`] 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::::parse( + &$argument_type, + &mut $reader + ), + Ok($value) + ); + $reader.set_cursor(0) + }}; + ($reader: expr, $argument_type: expr) => {{ + assert!( + $crate::argument_types::argument_type::ArgumentType::::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::::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(context: &$crate::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; +} + +fn bounds_from_reader( + reader: &mut StringReader, + error_type: &'static CommandErrorType<1>, +) -> Result, 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( + reader: &mut StringReader, + error_type: &'static CommandErrorType<1>, +) -> Result, 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::::new(min, max)) + } +} + +/// Tries to read a number of a type from a reader. +fn read_number_from_reader( + reader: &mut StringReader, + error_type: &'static CommandErrorType<1>, +) -> Result, 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::().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 { + 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 { + 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 { + 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 { + 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; diff --git a/crates/pumpkin/src/command/argument_types/nbt.rs b/crates/pumpkin-command/src/argument_types/nbt.rs similarity index 85% rename from crates/pumpkin/src/command/argument_types/nbt.rs rename to crates/pumpkin-command/src/argument_types/nbt.rs index df5582fcd..cc8dda5b2 100644 --- a/crates/pumpkin/src/command/argument_types/nbt.rs +++ b/crates/pumpkin-command/src/argument_types/nbt.rs @@ -1,6 +1,6 @@ use pumpkin_nbt::tag::NbtTag; -use crate::command::{ +use crate::{ argument_types::argument_type::{ArgumentType, JavaClientArgumentType}, context::command_context::CommandContext, errors::{command_syntax_error::CommandSyntaxError, error_types::CommandErrorType}, @@ -15,7 +15,7 @@ use pumpkin_nbt::compound::NbtCompound; /// Parses any type of NBT tag from SNBT. pub struct NbtTagArgumentType; -impl ArgumentType for NbtTagArgumentType { +impl ArgumentType for NbtTagArgumentType { type Item = NbtTag; fn parse(&self, reader: &mut StringReader) -> Result { @@ -28,7 +28,7 @@ impl ArgumentType for NbtTagArgumentType { fn list_suggestions( &self, - _context: &CommandContext, + _context: &CommandContext, builder: SuggestionsBuilder, ) -> Suggestions { SnbtParser::parse_for_suggestions(builder) @@ -41,8 +41,8 @@ impl ArgumentType for NbtTagArgumentType { impl NbtTagArgumentType { /// Returns the parsed [`NbtTag`] from the name of the argument. - pub fn get<'a>( - context: &'a CommandContext, + pub fn get<'a, S: crate::source::CommandSource>( + context: &'a CommandContext, name: &'_ str, ) -> Result<&'a NbtTag, CommandSyntaxError> { context.get_argument(name) @@ -57,7 +57,7 @@ pub const EXPECTED_COMPOUND_ERROR_TYPE: CommandErrorType<0> = CommandErrorType:: /// Parses **only** compound NBT tags from SNBT. pub struct NbtCompoundArgumentType; -impl ArgumentType for NbtCompoundArgumentType { +impl ArgumentType for NbtCompoundArgumentType { type Item = NbtCompound; fn parse(&self, reader: &mut StringReader) -> Result { @@ -81,8 +81,8 @@ impl ArgumentType for NbtCompoundArgumentType { impl NbtCompoundArgumentType { /// Returns the parsed [`NbtCompound`] from the name of the argument. - pub fn get<'a>( - context: &'a CommandContext, + pub fn get<'a, S: crate::source::CommandSource>( + context: &'a CommandContext, name: &'_ str, ) -> Result<&'a NbtCompound, CommandSyntaxError> { context.get_argument(name) diff --git a/crates/pumpkin/src/command/argument_types/nbt_path.rs b/crates/pumpkin-command/src/argument_types/nbt_path.rs similarity index 98% rename from crates/pumpkin/src/command/argument_types/nbt_path.rs rename to crates/pumpkin-command/src/argument_types/nbt_path.rs index 958a6fe94..22408d2d4 100644 --- a/crates/pumpkin/src/command/argument_types/nbt_path.rs +++ b/crates/pumpkin-command/src/argument_types/nbt_path.rs @@ -21,12 +21,12 @@ use pumpkin_nbt::compound::NbtCompound; use pumpkin_nbt::tag::NbtTag; use pumpkin_util::text::TextComponent; -use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType}; -use crate::command::context::command_context::CommandContext; -use crate::command::errors::command_syntax_error::CommandSyntaxError; -use crate::command::errors::error_types::CommandErrorType; -use crate::command::snbt::SnbtParser; -use crate::command::string_reader::StringReader; +use crate::argument_types::argument_type::{ArgumentType, JavaClientArgumentType}; +use crate::context::command_context::CommandContext; +use crate::errors::command_syntax_error::CommandSyntaxError; +use crate::errors::error_types::CommandErrorType; +use crate::snbt::SnbtParser; +use crate::string_reader::StringReader; pub const ERROR_INVALID_NODE: CommandErrorType<0> = CommandErrorType::new( translation::java::ARGUMENTS_NBTPATH_NODE_INVALID, @@ -666,7 +666,7 @@ impl std::fmt::Display for NbtPath { /// Argument type for parsing NBT Paths. pub struct NbtPathArgumentType; -impl ArgumentType for NbtPathArgumentType { +impl ArgumentType for NbtPathArgumentType { type Item = NbtPath; fn parse(&self, reader: &mut StringReader) -> Result { @@ -706,8 +706,8 @@ impl ArgumentType for NbtPathArgumentType { } impl NbtPathArgumentType { - pub fn get<'a>( - context: &'a CommandContext, + pub fn get<'a, S: crate::source::CommandSource>( + context: &'a CommandContext, name: &'_ str, ) -> Result<&'a NbtPath, CommandSyntaxError> { context.get_argument(name) diff --git a/crates/pumpkin/src/command/argument_types/particle.rs b/crates/pumpkin-command/src/argument_types/particle.rs similarity index 69% rename from crates/pumpkin/src/command/argument_types/particle.rs rename to crates/pumpkin-command/src/argument_types/particle.rs index 0c6f969a9..f9386651b 100644 --- a/crates/pumpkin/src/command/argument_types/particle.rs +++ b/crates/pumpkin-command/src/argument_types/particle.rs @@ -1,10 +1,10 @@ -use crate::command::argument_types::FromStringReader; -use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType}; -use crate::command::context::command_context::CommandContext; -use crate::command::errors::command_syntax_error::CommandSyntaxError; -use crate::command::errors::error_types::CommandErrorType; -use crate::command::string_reader::StringReader; -use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder}; +use crate::argument_types::FromStringReader; +use crate::argument_types::argument_type::{ArgumentType, JavaClientArgumentType}; +use crate::context::command_context::CommandContext; +use crate::errors::command_syntax_error::CommandSyntaxError; +use crate::errors::error_types::CommandErrorType; +use crate::string_reader::StringReader; +use crate::suggestion::suggestions::{Suggestions, SuggestionsBuilder}; use pumpkin_data::particle::Particle; use pumpkin_data::translation; use pumpkin_util::identifier::Identifier; @@ -17,7 +17,7 @@ pub const ERROR_UNKNOWN_PARTICLE: CommandErrorType<1> = CommandErrorType::new( pub struct ParticleArgumentType; -impl ArgumentType for ParticleArgumentType { +impl ArgumentType for ParticleArgumentType { type Item = Particle; fn parse(&self, reader: &mut StringReader) -> Result { @@ -43,7 +43,7 @@ impl ArgumentType for ParticleArgumentType { fn list_suggestions( &self, - _context: &CommandContext, + _context: &CommandContext, builder: SuggestionsBuilder, ) -> Suggestions { let particles = (0..=124u16) @@ -55,7 +55,10 @@ impl ArgumentType for ParticleArgumentType { } impl ParticleArgumentType { - pub fn get(context: &CommandContext, name: &str) -> Result { + pub fn get( + context: &CommandContext, + name: &str, + ) -> Result { Ok(*context.get_argument::(name)?) } } diff --git a/crates/pumpkin/src/command/argument_types/placed_feature.rs b/crates/pumpkin-command/src/argument_types/placed_feature.rs similarity index 67% rename from crates/pumpkin/src/command/argument_types/placed_feature.rs rename to crates/pumpkin-command/src/argument_types/placed_feature.rs index 1d2f33258..13fae8546 100644 --- a/crates/pumpkin/src/command/argument_types/placed_feature.rs +++ b/crates/pumpkin-command/src/argument_types/placed_feature.rs @@ -1,16 +1,16 @@ use pumpkin_protocol::java::client::play::SuggestionProviders; use pumpkin_util::identifier::Identifier; -use crate::command::argument_types::FromStringReader; -use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType}; -use crate::command::context::command_context::CommandContext; -use crate::command::errors::command_syntax_error::CommandSyntaxError; -use crate::command::string_reader::StringReader; -use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder}; +use crate::argument_types::FromStringReader; +use crate::argument_types::argument_type::{ArgumentType, JavaClientArgumentType}; +use crate::context::command_context::CommandContext; +use crate::errors::command_syntax_error::CommandSyntaxError; +use crate::string_reader::StringReader; +use crate::suggestion::suggestions::{Suggestions, SuggestionsBuilder}; pub struct PlacedFeatureNameArgumentType; -impl ArgumentType for PlacedFeatureNameArgumentType { +impl ArgumentType for PlacedFeatureNameArgumentType { type Item = Identifier; fn parse(&self, reader: &mut StringReader) -> Result { @@ -19,7 +19,7 @@ impl ArgumentType for PlacedFeatureNameArgumentType { fn list_suggestions( &self, - _context: &CommandContext, + _context: &CommandContext, builder: SuggestionsBuilder, ) -> Suggestions { let names = pumpkin_world::generation::feature::placed_features::all_placed_feature_names(); diff --git a/crates/pumpkin/src/command/argument_types/pool.rs b/crates/pumpkin-command/src/argument_types/pool.rs similarity index 66% rename from crates/pumpkin/src/command/argument_types/pool.rs rename to crates/pumpkin-command/src/argument_types/pool.rs index 02b126c00..52062a2bf 100644 --- a/crates/pumpkin/src/command/argument_types/pool.rs +++ b/crates/pumpkin-command/src/argument_types/pool.rs @@ -1,15 +1,15 @@ -use crate::command::argument_types::FromStringReader; -use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType}; -use crate::command::context::command_context::CommandContext; -use crate::command::errors::command_syntax_error::CommandSyntaxError; -use crate::command::string_reader::StringReader; -use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder}; +use crate::argument_types::FromStringReader; +use crate::argument_types::argument_type::{ArgumentType, JavaClientArgumentType}; +use crate::context::command_context::CommandContext; +use crate::errors::command_syntax_error::CommandSyntaxError; +use crate::string_reader::StringReader; +use crate::suggestion::suggestions::{Suggestions, SuggestionsBuilder}; use pumpkin_protocol::java::client::play::SuggestionProviders; use pumpkin_util::identifier::Identifier; pub struct PoolNameArgumentType; -impl ArgumentType for PoolNameArgumentType { +impl ArgumentType for PoolNameArgumentType { type Item = Identifier; fn parse(&self, reader: &mut StringReader) -> Result { @@ -18,7 +18,7 @@ impl ArgumentType for PoolNameArgumentType { fn list_suggestions( &self, - _context: &CommandContext, + _context: &CommandContext, builder: SuggestionsBuilder, ) -> Suggestions { let names = pumpkin_world::generation::structure::template::all_pool_names(); diff --git a/crates/pumpkin/src/command/argument_types/range.rs b/crates/pumpkin-command/src/argument_types/range.rs similarity index 96% rename from crates/pumpkin/src/command/argument_types/range.rs rename to crates/pumpkin-command/src/argument_types/range.rs index 23f3ddf5d..74e6542ad 100644 --- a/crates/pumpkin/src/command/argument_types/range.rs +++ b/crates/pumpkin-command/src/argument_types/range.rs @@ -1,6 +1,6 @@ use pumpkin_util::math::bounds::{DoubleBounds, IntBounds}; -use crate::command::{ +use crate::{ argument_types::{ FromStringReader, argument_type::{ArgumentType, JavaClientArgumentType}, @@ -16,7 +16,7 @@ use crate::command::{ /// - `min..max`: All integers that are between `min` and `max`, inclusive. Mathematically representable as `[min, max]`. pub struct IntRangeArgumentType; -impl ArgumentType for IntRangeArgumentType { +impl ArgumentType for IntRangeArgumentType { type Item = IntBounds; fn parse(&self, reader: &mut StringReader) -> Result { @@ -41,7 +41,7 @@ impl_copy_get!(IntRangeArgumentType, IntBounds); /// - `min..max`: All numbers that are between `min` and `max`, inclusive. Mathematically representable as `[min, max]`. pub struct FloatRangeArgumentType; -impl ArgumentType for FloatRangeArgumentType { +impl ArgumentType for FloatRangeArgumentType { type Item = DoubleBounds; fn parse(&self, reader: &mut StringReader) -> Result { @@ -63,10 +63,9 @@ impl_copy_get!(FloatRangeArgumentType, DoubleBounds); mod test { use pumpkin_util::math::bounds::{DoubleBounds, IntBounds}; - use crate::command::{ + use crate::{ argument_types::{ SWAPPED_BOUNDS_ERROR_TYPE, - argument_type::ArgumentType, range::{FloatRangeArgumentType, IntRangeArgumentType}, }, string_reader::StringReader, diff --git a/crates/pumpkin/src/command/argument_types/resource.rs b/crates/pumpkin-command/src/argument_types/resource.rs similarity index 91% rename from crates/pumpkin/src/command/argument_types/resource.rs rename to crates/pumpkin-command/src/argument_types/resource.rs index 67b2f8ee7..4d5efc3cc 100644 --- a/crates/pumpkin/src/command/argument_types/resource.rs +++ b/crates/pumpkin-command/src/argument_types/resource.rs @@ -1,11 +1,11 @@ -use crate::command::argument_types::FromStringReader; -use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType}; -use crate::command::context::command_context::CommandContext; -use crate::command::errors::command_syntax_error::CommandSyntaxError; -use crate::command::errors::error_types::{CommandErrorType, DISPATCHER_PARSE_EXCEPTION}; -use crate::command::node::attached::AttachedNode; -use crate::command::string_reader::StringReader; -use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder}; +use crate::argument_types::FromStringReader; +use crate::argument_types::argument_type::{ArgumentType, JavaClientArgumentType}; +use crate::context::command_context::CommandContext; +use crate::errors::command_syntax_error::CommandSyntaxError; +use crate::errors::error_types::{CommandErrorType, DISPATCHER_PARSE_EXCEPTION}; +use crate::node::attached::AttachedNode; +use crate::string_reader::StringReader; +use crate::suggestion::suggestions::{Suggestions, SuggestionsBuilder}; use pumpkin_data::entity::EntityType; use pumpkin_data::translation; use pumpkin_protocol::java::client::play::SuggestionProviders; @@ -123,7 +123,7 @@ pub struct ResourceArgument( pub &'static (dyn Fn(Identifier) -> Option<&'static (dyn Any + Send + Sync)> + Send + Sync), ); -impl ArgumentType for ResourceArgument { +impl ArgumentType for ResourceArgument { type Item = &'static (dyn Any + Send + Sync); fn parse(&self, reader: &mut StringReader) -> Result { @@ -139,7 +139,7 @@ impl ArgumentType for ResourceArgument { fn list_suggestions( &self, - _context: &CommandContext, + _context: &CommandContext, suggestions_builder: SuggestionsBuilder, ) -> Suggestions { if self.0 == ENTITY_TYPE_REGISTRY { @@ -225,8 +225,8 @@ impl ArgumentType for ResourceArgument { } impl ResourceArgument { - pub fn get_resource( - context: &CommandContext, + pub fn get_resource( + context: &CommandContext, name: &str, registry_key: &Identifier, ) -> Result<&'static T, CommandSyntaxError> { @@ -291,36 +291,36 @@ impl ResourceArgument { } } - pub fn get_entity_type( - context: &CommandContext, + pub fn get_entity_type( + context: &CommandContext, name: &str, ) -> Result<&'static EntityType, CommandSyntaxError> { Self::get_resource(context, name, ENTITY_TYPE_REGISTRY) } - pub fn get_enchantment( - context: &CommandContext, + pub fn get_enchantment( + context: &CommandContext, name: &str, ) -> Result<&'static pumpkin_data::Enchantment, CommandSyntaxError> { Self::get_resource(context, name, ENCHANTMENT_REGISTRY) } - pub fn get_mob_effect( - context: &CommandContext, + pub fn get_mob_effect( + context: &CommandContext, name: &str, ) -> Result<&'static pumpkin_data::effect::StatusEffect, CommandSyntaxError> { Self::get_resource(context, name, MOB_EFFECT_REGISTRY) } - pub fn get_damage_type( - context: &CommandContext, + pub fn get_damage_type( + context: &CommandContext, name: &str, ) -> Result<&'static pumpkin_data::damage::DamageType, CommandSyntaxError> { Self::get_resource(context, name, DAMAGE_TYPE_REGISTRY) } - pub fn get_summonable_entity_type( - context: &CommandContext, + pub fn get_summonable_entity_type( + context: &CommandContext, name: &str, ) -> Result<&'static EntityType, CommandSyntaxError> { let val: &'static EntityType = Self::get_resource(context, name, ENTITY_TYPE_REGISTRY)?; diff --git a/crates/pumpkin/src/command/argument_types/resource_or_tag.rs b/crates/pumpkin-command/src/argument_types/resource_or_tag.rs similarity index 77% rename from crates/pumpkin/src/command/argument_types/resource_or_tag.rs rename to crates/pumpkin-command/src/argument_types/resource_or_tag.rs index 8bd78902e..d4e65c82b 100644 --- a/crates/pumpkin/src/command/argument_types/resource_or_tag.rs +++ b/crates/pumpkin-command/src/argument_types/resource_or_tag.rs @@ -1,21 +1,18 @@ -use std::collections::BTreeSet; - use pumpkin_data::structures::StructureSet; use pumpkin_data::tag::{self, RegistryKey}; use pumpkin_data::translation; use pumpkin_util::identifier::Identifier; use pumpkin_util::text::TextComponent; -use pumpkin_world::poi::POI_TYPE_NETHER_PORTAL; -use crate::command::argument_types::FromStringReader; -use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType}; -use crate::command::argument_types::resource_key::BIOME_REGISTRY; -use crate::command::context::command_context::CommandContext; -use crate::command::errors::command_syntax_error::CommandSyntaxError; -use crate::command::errors::error_types::CommandErrorType; -use crate::command::string_reader::StringReader; -use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder}; +use crate::argument_types::FromStringReader; +use crate::argument_types::argument_type::{ArgumentType, JavaClientArgumentType}; +use crate::context::command_context::CommandContext; +use crate::errors::command_syntax_error::CommandSyntaxError; +use crate::errors::error_types::CommandErrorType; +use crate::string_reader::StringReader; +use crate::suggestion::suggestions::{Suggestions, SuggestionsBuilder}; +pub static BIOME_REGISTRY: Identifier = Identifier::vanilla_static("worldgen/biome"); pub static STRUCTURE_REGISTRY: Identifier = Identifier::vanilla_static("worldgen/structure"); pub static POI_REGISTRY: Identifier = Identifier::vanilla_static("point_of_interest_type"); @@ -78,7 +75,7 @@ fn suggest_for_registry(registry: &Identifier, builder: SuggestionsBuilder) -> S .map(|name| format!("minecraft:{name}")), ) .build() - } else if *registry == *BIOME_REGISTRY { + } else if *registry == BIOME_REGISTRY { let biomes = pumpkin_data::biome::Biome::ALL .iter() .map(|biome| format!("minecraft:{}", biome.registry_id)); @@ -88,21 +85,38 @@ fn suggest_for_registry(registry: &Identifier, builder: SuggestionsBuilder) -> S } else if *registry == POI_REGISTRY { // There is no generated POI type registry (yet), so offer the types // known from tag data plus the ones the server actually creates. - let mut names: BTreeSet = tag::get_latest_map(RegistryKey::PointOfInterestType) - .into_iter() - .flat_map(|map| map.values().flat_map(|tag| tag.0.iter())) - .map(|name| format!("minecraft:{name}")) - .collect(); - names.insert(POI_TYPE_NETHER_PORTAL.to_string()); + let poi_types = [ + "minecraft:armorer", + "minecraft:butcher", + "minecraft:cartographer", + "minecraft:cleric", + "minecraft:farmer", + "minecraft:fisherman", + "minecraft:fletcher", + "minecraft:leatherworker", + "minecraft:librarian", + "minecraft:mason", + "minecraft:shepherd", + "minecraft:toolsmith", + "minecraft:weaponsmith", + "minecraft:home", + "minecraft:meeting", + "minecraft:beehive", + "minecraft:bee_nest", + "minecraft:nether_portal", + "minecraft:lodestone", + "minecraft:lightning_rod", + ]; builder .filter_and_suggest_iter( - names - .into_iter() + poi_types + .iter() + .map(|&s| s.to_string()) .chain(tag_names(RegistryKey::PointOfInterestType)), ) .build() } else { - Suggestions::empty() + builder.build() } } @@ -113,7 +127,7 @@ fn suggest_for_registry(registry: &Identifier, builder: SuggestionsBuilder) -> S /// the command. pub struct ResourceOrTagKeyArgument(pub Identifier); -impl ArgumentType for ResourceOrTagKeyArgument { +impl ArgumentType for ResourceOrTagKeyArgument { type Item = ResourceOrTag; fn parse(&self, reader: &mut StringReader) -> Result { @@ -122,13 +136,13 @@ impl ArgumentType for ResourceOrTagKeyArgument { fn list_suggestions( &self, - _context: &CommandContext, + _context: &CommandContext, builder: SuggestionsBuilder, ) -> Suggestions { suggest_for_registry(&self.0, builder) } - fn client_side_parser(&'_ self) -> JavaClientArgumentType { + fn client_side_parser(&self) -> JavaClientArgumentType { JavaClientArgumentType::ResourceOrTagKey { identifier: self.0.clone(), } @@ -148,7 +162,7 @@ impl ArgumentType for ResourceOrTagKeyArgument { /// generated entry data (such as POI types) are accepted as-is. pub struct ResourceOrTagArgument(pub Identifier); -impl ArgumentType for ResourceOrTagArgument { +impl ArgumentType for ResourceOrTagArgument { type Item = ResourceOrTag; fn parse(&self, reader: &mut StringReader) -> Result { @@ -156,7 +170,7 @@ impl ArgumentType for ResourceOrTagArgument { match &value { ResourceOrTag::Resource(id) => { - if self.0 == *BIOME_REGISTRY + if self.0 == BIOME_REGISTRY && !(id.is_vanilla() && pumpkin_data::biome::Biome::from_name(id.path()).is_some()) { @@ -167,7 +181,7 @@ impl ArgumentType for ResourceOrTagArgument { } } ResourceOrTag::Tag(id) => { - let registry_key = if self.0 == *BIOME_REGISTRY { + let registry_key = if self.0 == BIOME_REGISTRY { Some(RegistryKey::WorldgenBiome) } else if self.0 == POI_REGISTRY { Some(RegistryKey::PointOfInterestType) @@ -191,7 +205,7 @@ impl ArgumentType for ResourceOrTagArgument { fn list_suggestions( &self, - _context: &CommandContext, + _context: &CommandContext, builder: SuggestionsBuilder, ) -> Suggestions { suggest_for_registry(&self.0, builder) diff --git a/crates/pumpkin/src/command/argument_types/slot.rs b/crates/pumpkin-command/src/argument_types/slot.rs similarity index 81% rename from crates/pumpkin/src/command/argument_types/slot.rs rename to crates/pumpkin-command/src/argument_types/slot.rs index 88f154471..5c8445287 100644 --- a/crates/pumpkin/src/command/argument_types/slot.rs +++ b/crates/pumpkin-command/src/argument_types/slot.rs @@ -1,9 +1,9 @@ -use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType}; -use crate::command::context::command_context::CommandContext; -use crate::command::errors::command_syntax_error::CommandSyntaxError; -use crate::command::errors::error_types::CommandErrorType; -use crate::command::string_reader::StringReader; -use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder}; +use crate::argument_types::argument_type::{ArgumentType, JavaClientArgumentType}; +use crate::context::command_context::CommandContext; +use crate::errors::command_syntax_error::CommandSyntaxError; +use crate::errors::error_types::CommandErrorType; +use crate::string_reader::StringReader; +use crate::suggestion::suggestions::{Suggestions, SuggestionsBuilder}; use pumpkin_data::slot_ranges::{ SLOT_RANGE_ALL_NAMES, SLOT_RANGE_SINGLE_SLOT_NAMES, get_slot_range, }; @@ -22,7 +22,7 @@ pub const ONLY_SINGLE_SLOT_ALLOWED_ERROR_TYPE: CommandErrorType<1> = CommandErro pub struct SlotArgumentType; -impl ArgumentType for SlotArgumentType { +impl ArgumentType for SlotArgumentType { type Item = usize; fn parse(&self, reader: &mut StringReader) -> Result { @@ -46,7 +46,7 @@ impl ArgumentType for SlotArgumentType { fn list_suggestions( &self, - _context: &CommandContext, + _context: &CommandContext, builder: SuggestionsBuilder, ) -> Suggestions { builder @@ -63,7 +63,7 @@ impl_copy_get!(SlotArgumentType, usize); pub struct SlotsArgumentType; -impl ArgumentType for SlotsArgumentType { +impl ArgumentType for SlotsArgumentType { type Item = &'static [usize]; fn parse(&self, reader: &mut StringReader) -> Result { @@ -83,7 +83,7 @@ impl ArgumentType for SlotsArgumentType { fn list_suggestions( &self, - _context: &CommandContext, + _context: &CommandContext, builder: SuggestionsBuilder, ) -> Suggestions { builder.filter_and_suggest(&SLOT_RANGE_ALL_NAMES).build() diff --git a/crates/pumpkin/src/command/argument_types/sound_category.rs b/crates/pumpkin-command/src/argument_types/sound_category.rs similarity index 74% rename from crates/pumpkin/src/command/argument_types/sound_category.rs rename to crates/pumpkin-command/src/argument_types/sound_category.rs index eca44c77f..f2a4511f4 100644 --- a/crates/pumpkin/src/command/argument_types/sound_category.rs +++ b/crates/pumpkin-command/src/argument_types/sound_category.rs @@ -2,12 +2,12 @@ use pumpkin_data::sound::SoundCategory; use pumpkin_data::translation; use pumpkin_util::text::TextComponent; -use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType}; -use crate::command::context::command_context::CommandContext; -use crate::command::errors::command_syntax_error::CommandSyntaxError; -use crate::command::errors::error_types::CommandErrorType; -use crate::command::string_reader::StringReader; -use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder}; +use crate::argument_types::argument_type::{ArgumentType, JavaClientArgumentType}; +use crate::context::command_context::CommandContext; +use crate::errors::command_syntax_error::CommandSyntaxError; +use crate::errors::error_types::CommandErrorType; +use crate::string_reader::StringReader; +use crate::suggestion::suggestions::{Suggestions, SuggestionsBuilder}; pub const ERROR_INVALID_SOURCE: CommandErrorType<1> = CommandErrorType::new( translation::java::ARGUMENT_ENUM_INVALID, @@ -22,7 +22,7 @@ const CATEGORIES: [&str; 10] = [ #[derive(Clone, Copy)] pub struct SoundCategoryArgumentType; -impl ArgumentType for SoundCategoryArgumentType { +impl ArgumentType for SoundCategoryArgumentType { type Item = SoundCategory; fn parse(&self, reader: &mut StringReader) -> Result { @@ -54,7 +54,7 @@ impl ArgumentType for SoundCategoryArgumentType { fn list_suggestions( &self, - _context: &CommandContext, + _context: &CommandContext, builder: SuggestionsBuilder, ) -> Suggestions { builder.filter_and_suggest(&CATEGORIES).build() @@ -62,7 +62,10 @@ impl ArgumentType for SoundCategoryArgumentType { } impl SoundCategoryArgumentType { - pub fn get(context: &CommandContext, name: &str) -> Result { + pub fn get( + context: &CommandContext, + name: &str, + ) -> Result { context.get_argument::(name).copied() } } diff --git a/crates/pumpkin/src/command/argument_types/structure.rs b/crates/pumpkin-command/src/argument_types/structure.rs similarity index 67% rename from crates/pumpkin/src/command/argument_types/structure.rs rename to crates/pumpkin-command/src/argument_types/structure.rs index ace479eab..8079a527c 100644 --- a/crates/pumpkin/src/command/argument_types/structure.rs +++ b/crates/pumpkin-command/src/argument_types/structure.rs @@ -1,15 +1,15 @@ -use crate::command::argument_types::FromStringReader; -use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType}; -use crate::command::context::command_context::CommandContext; -use crate::command::errors::command_syntax_error::CommandSyntaxError; -use crate::command::string_reader::StringReader; -use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder}; +use crate::argument_types::FromStringReader; +use crate::argument_types::argument_type::{ArgumentType, JavaClientArgumentType}; +use crate::context::command_context::CommandContext; +use crate::errors::command_syntax_error::CommandSyntaxError; +use crate::string_reader::StringReader; +use crate::suggestion::suggestions::{Suggestions, SuggestionsBuilder}; use pumpkin_protocol::java::client::play::SuggestionProviders; use pumpkin_util::identifier::Identifier; pub struct StructureNameArgumentType; -impl ArgumentType for StructureNameArgumentType { +impl ArgumentType for StructureNameArgumentType { type Item = Identifier; fn parse(&self, reader: &mut StringReader) -> Result { @@ -18,7 +18,7 @@ impl ArgumentType for StructureNameArgumentType { fn list_suggestions( &self, - _context: &CommandContext, + _context: &CommandContext, builder: SuggestionsBuilder, ) -> Suggestions { let names = pumpkin_world::generation::structure::template::all_structure_names(); diff --git a/crates/pumpkin/src/command/argument_types/team_color.rs b/crates/pumpkin-command/src/argument_types/team_color.rs similarity index 75% rename from crates/pumpkin/src/command/argument_types/team_color.rs rename to crates/pumpkin-command/src/argument_types/team_color.rs index 618d7abc5..3db1aa68d 100644 --- a/crates/pumpkin/src/command/argument_types/team_color.rs +++ b/crates/pumpkin-command/src/argument_types/team_color.rs @@ -1,9 +1,9 @@ -use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType}; -use crate::command::context::command_context::CommandContext; -use crate::command::errors::command_syntax_error::CommandSyntaxError; -use crate::command::errors::error_types::CommandErrorType; -use crate::command::string_reader::StringReader; -use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder}; +use crate::argument_types::argument_type::{ArgumentType, JavaClientArgumentType}; +use crate::context::command_context::CommandContext; +use crate::errors::command_syntax_error::CommandSyntaxError; +use crate::errors::error_types::CommandErrorType; +use crate::string_reader::StringReader; +use crate::suggestion::suggestions::{Suggestions, SuggestionsBuilder}; use pumpkin_data::translation::java::ARGUMENT_COLOR_INVALID; use pumpkin_util::text::TextComponent; use pumpkin_util::text::color::NamedColor; @@ -13,7 +13,7 @@ pub const INVALID_VALUE_ERROR_TYPE: CommandErrorType<1> = pub struct TeamColorArgumentType; -impl ArgumentType for TeamColorArgumentType { +impl ArgumentType for TeamColorArgumentType { type Item = NamedColor; fn parse(&self, reader: &mut StringReader) -> Result { @@ -31,7 +31,7 @@ impl ArgumentType for TeamColorArgumentType { fn list_suggestions( &self, - _context: &CommandContext, + _context: &CommandContext, builder: SuggestionsBuilder, ) -> Suggestions { builder diff --git a/crates/pumpkin/src/command/argument_types/template.rs b/crates/pumpkin-command/src/argument_types/template.rs similarity index 67% rename from crates/pumpkin/src/command/argument_types/template.rs rename to crates/pumpkin-command/src/argument_types/template.rs index 6f366f4e5..089f1bebb 100644 --- a/crates/pumpkin/src/command/argument_types/template.rs +++ b/crates/pumpkin-command/src/argument_types/template.rs @@ -1,15 +1,15 @@ -use crate::command::argument_types::FromStringReader; -use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType}; -use crate::command::context::command_context::CommandContext; -use crate::command::errors::command_syntax_error::CommandSyntaxError; -use crate::command::string_reader::StringReader; -use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder}; +use crate::argument_types::FromStringReader; +use crate::argument_types::argument_type::{ArgumentType, JavaClientArgumentType}; +use crate::context::command_context::CommandContext; +use crate::errors::command_syntax_error::CommandSyntaxError; +use crate::string_reader::StringReader; +use crate::suggestion::suggestions::{Suggestions, SuggestionsBuilder}; use pumpkin_protocol::java::client::play::SuggestionProviders; use pumpkin_util::identifier::Identifier; pub struct TemplateNameArgumentType; -impl ArgumentType for TemplateNameArgumentType { +impl ArgumentType for TemplateNameArgumentType { type Item = String; fn parse(&self, reader: &mut StringReader) -> Result { @@ -19,7 +19,7 @@ impl ArgumentType for TemplateNameArgumentType { fn list_suggestions( &self, - _context: &CommandContext, + _context: &CommandContext, builder: SuggestionsBuilder, ) -> Suggestions { let names = pumpkin_world::generation::structure::template::all_template_names(); diff --git a/crates/pumpkin/src/command/argument_types/time.rs b/crates/pumpkin-command/src/argument_types/time.rs similarity index 88% rename from crates/pumpkin/src/command/argument_types/time.rs rename to crates/pumpkin-command/src/argument_types/time.rs index 78ce2c06b..68c9329b6 100644 --- a/crates/pumpkin/src/command/argument_types/time.rs +++ b/crates/pumpkin-command/src/argument_types/time.rs @@ -1,9 +1,9 @@ -use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType}; -use crate::command::context::command_context::CommandContext; -use crate::command::errors::command_syntax_error::CommandSyntaxError; -use crate::command::errors::error_types::CommandErrorType; -use crate::command::string_reader::StringReader; -use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder}; +use crate::argument_types::argument_type::{ArgumentType, JavaClientArgumentType}; +use crate::context::command_context::CommandContext; +use crate::errors::command_syntax_error::CommandSyntaxError; +use crate::errors::error_types::CommandErrorType; +use crate::string_reader::StringReader; +use crate::suggestion::suggestions::{Suggestions, SuggestionsBuilder}; use pumpkin_data::translation; use pumpkin_util::text::TextComponent; @@ -43,7 +43,7 @@ impl TimeArgumentType { } } -impl ArgumentType for TimeArgumentType { +impl ArgumentType for TimeArgumentType { type Item = i32; fn parse(&self, reader: &mut StringReader) -> Result { @@ -74,7 +74,7 @@ impl ArgumentType for TimeArgumentType { fn list_suggestions( &self, - _context: &CommandContext, + _context: &CommandContext, suggestions_builder: SuggestionsBuilder, ) -> Suggestions { let mut reader = StringReader::new(suggestions_builder.remaining()); @@ -95,15 +95,18 @@ impl ArgumentType for TimeArgumentType { impl TimeArgumentType { /// Returns a [`CommandContext`]'s parsed time argument in the form /// of its duration, in ticks. - pub fn get(context: &CommandContext, name: &str) -> Result { + pub fn get( + context: &CommandContext, + name: &str, + ) -> Result { Ok(*context.get_argument(name)?) } } #[cfg(test)] mod test { - use crate::command::{ - argument_types::{argument_type::ArgumentType, time, time::TimeArgumentType}, + use crate::{ + argument_types::{time, time::TimeArgumentType}, string_reader::StringReader, }; diff --git a/crates/pumpkin/src/command/argument_types/uuid.rs b/crates/pumpkin-command/src/argument_types/uuid.rs similarity index 93% rename from crates/pumpkin/src/command/argument_types/uuid.rs rename to crates/pumpkin-command/src/argument_types/uuid.rs index dc60b8c01..71d58380f 100644 --- a/crates/pumpkin/src/command/argument_types/uuid.rs +++ b/crates/pumpkin-command/src/argument_types/uuid.rs @@ -2,7 +2,7 @@ use pumpkin_data::translation::java::ARGUMENT_UUID_INVALID; use pumpkin_util::uuid::parse_uuid; use uuid::Uuid; -use crate::command::{ +use crate::{ argument_types::argument_type::{ArgumentType, JavaClientArgumentType}, errors::{command_syntax_error::CommandSyntaxError, error_types::CommandErrorType}, string_reader::StringReader, @@ -15,7 +15,7 @@ pub const INVALID_UUID_ERROR_TYPE: CommandErrorType<0> = #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct UuidArgumentType; -impl ArgumentType for UuidArgumentType { +impl ArgumentType for UuidArgumentType { type Item = Uuid; fn parse(&self, reader: &mut StringReader) -> Result { diff --git a/crates/pumpkin/src/command/context/command_context.rs b/crates/pumpkin-command/src/context/command_context.rs similarity index 85% rename from crates/pumpkin/src/command/context/command_context.rs rename to crates/pumpkin-command/src/context/command_context.rs index 2b5a95fa7..95f3a54f9 100644 --- a/crates/pumpkin/src/command/context/command_context.rs +++ b/crates/pumpkin-command/src/context/command_context.rs @@ -1,13 +1,11 @@ -use crate::command::context::command_source::{CommandSource, ReturnValue}; -use crate::command::context::string_range::StringRange; -use crate::command::errors::command_syntax_error::CommandSyntaxError; -use crate::command::errors::error_types::DISPATCHER_PARSE_EXCEPTION; -use crate::command::node::attached::NodeId; -use crate::command::node::dispatcher::{CommandDispatcher, ResultConsumer}; -use crate::command::node::tree::Tree; -use crate::command::node::{Command, RedirectModifier}; -use crate::server::Server; -use crate::world::World; +use crate::context::string_range::StringRange; +use crate::errors::command_syntax_error::CommandSyntaxError; +use crate::errors::error_types::DISPATCHER_PARSE_EXCEPTION; +use crate::node::attached::NodeId; +use crate::node::dispatcher::{CommandDispatcher, ResultConsumer}; +use crate::node::tree::Tree; +use crate::node::{Command, RedirectModifier}; +use crate::source::{CommandSource, DummySource, ReturnValue}; use pumpkin_util::text::TextComponent; use rustc_hash::FxHashMap; use std::any::Any; @@ -53,9 +51,9 @@ impl ParsedArgument { /// Represents the context used when commands are run. #[derive(Clone)] -pub struct CommandContext<'a> { +pub struct CommandContext<'a, S: CommandSource = DummySource> { /// The source running the commands. - pub source: Arc, + pub source: Arc, /// The input string ran as the command. pub input: String, @@ -65,7 +63,7 @@ pub struct CommandContext<'a> { pub arguments: FxHashMap>, /// The tree this context is related to. - pub tree: &'a Tree, + pub tree: &'a Tree, /// The root that this context will use, bound to the tree. /// Not necessarily the root node of the tree, however. @@ -81,20 +79,28 @@ pub struct CommandContext<'a> { pub child: Option>, /// The redirect modifier of this context. - pub modifier: RedirectModifier, + pub modifier: RedirectModifier, /// Whether this context forks or not. pub forks: bool, /// The command stored in this context which /// is run to get a command result. - pub command: Option, + pub command: Option>, } -impl CommandContext<'_> { +impl std::ops::Deref for CommandContext<'_, S> { + type Target = S; + + fn deref(&self) -> &Self::Target { + &self.source + } +} + +impl CommandContext<'_, S> { /// Copies this context with the source provided. #[must_use] - pub fn with_source(&self, source: Arc) -> Self { + pub fn with_source(&self, source: Arc) -> Self { Self { source, input: self.input.clone(), @@ -136,8 +142,8 @@ impl CommandContext<'_> { /// A simple example that takes two arguments specified from the node /// and returns their sum as the status output of the `Executor`: /// ``` - /// use pumpkin::command::context::command_context::CommandContext; - /// use pumpkin::command::node::{CommandExecutor, CommandExecutorResult}; + /// use pumpkin_command::context::command_context::CommandContext; + /// use pumpkin_command::node::{CommandExecutor, CommandExecutorResult}; /// /// struct Executor; /// impl CommandExecutor for Executor { @@ -170,38 +176,29 @@ impl CommandContext<'_> { ))) }) } - - /// Gets the server of this context. - #[must_use] - pub fn server(&self) -> &Arc { - self.source.server() - } - - /// Gets the world of this context. - #[must_use] - pub fn world(&self) -> &Arc { - self.source.world() - } } /// Represents a linked chain of [`CommandContext`]s, where the previous links to the next as a child. #[derive(Clone)] -pub struct ContextChain<'a> { +pub struct ContextChain<'a, S: CommandSource = DummySource> { /// The modifiers of this context chain. - modifiers: Vec>>, + modifiers: Vec>>, /// That specific [`CommandContext`] to execute. - execute: Arc>, + execute: Arc>, } -impl<'a> ContextChain<'a> { +impl<'a, S: CommandSource> ContextChain<'a, S> { /// Creates a new chain of contexts from a vector of them and one to execute. /// /// # Panics /// /// Panics if the `execute` given is non-executable. #[must_use] - pub fn new(modifiers: Vec>>, execute: Arc>) -> Self { + pub fn new( + modifiers: Vec>>, + execute: Arc>, + ) -> Self { assert!( execute.command.is_some(), "Expected last command in chain to be executable" @@ -212,7 +209,7 @@ impl<'a> ContextChain<'a> { /// Tries to flatten a [`CommandContext`]. If no command /// is available at the end of chain, [`None`] is returned. #[must_use] - pub fn try_flatten(root: &CommandContext<'a>) -> Option { + pub fn try_flatten(root: &CommandContext<'a, S>) -> Option { let mut modifiers = Vec::new(); let mut current = Arc::new(root.clone()); loop { @@ -231,11 +228,11 @@ impl<'a> ContextChain<'a> { /// Runs the given modifier with provided details. pub fn run_modifier( - modifier: &CommandContext<'a>, - source: &Arc, - result_consumer: &dyn ResultConsumer, + modifier: &CommandContext<'a, S>, + source: &Arc, + result_consumer: &dyn ResultConsumer, forked_mode: bool, - ) -> Result>, CommandSyntaxError> { + ) -> Result>, CommandSyntaxError> { let source_modifier = &modifier.modifier; if matches!(source_modifier, RedirectModifier::KeepSource) { @@ -261,9 +258,9 @@ impl<'a> ContextChain<'a> { /// /// Panics if the `executable` provided cannot be executed. pub fn run_executable( - executable: &CommandContext<'a>, - source: &Arc, - result_consumer: &dyn ResultConsumer, + executable: &CommandContext<'a, S>, + source: &Arc, + result_consumer: &dyn ResultConsumer, forked_mode: bool, ) -> Result { let context_to_use = executable.with_source(source.clone()); @@ -288,15 +285,15 @@ impl<'a> ContextChain<'a> { /// Executes all contexts in the chain, returning the ultimate result. pub fn execute_all( &self, - source: &Arc, - result_consumer: &dyn ResultConsumer, + source: &Arc, + result_consumer: &dyn ResultConsumer, ) -> Result { if self.modifiers.is_empty() { return Self::run_executable(&self.execute, source, result_consumer, false); } let mut forked_mode = false; - let mut current_sources: Vec> = vec![source.clone()]; + let mut current_sources: Vec> = vec![source.clone()]; for modifier in &self.modifiers { forked_mode |= modifier.forks; @@ -338,7 +335,7 @@ impl<'a> ContextChain<'a> { /// Gets a reference to the top context of this chain. #[must_use] - pub fn get_top_context(&'_ self) -> &'_ Arc> { + pub fn get_top_context(&'_ self) -> &'_ Arc> { if self.modifiers.is_empty() { &self.execute } else { @@ -347,7 +344,7 @@ impl<'a> ContextChain<'a> { } /// Gets a mutable reference to the top context of this chain. - pub fn get_top_context_mut(&'_ mut self) -> &mut Arc> { + pub fn get_top_context_mut(&'_ mut self) -> &mut Arc> { if self.modifiers.is_empty() { &mut self.execute } else { @@ -373,12 +370,12 @@ impl<'a> ContextChain<'a> { /// /// This builder's lifetime is bound to the dispatcher provided to it. #[derive(Clone)] -pub struct CommandContextBuilder<'a> { +pub struct CommandContextBuilder<'a, S: CommandSource = DummySource> { /// The dispatcher this builder is related to. - pub dispatcher: &'a CommandDispatcher, + pub dispatcher: &'a CommandDispatcher, /// The source running the commands. - pub source: Arc, + pub source: Arc, /// Arguments which have been parsed and /// can be fetched for command execution. @@ -398,24 +395,24 @@ pub struct CommandContextBuilder<'a> { pub child: Option>, /// The redirect modifier of this context. - pub modifier: RedirectModifier, + pub modifier: RedirectModifier, /// Whether this context forks or not. pub forks: bool, /// The command stored in this context which /// is run to get a command result. - pub command: Option, + pub command: Option>, } -impl<'a> CommandContextBuilder<'a> { +impl<'a, S: CommandSource> CommandContextBuilder<'a, S> { /// Creates a new [`CommandContextBuilder`] from the properties required to initialize one. /// /// Note that builder's lifetime is bound to the dispatcher provided to it. #[must_use] pub fn new( - dispatcher: &'a CommandDispatcher, - source: Arc, + dispatcher: &'a CommandDispatcher, + source: Arc, root: NodeId, start: usize, ) -> Self { @@ -435,7 +432,7 @@ impl<'a> CommandContextBuilder<'a> { /// Builds the required [`CommandContext`], consuming itself in the process. #[must_use] - pub fn build(self, input: &str) -> CommandContext<'a> { + pub fn build(self, input: &str) -> CommandContext<'a, S> { CommandContext { source: self.source, input: input.to_string(), @@ -452,7 +449,7 @@ impl<'a> CommandContextBuilder<'a> { } /// Mutates itself with the new source set. - pub fn with_source(&mut self, source: Arc) { + pub fn with_source(&mut self, source: Arc) { self.source = source; } @@ -462,7 +459,7 @@ impl<'a> CommandContextBuilder<'a> { } /// Mutates itself with the new command set. - pub fn with_command(&mut self, command: Option) { + pub fn with_command(&mut self, command: Option>) { self.command = command; } @@ -538,18 +535,16 @@ impl<'a> CommandContextBuilder<'a> { #[cfg(test)] mod test { - use crate::command::argument_builder::{ArgumentBuilder, CommandArgumentBuilder}; - use crate::command::context::command_context::{ + use crate::argument_builder::{ArgumentBuilder, CommandArgumentBuilder}; + use crate::context::command_context::{ CommandContext, CommandContextBuilder, ContextChain, ParsedArgument, Stage, }; - use crate::command::context::command_source::CommandSource; - use crate::command::context::string_range::StringRange; - use crate::command::errors::command_syntax_error::CommandSyntaxError; - use crate::command::node::dispatcher::{CommandDispatcher, EmptyResultConsumer}; - use crate::command::node::tree::ROOT_NODE_ID; - use crate::command::node::{ - CommandExecutor, CommandExecutorResult, RedirectModifier, Redirection, - }; + use crate::context::string_range::StringRange; + use crate::errors::command_syntax_error::CommandSyntaxError; + use crate::node::dispatcher::{CommandDispatcher, EmptyResultConsumer}; + use crate::node::tree::ROOT_NODE_ID; + use crate::node::{CommandExecutor, CommandExecutorResult, RedirectModifier, Redirection}; + use crate::source::DummySource; use pumpkin_util::math::vector3::Vector3; use std::sync::Arc; @@ -562,12 +557,8 @@ mod test { // For testing purposes fn builder(dispatcher: &'_ CommandDispatcher) -> CommandContextBuilder<'_> { - let mut builder = CommandContextBuilder::new( - dispatcher, - Arc::new(CommandSource::dummy()), - ROOT_NODE_ID, - 0, - ); + let mut builder = + CommandContextBuilder::new(dispatcher, Arc::new(DummySource::dummy()), ROOT_NODE_ID, 0); let parsed_argument = ParsedArgument::new(StringRange::between(0, 1), Box::new(6789i32)); @@ -611,7 +602,7 @@ mod test { dispatcher .register(CommandArgumentBuilder::new("foo", "A test command").executes(TenExecutor)); - let source = Arc::new(CommandSource::dummy()); + let source = Arc::new(DummySource::dummy()); let result = dispatcher.parse_input("foo", &source); let top_context = result.context.build("foo"); let chain = ContextChain::try_flatten(&top_context) @@ -629,7 +620,7 @@ mod test { CommandArgumentBuilder::new("bar", "Another test command").redirect(Redirection::Root), ); - let source = Arc::new(CommandSource::dummy()); + let source = Arc::new(DummySource::dummy()); let result = dispatcher.parse_input("bar foo", &source); let top_context = result.context.build("bar foo"); let chain = ContextChain::try_flatten(&top_context) @@ -644,7 +635,7 @@ mod test { dispatcher .register(CommandArgumentBuilder::new("foo", "A test command").executes(TenExecutor)); - let source = Arc::new(CommandSource::dummy()); + let source = Arc::new(DummySource::dummy()); let result = dispatcher.parse_input("foo", &source); let top_context = result.context.build("foo"); let chain = ContextChain::try_flatten(&top_context) @@ -667,7 +658,7 @@ mod test { .redirect(Redirection::Root), ); - let source = Arc::new(CommandSource::dummy()); + let source = Arc::new(DummySource::dummy()); let result = dispatcher.parse_input("bar qux foo", &source); let top_context = result.context.build("bar qux foo"); let chain = ContextChain::try_flatten(&top_context) @@ -691,7 +682,7 @@ mod test { let mut dispatcher = CommandDispatcher::new(); dispatcher.register(CommandArgumentBuilder::new("foo", "A test command")); - let source = Arc::new(CommandSource::dummy()); + let source = DummySource::dummy(); let result = dispatcher.parse_input("foo", &source); let top_context = result.context.build("foo"); @@ -716,15 +707,15 @@ mod test { dispatcher.register( CommandArgumentBuilder::new("bar", "Another test command").redirect_with_modifier( Redirection::Root, - RedirectModifier::Custom(Arc::new(|context| { + RedirectModifier::Custom(Arc::new(|context: &CommandContext| { let mut new_source = context.source.as_ref().clone(); new_source.position = Vector3::new(0f64, 10f64, 0f64); Ok(vec![Arc::new(new_source)]) })), ), ); - let source = Arc::new(CommandSource::dummy()); - let result = dispatcher.parse_input("bar foo", &source); + let source = Arc::new(DummySource::dummy()); + let result = dispatcher.parse_input("bar foo", source.as_ref()); let top_context = result.context.build("bar foo"); let chain = ContextChain::try_flatten(&top_context) .expect("The context should have properly flattened, as it has a command to execute"); diff --git a/crates/pumpkin-command/src/context/mod.rs b/crates/pumpkin-command/src/context/mod.rs new file mode 100644 index 000000000..ce756604f --- /dev/null +++ b/crates/pumpkin-command/src/context/mod.rs @@ -0,0 +1,5 @@ +pub mod command_context; +pub mod string_range; + +pub use command_context::*; +pub use string_range::*; diff --git a/crates/pumpkin-command/src/context/string_range.rs b/crates/pumpkin-command/src/context/string_range.rs new file mode 100644 index 000000000..2ac00becc --- /dev/null +++ b/crates/pumpkin-command/src/context/string_range.rs @@ -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> for StringRange { + fn from(value: Range) -> Self { + Self::between(value.start, value.end) + } +} + +impl From for Range { + fn from(value: StringRange) -> Self { + value.start..value.end + } +} diff --git a/crates/pumpkin-command/src/dispatcher.rs b/crates/pumpkin-command/src/dispatcher.rs new file mode 100644 index 000000000..5c398ef88 --- /dev/null +++ b/crates/pumpkin-command/src/dispatcher.rs @@ -0,0 +1 @@ +pub use crate::node::dispatcher::*; diff --git a/crates/pumpkin/src/command/errors/command_syntax_error.rs b/crates/pumpkin-command/src/errors/command_syntax_error.rs similarity index 97% rename from crates/pumpkin/src/command/errors/command_syntax_error.rs rename to crates/pumpkin-command/src/errors/command_syntax_error.rs index 6c07ef85a..1ba60d4ae 100644 --- a/crates/pumpkin/src/command/errors/command_syntax_error.rs +++ b/crates/pumpkin-command/src/errors/command_syntax_error.rs @@ -1,6 +1,6 @@ use pumpkin_util::text::TextComponent; -use crate::command::errors::error_types::AnyCommandErrorType; +use crate::errors::error_types::AnyCommandErrorType; /// A struct detailing the context of a syntax error, including where it happened. #[derive(Debug, Clone, PartialEq, Eq)] diff --git a/crates/pumpkin/src/command/errors/error_types.rs b/crates/pumpkin-command/src/errors/error_types.rs similarity index 98% rename from crates/pumpkin/src/command/errors/error_types.rs rename to crates/pumpkin-command/src/errors/error_types.rs index 21896ff1e..6c94ee353 100644 --- a/crates/pumpkin/src/command/errors/error_types.rs +++ b/crates/pumpkin-command/src/errors/error_types.rs @@ -113,7 +113,7 @@ pub const DISPATCHER_PARSE_EXCEPTION: CommandErrorType<1> = CommandErrorType::ne translation::bedrock::COMMANDS_GENERIC_SYNTAX, ); -use crate::command::errors::{ +use crate::errors::{ command_syntax_error::{CommandSyntaxError, ContextProvider}, error_types::sealed::Sealed, }; @@ -163,6 +163,7 @@ impl CommandErrorType { /// Creates an error without context from itself and from a slice of `N` arguments. #[must_use] + #[expect(deprecated)] pub fn create_without_context_args_slice( &'static self, args: &[TextComponent; N], @@ -178,6 +179,7 @@ impl CommandErrorType { } /// Creates an error with context from itself and from a slice of `N` arguments. + #[expect(deprecated)] pub fn create_args_slice( &'static self, context_provider: &C, @@ -344,8 +346,8 @@ error_type_no_arg_slice_impl!(N = 4 => arg1, arg2, arg3, arg4 | "by taking 4 tra #[cfg(test)] mod test { - use crate::command::errors::error_types::{CommandErrorType, LiteralCommandErrorType}; - use crate::command::string_reader::StringReader; + use crate::errors::error_types::{CommandErrorType, LiteralCommandErrorType}; + use crate::string_reader::StringReader; use pumpkin_util::text::TextComponent; const TEST_LITERAL_ERROR_TYPE: LiteralCommandErrorType = @@ -382,6 +384,7 @@ mod test { } #[test] + #[expect(deprecated)] fn create_translatable_error() { let mut reader = StringReader::new("foo bar"); reader.set_cursor(4); diff --git a/crates/pumpkin/src/command/errors/mod.rs b/crates/pumpkin-command/src/errors/mod.rs similarity index 100% rename from crates/pumpkin/src/command/errors/mod.rs rename to crates/pumpkin-command/src/errors/mod.rs diff --git a/crates/pumpkin-command/src/lib.rs b/crates/pumpkin-command/src/lib.rs new file mode 100644 index 000000000..a17e4c422 --- /dev/null +++ b/crates/pumpkin-command/src/lib.rs @@ -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; diff --git a/crates/pumpkin/src/command/node/attached.rs b/crates/pumpkin-command/src/node/attached.rs similarity index 88% rename from crates/pumpkin/src/command/node/attached.rs rename to crates/pumpkin-command/src/node/attached.rs index e628bd57d..1c9095dc0 100644 --- a/crates/pumpkin/src/command/node/attached.rs +++ b/crates/pumpkin-command/src/node/attached.rs @@ -1,13 +1,14 @@ -use crate::command::context::string_range::StringRange; -use crate::command::errors::command_syntax_error::CommandSyntaxError; -use crate::command::errors::error_types::LITERAL_INCORRECT; -use crate::command::node::detached::GlobalNodeId; -use crate::command::node::tree::ROOT_NODE_ID; -use crate::command::node::{ +use crate::context::string_range::StringRange; +use crate::errors::command_syntax_error::CommandSyntaxError; +use crate::errors::error_types::LITERAL_INCORRECT; +use crate::node::detached::GlobalNodeId; +use crate::node::tree::ROOT_NODE_ID; +use crate::node::{ ArgumentNodeMetadata, Command, CommandNodeMetadata, LiteralNodeMetadata, NodeMetadata, OwnedNodeData, RedirectModifier, Redirection, Requirements, }; -use crate::command::string_reader::StringReader; +use crate::source::{CommandSource, DummySource}; +use crate::string_reader::StringReader; use pumpkin_util::text::TextComponent; use rustc_hash::FxHashMap; use std::num::NonZero; @@ -70,18 +71,18 @@ impl From for NodeId { /// Represents a node which has been attached as the root of a `Tree`. #[derive(Clone)] -pub struct RootAttachedNode { - pub owned: OwnedNodeData, +pub struct RootAttachedNode { + pub owned: OwnedNodeData, pub children: FxHashMap, } -impl Default for RootAttachedNode { +impl Default for RootAttachedNode { fn default() -> Self { Self::new() } } -impl RootAttachedNode { +impl RootAttachedNode { #[must_use] pub fn new() -> Self { Self { @@ -100,8 +101,8 @@ impl RootAttachedNode { /// Represents a literal, non-command node that has already been attached /// to a `Tree`. #[derive(Clone)] -pub struct LiteralAttachedNode { - pub owned: OwnedNodeData, +pub struct LiteralAttachedNode { + pub owned: OwnedNodeData, pub children: FxHashMap, pub redirect: Option, pub meta: LiteralNodeMetadata, @@ -110,8 +111,8 @@ pub struct LiteralAttachedNode { /// Represents a literal, command node that has already been attached /// to a `Tree`. #[derive(Clone)] -pub struct CommandAttachedNode { - pub owned: OwnedNodeData, +pub struct CommandAttachedNode { + pub owned: OwnedNodeData, pub children: FxHashMap, pub redirect: Option, pub meta: CommandNodeMetadata, @@ -120,11 +121,11 @@ pub struct CommandAttachedNode { /// Represents a node that accepts a specific type of argument that has already been attached /// to a `Tree`. #[derive(Clone)] -pub struct ArgumentAttachedNode { - pub owned: OwnedNodeData, +pub struct ArgumentAttachedNode { + pub owned: OwnedNodeData, pub children: FxHashMap, pub redirect: Option, - pub meta: ArgumentNodeMetadata, + pub meta: ArgumentNodeMetadata, } /// Allows a way to store the kind of node @@ -139,24 +140,24 @@ pub enum NodeClassification { /// Represents a node not attached to a `Tree` yet. #[derive(Clone)] -pub enum AttachedNode { - Root(RootAttachedNode), - Literal(LiteralAttachedNode), - Command(CommandAttachedNode), - Argument(ArgumentAttachedNode), +pub enum AttachedNode { + Root(RootAttachedNode), + Literal(LiteralAttachedNode), + Command(CommandAttachedNode), + Argument(ArgumentAttachedNode), } /// Returned when a literal could not be parsed. pub struct CouldNotParseLiteral; -impl AttachedNode { +impl AttachedNode { /// Creates an [`AttachedNode`] from its properties allowing any [`NodeMetadata`]. #[must_use] pub fn from_parts( - owned: OwnedNodeData, + owned: OwnedNodeData, children: FxHashMap, redirect: Option, - meta: NodeMetadata, + meta: NodeMetadata, ) -> Self { match meta { NodeMetadata::Root => Self::Root(RootAttachedNode { owned, children }), @@ -201,7 +202,7 @@ impl AttachedNode { /// Gets a reference to the owned data of this node. #[must_use] - pub const fn owned_node_data_ref(&self) -> &OwnedNodeData { + pub const fn owned_node_data_ref(&self) -> &OwnedNodeData { match self { Self::Root(node) => &node.owned, Self::Literal(node) => &node.owned, @@ -211,7 +212,7 @@ impl AttachedNode { } /// Gets a mutable reference to the owned data of this node. - pub const fn owned_node_data_mut_ref(&mut self) -> &mut OwnedNodeData { + pub const fn owned_node_data_mut_ref(&mut self) -> &mut OwnedNodeData { match self { Self::Root(node) => &mut node.owned, Self::Literal(node) => &mut node.owned, @@ -285,23 +286,23 @@ impl AttachedNode { /// Gets all the requirements required for this node to be run. #[must_use] - pub const fn requirements(&self) -> &Requirements { + pub const fn requirements(&self) -> &Requirements { &self.owned_node_data_ref().requirements } /// Overrides all the requirements for this node to be run to a value. - pub fn set_requirement(&mut self, requirements: Requirements) { + pub fn set_requirement(&mut self, requirements: Requirements) { self.owned_node_data_mut_ref().requirements = requirements; } /// Gets the modifier for this node to be run. #[must_use] - pub const fn modifier(&self) -> &RedirectModifier { + pub const fn modifier(&self) -> &RedirectModifier { &self.owned_node_data_ref().modifier } /// Sets the modifier for this node to a value. - pub fn set_modifier(&mut self, modifier: RedirectModifier) { + pub fn set_modifier(&mut self, modifier: RedirectModifier) { self.owned_node_data_mut_ref().modifier = modifier; } @@ -318,12 +319,12 @@ impl AttachedNode { /// Gets the executable command for this node. #[must_use] - pub fn command(&self) -> &Option { + pub fn command(&self) -> &Option> { &self.owned_node_data_ref().command } /// Sets the executable command for this node. - pub fn set_command(&mut self, command: Option) { + pub fn set_command(&mut self, command: Option>) { self.owned_node_data_mut_ref().command = command; } diff --git a/crates/pumpkin/src/command/node/detached.rs b/crates/pumpkin-command/src/node/detached.rs similarity index 69% rename from crates/pumpkin/src/command/node/detached.rs rename to crates/pumpkin-command/src/node/detached.rs index 8f882883f..40807c4c3 100644 --- a/crates/pumpkin/src/command/node/detached.rs +++ b/crates/pumpkin-command/src/node/detached.rs @@ -1,9 +1,10 @@ -use crate::command::argument_types::argument_type::AnyArgumentType; -use crate::command::node::{ +use crate::argument_types::argument_type::AnyArgumentType; +use crate::node::{ ArgumentNodeMetadata, Command, CommandNodeMetadata, LiteralNodeMetadata, NodeMetadata, OwnedNodeData, RedirectModifier, Redirection, Requirements, }; -use crate::command::suggestion::provider::SuggestionProvider; +use crate::source::{CommandSource, DummySource}; +use crate::suggestion::provider::SuggestionProvider; use rustc_hash::FxHashMap; use std::borrow::Cow; use std::num::NonZero; @@ -44,26 +45,26 @@ impl Default for GlobalNodeId { /// /// To be of any utility, this must be attached to a tree later. #[derive(Clone)] -pub struct LiteralDetachedNode { - pub owned: OwnedNodeData, - pub children: FxHashMap, +pub struct LiteralDetachedNode { + pub owned: OwnedNodeData, + pub children: FxHashMap>, pub redirect: Option, pub meta: LiteralNodeMetadata, } -impl LiteralDetachedNode { +impl LiteralDetachedNode { /// Creates a detached literal node from its properties, /// without any children. /// /// # Note - /// Prefer using the [`literal`](crate::command::argument_builder::literal) function over this one. + /// Prefer using the [`literal`](crate::argument_builder::literal) function over this one. pub fn new( global_id: GlobalNodeId, literal: impl Into>, - command: Option, - requirements: Requirements, + command: Option>, + requirements: Requirements, redirect: Option, - modifier: RedirectModifier, + modifier: RedirectModifier, forks: bool, ) -> Self { Self { @@ -88,28 +89,28 @@ impl LiteralDetachedNode { /// /// To be of any utility, this must be attached to a tree later. #[derive(Clone)] -pub struct CommandDetachedNode { - pub owned: OwnedNodeData, - pub children: FxHashMap, +pub struct CommandDetachedNode { + pub owned: OwnedNodeData, + pub children: FxHashMap>, pub redirect: Option, pub meta: CommandNodeMetadata, } -impl CommandDetachedNode { +impl CommandDetachedNode { /// Creates a detached literal node from its properties, /// without any children. /// /// # Note - /// Prefer using the [`command`](crate::command::argument_builder::command) function over this one. + /// Prefer using the [`command`](crate::argument_builder::command) function over this one. #[expect(clippy::too_many_arguments)] pub fn new( global_id: GlobalNodeId, literal: impl Into>, description: impl Into>, - command: Option, - requirements: Requirements, + command: Option>, + requirements: Requirements, redirect: Option, - modifier: RedirectModifier, + modifier: RedirectModifier, forks: bool, ) -> Self { Self { @@ -131,30 +132,30 @@ impl CommandDetachedNode { /// /// To be of any utility, this must be attached to a tree later. #[derive(Clone)] -pub struct ArgumentDetachedNode { - pub owned: OwnedNodeData, - pub children: FxHashMap, +pub struct ArgumentDetachedNode { + pub owned: OwnedNodeData, + pub children: FxHashMap>, pub redirect: Option, - pub meta: ArgumentNodeMetadata, + pub meta: ArgumentNodeMetadata, } -impl ArgumentDetachedNode { +impl ArgumentDetachedNode { /// Creates a detached argument node from its properties, /// without any children. /// /// # Note - /// Prefer using the [`argument`](crate::command::argument_builder::argument) function over this one. + /// Prefer using the [`argument`](crate::argument_builder::argument) function over this one. #[expect(clippy::too_many_arguments)] pub fn new( global_id: GlobalNodeId, name: impl Into>, - argument_type: Arc, - command: Option, - requirements: Requirements, + argument_type: Arc>, + command: Option>, + requirements: Requirements, redirect: Option, - modifier: RedirectModifier, + modifier: RedirectModifier, forks: bool, - suggestion_provider: Option>, + suggestion_provider: Option>>, ) -> Self { Self { owned: OwnedNodeData { @@ -173,45 +174,45 @@ impl ArgumentDetachedNode { /// Represents a node not attached to a `Tree` yet. #[derive(Clone)] -pub enum DetachedNode { - Literal(LiteralDetachedNode), - Command(CommandDetachedNode), - Argument(ArgumentDetachedNode), +pub enum DetachedNode { + Literal(LiteralDetachedNode), + Command(CommandDetachedNode), + Argument(ArgumentDetachedNode), } /// Represents a [`DetachedNode`] that has been irreversibly /// decomposed into its elements so that it can be recast /// into a new `AttachedNode`. -pub struct DecomposedNode { - pub owned: OwnedNodeData, - pub children: FxHashMap, +pub struct DecomposedNode { + pub owned: OwnedNodeData, + pub children: FxHashMap>, pub redirect: Option, - pub meta: NodeMetadata, + pub meta: NodeMetadata, } -impl From for DetachedNode { - fn from(node: LiteralDetachedNode) -> Self { +impl From> for DetachedNode { + fn from(node: LiteralDetachedNode) -> Self { Self::Literal(node) } } -impl From for DetachedNode { - fn from(node: CommandDetachedNode) -> Self { +impl From> for DetachedNode { + fn from(node: CommandDetachedNode) -> Self { Self::Command(node) } } -impl From for DetachedNode { - fn from(node: ArgumentDetachedNode) -> Self { +impl From> for DetachedNode { + fn from(node: ArgumentDetachedNode) -> Self { Self::Argument(node) } } -impl DetachedNode { +impl DetachedNode { /// Irreversibly decomposes this [`DetachedNode`] into its constituent elements. /// This allows it to then be recast into a new `AttachedNode`. #[must_use] - pub fn decompose(self) -> DecomposedNode { + pub fn decompose(self) -> DecomposedNode { match self { Self::Literal(node) => DecomposedNode { owned: node.owned, diff --git a/crates/pumpkin/src/command/node/dispatcher.rs b/crates/pumpkin-command/src/node/dispatcher.rs similarity index 88% rename from crates/pumpkin/src/command/node/dispatcher.rs rename to crates/pumpkin-command/src/node/dispatcher.rs index ed5762433..6b6731777 100644 --- a/crates/pumpkin/src/command/node/dispatcher.rs +++ b/crates/pumpkin-command/src/node/dispatcher.rs @@ -1,19 +1,17 @@ -use crate::command::argument_builder::{ArgumentBuilder, CommandArgumentBuilder}; -use crate::command::context::command_context::{ - CommandContext, CommandContextBuilder, ContextChain, -}; -use crate::command::context::command_source::{CommandSource, ReturnValue}; -use crate::command::errors::command_syntax_error::CommandSyntaxError; -use crate::command::errors::error_types::{ +use crate::argument_builder::{ArgumentBuilder, CommandArgumentBuilder}; +use crate::context::command_context::{CommandContext, CommandContextBuilder, ContextChain}; +use crate::errors::command_syntax_error::CommandSyntaxError; +use crate::errors::error_types::{ DISPATCHER_EXPECTED_ARGUMENT_SEPARATOR, DISPATCHER_UNKNOWN_ARGUMENT, DISPATCHER_UNKNOWN_COMMAND, LiteralCommandErrorType, }; -use crate::command::node::Redirection; -use crate::command::node::attached::{CommandNodeId, NodeId}; -use crate::command::node::detached::CommandDetachedNode; -use crate::command::node::tree::{NodeIdClassification, ROOT_NODE_ID, Tree}; -use crate::command::string_reader::StringReader; -use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder}; +use crate::node::Redirection; +use crate::node::attached::{CommandNodeId, NodeId}; +use crate::node::detached::CommandDetachedNode; +use crate::node::tree::{NodeIdClassification, ROOT_NODE_ID, Tree}; +use crate::source::{CommandSource, DummySource, ReturnValue}; +use crate::string_reader::StringReader; +use crate::suggestion::suggestions::{Suggestions, SuggestionsBuilder}; use pumpkin_data::translation::java::COMMAND_CONTEXT_HERE; use pumpkin_protocol::java::client::play::CommandSuggestion; use pumpkin_util::text::TextComponent; @@ -21,7 +19,7 @@ use pumpkin_util::text::click::ClickEvent; use pumpkin_util::text::color::{Color, NamedColor}; use rustc_hash::{FxHashMap, FxHashSet}; use std::collections::BTreeMap; -use std::sync::{Arc, LazyLock}; +use std::sync::Arc; pub const ARG_SEPARATOR: &str = " "; pub const ARG_SEPARATOR_CHAR: char = ' '; @@ -38,47 +36,41 @@ pub const UNRESOLVED_REDIRECT: LiteralCommandErrorType = LiteralCommandErrorType::new("Could not resolve redirect to node"); /// Represents the result of parsing. -pub struct ParsingResult<'a> { - pub context: CommandContextBuilder<'a>, +pub struct ParsingResult<'a, S: CommandSource = DummySource> { + pub context: CommandContextBuilder<'a, S>, pub errors: FxHashMap, pub reader: StringReader<'static>, } /// Structs implementing this trait are able to execute upon command completion. -pub trait ResultConsumer: Sync + Send { - fn on_command_completion(&self, context: &CommandContext, result: ReturnValue); +pub trait ResultConsumer: Sync + Send { + fn on_command_completion(&self, context: &CommandContext, result: ReturnValue); } /// A [`ResultConsumer`] which does nothing. pub struct EmptyResultConsumer; -impl ResultConsumer for EmptyResultConsumer { - fn on_command_completion(&self, _context: &CommandContext, _result: ReturnValue) {} +impl ResultConsumer for EmptyResultConsumer { + fn on_command_completion(&self, _context: &CommandContext, _result: ReturnValue) {} } -pub static EMPTY_CONSUMER: LazyLock> = - LazyLock::new(|| Arc::new(EmptyResultConsumer)); - /// A [`ResultConsumer`] which defers the given result to the source provided. pub struct ResultDeferrer; -impl ResultConsumer for ResultDeferrer { - fn on_command_completion(&self, context: &CommandContext, result: ReturnValue) { - context.source.command_result_taker.call(result); +impl ResultConsumer for ResultDeferrer { + fn on_command_completion(&self, context: &CommandContext, result: ReturnValue) { + context.source.call_result(result); } } -pub static RESULT_DEFERRER: LazyLock> = - LazyLock::new(|| Arc::new(ResultDeferrer)); - /// The core command dispatcher, used to register, parse and execute commands. /// /// Internally, this dispatcher stores a [`Tree`]. Refer to its documentation /// for more information about nodes. #[derive(Clone)] -pub struct CommandDispatcher { - pub tree: Tree, - pub consumer: Arc, +pub struct CommandDispatcher { + pub tree: Tree, + pub consumer: Arc>, /// Primary names of commands that have been turned off through the server /// configuration. A disabled command behaves as if it does not exist: it @@ -91,13 +83,13 @@ pub struct CommandDispatcher { inactive_plugin_commands: FxHashSet, } -impl Default for CommandDispatcher { +impl Default for CommandDispatcher { fn default() -> Self { Self::new() } } -impl CommandDispatcher { +impl CommandDispatcher { /// Creates a new [`CommandDispatcher`] with a new [`Tree`]. #[must_use] pub fn new() -> Self { @@ -105,10 +97,11 @@ impl CommandDispatcher { } /// Creates this [`CommandDispatcher`] from a pre-existing tree. - pub fn from_existing_tree(tree: Tree) -> Self { + #[must_use] + pub fn from_existing_tree(tree: Tree) -> Self { Self { tree, - consumer: RESULT_DEFERRER.clone(), + consumer: Arc::new(ResultDeferrer), disabled: FxHashSet::default(), inactive_plugin_commands: FxHashSet::default(), } @@ -133,7 +126,7 @@ impl CommandDispatcher { } /// Makes a plugin command and its aliases unavailable until registration. - pub(crate) fn deactivate_plugin_command_and_aliases(&mut self, name: &str) { + pub fn deactivate_plugin_command_and_aliases(&mut self, name: &str) { let primary_name = self.primary_command_name(name); for alias in self.tree_alias_names(&primary_name) { self.deactivate_plugin_command(alias); @@ -142,7 +135,7 @@ impl CommandDispatcher { } /// Makes every root command registered by a plugin source unavailable. - pub(crate) fn deactivate_commands_from_source(&mut self, source: &str) { + pub fn deactivate_commands_from_source(&mut self, source: &str) { let primary_names = self .tree .get_root_children() @@ -253,7 +246,7 @@ impl CommandDispatcher { /// Note that, at least for now with this system, there is no way to /// unregister a command. This is due to redirection to /// potentially unregistered (freed) nodes. - pub fn register(&mut self, command_node: impl Into) -> CommandNodeId { + pub fn register(&mut self, command_node: impl Into>) -> CommandNodeId { let mut node = command_node.into(); let name = Self::normalize_command_name(&node.meta.literal); node.meta.literal = name.clone().into(); @@ -268,17 +261,13 @@ impl CommandDispatcher { if self.tree.get(&single_slash).is_none() { let main_node = &self.tree[main_node_id]; let description = main_node.meta.description.clone(); - let mut alias = crate::command::argument_builder::CommandArgumentBuilder::new( - single_slash, - description, - ); + let mut alias = + crate::argument_builder::CommandArgumentBuilder::new(single_slash, description); if let Some(executor) = &main_node.owned.command { alias = alias.executes_arc(executor.clone()); alias = alias.overwrite_requirements(main_node.owned.requirements.clone()); } - alias = alias.redirect(crate::command::node::Redirection::Local( - main_node_id.into(), - )); + alias = alias.redirect(crate::node::Redirection::Local(main_node_id.into())); self.tree.add_child_to_root(alias.build()); } } else if let Some(stripped) = name.strip_prefix('/') { @@ -286,17 +275,13 @@ impl CommandDispatcher { if self.tree.get(&double_slash).is_none() { let main_node = &self.tree[main_node_id]; let description = main_node.meta.description.clone(); - let mut alias = crate::command::argument_builder::CommandArgumentBuilder::new( - double_slash, - description, - ); + let mut alias = + crate::argument_builder::CommandArgumentBuilder::new(double_slash, description); if let Some(executor) = &main_node.owned.command { alias = alias.executes_arc(executor.clone()); alias = alias.overwrite_requirements(main_node.owned.requirements.clone()); } - alias = alias.redirect(crate::command::node::Redirection::Local( - main_node_id.into(), - )); + alias = alias.redirect(crate::node::Redirection::Local(main_node_id.into())); self.tree.add_child_to_root(alias.build()); } } @@ -313,10 +298,10 @@ impl CommandDispatcher { /// Note that, at least for now with this system, there is no way to /// unregister a command. This is due to redirection to /// potentially unregistered (freed) nodes. - pub fn register_with_aliases>( + pub fn register_with_aliases>( &mut self, - command_node: impl Into, - aliases: &[S], + command_node: impl Into>, + aliases: &[Str], ) -> CommandNodeId { let main_node_id = self.register(command_node); @@ -365,11 +350,7 @@ impl CommandDispatcher { /// /// # Note /// This does not cache parsed input. - pub fn execute_input( - &self, - input: &str, - source: &CommandSource, - ) -> Result { + pub fn execute_input(&self, input: &str, source: &S) -> Result { let mut reader = StringReader::new(input); // A disabled command must behave as if it does not exist from every @@ -390,14 +371,14 @@ impl CommandDispatcher { pub fn execute_reader( &self, reader: &mut StringReader<'_>, - source: &CommandSource, + source: &S, ) -> Result { let parsed = self.parse(reader, source); self.execute(parsed) } /// Executes a given result that has already been parsed from an input. - pub fn execute(&self, parsed: ParsingResult<'_>) -> Result { + pub fn execute(&self, parsed: ParsingResult<'_, S>) -> Result { if parsed.reader.peek().is_some() { return if let Some(err) = parsed.errors.values().next() { Err(err.clone()) @@ -425,17 +406,13 @@ impl CommandDispatcher { /// Only parses a given source with the specified source. #[must_use] - pub fn parse_input(&self, command: &str, source: &CommandSource) -> ParsingResult<'_> { + pub fn parse_input(&self, command: &str, source: &S) -> ParsingResult<'_, S> { let mut reader = StringReader::new(command); self.parse(&mut reader, source) } /// Parses a command owned by a [`StringReader`] with the provided source. - pub fn parse( - &self, - reader: &mut StringReader<'_>, - source: &CommandSource, - ) -> ParsingResult<'_> { + pub fn parse(&self, reader: &mut StringReader<'_>, source: &S) -> ParsingResult<'_, S> { let context = CommandContextBuilder::new( self, Arc::new(source.clone()), @@ -449,11 +426,11 @@ impl CommandDispatcher { &'a self, node: NodeId, original_reader: &mut StringReader<'_>, - context_so_far: &CommandContextBuilder<'a>, - ) -> ParsingResult<'a> { + context_so_far: &CommandContextBuilder<'a, S>, + ) -> ParsingResult<'a, S> { let source = context_so_far.source.clone(); let mut errors: FxHashMap = FxHashMap::default(); - let mut potentials: Vec = Vec::new(); + let mut potentials: Vec> = Vec::new(); let cursor = original_reader.cursor(); for child in self.tree.get_relevant_nodes(original_reader, node) { @@ -539,16 +516,7 @@ impl CommandDispatcher { /// /// If the input starts with one slash (`/`), it is removed /// inside the call itself. - /// - /// # Panics - /// - /// Panics if the source given to it is a dummy one. - pub fn handle_command<'a>(&'a self, source: &CommandSource, mut input: &'a str) { - assert!( - source.server.is_some(), - "Source provided to this command was a dummy source" - ); - + pub fn handle_command<'a>(&'a self, source: &S, mut input: &'a str) { // If input starts with '/', but the command with that leading slash is NOT // registered, while the stripped command IS registered (or if it's an unknown command // starting with a single slash, e.g. from console input), strip one leading slash. @@ -583,7 +551,12 @@ impl CommandDispatcher { /// Sends a command error to the provided source. /// This also shows the contextual information /// leading up to the error if necessary. - pub fn send_error_to_source(source: &CommandSource, error: CommandSyntaxError, command: &str) { + #[expect(deprecated)] + pub fn send_error_to_source( + source: &impl CommandSource, + error: CommandSyntaxError, + command: &str, + ) { source.send_message(error.message.color(Color::Named(NamedColor::Red))); if let Some(context) = error.context { @@ -631,7 +604,7 @@ impl CommandDispatcher { #[must_use] pub fn get_completion_suggestions_at_end( &self, - parsing_result: ParsingResult<'_>, + parsing_result: ParsingResult<'_, S>, ) -> Suggestions { let length = parsing_result.reader.total_length(); self.get_completion_suggestions(parsing_result, length) @@ -644,7 +617,7 @@ impl CommandDispatcher { #[must_use] pub fn get_completion_suggestions( &self, - parsing_result: ParsingResult<'_>, + parsing_result: ParsingResult<'_, S>, cursor: usize, ) -> Suggestions { let context = parsing_result.context; @@ -711,7 +684,7 @@ impl CommandDispatcher { /// This function currently panics if the source provided was a dummy source. /// This is subject to change in the future. #[must_use] - pub fn suggest(&self, input: &str, source: &CommandSource) -> Vec { + pub fn suggest(&self, input: &str, source: &S) -> Vec { self.suggest_with_range(input, source) .suggestions .into_iter() @@ -723,7 +696,7 @@ impl CommandDispatcher { } #[must_use] - pub fn suggest_with_range(&self, input: &str, source: &CommandSource) -> Suggestions { + pub fn suggest_with_range(&self, input: &str, source: &S) -> Suggestions { // Never suggest arguments for a command that has been turned off. if self.is_disabled(Self::command_name(input)) { return Suggestions::empty(); @@ -756,7 +729,7 @@ impl CommandDispatcher { /// The map returned has the key as the command name /// and the value as the command's description. #[must_use] - pub fn get_all_permitted_commands(&self, source: &CommandSource) -> BTreeMap<&str, &str> { + pub fn get_all_permitted_commands(&self, source: &S) -> BTreeMap<&str, &str> { let mut commands: BTreeMap<&str, &str> = BTreeMap::new(); for command in self.tree.get_root_children() { @@ -777,10 +750,7 @@ impl CommandDispatcher { /// The key is the command identifier, /// and the value is a tuple of `(description, usage)`. #[must_use] - pub fn get_all_permitted_commands_usage( - &self, - source: &CommandSource, - ) -> BTreeMap<&str, (&str, Box)> { + pub fn get_all_permitted_commands_usage(&self, source: &S) -> BTreeMap<&str, (&str, Box)> { let mut commands: BTreeMap<&str, (&str, Box)> = BTreeMap::new(); for (command_node_id, usage) in self.get_usage_of_commands(source) { @@ -801,7 +771,7 @@ impl CommandDispatcher { #[must_use] pub fn get_all_permitted_commands_usage_by_plugin( &self, - source: &CommandSource, + source: &S, plugin_name: &str, ) -> BTreeMap<&str, (&str, Box)> { let mut commands: BTreeMap<&str, (&str, Box)> = BTreeMap::new(); @@ -831,7 +801,7 @@ impl CommandDispatcher { #[must_use] pub fn get_permitted_command_usage( &self, - source: &CommandSource, + source: &S, command: &str, ) -> Option<(&str, Box)> { let command_node_id = self.tree.get(command)?; @@ -846,11 +816,7 @@ impl CommandDispatcher { /// Returns the usage of the given command node. #[must_use] - pub fn get_usage_of_command( - &self, - command_node: CommandNodeId, - source: &CommandSource, - ) -> Option { + pub fn get_usage_of_command(&self, command_node: CommandNodeId, source: &S) -> Option { // We know the root DOES NOT have an executor, so we pass false to `is_optional`. self.get_usage_recursive(command_node.into(), source, false, false, None) .map(|mut usage| { @@ -862,11 +828,7 @@ impl CommandDispatcher { /// Returns the usage of each child of the given node (permitted for the given source). #[must_use] - pub fn get_usage_of_children( - &self, - node: NodeId, - source: &CommandSource, - ) -> FxHashMap { + pub fn get_usage_of_children(&self, node: NodeId, source: &S) -> FxHashMap { let mut map = FxHashMap::default(); let is_optional = self.tree[node].command().is_some(); @@ -881,10 +843,7 @@ impl CommandDispatcher { /// Returns the usage of each command (permitted for the given source). #[must_use] - pub fn get_usage_of_commands( - &self, - source: &CommandSource, - ) -> FxHashMap { + pub fn get_usage_of_commands(&self, source: &S) -> FxHashMap { self.get_usage_of_children(ROOT_NODE_ID, source) .into_iter() // This is safe because every child of the root child is a command node. @@ -900,7 +859,7 @@ impl CommandDispatcher { fn get_usage_recursive( &self, node: NodeId, - source: &CommandSource, + source: &S, is_optional: bool, deep: bool, redirector_usage_text: Option, @@ -1007,15 +966,15 @@ impl CommandDispatcher { #[cfg(test)] mod test { - use crate::command::argument_builder::{ + use crate::argument_builder::{ ArgumentBuilder, CommandArgumentBuilder, LiteralArgumentBuilder, RequiredArgumentBuilder, }; - use crate::command::argument_types::core::integer::IntegerArgumentType; - use crate::command::context::command_context::CommandContext; - use crate::command::context::command_source::CommandSource; - use crate::command::errors::error_types::DISPATCHER_UNKNOWN_COMMAND; - use crate::command::node::dispatcher::CommandDispatcher; - use crate::command::node::{CommandExecutor, CommandExecutorResult}; + use crate::argument_types::core::integer::IntegerArgumentType; + use crate::context::command_context::CommandContext; + use crate::errors::error_types::DISPATCHER_UNKNOWN_COMMAND; + use crate::node::dispatcher::CommandDispatcher; + use crate::node::{CommandExecutor, CommandExecutorResult}; + use crate::source::DummySource; #[test] fn unknown_command() { @@ -1023,7 +982,7 @@ mod test { dispatcher.register( CommandArgumentBuilder::new("unknown", "A command without an executor").build(), ); - let source = CommandSource::dummy(); + let source = DummySource::dummy(); let result = dispatcher.execute_input("unknown", &source); assert!(result.is_err_and(|error| error.error_type == &DISPATCHER_UNKNOWN_COMMAND)); } @@ -1034,7 +993,7 @@ mod test { let executor: fn(&CommandContext) -> CommandExecutorResult = |_| Ok(1); dispatcher .register(CommandArgumentBuilder::new("simple", "A simple command").executes(executor)); - let source = CommandSource::dummy(); + let source = DummySource::dummy(); let result = dispatcher.execute_input("simple", &source); assert_eq!(result, Ok(1)); } @@ -1050,7 +1009,7 @@ mod test { .register(CommandArgumentBuilder::new("simple", "A simple command").executes(executor)); dispatcher.disable_command("simple"); - let source = CommandSource::dummy(); + let source = DummySource::dummy(); let result = dispatcher.execute_input("simple", &source); assert!(result.is_err_and(|error| error.error_type == &DISPATCHER_UNKNOWN_COMMAND)); } @@ -1066,7 +1025,7 @@ mod test { }; dispatcher.register_with_aliases(command(), &["Plugin-Alias"]); - let source = CommandSource::dummy(); + let source = DummySource::dummy(); assert_eq!(dispatcher.execute_input("plugin-command", &source), Ok(1)); assert_eq!(dispatcher.execute_input("plugin-alias", &source), Ok(1)); @@ -1170,7 +1129,7 @@ mod test { ), ), ); - let source = CommandSource::dummy(); + let source = DummySource::dummy(); assert_eq!( dispatcher.execute_input("arithmetic 3 + -7", &source), Ok(-4) @@ -1194,7 +1153,7 @@ mod test { // Note that we CANNOT use redirect here as node itself needs to execute the command, // not its 'children'. dispatcher.register(CommandArgumentBuilder::new("b", "An alias for /a").executes(executor)); - let source = CommandSource::dummy(); + let source = DummySource::dummy(); assert_eq!(dispatcher.execute_input("a", &source), Ok(1)); assert_eq!(dispatcher.execute_input("b", &source), Ok(1)); } @@ -1215,7 +1174,7 @@ mod test { )); // Note that this time, we SHOULD use redirect - it is leading to another node having `command`. dispatcher.register(CommandArgumentBuilder::new("b", "An alias for /a").redirect(a)); - let source = CommandSource::dummy(); + let source = DummySource::dummy(); assert_eq!(dispatcher.execute_input("a 5", &source), Ok(5)); assert_eq!(dispatcher.execute_input("b 7", &source), Ok(7)); } @@ -1246,7 +1205,7 @@ mod test { dispatcher.register(builder); - let source = CommandSource::dummy(); + let source = DummySource::dummy(); assert_eq!(dispatcher.execute_input("recurse", &source), Ok(1)); assert_eq!(dispatcher.execute_input("recurse 4", &source), Ok(1)); assert_eq!(dispatcher.execute_input("recurse 9 -1", &source), Ok(1)); @@ -1269,7 +1228,7 @@ mod test { CommandArgumentBuilder::new("//set", "WorldEdit set command").executes(executor), ); - let source = CommandSource::dummy(); + let source = DummySource::dummy(); // Direct execution with //set assert_eq!(dispatcher.execute_input("//set", &source), Ok(42)); // Execution via /set alias (as sent by Java client for //set) diff --git a/crates/pumpkin-command/src/node/mod.rs b/crates/pumpkin-command/src/node/mod.rs new file mode 100644 index 000000000..825120d43 --- /dev/null +++ b/crates/pumpkin-command/src/node/mod.rs @@ -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; + +/// A struct implementing this trait is able to run with a given context. +pub trait CommandExecutor: Sync + Send { + /// Executes this executor for a command. + fn execute(&self, context: &CommandContext) -> CommandExecutorResult; +} + +impl CommandExecutor for F +where + F: Fn(&CommandContext) -> CommandExecutorResult + Send + Sync, +{ + fn execute(&self, context: &CommandContext) -> CommandExecutorResult { + self(context) + } +} + +/// A function that takes a context and returns a command result. +pub type Command = Arc>; + +/// Represents the result of [`Arc`]s from a [`CommandContext`]. +pub type RedirectModifierResult = Result>, CommandSyntaxError>; + +/// A function that performs the required modification. +pub type RedirectModifierExecutor = + dyn Fn(&CommandContext) -> RedirectModifierResult + Send + Sync; + +/// A function that returns a new collection of sources from a given context. +#[derive(Clone)] +pub enum RedirectModifier { + /// Always returns only the source from the given context. + KeepSource, + + /// Returns multiple [`CommandSource`]s from one context via + /// custom behavior. + Custom(Arc>), +} + +impl RedirectModifier { + /// Tries to provide a [`Vec`] of [`Arc`] 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 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: &S) -> RequirementResult { + self.0(command_source) + } +} + +impl From for Requirement +where + F: Fn(&S) -> RequirementResult + Send + Sync + 'static, +{ + fn from(value: F) -> Self { + Self(Arc::new(value)) + } +} + +// Permissions +impl From for Requirement { + fn from(value: String) -> Self { + Self(Arc::new({ + let permission = Arc::new(value); + + move |source: &S| source.has_permission(&permission) + })) + } +} + +impl From<&'static str> for Requirement { + 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(pub Vec>); + +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: &S) -> 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>, +} + +/// 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>) -> 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, +} + +impl CommandNodeMetadata { + pub fn new( + literal: impl Into>, + description: impl Into>, + ) -> 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>, + pub suggestion_provider: Option>>, +} + +impl ArgumentNodeMetadata { + pub fn new( + name: impl Into>, + argument_type: Arc>, + suggestion_provider: Option>>, + ) -> 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> From for Redirection { + fn from(value: T) -> Self { + Self::Local(value.into()) + } +} + +impl From for Redirection { + fn from(value: GlobalNodeId) -> Self { + Self::Global(value) + } +} diff --git a/crates/pumpkin/src/command/node/tree.rs b/crates/pumpkin-command/src/node/tree.rs similarity index 90% rename from crates/pumpkin/src/command/node/tree.rs rename to crates/pumpkin-command/src/node/tree.rs index 268c2525d..5679a8a5d 100644 --- a/crates/pumpkin/src/command/node/tree.rs +++ b/crates/pumpkin-command/src/node/tree.rs @@ -1,15 +1,15 @@ -use crate::command::context::command_context::{CommandContextBuilder, ParsedArgument}; -use crate::command::context::command_source::CommandSource; -use crate::command::context::string_range::StringRange; -use crate::command::errors::command_syntax_error::CommandSyntaxError; -use crate::command::errors::error_types::LITERAL_INCORRECT; -use crate::command::node::Redirection; -use crate::command::node::attached::{ +use crate::context::command_context::{CommandContextBuilder, ParsedArgument}; +use crate::context::string_range::StringRange; +use crate::errors::command_syntax_error::CommandSyntaxError; +use crate::errors::error_types::LITERAL_INCORRECT; +use crate::node::Redirection; +use crate::node::attached::{ ArgumentAttachedNode, ArgumentNodeId, AttachedNode, CommandAttachedNode, CommandNodeId, LiteralAttachedNode, LiteralNodeId, NodeClassification, NodeId, RootAttachedNode, }; -use crate::command::node::detached::{CommandDetachedNode, DetachedNode, GlobalNodeId}; -use crate::command::string_reader::StringReader; +use crate::node::detached::{CommandDetachedNode, DetachedNode, GlobalNodeId}; +use crate::source::{CommandSource, DummySource}; +use crate::string_reader::StringReader; use pumpkin_util::text::TextComponent; use rustc_hash::{FxBuildHasher, FxHashMap, FxHashSet}; use std::num::NonZero; @@ -20,10 +20,10 @@ use std::sync::Arc; pub const ROOT_NODE_ID: NodeId = NodeId(NonZero::new(1).expect("1 is non-zero")); /// A consumer which takes ambiguity of input (when two or more nodes are satisfied) -pub trait AmbiguityConsumer { +pub trait AmbiguityConsumer { fn ambiguous( &mut self, - tree: &Tree, + tree: &Tree, parent: NodeId, child: NodeId, sibling: NodeId, @@ -76,12 +76,12 @@ pub enum NodeIdClassification { /// - **Argument**: /// Parses and accepts a specific type of value. This is very dynamic. #[derive(Clone)] -pub struct Tree { +pub struct Tree { /// All the nodes stored in this tree. /// /// In this vector, indices starting at 0 indicates the first node (ID = 1), /// 1 indicates the second node (ID = 2) and so on. - nodes: Vec, + nodes: Vec>, /// Keys linking [`GlobalNodeId`] to the [`NodeId`] for this tree. /// Useful for redirecting. @@ -91,13 +91,13 @@ pub struct Tree { command_node_mappings: FxHashMap, } -impl Default for Tree { +impl Default for Tree { fn default() -> Self { Self::new() } } -impl Tree { +impl Tree { /// Constructs a new tree, containing a new root node without children. #[must_use] pub fn new() -> Self { @@ -130,7 +130,7 @@ impl Tree { /// Helper to attach a given [`AttachedNode`], returning /// its [`NodeId`]. - fn add(&mut self, node: AttachedNode) -> NodeId { + fn add(&mut self, node: AttachedNode) -> NodeId { let global_id = node.global_id(); let local_id = self.alloc(); @@ -144,7 +144,7 @@ impl Tree { /// Helper to attach a [`DetachedNode`] irreversibly /// into this [`Tree`], returning the ID of the now attached /// node. - fn attach(&mut self, node: DetachedNode) -> NodeId { + fn attach(&mut self, node: DetachedNode) -> NodeId { // First, we decompose this node. let node = node.decompose(); @@ -177,7 +177,7 @@ impl Tree { } /// Adds a [`CommandDetachedNode`] to the root node of this tree. - pub fn add_child_to_root(&mut self, node: impl Into) -> CommandNodeId { + pub fn add_child_to_root(&mut self, node: impl Into>) -> CommandNodeId { // First, attach the node to this tree. let node = node.into(); let name = node.meta.literal.to_string(); @@ -199,7 +199,7 @@ impl Tree { /// /// Essentially, this means that a [`CommandDetachedNode`] must have the root node /// of the tree *as its parent*, to be attached to the tree. - pub fn add_child(&mut self, parent: NodeId, node: impl Into) -> NodeId { + pub fn add_child(&mut self, parent: NodeId, node: impl Into>) -> NodeId { let node = node.into(); assert!( parent == ROOT_NODE_ID || !matches!(node, DetachedNode::Command(_)), @@ -269,12 +269,12 @@ impl Tree { /// Returns whether the given node is able to be used by a given source. #[must_use] - pub fn can_use(&self, node: NodeId, source: &CommandSource) -> bool { + pub fn can_use(&self, node: NodeId, source: &S) -> bool { self[node].requirements().evaluate(source) } /// Finds ambiguities of input and gives them to the [`AmbiguityConsumer`]. - pub fn find_ambiguities(&self, node: NodeId, consumer: &mut impl AmbiguityConsumer) { + pub fn find_ambiguities(&self, node: NodeId, consumer: &mut impl AmbiguityConsumer) { let mut matches: FxHashSet = FxHashSet::default(); for child in self.get_children(node) { @@ -363,13 +363,13 @@ impl Tree { &self, node_id: NodeId, reader: &'a mut StringReader<'_>, - command_context_builder: &'a mut CommandContextBuilder<'_>, + command_context_builder: &'a mut CommandContextBuilder<'_, S>, ) -> Result<(), CommandSyntaxError> { match &self[node_id] { AttachedNode::Root(_) => {} AttachedNode::Literal(node) => { let start = reader.cursor(); - let Ok(end) = AttachedNode::parse_literal(reader, &node.meta.literal) else { + let Ok(end) = AttachedNode::::parse_literal(reader, &node.meta.literal) else { return Err(LITERAL_INCORRECT .create(reader, TextComponent::text(node.meta.literal.to_string()))); }; @@ -377,7 +377,7 @@ impl Tree { } AttachedNode::Command(node) => { let start = reader.cursor(); - let Ok(end) = AttachedNode::parse_literal(reader, &node.meta.literal) else { + let Ok(end) = AttachedNode::::parse_literal(reader, &node.meta.literal) else { return Err(LITERAL_INCORRECT .create(reader, TextComponent::text(node.meta.literal.to_string()))); }; @@ -419,20 +419,20 @@ impl Tree { } /// Returns an iterator to all the nodes of this tree. - pub fn iter(&self) -> std::slice::Iter<'_, AttachedNode> { + pub fn iter(&self) -> std::slice::Iter<'_, AttachedNode> { self.nodes.iter() } } -impl Index for Tree { - type Output = AttachedNode; +impl Index for Tree { + type Output = AttachedNode; fn index(&self, index: NodeId) -> &Self::Output { &self.nodes[index.0.get() - 1] } } -impl IndexMut for Tree { +impl IndexMut for Tree { fn index_mut(&mut self, index: NodeId) -> &mut Self::Output { &mut self.nodes[index.0.get() - 1] } @@ -441,8 +441,8 @@ impl IndexMut for Tree { /// Macro helper to create [`Index`] and [`IndexMut`] for [`Tree`] with typed IDs. macro_rules! impl_index_index_mut { ($node_id: ident -> AttachedNode::$attached_node_enum: ident($attached_node: ident)) => { - impl Index<$node_id> for Tree { - type Output = $attached_node; + impl Index<$node_id> for Tree { + type Output = $attached_node; fn index(&self, index: $node_id) -> &Self::Output { if let AttachedNode::$attached_node_enum(node) = &self.nodes[index.0.get() - 1] { @@ -456,7 +456,7 @@ macro_rules! impl_index_index_mut { } } - impl IndexMut<$node_id> for Tree { + impl IndexMut<$node_id> for Tree { fn index_mut(&mut self, index: $node_id) -> &mut Self::Output { if let AttachedNode::$attached_node_enum(node) = &mut self.nodes[index.0.get() - 1] { @@ -476,9 +476,9 @@ impl_index_index_mut!(LiteralNodeId -> AttachedNode::Literal(LiteralAttachedNode impl_index_index_mut!(CommandNodeId -> AttachedNode::Command(CommandAttachedNode)); impl_index_index_mut!(ArgumentNodeId -> AttachedNode::Argument(ArgumentAttachedNode)); -impl<'a> IntoIterator for &'a Tree { - type Item = &'a AttachedNode; - type IntoIter = std::slice::Iter<'a, AttachedNode>; +impl<'a, S: CommandSource> IntoIterator for &'a Tree { + type Item = &'a AttachedNode; + type IntoIter = std::slice::Iter<'a, AttachedNode>; fn into_iter(self) -> Self::IntoIter { self.iter() @@ -487,17 +487,18 @@ impl<'a> IntoIterator for &'a Tree { #[cfg(test)] mod test { - use crate::command::argument_builder::{ + use crate::argument_builder::{ ArgumentBuilder, CommandArgumentBuilder, LiteralArgumentBuilder, RequiredArgumentBuilder, }; - use crate::command::argument_types::core::string::StringArgumentType; - use crate::command::node::attached::NodeId; - use crate::command::node::tree::{AmbiguityConsumer, Tree}; + use crate::argument_types::core::string::StringArgumentType; + use crate::node::attached::NodeId; + use crate::node::tree::{AmbiguityConsumer, Tree}; + use crate::source::DummySource; #[test] fn adding_nodes() { // New tree (containing only one root node) - let mut tree = Tree::new(); + let mut tree = Tree::::new(); assert_eq!(tree.size(), 1); // Adding one node. @@ -516,7 +517,7 @@ mod test { #[test] fn adding_children_to_attached_node() { - let mut tree = Tree::new(); + let mut tree = Tree::::new(); let parent: NodeId = tree .add_child_to_root(CommandArgumentBuilder::new("foo", "A test command")) @@ -532,7 +533,7 @@ mod test { #[test] #[should_panic = "Cannot add a CommandDetachedNode as a child of a non-root node"] fn adding_command_node_to_non_root_node() { - let mut tree = Tree::new(); + let mut tree = Tree::::new(); let parent: NodeId = tree .add_child_to_root(CommandArgumentBuilder::new("foo", "A test command")) diff --git a/crates/pumpkin/src/command/parser.rs b/crates/pumpkin-command/src/parser.rs similarity index 99% rename from crates/pumpkin/src/command/parser.rs rename to crates/pumpkin-command/src/parser.rs index 2c755938a..136c2be27 100644 --- a/crates/pumpkin/src/command/parser.rs +++ b/crates/pumpkin-command/src/parser.rs @@ -1,6 +1,6 @@ use std::borrow::Cow; -use crate::command::{ +use crate::{ errors::error_types::{AnyCommandErrorType, CommandErrorType}, string_reader::StringReader, }; diff --git a/crates/pumpkin/src/command/snbt/markers.rs b/crates/pumpkin-command/src/snbt/markers.rs similarity index 99% rename from crates/pumpkin/src/command/snbt/markers.rs rename to crates/pumpkin-command/src/snbt/markers.rs index 3717e514e..d9966914c 100644 --- a/crates/pumpkin/src/command/snbt/markers.rs +++ b/crates/pumpkin-command/src/snbt/markers.rs @@ -1,4 +1,4 @@ -use crate::command::{ +use crate::{ errors::error_types::CommandErrorType, snbt::rules::{EXPECTED_BINARY_NUMERAL, EXPECTED_DECIMAL_NUMERAL, EXPECTED_HEX_NUMERAL}, }; diff --git a/crates/pumpkin/src/command/snbt/mod.rs b/crates/pumpkin-command/src/snbt/mod.rs similarity index 97% rename from crates/pumpkin/src/command/snbt/mod.rs rename to crates/pumpkin-command/src/snbt/mod.rs index e5bafd1e2..31d285fd3 100644 --- a/crates/pumpkin/src/command/snbt/mod.rs +++ b/crates/pumpkin-command/src/snbt/mod.rs @@ -5,14 +5,12 @@ mod rules; #[cfg(test)] mod tests; -use crate::command::errors::command_syntax_error::{CommandSyntaxError, CommandSyntaxErrorContext}; -use crate::command::errors::error_types::{CommandErrorType, LITERAL_INCORRECT}; -use crate::command::parser::{Parser, ParserErrors}; -use crate::command::snbt::markers::{ - ArrayPrefix, Base, IntegerLiteral, Sign, SignedPrefix, TypeSuffix, -}; -use crate::command::string_reader::StringReader; -use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder}; +use crate::errors::command_syntax_error::{CommandSyntaxError, CommandSyntaxErrorContext}; +use crate::errors::error_types::{CommandErrorType, LITERAL_INCORRECT}; +use crate::parser::{Parser, ParserErrors}; +use crate::snbt::markers::{ArrayPrefix, Base, IntegerLiteral, Sign, SignedPrefix, TypeSuffix}; +use crate::string_reader::StringReader; +use crate::suggestion::suggestions::{Suggestions, SuggestionsBuilder}; use pumpkin_codecs::Number; use pumpkin_data::translation; use pumpkin_nbt::tag::NbtTag; @@ -62,6 +60,7 @@ pub struct SnbtParser<'r, 's> { // impl SnbtParser<'_, '_> { /// Parses SNBT with a given [`StringReader`], giving the result or error from parsing. + #[expect(deprecated)] pub fn parse_for_commands(reader: &mut StringReader) -> Result { let (result, errors) = { let mut parser = SnbtParser { diff --git a/crates/pumpkin/src/command/snbt/operations.rs b/crates/pumpkin-command/src/snbt/operations.rs similarity index 95% rename from crates/pumpkin/src/command/snbt/operations.rs rename to crates/pumpkin-command/src/snbt/operations.rs index 7591fef45..63d0e31d2 100644 --- a/crates/pumpkin/src/command/snbt/operations.rs +++ b/crates/pumpkin-command/src/snbt/operations.rs @@ -2,7 +2,7 @@ use pumpkin_data::translation; use pumpkin_nbt::{nbt_ops::NbtOps, tag::NbtTag}; use pumpkin_util::uuid::parse_uuid_vec; -use crate::command::{errors::error_types::CommandErrorType, parser::Parser, snbt::SnbtParser}; +use crate::{errors::error_types::CommandErrorType, parser::Parser, snbt::SnbtParser}; use pumpkin_codecs::DynamicOps; pub const EXPECTED_NUMBER_OR_BOOLEAN: CommandErrorType<0> = CommandErrorType::new( diff --git a/crates/pumpkin/src/command/snbt/rules.rs b/crates/pumpkin-command/src/snbt/rules.rs similarity index 99% rename from crates/pumpkin/src/command/snbt/rules.rs rename to crates/pumpkin-command/src/snbt/rules.rs index 045fc2dc5..a3fa8b2a9 100644 --- a/crates/pumpkin/src/command/snbt/rules.rs +++ b/crates/pumpkin-command/src/snbt/rules.rs @@ -1,12 +1,12 @@ use std::collections::HashMap; -use crate::command::errors::error_types::{CommandErrorType, LITERAL_INCORRECT}; -use crate::command::parser::Parser; -use crate::command::snbt::markers::{ +use crate::errors::error_types::{CommandErrorType, LITERAL_INCORRECT}; +use crate::parser::Parser; +use crate::snbt::markers::{ ArrayPrefix, Base, IntegerLiteral, IntegerSuffix, Sign, Signed, SignedPrefix, TypeSuffix, }; -use crate::command::snbt::operations::SnbtOperations; -use crate::command::snbt::{NUMBER_PARSE_FAILURE, SnbtParser}; +use crate::snbt::operations::SnbtOperations; +use crate::snbt::{NUMBER_PARSE_FAILURE, SnbtParser}; use pumpkin_codecs::{DynamicOps, Number}; use pumpkin_data::translation; use pumpkin_nbt::compound::NbtCompound; diff --git a/crates/pumpkin/src/command/snbt/tests.rs b/crates/pumpkin-command/src/snbt/tests.rs similarity index 99% rename from crates/pumpkin/src/command/snbt/tests.rs rename to crates/pumpkin-command/src/snbt/tests.rs index 09687cb4c..c989972d1 100644 --- a/crates/pumpkin/src/command/snbt/tests.rs +++ b/crates/pumpkin-command/src/snbt/tests.rs @@ -2,7 +2,7 @@ use pumpkin_nbt::{compound::NbtCompound, nbt_compound_tag, tag::NbtTag}; const BUILT_IN_LIKE_SUGGESTIONS: &[&str] = &["(", "bool", "false", "true", "uuid"]; -use crate::command::{ +use crate::{ errors::command_syntax_error::CommandSyntaxError, snbt::SnbtParser, string_reader::StringReader, suggestion::suggestions::SuggestionsBuilder, }; diff --git a/crates/pumpkin-command/src/source.rs b/crates/pumpkin-command/src/source.rs new file mode 100644 index 000000000..aba8177a9 --- /dev/null +++ b/crates/pumpkin-command/src/source.rs @@ -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; + +#[derive(Clone, Default)] +pub struct ResultValueTaker(pub Vec); + +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 { + Vector3::default() + } + + /// Returns the rotation (pitch, yaw) of this command source. + fn rotation(&self) -> Vector2 { + 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 { + 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, + pub rotation: Vector2, + 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 { + self.position + } + fn rotation(&self) -> Vector2 { + 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 CommandSource for Arc { + 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 { + (**self).position() + } + + fn rotation(&self) -> Vector2 { + (**self).rotation() + } + + fn entity_anchor(&self) -> EntityAnchor { + (**self).entity_anchor() + } + + fn anchor_position(&self, anchor: EntityAnchor) -> Vector3 { + (**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) + } +} diff --git a/crates/pumpkin/src/command/string_reader.rs b/crates/pumpkin-command/src/string_reader.rs similarity index 98% rename from crates/pumpkin/src/command/string_reader.rs rename to crates/pumpkin-command/src/string_reader.rs index 6a9a8eb28..c1b527182 100644 --- a/crates/pumpkin/src/command/string_reader.rs +++ b/crates/pumpkin-command/src/string_reader.rs @@ -1,7 +1,7 @@ -use crate::command::errors::command_syntax_error::{ +use crate::errors::command_syntax_error::{ CommandSyntaxError, CommandSyntaxErrorContext, ContextProvider, }; -use crate::command::errors::error_types::{self, CommandErrorType}; +use crate::errors::error_types::{self, CommandErrorType}; use pumpkin_util::text::TextComponent; use std::borrow::Cow; use std::str::FromStr; @@ -367,7 +367,7 @@ impl ContextProvider for StringReader<'_> { #[cfg(test)] mod test { - use crate::command::{errors::error_types, string_reader::StringReader}; + use crate::{errors::error_types, string_reader::StringReader}; #[test] fn non_parsing_methods() { diff --git a/crates/pumpkin/src/command/suggestion/mod.rs b/crates/pumpkin-command/src/suggestion/mod.rs similarity index 98% rename from crates/pumpkin/src/command/suggestion/mod.rs rename to crates/pumpkin-command/src/suggestion/mod.rs index 241949657..f5f32ea33 100644 --- a/crates/pumpkin/src/command/suggestion/mod.rs +++ b/crates/pumpkin-command/src/suggestion/mod.rs @@ -5,7 +5,7 @@ use pumpkin_util::text::TextComponent; use std::fmt::Debug; use std::hash::Hash; -use crate::command::context::string_range::StringRange; +use crate::context::string_range::StringRange; /// A structure that describes the text of a suggestion. /// It's actual value can either be a [`String`], or an [`i32`]. @@ -21,7 +21,7 @@ use crate::command::context::string_range::StringRange; /// be fully expressed as a [`SuggestionText::Integer`] (no leading zeros). /// This is important to establish proper ordering. /// ``` -/// use pumpkin::command::suggestion::SuggestionText; +/// use pumpkin_command::suggestion::SuggestionText; /// /// let five_suggestion_1 = SuggestionText::new(5); /// let five_suggestion_2 = SuggestionText::new("5"); @@ -228,8 +228,8 @@ impl Suggestion { #[cfg(test)] mod test { - use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder}; - use crate::command::{context::string_range::StringRange, suggestion::Suggestion}; + use crate::suggestion::suggestions::{Suggestions, SuggestionsBuilder}; + use crate::{context::string_range::StringRange, suggestion::Suggestion}; use std::slice; #[test] diff --git a/crates/pumpkin/src/command/suggestion/provider.rs b/crates/pumpkin-command/src/suggestion/provider.rs similarity index 69% rename from crates/pumpkin/src/command/suggestion/provider.rs rename to crates/pumpkin-command/src/suggestion/provider.rs index 35d427c98..ea619e5a3 100644 --- a/crates/pumpkin/src/command/suggestion/provider.rs +++ b/crates/pumpkin-command/src/suggestion/provider.rs @@ -1,12 +1,13 @@ -use crate::command::context::command_context::CommandContext; -use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder}; +use crate::context::command_context::CommandContext; +use crate::source::{CommandSource, DummySource}; +use crate::suggestion::suggestions::{Suggestions, SuggestionsBuilder}; /// The [`Suggestions`] given by a [`SuggestionProvider`]. pub type SuggestionProviderResult = Suggestions; /// A trait allowing an object to provide suggestions using a /// [`CommandContext`] and [`SuggestionsBuilder`]. -pub trait SuggestionProvider: Send + Sync { +pub trait SuggestionProvider: Send + Sync { /// Uses a [`CommandContext`] and [`SuggestionsBuilder`] to suggest. /// /// # Arguments @@ -17,7 +18,7 @@ pub trait SuggestionProvider: Send + Sync { /// The [`Suggestions`] representing the suggested items. fn suggest( &self, - context: &CommandContext, + context: &CommandContext, builder: SuggestionsBuilder, ) -> SuggestionProviderResult; } diff --git a/crates/pumpkin/src/command/suggestion/suggestions.rs b/crates/pumpkin-command/src/suggestion/suggestions.rs similarity index 99% rename from crates/pumpkin/src/command/suggestion/suggestions.rs rename to crates/pumpkin-command/src/suggestion/suggestions.rs index aa8a76264..a509faaa5 100644 --- a/crates/pumpkin/src/command/suggestion/suggestions.rs +++ b/crates/pumpkin-command/src/suggestion/suggestions.rs @@ -1,5 +1,5 @@ -use crate::command::context::string_range::StringRange; -use crate::command::suggestion::{Suggestion, SuggestionText}; +use crate::context::string_range::StringRange; +use crate::suggestion::{Suggestion, SuggestionText}; use pumpkin_util::text::TextComponent; use std::borrow::Borrow; use std::cmp::Ordering; diff --git a/crates/pumpkin/Cargo.toml b/crates/pumpkin/Cargo.toml index f07c93d02..727f05f99 100644 --- a/crates/pumpkin/Cargo.toml +++ b/crates/pumpkin/Cargo.toml @@ -24,6 +24,7 @@ rayon.workspace = true pumpkin-util.workspace = true pumpkin-nbt.workspace = true pumpkin-config.workspace = true +pumpkin-command.workspace = true pumpkin-inventory.workspace = true pumpkin-world.workspace = true pumpkin-data = { workspace = true, features = ["default"] } diff --git a/crates/pumpkin/src/command/argument_builder.rs b/crates/pumpkin/src/command/argument_builder.rs index 834c50a73..7b00961c0 100644 --- a/crates/pumpkin/src/command/argument_builder.rs +++ b/crates/pumpkin/src/command/argument_builder.rs @@ -1,165 +1,69 @@ -use crate::command::argument_builder::private::Sealed; -use crate::command::argument_types::argument_type::AnyArgumentType; -use crate::command::node::detached::{ - ArgumentDetachedNode, CommandDetachedNode, DetachedNode, GlobalNodeId, LiteralDetachedNode, -}; -use crate::command::node::{ - Command, CommandExecutor, RedirectModifier, Redirection, Requirement, Requirements, -}; -use crate::command::suggestion::provider::SuggestionProvider; -use rustc_hash::FxHashMap; use std::borrow::Cow; use std::sync::Arc; -/// Represents an intermediate struct for -/// building arguments for commands. -/// -/// # Note -/// -/// This is an implementation detail. -struct CommonArgumentBuilder { - pub global_id: GlobalNodeId, - pub arguments: FxHashMap, - pub command: Option, - pub requirements: Requirements, - pub target: Option, - pub modifier: RedirectModifier, - pub forks: bool, -} +use crate::command::context::command_source::CommandSource; +use crate::command::node::detached::DetachedNode; +use crate::command::node::{ + ArcCommandExecutorAdapter, Command, CommandExecutor, CommandExecutorAdapter, RedirectModifier, + Requirement, +}; +use crate::command::suggestion::provider::{SuggestionProvider, SuggestionProviderAdapter}; -impl CommonArgumentBuilder { - fn new() -> Self { - Self { - global_id: GlobalNodeId::new(), - arguments: FxHashMap::default(), - command: None, - requirements: Requirements::new(), - target: None, - modifier: RedirectModifier::KeepSource, - forks: false, - } - } -} +use pumpkin_command::argument_builder::ArgumentBuilder as CoreArgumentBuilder; +pub use pumpkin_command::node::Redirection; -impl Default for CommonArgumentBuilder { - fn default() -> Self { - Self::new() - } -} +pub struct CommandArgumentBuilder( + pub pumpkin_command::argument_builder::CommandArgumentBuilder, +); +pub struct LiteralArgumentBuilder( + pub pumpkin_command::argument_builder::LiteralArgumentBuilder, +); +pub struct RequiredArgumentBuilder( + pub pumpkin_command::argument_builder::RequiredArgumentBuilder, +); -/// A short-form way to create a new [`CommandArgumentBuilder`] -/// from a literal and a command description. -/// -/// This can be imported directly without a prefix, or imported with the `argument_builder::` prefix. Here's an example of usage: -/// ``` -/// use pumpkin::command::argument_builder::command; -/// -/// let builder = command("foo", "A test command"); -/// ``` -/// -/// The builder returned will eventually construct a [`CommandDetachedNode`]. -/// This node can then be registered into a dispatcher. pub fn command( literal: impl Into>, description: impl Into>, ) -> 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>) -> LiteralArgumentBuilder { - LiteralArgumentBuilder::new(literal) + LiteralArgumentBuilder(pumpkin_command::argument_builder::literal(literal)) } -/// A short-form way to create a new [`RequiredArgumentBuilder`] -/// from an argument type and name. -/// -/// This can be imported directly without a prefix, or imported with the `argument_builder::` prefix. Here's an example of usage: -/// ``` -/// use pumpkin::command::{ -/// argument_builder::argument, -/// argument_types::core::integer::IntegerArgumentType -/// }; -/// -/// let argument_builder = argument("bar", IntegerArgumentType::new(1, 10)); -/// ``` -/// -/// The builder returned will eventually construct a [`ArgumentDetachedNode`]. pub fn argument( name: impl Into>, - arg_type: impl AnyArgumentType + 'static, + arg_type: impl pumpkin_command::argument_types::argument_type::AnyArgumentType + + 'static, ) -> RequiredArgumentBuilder { - RequiredArgumentBuilder::new(name, arg_type) + RequiredArgumentBuilder(pumpkin_command::argument_builder::argument(name, arg_type)) } -/// A builder that builds a literal, non-command [`DetachedNode`]. -pub struct LiteralArgumentBuilder { - common: CommonArgumentBuilder, - literal: Cow<'static, str>, -} +pub trait ArgumentBuilder: Sized { + type Node; -/// A builder that builds a command [`DetachedNode`]. -pub struct CommandArgumentBuilder { - common: CommonArgumentBuilder, - literal: Cow<'static, str>, - description: Cow<'static, str>, - source: Option, -} - -/// A builder that builds an argument [`DetachedNode`]. -pub struct RequiredArgumentBuilder { - common: CommonArgumentBuilder, - name: Cow<'static, str>, - argument_type: Arc, - suggestion_provider: Option>, -} - -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>: 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) -> Self; - /// Gets the command to execute for the node being built. #[must_use] fn command(&self) -> Option; - /// Sets the command to execute for the node being built. #[must_use] fn executes(self, command: impl CommandExecutor + 'static) -> Self { self.executes_arc(Arc::new(command)) } - /// Sets the command to execute for the node being built. #[must_use] fn executes_arc(self, command: Arc) -> Self; - /// Sets the redirect target of the node being built to another, without a modifier. #[must_use] fn redirect(self, redirection: impl Into) -> Self; - /// Sets the redirect target of the node being built to another, with a given modifier. #[must_use] fn redirect_with_modifier( self, @@ -167,392 +71,218 @@ pub trait ArgumentBuilder>: Sized + Sealed { redirect_modifier: RedirectModifier, ) -> Self; - /// Forks the given context, using multiple for later. #[must_use] fn fork(self, redirection: impl Into, redirect_modifier: RedirectModifier) -> Self; - /// Forwards the given context, with the given `fork` flag. - #[must_use] - fn forward( - self, - redirection: impl Into, - redirect_modifier: RedirectModifier, - fork: bool, - ) -> Self; - - /// Gets a reference to the arguments of the node to be built. - #[must_use] - fn arguments(&self) -> &FxHashMap; - - /// Gets the node to which the node being built by this [`ArgumentBuilder`] redirects. - #[must_use] - fn target(&self) -> Option; - - /// 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) -> Self; - /// Overwrites the current requirements of this node to a new value. #[must_use] - fn overwrite_requirements(self, requirements: Requirements) -> Self; - - /// Gets the redirect modifier of the node this [`ArgumentBuilder`] is building. - #[must_use] - fn redirect_modifier(&self) -> RedirectModifier; - - /// Whether this builder forks. - #[must_use] - fn forks(&self) -> bool; - - /// Returns the 'future [`GlobalNodeId`]' of the node that will be produced by this Builder. - /// Very useful for redirects. - #[must_use] - fn id(&self) -> GlobalNodeId; - - /// Builds the node represented by this builder, consuming itself in the process. - #[must_use] - fn build(self) -> N; -} - -// Implement the private trait for our builders! -impl Sealed for LiteralArgumentBuilder {} -impl Sealed for CommandArgumentBuilder {} -impl Sealed for RequiredArgumentBuilder {} - -/// Helper macro to implement repeated code of `ArgumentBuilder` for our types. -macro_rules! impl_boilerplate_argument_builder { - () => { - fn then(mut self, argument: impl Into) -> 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 { - self.common.command.clone() - } - - fn executes_arc(mut self, command: Arc) -> Self { - self.common.command = Some(command); - self - } - - fn requires(mut self, requirement: impl Into) -> 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) -> Self { - self.forward(redirection.into(), RedirectModifier::KeepSource, false) - } - - fn redirect_with_modifier(self, redirection: impl Into, redirect_modifier: RedirectModifier) -> Self { - self.forward(redirection.into(), redirect_modifier, false) - } - - fn fork(self, redirection: impl Into, redirect_modifier: RedirectModifier) -> Self { - self.forward(redirection.into(), redirect_modifier, true) - } - - fn forward(mut self, redirection: impl Into, 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 { - &self.common.arguments - } - - fn target(&self) -> Option { - 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>) -> 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>, - description: impl Into>, - ) -> 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) -> Self { - self.source = Some(source.into()); - self - } + fn build(self) -> Self::Node; } impl RequiredArgumentBuilder { - /// Creates a new [`RequiredArgumentBuilder`] from a name and an argument type. - pub fn new( - name: impl Into>, - arg_type: impl AnyArgumentType + 'static, - ) -> Self { - Self { - common: CommonArgumentBuilder::new(), - name: name.into(), - argument_type: Arc::new(arg_type), - suggestion_provider: None, - } - } - - /// Sets the [`SuggestionProvider`] of this builder for the `ArgumentDetachedNode`. #[must_use] pub fn suggests(self, provider: impl SuggestionProvider + 'static) -> Self { - self.suggests_arc(Arc::new(provider)) - } - - /// Sets the [`SuggestionProvider`] of this builder for the `ArgumentDetachedNode`. - #[must_use] - pub fn suggests_arc(mut self, provider: Arc) -> Self { - self.suggestion_provider = Some(provider); - self + Self(self.0.suggests(SuggestionProviderAdapter(provider))) } } -impl ArgumentBuilder for LiteralArgumentBuilder { - impl_boilerplate_argument_builder!(); +impl ArgumentBuilder for CommandArgumentBuilder { + type Node = pumpkin_command::node::detached::CommandDetachedNode; - fn build(self) -> LiteralDetachedNode { - let mut node = LiteralDetachedNode::new( - self.common.global_id, - self.literal, - self.common.command, - self.common.requirements, - self.common.target, - self.common.modifier, - self.common.forks, - ); - node.children = self.common.arguments; - node + fn then(self, child: impl Into) -> Self { + Self(self.0.then(child.into())) + } + + fn command(&self) -> Option { + self.0.command() + } + + fn executes(self, command: impl CommandExecutor + 'static) -> Self { + Self(self.0.executes(CommandExecutorAdapter(command))) + } + + fn executes_arc(self, command: Arc) -> Self { + Self( + self.0 + .executes_arc(Arc::new(ArcCommandExecutorAdapter(command))), + ) + } + + fn redirect(self, redirection: impl Into) -> Self { + Self(self.0.redirect(redirection)) + } + + fn redirect_with_modifier( + self, + redirection: impl Into, + redirect_modifier: RedirectModifier, + ) -> Self { + Self( + self.0 + .redirect_with_modifier(redirection, redirect_modifier), + ) + } + + fn fork( + self, + redirection: impl Into, + redirect_modifier: RedirectModifier, + ) -> Self { + Self(self.0.fork(redirection, redirect_modifier)) + } + + fn requires(self, requirement: impl Into) -> Self { + Self(self.0.requires(requirement)) + } + + fn build(self) -> Self::Node { + self.0.build() } } -impl ArgumentBuilder for CommandArgumentBuilder { - impl_boilerplate_argument_builder!(); +impl ArgumentBuilder for LiteralArgumentBuilder { + type Node = pumpkin_command::node::detached::LiteralDetachedNode; - fn build(self) -> CommandDetachedNode { - let mut node = CommandDetachedNode::new( - self.common.global_id, - self.literal, - self.description, - self.common.command, - self.common.requirements, - self.common.target, - self.common.modifier, - self.common.forks, - ); - node.meta.source = self.source; - node.children = self.common.arguments; - node + fn then(self, child: impl Into) -> Self { + Self(self.0.then(child.into())) + } + + fn command(&self) -> Option { + self.0.command() + } + + fn executes(self, command: impl CommandExecutor + 'static) -> Self { + Self(self.0.executes(CommandExecutorAdapter(command))) + } + + fn executes_arc(self, command: Arc) -> Self { + Self( + self.0 + .executes_arc(Arc::new(ArcCommandExecutorAdapter(command))), + ) + } + + fn redirect(self, redirection: impl Into) -> Self { + Self(self.0.redirect(redirection)) + } + + fn redirect_with_modifier( + self, + redirection: impl Into, + redirect_modifier: RedirectModifier, + ) -> Self { + Self( + self.0 + .redirect_with_modifier(redirection, redirect_modifier), + ) + } + + fn fork( + self, + redirection: impl Into, + redirect_modifier: RedirectModifier, + ) -> Self { + Self(self.0.fork(redirection, redirect_modifier)) + } + + fn requires(self, requirement: impl Into) -> Self { + Self(self.0.requires(requirement)) + } + + fn build(self) -> Self::Node { + self.0.build() } } -impl ArgumentBuilder for RequiredArgumentBuilder { - impl_boilerplate_argument_builder!(); +impl ArgumentBuilder for RequiredArgumentBuilder { + type Node = pumpkin_command::node::detached::ArgumentDetachedNode; - fn build(self) -> ArgumentDetachedNode { - let mut node = ArgumentDetachedNode::new( - self.common.global_id, - self.name, - self.argument_type, - self.common.command, - self.common.requirements, - self.common.target, - self.common.modifier, - self.common.forks, - self.suggestion_provider, - ); - node.children = self.common.arguments; - node + fn then(self, child: impl Into) -> Self { + Self(self.0.then(child.into())) + } + + fn command(&self) -> Option { + self.0.command() + } + + fn executes(self, command: impl CommandExecutor + 'static) -> Self { + Self(self.0.executes(CommandExecutorAdapter(command))) + } + + fn executes_arc(self, command: Arc) -> Self { + Self( + self.0 + .executes_arc(Arc::new(ArcCommandExecutorAdapter(command))), + ) + } + + fn redirect(self, redirection: impl Into) -> Self { + Self(self.0.redirect(redirection)) + } + + fn redirect_with_modifier( + self, + redirection: impl Into, + redirect_modifier: RedirectModifier, + ) -> Self { + Self( + self.0 + .redirect_with_modifier(redirection, redirect_modifier), + ) + } + + fn fork( + self, + redirection: impl Into, + redirect_modifier: RedirectModifier, + ) -> Self { + Self(self.0.fork(redirection, redirect_modifier)) + } + + fn requires(self, requirement: impl Into) -> Self { + Self(self.0.requires(requirement)) + } + + fn build(self) -> Self::Node { + self.0.build() } } -#[cfg(test)] -mod test { - use crate::command::argument_builder::{ArgumentBuilder, argument, command, literal}; - use crate::command::argument_types::core::double::DoubleArgumentType; - use crate::command::argument_types::core::integer::IntegerArgumentType; - use crate::command::argument_types::core::string::StringArgumentType; - use crate::command::errors::error_types; - use crate::command::node::Redirection; - use crate::command::node::attached::AttachedNode; - use crate::command::node::tree::Tree; - use crate::command::string_reader::StringReader; - - #[test] - fn literal_one() { - let builder = literal("test"); - let node = builder.build(); - - assert_eq!(node.meta.literal, "test"); - } - - #[test] - fn required_one() { - let builder = argument("test", IntegerArgumentType::new(1, 10)); - let node = builder.build(); - - assert_eq!(node.meta.name, "test"); - - let mut reader1 = StringReader::new("5"); - let mut reader2 = StringReader::new("11"); - - let boxed_result = node - .meta - .argument_type - .parse(&mut reader1) - .expect("The parsing should not have errored"); - let result = boxed_result - .downcast::() - .expect("Downcasting shouldn't have failed"); - assert_eq!(result, Box::new(5)); - - let error = node - .meta - .argument_type - .parse(&mut reader2) - .expect_err("The parsing should have errored as 11 is outside the range"); - assert!(error.is(&error_types::INTEGER_TOO_HIGH)); - } - - #[test] - fn literal_multiple() { - let mut builder = command("letter", "A test command"); - for letter in 'a'..='z' { - // Add a node per letter for the argument. - let letter_string = letter.to_string(); - builder = builder.then(literal(letter_string)); - } - - let node = builder.build(); - assert_eq!(node.children.len(), 26); - } - - #[test] - fn required_multiple() { - let builder = command("test", "A test command") - .then(argument("number", DoubleArgumentType::any())) - .then(argument("word", StringArgumentType::SingleWord)); - - let node = builder.build(); - assert_eq!(node.children.len(), 2); - } - - #[test] - fn redirect() { - let builder = command("test", "A test command").redirect(Redirection::Root); - - let mut tree = Tree::new(); - let node_id = tree.add_child_to_root(builder); - - let node = &tree[node_id]; - let redirect = node - .redirect - .expect("Redirection should exist as it was added before"); - - let target_id = tree - .resolve(redirect) - .expect("Target should have been resolved properly"); - let target = &tree[target_id]; - - assert!(matches!(target, AttachedNode::Root(_))); - } - - #[test] - #[should_panic = "Cannot forward a node with children. The node must have no children to redirect somewhere else"] - fn redirect_after_child() { - let _ = command("test", "A test command") - .then(literal("child")) - .redirect(Redirection::Root); - } - - #[test] - #[should_panic = "Cannot add children to a redirected node"] - fn redirect_before_child() { - let _ = command("test", "A test command") - .redirect(Redirection::Root) - .then(literal("child")); - } - - #[test] - #[should_panic = "Cannot add a CommandDetachedNode as a child of a builder"] - fn add_command_as_child() { - let _ = command("foo", "A test command").then(command("bar", "Another test command")); +impl From + for pumpkin_command::node::detached::CommandDetachedNode +{ + fn from(b: CommandArgumentBuilder) -> Self { + b.0.build() + } +} + +impl From for DetachedNode { + fn from(b: LiteralArgumentBuilder) -> Self { + Self::Literal(b.0.build()) + } +} + +impl From for DetachedNode { + fn from(b: RequiredArgumentBuilder) -> Self { + Self::Argument(b.0.build()) + } +} + +impl From + for pumpkin_command::node::detached::LiteralDetachedNode +{ + fn from(b: LiteralArgumentBuilder) -> Self { + b.0.build() + } +} + +impl From + for pumpkin_command::node::detached::ArgumentDetachedNode +{ + fn from(b: RequiredArgumentBuilder) -> Self { + b.0.build() } } diff --git a/crates/pumpkin/src/command/argument_types/entity.rs b/crates/pumpkin/src/command/argument_types/entity.rs index dee6f6a9b..73cbf0896 100644 --- a/crates/pumpkin/src/command/argument_types/entity.rs +++ b/crates/pumpkin/src/command/argument_types/entity.rs @@ -79,7 +79,7 @@ impl EntityArgumentType { } } -impl ArgumentType for EntityArgumentType { +impl ArgumentType for EntityArgumentType { type Item = EntitySelector; fn parse(&self, reader: &mut StringReader) -> Result { @@ -126,7 +126,7 @@ impl EntityArgumentType { self, reader: &mut StringReader, allow_selectors: bool, - ) -> Result<::Item, CommandSyntaxError> { + ) -> Result<>::Item, CommandSyntaxError> { let selector = { let parser = EntitySelectorParser::new(reader, allow_selectors); parser.parse_and_consume()? diff --git a/crates/pumpkin/src/command/argument_types/entity_anchor.rs b/crates/pumpkin/src/command/argument_types/entity_anchor.rs index 111ba246c..83bc917bd 100644 --- a/crates/pumpkin/src/command/argument_types/entity_anchor.rs +++ b/crates/pumpkin/src/command/argument_types/entity_anchor.rs @@ -1,82 +1,13 @@ -use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType}; -use crate::command::context::command_context::CommandContext; -use crate::command::context::command_source::CommandSource; -use crate::command::errors::command_syntax_error::CommandSyntaxError; -use crate::command::errors::error_types::CommandErrorType; -use crate::command::string_reader::StringReader; -use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder}; use crate::entity::Entity; -use pumpkin_data::translation; +pub use pumpkin_command::argument_types::entity_anchor::*; use pumpkin_util::math::vector3::Vector3; -use pumpkin_util::text::TextComponent; -pub const INVALID_ERROR_TYPE: CommandErrorType<1> = CommandErrorType::new( - translation::java::ARGUMENT_ANCHOR_INVALID, - translation::java::ARGUMENT_ANCHOR_INVALID, -); - -pub struct EntityAnchorArgumentType; - -impl ArgumentType for EntityAnchorArgumentType { - type Item = EntityAnchor; - - fn parse(&self, reader: &mut StringReader) -> Result { - 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 { - examples!("eyes", "feet") - } +pub trait EntityAnchorExt { + fn transform_position(self, position: Vector3, entity: &Entity) -> Vector3; + fn position_at_entity(self, entity: &Entity) -> Vector3; } -impl_copy_get!(EntityAnchorArgumentType, EntityAnchor); - -#[derive(Copy, Clone, PartialEq, Eq, Debug)] -pub enum EntityAnchor { - Feet, - Eyes, -} - -impl EntityAnchor { - /// Gets the [`EntityAnchor`] whose identity is the ID provided. - #[must_use] - pub fn from_id(id: &str) -> Option { - match id { - "feet" => Some(Self::Feet), - "eyes" => Some(Self::Eyes), - _ => None, - } - } - - /// Gets the ID of this [`EntityAnchor`] - #[must_use] - pub const fn id(self) -> &'static str { - match self { - Self::Feet => "feet", - Self::Eyes => "eyes", - } - } - +impl EntityAnchorExt for EntityAnchor { fn transform_position(self, position: Vector3, entity: &Entity) -> Vector3 { match self { Self::Feet => position, @@ -84,18 +15,7 @@ impl EntityAnchor { } } - /// Gets the position of an entity with respect to this anchor. - pub fn position_at_entity(self, entity: &Entity) -> Vector3 { + fn position_at_entity(self, entity: &Entity) -> Vector3 { 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 { - let pos = command_source.position; - command_source - .entity - .as_ref() - .map_or_else(|| pos, |e| self.position_at_entity(e.get_entity())) - } } diff --git a/crates/pumpkin/src/command/argument_types/game_profile.rs b/crates/pumpkin/src/command/argument_types/game_profile.rs index cf9a1b8e5..c33bd7973 100644 --- a/crates/pumpkin/src/command/argument_types/game_profile.rs +++ b/crates/pumpkin/src/command/argument_types/game_profile.rs @@ -228,7 +228,7 @@ impl GameProfileResult { #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct GameProfileArgumentType; -impl ArgumentType for GameProfileArgumentType { +impl ArgumentType for GameProfileArgumentType { type Item = GameProfileResult; fn parse(&self, reader: &mut StringReader) -> Result { @@ -256,7 +256,7 @@ impl GameProfileArgumentType { fn parse_with_allow_selectors( reader: &mut StringReader, allow_selectors: bool, - ) -> Result<::Item, CommandSyntaxError> { + ) -> Result<>::Item, CommandSyntaxError> { if reader.peek() == Some('@') { // We read a selector variable. let parser = EntitySelectorParser::new(reader, allow_selectors); diff --git a/crates/pumpkin/src/command/argument_types/mod.rs b/crates/pumpkin/src/command/argument_types/mod.rs index 1b07389c3..97bb864d8 100644 --- a/crates/pumpkin/src/command/argument_types/mod.rs +++ b/crates/pumpkin/src/command/argument_types/mod.rs @@ -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`] of examples from /// the given string literals. +#[macro_export] macro_rules! examples { ( $( $example:literal ),* ) => { vec! [ @@ -19,235 +9,12 @@ macro_rules! examples { }; } -// Helper methods for assertion with a `StringReader`: +pub use pumpkin_command::argument_types::*; -/// Asserts that the result read by `reader` with the argument -/// type `$argument_type` used to parse is equal to `Ok($value)`. -/// Also resets the reader's cursor back to the start. -#[cfg(test)] -macro_rules! assert_parse_ok_reset { - ($reader: expr, $argument_type: expr, $value: expr) => {{ - assert_eq!($argument_type.parse(&mut $reader), Ok($value)); - $reader.set_cursor(0) - }}; - ($reader: expr, $argument_type: expr) => {{ - assert!($argument_type.parse(&mut $reader).is_ok()); - $reader.set_cursor(0) - }}; -} - -/// Asserts that the result read by `reader` with the argument -/// type `$argument_type` used to parse is an `Err` containing the type of error as `$error_type`. -/// Also resets the reader's cursor back to the start. -#[cfg(test)] -macro_rules! assert_parse_err_reset { - ($reader: expr, $argument_type: expr, $error_type: expr) => { - let error_type_dyn: &'static dyn crate::command::errors::error_types::AnyCommandErrorType = - $error_type; - assert_eq!( - $argument_type.parse(&mut $reader).map_err(|e| e.error_type), - Err(error_type_dyn) - ); - $reader.set_cursor(0) - }; -} - -/// Macro to implement a single `get()` function for an argument type whose `Item` is `Copy`. -macro_rules! impl_copy_get { - ($ty:ty, $item:ty) => { - impl $ty { - #[doc = concat!("Returns a `CommandContext`'s parsed `", stringify!($item), "` argument.")] - pub fn get(context: &$crate::command::context::command_context::CommandContext, name: &str) -> Result<$item, CommandSyntaxError> { - Ok(*context.get_argument(name)?) - } - } - }; -} - -const EMPTY_BOUNDS_ERROR_TYPE: CommandErrorType<0> = CommandErrorType::new( - translation::java::ARGUMENT_RANGE_EMPTY, - translation::java::ARGUMENT_RANGE_EMPTY, -); -const SWAPPED_BOUNDS_ERROR_TYPE: CommandErrorType<0> = CommandErrorType::new( - translation::java::ARGUMENT_RANGE_SWAPPED, - translation::java::ARGUMENT_RANGE_SWAPPED, -); -const INVALID_IDENTIFIER_ERROR_TYPE: CommandErrorType<0> = CommandErrorType::new( - translation::java::ARGUMENT_ID_INVALID, - translation::java::ARGUMENT_ID_INVALID, -); - -/// A trait to try getting a value from a [`StringReader`]. -pub trait FromStringReader: Sized { - /// Tries to parse a value of this type from a [`StringReader`]. - /// - /// If this fails, a [`CommandSyntaxError`] is returned with the erroring details. - fn from_reader(reader: &mut StringReader) -> Result; -} - -fn bounds_from_reader( - reader: &mut StringReader, - error_type: &'static CommandErrorType<1>, -) -> Result, 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( - reader: &mut StringReader, - error_type: &'static CommandErrorType<1>, -) -> Result, 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::::new(min, max)) - } -} - -/// Tries to read a number of a type from a reader. -fn read_number_from_reader( - reader: &mut StringReader, - error_type: &'static CommandErrorType<1>, -) -> Result, 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::().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 { - 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 { - 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 { - 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 { - let start = reader.cursor(); - while let Some(c) = reader.peek() - && Self::is_valid_char(c) - { - reader.skip(); - } - - let raw_identifier = &reader.string()[start..reader.cursor()]; - let identifier_result = Self::parse(raw_identifier); - - identifier_result.map_or_else( - |_| { - reader.set_cursor(start); - Err(INVALID_IDENTIFIER_ERROR_TYPE.create(reader)) - }, - Ok, - ) - } -} - -pub mod argument_type; -pub mod attribute; -pub mod block; -pub mod block_predicate; -pub mod component; -pub mod coordinates; -pub mod core; -pub mod dialog; pub mod entity; pub mod entity_anchor; pub mod entity_selector; pub mod game_profile; -pub mod gamemode; -pub mod hex_color; -pub mod identifier; -pub mod item; -pub mod item_predicate; -pub mod nbt; -pub mod nbt_path; pub mod objective; -pub mod particle; -pub mod placed_feature; -pub mod pool; -pub mod range; -pub mod resource; pub mod resource_key; -pub mod resource_or_tag; -pub mod slot; -pub mod sound_category; -pub mod structure; pub mod team; -pub mod team_color; -pub mod template; -pub mod time; -pub mod uuid; diff --git a/crates/pumpkin/src/command/argument_types/objective.rs b/crates/pumpkin/src/command/argument_types/objective.rs index aab94593c..fcd942328 100644 --- a/crates/pumpkin/src/command/argument_types/objective.rs +++ b/crates/pumpkin/src/command/argument_types/objective.rs @@ -1,4 +1,5 @@ use crate::command::{ + CommandSource, argument_types::argument_type::{ArgumentType, JavaClientArgumentType}, context::command_context::CommandContext, errors::command_syntax_error::CommandSyntaxError, @@ -10,7 +11,7 @@ use crate::command::{ #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct ObjectiveArgumentType; -impl ArgumentType for ObjectiveArgumentType { +impl ArgumentType for ObjectiveArgumentType { type Item = String; fn parse(&self, reader: &mut StringReader) -> Result { diff --git a/crates/pumpkin/src/command/argument_types/resource_key.rs b/crates/pumpkin/src/command/argument_types/resource_key.rs index b4a2c4423..03006593e 100644 --- a/crates/pumpkin/src/command/argument_types/resource_key.rs +++ b/crates/pumpkin/src/command/argument_types/resource_key.rs @@ -1,3 +1,4 @@ +use crate::command::CommandSource; use crate::command::argument_types::FromStringReader; use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType}; use crate::command::context::command_context::CommandContext; @@ -42,7 +43,7 @@ pub static ERROR_INVALID: CommandErrorType<0> = CommandErrorType::new( translation::java::ARGUMENT_ID_INVALID, ); -impl ArgumentType for ResourceKeyArgument { +impl ArgumentType for ResourceKeyArgument { type Item = ResourceKey; fn parse(&self, reader: &mut StringReader) -> Result { diff --git a/crates/pumpkin/src/command/argument_types/team.rs b/crates/pumpkin/src/command/argument_types/team.rs index 87a07b1f9..e5dcb3072 100644 --- a/crates/pumpkin/src/command/argument_types/team.rs +++ b/crates/pumpkin/src/command/argument_types/team.rs @@ -1,4 +1,5 @@ use crate::command::{ + CommandSource, argument_types::argument_type::{ArgumentType, JavaClientArgumentType}, context::command_context::CommandContext, errors::command_syntax_error::CommandSyntaxError, @@ -10,7 +11,7 @@ use crate::command::{ #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct TeamArgumentType; -impl ArgumentType for TeamArgumentType { +impl ArgumentType for TeamArgumentType { type Item = String; fn parse(&self, reader: &mut StringReader) -> Result { diff --git a/crates/pumpkin/src/command/commands/bossbar.rs b/crates/pumpkin/src/command/commands/bossbar.rs index 1ec26191f..9653687e5 100644 --- a/crates/pumpkin/src/command/commands/bossbar.rs +++ b/crates/pumpkin/src/command/commands/bossbar.rs @@ -6,6 +6,7 @@ use pumpkin_util::permission::{Permission, PermissionDefault, PermissionRegistry use pumpkin_util::text::TextComponent; use pumpkin_util::text::hover::HoverEvent; +use crate::command::CommandSource; use crate::command::argument_builder::{ArgumentBuilder, argument, command, literal}; use crate::command::argument_types::FromStringReader; use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType}; @@ -50,7 +51,7 @@ const ERROR_CREATE_FAILED: CommandErrorType<1> = CommandErrorType::new( #[derive(Clone, Copy)] pub struct BossbarIdArgumentType; -impl ArgumentType for BossbarIdArgumentType { +impl ArgumentType for BossbarIdArgumentType { type Item = String; fn parse(&self, reader: &mut StringReader) -> Result { @@ -85,7 +86,7 @@ pub struct BossbarColorArgumentType; const COLORS: [&str; 7] = ["blue", "green", "pink", "purple", "red", "white", "yellow"]; -impl ArgumentType for BossbarColorArgumentType { +impl ArgumentType for BossbarColorArgumentType { type Item = BossbarColor; fn parse(&self, reader: &mut StringReader) -> Result { @@ -138,7 +139,7 @@ const STYLES: [&str; 5] = [ "progress", ]; -impl ArgumentType for BossbarStyleArgumentType { +impl ArgumentType for BossbarStyleArgumentType { type Item = BossbarDivisions; fn parse(&self, reader: &mut StringReader) -> Result { diff --git a/crates/pumpkin/src/command/commands/execute.rs b/crates/pumpkin/src/command/commands/execute.rs index e6eeae05c..82e9fdc11 100644 --- a/crates/pumpkin/src/command/commands/execute.rs +++ b/crates/pumpkin/src/command/commands/execute.rs @@ -21,7 +21,7 @@ use crate::command::argument_types::coordinates::swizzle::SwizzleArgumentType; use crate::command::argument_types::coordinates::vec3::Vec3ArgumentType; use crate::command::argument_types::core::string::StringArgumentType; use crate::command::argument_types::entity::EntityArgumentType; -use crate::command::argument_types::entity_anchor::EntityAnchorArgumentType; +use crate::command::argument_types::entity_anchor::{EntityAnchorArgumentType, EntityAnchorExt}; use crate::command::argument_types::identifier::IdentifierArgumentType; use crate::command::argument_types::nbt_path::{NbtPath, NbtPathArgumentType}; use crate::command::argument_types::objective::ObjectiveArgumentType; diff --git a/crates/pumpkin/src/command/commands/help.rs b/crates/pumpkin/src/command/commands/help.rs index 8bcf47d9d..fdcb0e946 100644 --- a/crates/pumpkin/src/command/commands/help.rs +++ b/crates/pumpkin/src/command/commands/help.rs @@ -1,3 +1,4 @@ +use crate::command::CommandSource; use crate::command::argument_builder::{ArgumentBuilder, argument, command}; use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType}; use crate::command::argument_types::core::string::StringArgumentType; @@ -35,7 +36,7 @@ enum HelpArgument { } struct HelpArgumentType; -impl ArgumentType for HelpArgumentType { +impl ArgumentType for HelpArgumentType { type Item = HelpArgument; fn parse(&self, reader: &mut StringReader) -> Result { @@ -68,7 +69,10 @@ impl ArgumentType for HelpArgumentType { // We use greedy phrases for now // as the `?` command as the argument // doesn't work for the unquoted word one. - let mut text = StringArgumentType::GreedyPhrase.parse(reader)?; + let mut text = ArgumentType::::parse( + &StringArgumentType::GreedyPhrase, + reader, + )?; { let mut integer_text = text.as_str(); diff --git a/crates/pumpkin/src/command/commands/rotate.rs b/crates/pumpkin/src/command/commands/rotate.rs index 7247926ed..1dcdfff93 100644 --- a/crates/pumpkin/src/command/commands/rotate.rs +++ b/crates/pumpkin/src/command/commands/rotate.rs @@ -10,7 +10,9 @@ use crate::command::argument_builder::{ArgumentBuilder, argument, command, liter use crate::command::argument_types::coordinates::rotation::RotationArgumentType; use crate::command::argument_types::coordinates::vec3::Vec3ArgumentType; use crate::command::argument_types::entity::EntityArgumentType; -use crate::command::argument_types::entity_anchor::{EntityAnchor, EntityAnchorArgumentType}; +use crate::command::argument_types::entity_anchor::{ + EntityAnchor, EntityAnchorArgumentType, EntityAnchorExt, +}; use crate::command::context::command_context::CommandContext; use crate::command::node::dispatcher::CommandDispatcher; use crate::command::node::{CommandExecutor, CommandExecutorResult}; diff --git a/crates/pumpkin/src/command/commands/teleport.rs b/crates/pumpkin/src/command/commands/teleport.rs index bc6192d7e..567a20d6c 100644 --- a/crates/pumpkin/src/command/commands/teleport.rs +++ b/crates/pumpkin/src/command/commands/teleport.rs @@ -11,7 +11,9 @@ use crate::command::argument_builder::{ArgumentBuilder, argument, command, liter use crate::command::argument_types::coordinates::rotation::RotationArgumentType; use crate::command::argument_types::coordinates::vec3::Vec3ArgumentType; use crate::command::argument_types::entity::EntityArgumentType; -use crate::command::argument_types::entity_anchor::{EntityAnchor, EntityAnchorArgumentType}; +use crate::command::argument_types::entity_anchor::{ + EntityAnchor, EntityAnchorArgumentType, EntityAnchorExt, +}; use crate::command::context::command_context::CommandContext; use crate::command::errors::error_types::CommandErrorType; use crate::command::node::dispatcher::CommandDispatcher; diff --git a/crates/pumpkin/src/command/commands/test.rs b/crates/pumpkin/src/command/commands/test.rs index ccd192cf7..35ad6f06d 100644 --- a/crates/pumpkin/src/command/commands/test.rs +++ b/crates/pumpkin/src/command/commands/test.rs @@ -11,6 +11,7 @@ use pumpkin_util::text::TextComponent; use tracing::info; use crate::command::CommandSender; +use crate::command::CommandSource; use crate::command::argument_builder::{ArgumentBuilder, argument, command, literal}; use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType}; use crate::command::argument_types::core::bool::BoolArgumentType; @@ -59,7 +60,7 @@ impl GameTestReporter for CommandTestReporter { /// matching the behavior of the old command implementation. struct TestInstanceArgumentType; -impl ArgumentType for TestInstanceArgumentType { +impl ArgumentType for TestInstanceArgumentType { type Item = String; fn parse(&self, reader: &mut StringReader) -> Result { diff --git a/crates/pumpkin/src/command/context/command_source.rs b/crates/pumpkin/src/command/context/command_source.rs index 76cd2b332..00e35f168 100644 --- a/crates/pumpkin/src/command/context/command_source.rs +++ b/crates/pumpkin/src/command/context/command_source.rs @@ -1,12 +1,16 @@ use crate::command::CommandSender; -use crate::command::argument_types::entity_anchor::EntityAnchor; -use crate::command::errors::command_syntax_error::CommandSyntaxError; -use crate::command::errors::error_types::CommandErrorType; +use crate::command::argument_types::entity_anchor::{EntityAnchor, EntityAnchorExt}; use crate::entity::EntityBase; use crate::entity::player::Player; use crate::server::Server; use crate::world::World; +use pumpkin_command::errors::command_syntax_error::CommandSyntaxError; +use pumpkin_command::errors::error_types::CommandErrorType; +pub use pumpkin_command::source::{ + ResultValueTaker, ReturnValue, ReturnValueCallable, ReturnValueCallback, +}; use pumpkin_data::translation; +use pumpkin_util::math::position::BlockPos; use pumpkin_util::math::vector2::Vector2; use pumpkin_util::math::vector3::Vector3; use pumpkin_util::math::wrap_degrees; @@ -23,50 +27,6 @@ pub const REQUIRES_ENTITY: CommandErrorType<0> = CommandErrorType::new( translation::java::PERMISSIONS_REQUIRES_ENTITY, ); -pub trait ReturnValueCallable: Send + Sync { - fn call(&self, value: ReturnValue); -} - -pub type ReturnValueCallback = Arc; - -/// Represents a collection of 'return value callbacks'. -#[derive(Clone)] -pub struct ResultValueTaker(pub Vec); - -impl ResultValueTaker { - /// Merges two takers, returning one. - #[must_use] - pub fn merge(taker_1: &Self, taker_2: &Self) -> Self { - let mut takers = Vec::with_capacity(taker_1.0.len() + taker_2.0.len()); - for taker in &taker_1.0 { - takers.push(taker.clone()); - } - for taker in &taker_2.0 { - takers.push(taker.clone()); - } - Self(takers) - } - - /// Constructs a new, empty result value taker. - #[must_use] - pub fn new() -> Self { - Self(Vec::new()) - } - - /// Calls all the contained callbacks of this taker with the returned result. - pub fn call(&self, return_value: ReturnValue) { - for callback in &self.0 { - callback.call(return_value); - } - } -} - -impl Default for ResultValueTaker { - fn default() -> Self { - Self::new() - } -} - /// Represents a source of a command, which /// contains its own state, which could keep track of its: /// - position @@ -505,28 +465,60 @@ impl CommandSource { } } -#[derive(Copy, Clone, PartialEq, Eq, Debug)] -pub enum ReturnValue { - Success(i32), - Failure, -} +impl pumpkin_command::source::CommandSource for CommandSource { + fn send_message(&self, message: TextComponent) { + self.send_message(message); + } -impl ReturnValue { - /// Get the success value of this return value. - #[must_use] - pub const fn success_value(self) -> bool { - match self { - Self::Success(_) => true, - Self::Failure => false, + fn send_error(&self, error: TextComponent) { + self.send_error(error); + } + + fn call_result(&self, result: ReturnValue) { + self.command_result_taker.call(result); + } + + fn has_permission(&self, permission: &str) -> bool { + self.has_permission(permission) + } + + fn position(&self) -> Vector3 { + self.position + } + + fn rotation(&self) -> Vector2 { + self.rotation + } + + fn check_block_loaded(&self, pos: &BlockPos) -> Result<(), CommandSyntaxError> { + let world = self.world(); + if world + .level + .read_chunk_sync(&pos.chunk_position(), |_| ()) + .is_none() + { + Err( + pumpkin_command::argument_types::coordinates::block_pos::NOT_LOADED_ERROR_TYPE + .create_without_context(), + ) + } else if !world.is_in_build_limit(*pos) { + Err( + pumpkin_command::argument_types::coordinates::block_pos::OUT_OF_WORLD_ERROR_TYPE + .create_without_context(), + ) + } else { + Ok(()) } } - /// Get the result integral value of this return value. - #[must_use] - pub const fn result_value(self) -> i32 { - match self { - Self::Success(value) => value, - Self::Failure => 0, - } + fn entity_anchor(&self) -> EntityAnchor { + self.entity_anchor + } + + fn anchor_position(&self, anchor: EntityAnchor) -> Vector3 { + let pos = self.position; + self.entity + .as_ref() + .map_or_else(|| pos, |e| anchor.position_at_entity(e.get_entity())) } } diff --git a/crates/pumpkin/src/command/context/mod.rs b/crates/pumpkin/src/command/context/mod.rs index f7db98b50..5d3a24764 100644 --- a/crates/pumpkin/src/command/context/mod.rs +++ b/crates/pumpkin/src/command/context/mod.rs @@ -1,3 +1,11 @@ -pub mod command_context; +pub mod command_context { + pub use pumpkin_command::context::command_context::*; + pub type CommandContext<'a> = + pumpkin_command::context::CommandContext<'a, crate::command::CommandSource>; +} pub mod command_source; -pub mod string_range; +pub mod string_range { + pub use pumpkin_command::context::string_range::*; +} +pub use command_context::CommandContext; +pub use pumpkin_command::context::*; diff --git a/crates/pumpkin/src/command/dispatcher.rs b/crates/pumpkin/src/command/dispatcher.rs deleted file mode 100644 index a3aa35c40..000000000 --- a/crates/pumpkin/src/command/dispatcher.rs +++ /dev/null @@ -1 +0,0 @@ -pub use crate::command::node::dispatcher::*; diff --git a/crates/pumpkin/src/command/mod.rs b/crates/pumpkin/src/command/mod.rs index fbace8996..5a0060f97 100644 --- a/crates/pumpkin/src/command/mod.rs +++ b/crates/pumpkin/src/command/mod.rs @@ -7,7 +7,7 @@ use std::sync::atomic::AtomicBool; use crate::block::entities::BlockEntity; use crate::block::entities::command_block::CommandBlockEntity; -use crate::command::context::command_source::CommandSource; +pub use crate::command::context::command_source::CommandSource; use crate::entity::EntityBase; use crate::entity::player::Player; use crate::server::Server; @@ -23,18 +23,69 @@ use pumpkin_util::permission::{PermissionDefault, PermissionLvl}; use pumpkin_util::text::TextComponent; use pumpkin_util::translation::Locale; +pub use pumpkin_command::*; + pub mod argument_builder; pub mod argument_types; pub mod client_suggestions; pub mod commands; pub mod context; -pub mod dispatcher; -pub mod errors; pub mod node; -pub mod parser; -pub mod snbt; -pub mod string_reader; -pub mod suggestion; + +pub mod dispatcher { + pub use pumpkin_command::dispatcher::*; + pub type CommandDispatcher = + pumpkin_command::dispatcher::CommandDispatcher; +} +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(pub T); + + impl + pumpkin_command::suggestion::provider::SuggestionProvider + for SuggestionProviderAdapter + { + fn suggest( + &self, + context: &pumpkin_command::context::command_context::CommandContext< + '_, + CommandSource, + >, + builder: SuggestionsBuilder, + ) -> SuggestionProviderResult { + self.0.suggest(context, builder) + } + } + } +} /// Whether console and RCON command output is broadcast to online operators. /// @@ -397,5 +448,6 @@ const fn command_block_y_rot(facing: Facing) -> f32 { } } +pub use context::command_context::CommandContext; pub use node::dispatcher::CommandDispatcher; pub use node::{Command, CommandExecutor, CommandExecutorResult}; diff --git a/crates/pumpkin/src/command/node/mod.rs b/crates/pumpkin/src/command/node/mod.rs index 54dd676a0..247589a92 100644 --- a/crates/pumpkin/src/command/node/mod.rs +++ b/crates/pumpkin/src/command/node/mod.rs @@ -1,263 +1,78 @@ -pub mod attached; -pub mod detached; -pub mod dispatcher; -pub mod tree; - -use crate::command::argument_types::argument_type::AnyArgumentType; use crate::command::context::command_context::CommandContext; use crate::command::context::command_source::CommandSource; -use crate::command::errors::command_syntax_error::CommandSyntaxError; -use crate::command::node::attached::NodeId; -use crate::command::node::detached::GlobalNodeId; -use crate::command::suggestion::provider::SuggestionProvider; -use std::borrow::Cow; -use std::sync::Arc; -/// Represents a [`CommandExecutor`]'s result. -/// -/// If the command **ran successfully**, an [`Ok`] is returned containing an [`i32`]. -/// This represents the 'output value' of the command, which is *homologous* to the -/// `int` that command executors in vanilla return **upon success**. -/// -/// **You should choose the successful result as `1` if**: -/// - you don't know what value to use for a success for your -/// own commands, or -/// - you don't understand what this value means, or -/// - you just simply don't care about this value at all -/// -/// If the command **fails**, an [`Err`] is returned, containing the [`CommandSyntaxError`] -/// that led to this result. -pub type CommandExecutorResult = Result; +pub use pumpkin_command::node::*; + +pub mod attached { + pub use pumpkin_command::node::attached::*; + pub type AttachedNode = + pumpkin_command::node::attached::AttachedNode; + pub type RootAttachedNode = + pumpkin_command::node::attached::RootAttachedNode; + pub type LiteralAttachedNode = + pumpkin_command::node::attached::LiteralAttachedNode; + pub type CommandAttachedNode = + pumpkin_command::node::attached::CommandAttachedNode; + pub type ArgumentAttachedNode = + pumpkin_command::node::attached::ArgumentAttachedNode; +} + +pub mod detached { + pub use pumpkin_command::node::detached::*; + pub type DetachedNode = + pumpkin_command::node::detached::DetachedNode; + pub type LiteralDetachedNode = + pumpkin_command::node::detached::LiteralDetachedNode; + pub type CommandDetachedNode = + pumpkin_command::node::detached::CommandDetachedNode; + pub type ArgumentDetachedNode = + pumpkin_command::node::detached::ArgumentDetachedNode; +} + +pub mod tree { + pub use pumpkin_command::node::tree::*; + pub type Tree = pumpkin_command::node::tree::Tree; +} + +pub mod dispatcher { + pub use pumpkin_command::dispatcher::*; + pub type CommandDispatcher = + pumpkin_command::dispatcher::CommandDispatcher; +} + +pub type Command = pumpkin_command::node::Command; +pub type Requirement = pumpkin_command::node::Requirement; +pub type Requirements = pumpkin_command::node::Requirements; +pub type RedirectModifier = pumpkin_command::node::RedirectModifier; +pub type RedirectModifierResult = pumpkin_command::node::RedirectModifierResult; +pub type RedirectModifierExecutor = pumpkin_command::node::RedirectModifierExecutor; /// A struct implementing this trait is able to run with a given context. pub trait CommandExecutor: Sync + Send { /// Executes this executor for a command. - fn execute(&self, context: &CommandContext) -> CommandExecutorResult; + fn execute(&self, context: &CommandContext) -> pumpkin_command::node::CommandExecutorResult; } -impl CommandExecutor for F -where - F: Fn(&CommandContext) -> CommandExecutorResult + Send + Sync, +pub struct CommandExecutorAdapter(pub T); + +impl pumpkin_command::node::CommandExecutor + for CommandExecutorAdapter { - fn execute(&self, context: &CommandContext) -> CommandExecutorResult { - self(context) + fn execute( + &self, + context: &pumpkin_command::context::command_context::CommandContext<'_, CommandSource>, + ) -> pumpkin_command::node::CommandExecutorResult { + self.0.execute(context) } } -/// A function that takes a context and returns a command result. -pub type Command = Arc; +pub struct ArcCommandExecutorAdapter(pub std::sync::Arc); -/// Represents the result of [`Arc`]s from a [`CommandContext`]. -pub type RedirectModifierResult = Result>, CommandSyntaxError>; - -/// A function that performs the required modification. -pub type RedirectModifierExecutor = dyn Fn(&CommandContext) -> RedirectModifierResult + Send + Sync; - -/// A function that returns a new collection of sources from a given context. -#[derive(Clone)] -pub enum RedirectModifier { - /// Always returns only the source from the given context. - KeepSource, - - /// Returns multiple [`CommandSource`]s from one context via - /// custom behavior. - Custom(Arc), -} - -impl RedirectModifier { - /// Tries to provide a [`Vec`] of [`Arc`] 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 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 From for Requirement -where - F: Fn(&CommandSource) -> RequirementResult + Send + Sync + 'static, -{ - fn from(value: F) -> Self { - Self(Arc::new(value)) - } -} - -// Permissions -impl From 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); - -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, -} - -/// 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>) -> 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, -} - -impl CommandNodeMetadata { - pub fn new( - literal: impl Into>, - description: impl Into>, - ) -> 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, - pub suggestion_provider: Option>, -} - -impl ArgumentNodeMetadata { - pub fn new( - name: impl Into>, - argument_type: Arc, - suggestion_provider: Option>, - ) -> 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> From for Redirection { - fn from(value: T) -> Self { - Self::Local(value.into()) - } -} - -impl From for Redirection { - fn from(value: GlobalNodeId) -> Self { - Self::Global(value) +impl pumpkin_command::node::CommandExecutor for ArcCommandExecutorAdapter { + fn execute( + &self, + context: &pumpkin_command::context::command_context::CommandContext<'_, CommandSource>, + ) -> pumpkin_command::node::CommandExecutorResult { + self.0.execute(context) } } diff --git a/crates/pumpkin/src/net/bedrock/mod.rs b/crates/pumpkin/src/net/bedrock/mod.rs index 5ea3e41ac..7f6170787 100644 --- a/crates/pumpkin/src/net/bedrock/mod.rs +++ b/crates/pumpkin/src/net/bedrock/mod.rs @@ -8,7 +8,7 @@ use std::{ net::SocketAddr, sync::{ Arc, - atomic::{AtomicBool, AtomicU32, Ordering}, + atomic::{AtomicBool, AtomicU32, AtomicUsize, Ordering}, }, }; @@ -47,7 +47,7 @@ use pumpkin_protocol::{ serial::{PacketRead, PacketReadSlice}, }; use tokio::{ - sync::mpsc::{Receiver, Sender}, + sync::mpsc::{Receiver, Sender, UnboundedReceiver, UnboundedSender, error::TryRecvError}, sync::{Mutex, RwLock, oneshot}, task::JoinHandle, }; @@ -58,7 +58,10 @@ pub mod login; use self::nethernet::NetherNetSession; use crate::{ entity::player::Player, - net::{DisconnectReason, PacketHandlerResult, PacketRateLimiter}, + net::{ + DisconnectReason, MAX_PENDING_BYTES, PacketHandlerResult, PacketRateLimiter, + decrement_pending_bytes, + }, plugin::api::events::world::chunk_send::ChunkSend, server::Server, }; @@ -101,12 +104,15 @@ pub struct BedrockClient { tasks: TaskTracker, rt_handle: tokio::runtime::Handle, - outgoing_packet_queue_send: Sender, + outgoing_packet_queue_send: UnboundedSender, /// A queue of serialized packets to send to the network - outgoing_packet_queue_recv: Mutex>>, + outgoing_packet_queue_recv: Mutex>>, - outgoing_packet_priority_send: Sender, - outgoing_packet_priority_recv: Mutex>>, + outgoing_packet_priority_send: UnboundedSender, + outgoing_packet_priority_recv: Mutex>>, + + /// Tracks total buffered payload bytes in the outgoing queues. + pub pending_bytes: Arc, /// The packet encoder for outgoing packets. network_writer: Arc>, @@ -135,8 +141,8 @@ impl BedrockClient { be_clients: Arc>>>, packet_limiter: PacketRateLimiter, ) -> Self { - let (send, recv) = tokio::sync::mpsc::channel(4096); - let (priority_send, priority_recv) = tokio::sync::mpsc::channel(4096); + let (send, recv) = tokio::sync::mpsc::unbounded_channel(); + let (priority_send, priority_recv) = tokio::sync::mpsc::unbounded_channel(); let (incoming_send, incoming_recv) = tokio::sync::mpsc::channel(4096); let rt_handle = tokio::runtime::Handle::current(); Self { @@ -154,6 +160,7 @@ impl BedrockClient { outgoing_packet_queue_recv: Mutex::new(Some(recv)), outgoing_packet_priority_send: priority_send, outgoing_packet_priority_recv: Mutex::new(Some(priority_recv)), + pending_bytes: Arc::new(AtomicUsize::new(0)), next_form_id: AtomicU32::new(0), inventory_opened: AtomicBool::new(false), client_cache_supported: AtomicBool::new(false), @@ -176,6 +183,8 @@ impl BedrockClient { } pub fn start_outgoing_packet_task(self: &Arc) { + const MAX_BATCH_SIZE: usize = 64; + let client = self.clone(); self.spawn_task(async move { let Some(mut packet_receiver) = client.outgoing_packet_queue_recv.lock().await.take() @@ -189,41 +198,70 @@ impl BedrockClient { }; let mut interval = tokio::time::interval(std::time::Duration::from_millis(100)); - while !client.close_token.is_cancelled() { - let packet = tokio::select! { + loop { + let recv_result = tokio::select! { biased; - () = client.close_token.cancelled() => break, - res = priority_packet_receiver.recv() => match res { - Some(p) => p, - None => break, - }, + res = priority_packet_receiver.recv() => res, + res = packet_receiver.recv() => res, _ = interval.tick() => { if !client.tick_connection().await { break; } continue; } - res = packet_receiver.recv() => match res { - Some(p) => p, - None => break, - }, + () = client.close_token.cancelled() => { + priority_packet_receiver + .try_recv() + .ok() + .or_else(|| packet_receiver.try_recv().ok()) + } }; - let data = packet.data.strip_prefix(&[BEDROCK_GAME_PACKET]); - let Some(data) = data else { - warn!("Refusing to send a non-game packet over NetherNet"); - continue; + let Some(packet) = recv_result else { + break; }; - if let Err(error) = client.session.send(Bytes::copy_from_slice(data)).await { - warn!( - "Failed to send NetherNet packet to {}: {error}", - client.address - ); - client.close().await; + + let mut packet_batch = Vec::with_capacity(MAX_BATCH_SIZE); + packet_batch.push(packet); + + while packet_batch.len() < MAX_BATCH_SIZE { + match priority_packet_receiver.try_recv() { + Ok(packet) => { + packet_batch.push(packet); + continue; + } + Err(TryRecvError::Disconnected | TryRecvError::Empty) => {} + } + + match packet_receiver.try_recv() { + Ok(packet) => packet_batch.push(packet), + Err(TryRecvError::Disconnected | TryRecvError::Empty) => break, + } } - if let Some(completion) = packet.completion { - let _ = completion.send(()); + for packet in packet_batch { + let packet_len = packet.data.len(); + let data = packet.data.strip_prefix(&[BEDROCK_GAME_PACKET]); + let Some(data) = data else { + warn!("Refusing to send a non-game packet over NetherNet"); + decrement_pending_bytes(&client.pending_bytes, packet_len); + continue; + }; + if let Err(error) = client.session.send(Bytes::copy_from_slice(data)).await { + warn!( + "Failed to send NetherNet packet to {}: {error}", + client.address + ); + decrement_pending_bytes(&client.pending_bytes, packet_len); + client.close().await; + return; + } + + decrement_pending_bytes(&client.pending_bytes, packet_len); + + if let Some(completion) = packet.completion { + let _ = completion.send(()); + } } } }); @@ -270,9 +308,14 @@ impl BedrockClient { } pub fn try_kick(&self, reason: DisconnectReason, message: String) { + warn!("Closing connection for {}: {message}", self.address); let packet = CDisconnect::new(reason as i32, message); if let Ok(data) = self.serialize_packet(&packet) { - self.try_enqueue_packet(data); + let packet_len = data.len(); + let _ = self.pending_bytes.fetch_add(packet_len, Ordering::AcqRel); + let _ = self + .outgoing_packet_priority_send + .send(OutgoingPacket::normal(data)); } if !self.close_token.is_cancelled() { self.close_token.cancel(); @@ -280,6 +323,7 @@ impl BedrockClient { } pub async fn kick(&self, reason: DisconnectReason, message: String) { + warn!("Closing connection for {}: {message}", self.address); self.send_packet(&CDisconnect::new(reason as i32, message)) .await; self.close().await; @@ -293,6 +337,7 @@ impl BedrockClient { filtered_message: String, send_packet: bool, ) { + warn!("Closing connection for {}: {message}", self.address); if send_packet { self.send_packet(&CDisconnect { reason: pumpkin_protocol::codec::var_int::VarInt(reason as i32), @@ -406,8 +451,9 @@ impl BedrockClient { self.player.store(Arc::new(Some(player))); } + #[allow(clippy::unused_async)] pub async fn enqueue_packet(&self, packet_data: Bytes) { - self.enqueue_packet_data(packet_data).await; + self.try_enqueue_packet_data(packet_data); } pub fn try_enqueue_packet(&self, packet_data: Bytes) { @@ -420,37 +466,40 @@ impl BedrockClient { /// # Arguments /// /// * `packet_data`: A `Bytes` payload representing the encoded packet. + #[allow(clippy::unused_async)] pub async fn enqueue_packet_data(&self, packet_data: Bytes) { - if let Err(err) = self - .outgoing_packet_queue_send - .send(OutgoingPacket::normal(packet_data)) - .await - { - // This is expected to fail if we are closed - if !self.is_closed() { - error!("Failed to add packet to the outgoing packet queue for client: {err}"); - } - } + self.try_enqueue_packet_data(packet_data); } pub fn try_enqueue_packet_data(&self, packet_data: Bytes) { + if self.is_closed() { + return; + } + + let packet_len = packet_data.len(); + let prev_bytes = self.pending_bytes.fetch_add(packet_len, Ordering::AcqRel); + let new_bytes = prev_bytes.saturating_add(packet_len); + + if new_bytes > MAX_PENDING_BYTES { + decrement_pending_bytes(&self.pending_bytes, packet_len); + if !self.is_closed() { + warn!( + "Bedrock client {} outbound packet buffer overflow ({} bytes > {} bytes). Closing connection.", + self.address, new_bytes, MAX_PENDING_BYTES + ); + self.close_token.cancel(); + } + return; + } + if let Err(err) = self .outgoing_packet_queue_send - .try_send(OutgoingPacket::normal(packet_data)) + .send(OutgoingPacket::normal(packet_data)) { - match err { - tokio::sync::mpsc::error::TrySendError::Full(_) => { - debug!( - "Failed to add packet to the outgoing packet queue for client: channel full" - ); - } - tokio::sync::mpsc::error::TrySendError::Closed(_) => { - if !self.is_closed() { - error!( - "Failed to add packet to the outgoing packet queue for client: channel closed" - ); - } - } + decrement_pending_bytes(&self.pending_bytes, packet_len); + // This is expected to fail if we are closed + if !self.is_closed() { + error!("Failed to add packet to the outgoing packet queue for client: {err}"); } } } @@ -512,12 +561,32 @@ impl BedrockClient { } pub async fn send_game_packet(&self, packet_data: Bytes) { + if self.is_closed() { + return; + } + + let packet_len = packet_data.len(); + let prev_bytes = self.pending_bytes.fetch_add(packet_len, Ordering::AcqRel); + let new_bytes = prev_bytes.saturating_add(packet_len); + + if new_bytes > MAX_PENDING_BYTES { + decrement_pending_bytes(&self.pending_bytes, packet_len); + if !self.is_closed() { + warn!( + "Bedrock client {} outbound packet buffer overflow ({} bytes > {} bytes). Closing connection.", + self.address, new_bytes, MAX_PENDING_BYTES + ); + self.close_token.cancel(); + } + return; + } + let (tx, rx) = oneshot::channel(); if let Err(err) = self .outgoing_packet_priority_send .send(OutgoingPacket::priority(packet_data, tx)) - .await { + decrement_pending_bytes(&self.pending_bytes, packet_len); if !self.is_closed() { error!("Failed to add priority packet to the outgoing packet queue: {err}"); } diff --git a/crates/pumpkin/src/net/java/mod.rs b/crates/pumpkin/src/net/java/mod.rs index 75300696e..3d159ef7f 100644 --- a/crates/pumpkin/src/net/java/mod.rs +++ b/crates/pumpkin/src/net/java/mod.rs @@ -3,7 +3,7 @@ use pumpkin_protocol::java::client::play::{ }; use pumpkin_world::level::SyncChunk; use std::net::SocketAddr; -use std::sync::atomic::{AtomicBool, AtomicI32, Ordering}; +use std::sync::atomic::{AtomicBool, AtomicI32, AtomicUsize, Ordering}; use std::time::{Instant, SystemTime, UNIX_EPOCH}; use std::{collections::VecDeque, io::Write, sync::Arc}; @@ -46,7 +46,7 @@ use tokio::{ sync::oneshot, }; use tokio::{ - sync::mpsc::{Receiver, Sender, error::TryRecvError}, + sync::mpsc::{UnboundedReceiver, UnboundedSender, error::TryRecvError}, task::JoinHandle, }; use tokio_util::sync::CancellationToken; @@ -66,7 +66,8 @@ use pending::PendingConnection; use crate::entity::player::Player; use crate::net::{ - ClientPlatform, GameProfile, PacketHandlerResult, PacketRateLimiter, PlayerConfig, + ClientPlatform, GameProfile, MAX_PENDING_BYTES, PacketHandlerResult, PacketRateLimiter, + PlayerConfig, decrement_pending_bytes, }; use crate::plugin::api::events::world::chunk_send::ChunkSend; use crate::plugin::player::player_custom_payload::PlayerCustomPayloadEvent; @@ -95,13 +96,15 @@ pub struct JavaClient { /// An notifier that is triggered when this client is closed. close_token: CancellationToken, /// A normal-priority queue of serialized packets to send to the network. - outgoing_packet_queue_send: Sender, + outgoing_packet_queue_send: UnboundedSender, /// A normal-priority queue of serialized packets to send to the network. - outgoing_packet_queue_recv: Option>, + outgoing_packet_queue_recv: Option>, /// A high-priority queue of serialized packets to send to the network. - outgoing_packet_priority_send: Sender, + outgoing_packet_priority_send: UnboundedSender, /// A high-priority queue of serialized packets to send to the network. - outgoing_packet_priority_recv: Option>, + outgoing_packet_priority_recv: Option>, + /// Tracks total buffered payload bytes in the outgoing queues. + pub pending_bytes: Arc, /// The packet encoder for outgoing packets. network_writer: std::sync::Mutex>>>, /// The packet decoder for incoming packets. @@ -224,8 +227,8 @@ impl JavaClient { gameprofile: GameProfile, config: PlayerConfig, ) -> Self { - let (send, recv) = tokio::sync::mpsc::channel(4096); - let (priority_send, priority_recv) = tokio::sync::mpsc::channel(4096); + let (send, recv) = tokio::sync::mpsc::unbounded_channel(); + let (priority_send, priority_recv) = tokio::sync::mpsc::unbounded_channel(); Self { id: pending.id, @@ -241,6 +244,7 @@ impl JavaClient { outgoing_packet_queue_recv: Some(recv), outgoing_packet_priority_send: priority_send, outgoing_packet_priority_recv: Some(priority_recv), + pending_bytes: Arc::new(AtomicUsize::new(0)), version: pending.version, network_writer: std::sync::Mutex::new(Some(pending.network_writer)), network_reader: std::sync::Mutex::new(Some(pending.network_reader)), @@ -480,16 +484,46 @@ impl JavaClient { } } + #[allow(clippy::unused_async)] pub async fn enqueue_packet(&self, packet_data: Bytes) { - self.enqueue_packet_data(packet_data).await; + self.try_enqueue_packet_data(packet_data); } + #[allow(clippy::unused_async)] pub async fn enqueue_packet_data(&self, packet_data: Bytes) { + self.try_enqueue_packet_data(packet_data); + } + + pub fn try_enqueue_packet(&self, packet_data: Bytes) { + self.try_enqueue_packet_data(packet_data); + } + + pub fn try_enqueue_packet_data(&self, packet_data: Bytes) { + if self.close_token.is_cancelled() { + return; + } + + let packet_len = packet_data.len(); + let prev_bytes = self.pending_bytes.fetch_add(packet_len, Ordering::AcqRel); + let new_bytes = prev_bytes.saturating_add(packet_len); + + if new_bytes > MAX_PENDING_BYTES { + decrement_pending_bytes(&self.pending_bytes, packet_len); + if !self.close_token.is_cancelled() { + warn!( + "Client {} outbound packet buffer overflow ({} bytes > {} bytes). Closing connection.", + self.id, new_bytes, MAX_PENDING_BYTES + ); + self.close(); + } + return; + } + if let Err(err) = self .outgoing_packet_queue_send .send(OutgoingPacket::normal(packet_data)) - .await { + decrement_pending_bytes(&self.pending_bytes, packet_len); // This is expected to fail if we are closed if !self.close_token.is_cancelled() { warn!( @@ -503,35 +537,6 @@ impl JavaClient { } } - pub fn try_enqueue_packet(&self, packet_data: Bytes) { - self.try_enqueue_packet_data(packet_data); - } - - pub fn try_enqueue_packet_data(&self, packet_data: Bytes) { - if let Err(err) = self - .outgoing_packet_queue_send - .try_send(OutgoingPacket::normal(packet_data)) - { - match err { - tokio::sync::mpsc::error::TrySendError::Full(_) => { - debug!( - "Failed to add packet to the outgoing packet queue for client {}: channel full", - self.id - ); - } - tokio::sync::mpsc::error::TrySendError::Closed(_) => { - if !self.close_token.is_cancelled() { - warn!( - "Failed to add packet to the outgoing packet queue for client {}: channel closed", - self.id - ); - self.close(); - } - } - } - } - } - pub async fn await_close_interrupt(&self) { self.close_token.cancelled().await; } @@ -562,31 +567,34 @@ impl JavaClient { } pub fn try_kick(&self, reason: &TextComponent) { - match self.connection_state.load() { + let serialized = match self.connection_state.load() { ConnectionState::Login => { let packet = CLoginDisconnect::new( serde_json::to_string(&reason.0).unwrap_or_else(|_| String::new()), ); - if let Ok(data) = self.serialize_packet(&packet) { - self.try_enqueue_packet(data); - } + self.serialize_packet(&packet).ok() } ConnectionState::Config => { let reason_text = reason.clone().get_text(); let packet = CConfigDisconnect::new(&reason_text); - if let Ok(data) = self.serialize_packet(&packet) { - self.try_enqueue_packet(data); - } + self.serialize_packet(&packet).ok() } ConnectionState::Play => { let packet = CPlayDisconnect::new(reason); - if let Ok(data) = self.serialize_packet(&packet) { - self.try_enqueue_packet(data); - } + self.serialize_packet(&packet).ok() } - _ => {} + _ => None, + }; + + if let Some(data) = serialized { + let packet_len = data.len(); + let _ = self.pending_bytes.fetch_add(packet_len, Ordering::AcqRel); + let _ = self + .outgoing_packet_priority_send + .send(OutgoingPacket::normal(data)); } - debug!("Closing connection for {}", self.id); + let reason_text = reason.clone().get_text(); + warn!("Closing connection for {}: {reason_text}", self.id); self.close(); } @@ -612,7 +620,8 @@ impl JavaClient { _ => {} } } - debug!("Closing connection for {}", self.id); + let reason_text = reason.clone().get_text(); + warn!("Closing connection for {}: {reason_text}", self.id); self.close(); } @@ -621,13 +630,33 @@ impl JavaClient { } pub async fn send_packet_now_data(&self, packet: Bytes) { + if self.close_token.is_cancelled() { + return; + } + + let packet_len = packet.len(); + let prev_bytes = self.pending_bytes.fetch_add(packet_len, Ordering::AcqRel); + let new_bytes = prev_bytes.saturating_add(packet_len); + + if new_bytes > MAX_PENDING_BYTES { + decrement_pending_bytes(&self.pending_bytes, packet_len); + if !self.close_token.is_cancelled() { + warn!( + "Client {} outbound packet buffer overflow ({} bytes > {} bytes). Closing connection.", + self.id, new_bytes, MAX_PENDING_BYTES + ); + self.close(); + } + return; + } + let (completion_tx, completion_rx) = oneshot::channel(); if let Err(err) = self .outgoing_packet_priority_send .send(OutgoingPacket::high_priority(packet, completion_tx)) - .await { + decrement_pending_bytes(&self.pending_bytes, packet_len); // It is expected that the packet will fail if we are closed if !self.close_token.is_cancelled() { warn!( @@ -701,6 +730,7 @@ impl JavaClient { /// - **Status:** Handles status request and ping packets. /// - **Login/Transfer:** Handles login and transfer packets. /// - **Config:** Handles configuration packets. + #[expect(clippy::too_many_lines)] pub fn start_outgoing_packet_task(&mut self) { const MAX_BATCH_SIZE: usize = 64; @@ -711,6 +741,7 @@ impl JavaClient { return; }; let close_token = self.close_token.clone(); + let pending_bytes = self.pending_bytes.clone(); let Some(mut writer) = self .network_writer .lock() @@ -721,12 +752,17 @@ impl JavaClient { }; let id = self.id; self.spawn_task(async move { - while !close_token.is_cancelled() { + loop { let recv_result = tokio::select! { biased; - () = close_token.cancelled() => None, res = priority_packet_receiver.recv() => res, res = packet_receiver.recv() => res, + () = close_token.cancelled() => { + priority_packet_receiver + .try_recv() + .ok() + .or_else(|| packet_receiver.try_recv().ok()) + } }; let Some(packet_data) = recv_result else { @@ -796,6 +832,9 @@ impl JavaClient { send_failed = true; } + let flushed_bytes: usize = written_packets.iter().map(|p| p.data.len()).sum(); + decrement_pending_bytes(&pending_bytes, flushed_bytes); + if send_failed { // We now need to close the connection to the client since the stream is in an unknown state. close_token.cancel(); diff --git a/crates/pumpkin/src/net/mod.rs b/crates/pumpkin/src/net/mod.rs index 05f351c1f..79f0ed0ff 100644 --- a/crates/pumpkin/src/net/mod.rs +++ b/crates/pumpkin/src/net/mod.rs @@ -9,7 +9,10 @@ use pumpkin_world::level::SyncChunk; use std::{ net::SocketAddr, num::NonZero, - sync::{Arc, atomic::Ordering}, + sync::{ + Arc, + atomic::{AtomicUsize, Ordering}, + }, }; use pumpkin_data::translation; @@ -119,6 +122,17 @@ pub enum PacketHandlerResult { ReadyToPlay(GameProfile, PlayerConfig), } +/// Maximum payload bytes that may be queued for a client before it is considered stalled/overflowing and disconnected. +pub const MAX_PENDING_BYTES: usize = 64 * 1024 * 1024; // 64 MB + +/// Defensively decrement an atomic pending byte counter without underflowing. +#[inline] +pub fn decrement_pending_bytes(pending_bytes: &AtomicUsize, bytes: usize) { + let _ = pending_bytes.fetch_update(Ordering::Release, Ordering::Relaxed, |val| { + Some(val.saturating_sub(bytes)) + }); +} + /// This is just a Wrapper for both Java & Bedrock connections #[expect(clippy::large_enum_variant)] pub enum ClientPlatform { @@ -176,6 +190,14 @@ impl ClientPlatform { } } + #[must_use] + pub fn pending_bytes(&self) -> usize { + match self { + Self::Java(java) => java.pending_bytes.load(Ordering::Relaxed), + Self::Bedrock(bedrock) => bedrock.pending_bytes.load(Ordering::Relaxed), + } + } + pub fn try_enqueue_packet_data(&self, packet_data: Bytes) { match self { Self::Java(java) => java.try_enqueue_packet_data(packet_data), @@ -694,4 +716,18 @@ mod tests { "Name containing DEL (127) should be invalid" ); } + + #[test] + fn decrement_pending_bytes_saturating() { + use super::decrement_pending_bytes; + use std::sync::atomic::{AtomicUsize, Ordering}; + + let counter = AtomicUsize::new(100); + decrement_pending_bytes(&counter, 40); + assert_eq!(counter.load(Ordering::Relaxed), 60); + + // Underflow protection: decrementing more than current value should clamp to 0 + decrement_pending_bytes(&counter, 100); + assert_eq!(counter.load(Ordering::Relaxed), 0); + } }