chore(refactor): add pumpkin-command crate
This commit is contained in:
parent
b961f97760
commit
4f88d77ab1
2
.gitignore
vendored
2
.gitignore
vendored
@ -111,6 +111,7 @@ data/*
|
||||
# project's configurations
|
||||
pumpkin.toml
|
||||
nethernet-key.der
|
||||
.pumpkin-server-id
|
||||
|
||||
|
||||
*.local.nbt
|
||||
@ -150,6 +151,7 @@ whitelist.json
|
||||
.console_history
|
||||
bukkit.yml
|
||||
commands.yml
|
||||
/config/
|
||||
help.yml
|
||||
permissions.yml
|
||||
spigot.yml
|
||||
|
||||
20
Cargo.lock
generated
20
Cargo.lock
generated
@ -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"
|
||||
|
||||
@ -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 }
|
||||
|
||||
25
crates/pumpkin-command/Cargo.toml
Normal file
25
crates/pumpkin-command/Cargo.toml
Normal file
@ -0,0 +1,25 @@
|
||||
[package]
|
||||
name = "pumpkin-command"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
pumpkin-util.workspace = true
|
||||
pumpkin-protocol = { workspace = true, features = ["default"] }
|
||||
pumpkin-data = { workspace = true, features = ["default"] }
|
||||
pumpkin-nbt.workspace = true
|
||||
pumpkin-world.workspace = true
|
||||
pumpkin-codecs.workspace = true
|
||||
|
||||
uuid.workspace = true
|
||||
rustc-hash.workspace = true
|
||||
thiserror.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
itertools.workspace = true
|
||||
tracing.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
589
crates/pumpkin-command/src/argument_builder.rs
Normal file
589
crates/pumpkin-command/src/argument_builder.rs
Normal file
@ -0,0 +1,589 @@
|
||||
use crate::argument_builder::private::Sealed;
|
||||
use crate::argument_types::argument_type::AnyArgumentType;
|
||||
use crate::node::detached::{
|
||||
ArgumentDetachedNode, CommandDetachedNode, DetachedNode, GlobalNodeId, LiteralDetachedNode,
|
||||
};
|
||||
use crate::node::{
|
||||
Command, CommandExecutor, RedirectModifier, Redirection, Requirement, Requirements,
|
||||
};
|
||||
use crate::source::{CommandSource, DummySource};
|
||||
use crate::suggestion::provider::SuggestionProvider;
|
||||
use rustc_hash::FxHashMap;
|
||||
use std::borrow::Cow;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Represents an intermediate struct for
|
||||
/// building arguments for commands.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// This is an implementation detail.
|
||||
struct CommonArgumentBuilder<S: CommandSource = DummySource> {
|
||||
pub global_id: GlobalNodeId,
|
||||
pub arguments: FxHashMap<String, DetachedNode<S>>,
|
||||
pub command: Option<Command<S>>,
|
||||
pub requirements: Requirements<S>,
|
||||
pub target: Option<Redirection>,
|
||||
pub modifier: RedirectModifier<S>,
|
||||
pub forks: bool,
|
||||
}
|
||||
|
||||
impl<S: CommandSource> CommonArgumentBuilder<S> {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
global_id: GlobalNodeId::new(),
|
||||
arguments: FxHashMap::default(),
|
||||
command: None,
|
||||
requirements: Requirements::new(),
|
||||
target: None,
|
||||
modifier: RedirectModifier::KeepSource,
|
||||
forks: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: CommandSource> Default for CommonArgumentBuilder<S> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// A short-form way to create a new [`CommandArgumentBuilder`]
|
||||
/// from a literal and a command description.
|
||||
///
|
||||
/// This can be imported directly without a prefix, or imported with the `argument_builder::` prefix. Here's an example of usage:
|
||||
/// ```
|
||||
/// use pumpkin_command::argument_builder::command;
|
||||
/// use pumpkin_command::source::DummySource;
|
||||
///
|
||||
/// let builder = command::<DummySource>("foo", "A test command");
|
||||
/// ```
|
||||
///
|
||||
/// The builder returned will eventually construct a [`CommandDetachedNode`].
|
||||
/// This node can then be registered into a dispatcher.
|
||||
pub fn command<S: CommandSource>(
|
||||
literal: impl Into<Cow<'static, str>>,
|
||||
description: impl Into<Cow<'static, str>>,
|
||||
) -> CommandArgumentBuilder<S> {
|
||||
CommandArgumentBuilder::new(literal, description)
|
||||
}
|
||||
|
||||
/// A short-form way to create a new [`LiteralArgumentBuilder`]
|
||||
/// from a literal.
|
||||
///
|
||||
/// This can be imported directly without a prefix, or imported with the `argument_builder::` prefix. Here's an example of usage:
|
||||
/// ```
|
||||
/// use pumpkin_command::argument_builder::literal;
|
||||
/// use pumpkin_command::source::DummySource;
|
||||
///
|
||||
/// let builder = literal::<DummySource>("bar");
|
||||
/// ```
|
||||
///
|
||||
/// The builder returned will eventually construct a [`LiteralDetachedNode`].
|
||||
pub fn literal<S: CommandSource>(
|
||||
literal: impl Into<Cow<'static, str>>,
|
||||
) -> LiteralArgumentBuilder<S> {
|
||||
LiteralArgumentBuilder::new(literal)
|
||||
}
|
||||
|
||||
/// A short-form way to create a new [`RequiredArgumentBuilder`]
|
||||
/// from an argument type and name.
|
||||
///
|
||||
/// This can be imported directly without a prefix, or imported with the `argument_builder::` prefix. Here's an example of usage:
|
||||
/// ```
|
||||
/// use pumpkin_command::{
|
||||
/// argument_builder::argument,
|
||||
/// argument_types::core::integer::IntegerArgumentType,
|
||||
/// source::DummySource,
|
||||
/// };
|
||||
///
|
||||
/// let argument_builder = argument::<DummySource>("bar", IntegerArgumentType::new(1, 10));
|
||||
/// ```
|
||||
///
|
||||
/// The builder returned will eventually construct a [`ArgumentDetachedNode`].
|
||||
pub fn argument<S: CommandSource>(
|
||||
name: impl Into<Cow<'static, str>>,
|
||||
arg_type: impl AnyArgumentType<S> + 'static,
|
||||
) -> RequiredArgumentBuilder<S> {
|
||||
RequiredArgumentBuilder::new(name, arg_type)
|
||||
}
|
||||
|
||||
/// A builder that builds a literal, non-command [`DetachedNode`].
|
||||
pub struct LiteralArgumentBuilder<S: CommandSource = DummySource> {
|
||||
common: CommonArgumentBuilder<S>,
|
||||
literal: Cow<'static, str>,
|
||||
}
|
||||
|
||||
/// A builder that builds a command [`DetachedNode`].
|
||||
pub struct CommandArgumentBuilder<S: CommandSource = DummySource> {
|
||||
common: CommonArgumentBuilder<S>,
|
||||
literal: Cow<'static, str>,
|
||||
description: Cow<'static, str>,
|
||||
source: Option<String>,
|
||||
}
|
||||
|
||||
/// A builder that builds an argument [`DetachedNode`].
|
||||
pub struct RequiredArgumentBuilder<S: CommandSource = DummySource> {
|
||||
common: CommonArgumentBuilder<S>,
|
||||
name: Cow<'static, str>,
|
||||
argument_type: Arc<dyn AnyArgumentType<S>>,
|
||||
suggestion_provider: Option<Arc<dyn SuggestionProvider<S>>>,
|
||||
}
|
||||
|
||||
mod private {
|
||||
// We want to make this trait private so that
|
||||
// we can implement it for only our
|
||||
// argument builders defined here.
|
||||
pub trait Sealed {}
|
||||
}
|
||||
|
||||
pub trait ArgumentBuilder<
|
||||
S: CommandSource = DummySource,
|
||||
N: Into<DetachedNode<S>> = DetachedNode<S>,
|
||||
>: Sized + Sealed
|
||||
{
|
||||
/// Puts an argument to be specified, right after this one is specified.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if this node is redirected to another node, or the child
|
||||
/// provided is of the type [`CommandDetachedNode`].
|
||||
#[must_use]
|
||||
fn then(self, child: impl Into<DetachedNode<S>>) -> Self;
|
||||
|
||||
/// Gets the command to execute for the node being built.
|
||||
#[must_use]
|
||||
fn command(&self) -> Option<Command<S>>;
|
||||
|
||||
/// Sets the command to execute for the node being built.
|
||||
#[must_use]
|
||||
fn executes(self, command: impl CommandExecutor<S> + 'static) -> Self {
|
||||
self.executes_arc(Arc::new(command))
|
||||
}
|
||||
|
||||
/// Sets the command to execute for the node being built.
|
||||
#[must_use]
|
||||
fn executes_arc(self, command: Arc<dyn CommandExecutor<S> + 'static>) -> Self;
|
||||
|
||||
/// Sets the redirect target of the node being built to another, without a modifier.
|
||||
#[must_use]
|
||||
fn redirect(self, redirection: impl Into<Redirection>) -> Self;
|
||||
|
||||
/// Sets the redirect target of the node being built to another, with a given modifier.
|
||||
#[must_use]
|
||||
fn redirect_with_modifier(
|
||||
self,
|
||||
redirection: impl Into<Redirection>,
|
||||
redirect_modifier: RedirectModifier<S>,
|
||||
) -> Self;
|
||||
|
||||
/// Forks the given context, using multiple for later.
|
||||
#[must_use]
|
||||
fn fork(
|
||||
self,
|
||||
redirection: impl Into<Redirection>,
|
||||
redirect_modifier: RedirectModifier<S>,
|
||||
) -> Self;
|
||||
|
||||
/// Forwards the given context, with the given `fork` flag.
|
||||
#[must_use]
|
||||
fn forward(
|
||||
self,
|
||||
redirection: impl Into<Redirection>,
|
||||
redirect_modifier: RedirectModifier<S>,
|
||||
fork: bool,
|
||||
) -> Self;
|
||||
|
||||
/// Gets a reference to the arguments of the node to be built.
|
||||
#[must_use]
|
||||
fn arguments(&self) -> &FxHashMap<String, DetachedNode<S>>;
|
||||
|
||||
/// Gets the node to which the node being built by this [`ArgumentBuilder`] redirects.
|
||||
#[must_use]
|
||||
fn target(&self) -> Option<Redirection>;
|
||||
|
||||
/// Adds a given predicate to this list of requirements of the node being built.
|
||||
///
|
||||
/// This means that it is possible to chain multiple predicates together, which must all
|
||||
/// be satisfied.
|
||||
///
|
||||
/// Permissions can also be inserted directly into this method as a `requirement`.
|
||||
#[must_use]
|
||||
fn requires(self, requirement: impl Into<Requirement<S>>) -> Self;
|
||||
|
||||
/// Overwrites the current requirements of this node to a new value.
|
||||
#[must_use]
|
||||
fn overwrite_requirements(self, requirements: Requirements<S>) -> Self;
|
||||
|
||||
/// Gets the redirect modifier of the node this [`ArgumentBuilder`] is building.
|
||||
#[must_use]
|
||||
fn redirect_modifier(&self) -> RedirectModifier<S>;
|
||||
|
||||
/// Whether this builder forks.
|
||||
#[must_use]
|
||||
fn forks(&self) -> bool;
|
||||
|
||||
/// Returns the 'future [`GlobalNodeId`]' of the node that will be produced by this Builder.
|
||||
/// Very useful for redirects.
|
||||
#[must_use]
|
||||
fn id(&self) -> GlobalNodeId;
|
||||
|
||||
/// Builds the node represented by this builder, consuming itself in the process.
|
||||
#[must_use]
|
||||
fn build(self) -> N;
|
||||
}
|
||||
|
||||
// Implement the private trait for our builders!
|
||||
impl<S: CommandSource> Sealed for LiteralArgumentBuilder<S> {}
|
||||
impl<S: CommandSource> Sealed for CommandArgumentBuilder<S> {}
|
||||
impl<S: CommandSource> Sealed for RequiredArgumentBuilder<S> {}
|
||||
|
||||
/// Helper macro to implement repeated code of `ArgumentBuilder` for our types.
|
||||
macro_rules! impl_boilerplate_argument_builder {
|
||||
($S:ident) => {
|
||||
fn then(mut self, argument: impl Into<DetachedNode<$S>>) -> Self {
|
||||
assert!(
|
||||
self.target().is_none(),
|
||||
"Cannot add children to a redirected node"
|
||||
);
|
||||
let node = argument.into();
|
||||
assert!(
|
||||
!matches!(node, DetachedNode::Command(_)),
|
||||
"Cannot add a CommandDetachedNode as a child of a builder"
|
||||
);
|
||||
|
||||
self.common.arguments.insert(node.name(), node);
|
||||
self
|
||||
}
|
||||
|
||||
fn command(&self) -> Option<Command<$S>> {
|
||||
self.common.command.clone()
|
||||
}
|
||||
|
||||
fn executes_arc(mut self, command: Arc<dyn CommandExecutor<$S> + 'static>) -> Self {
|
||||
self.common.command = Some(command);
|
||||
self
|
||||
}
|
||||
|
||||
fn requires(mut self, requirement: impl Into<Requirement<$S>>) -> Self {
|
||||
self.common.requirements.0.push(requirement.into());
|
||||
self
|
||||
}
|
||||
|
||||
fn overwrite_requirements(mut self, requirements: Requirements<$S>) -> Self {
|
||||
self.common.requirements = requirements;
|
||||
self
|
||||
}
|
||||
|
||||
fn redirect(self, redirection: impl Into<Redirection>) -> Self {
|
||||
self.forward(redirection.into(), RedirectModifier::KeepSource, false)
|
||||
}
|
||||
|
||||
fn redirect_with_modifier(
|
||||
self,
|
||||
redirection: impl Into<Redirection>,
|
||||
redirect_modifier: RedirectModifier<$S>,
|
||||
) -> Self {
|
||||
self.forward(redirection.into(), redirect_modifier, false)
|
||||
}
|
||||
|
||||
fn fork(
|
||||
self,
|
||||
redirection: impl Into<Redirection>,
|
||||
redirect_modifier: RedirectModifier<$S>,
|
||||
) -> Self {
|
||||
self.forward(redirection.into(), redirect_modifier, true)
|
||||
}
|
||||
|
||||
fn forward(
|
||||
mut self,
|
||||
redirection: impl Into<Redirection>,
|
||||
redirect_modifier: RedirectModifier<$S>,
|
||||
fork: bool,
|
||||
) -> Self {
|
||||
assert!(
|
||||
self.common.arguments.is_empty(),
|
||||
"Cannot forward a node with children. The node must have no children to redirect somewhere else"
|
||||
);
|
||||
self.common.target = Some(redirection.into());
|
||||
self.common.modifier = redirect_modifier;
|
||||
self.common.forks = fork;
|
||||
self
|
||||
}
|
||||
|
||||
fn arguments(&self) -> &FxHashMap<String, DetachedNode<$S>> {
|
||||
&self.common.arguments
|
||||
}
|
||||
|
||||
fn target(&self) -> Option<Redirection> {
|
||||
self.common.target.clone()
|
||||
}
|
||||
|
||||
fn redirect_modifier(&self) -> RedirectModifier<$S> {
|
||||
self.common.modifier.clone()
|
||||
}
|
||||
|
||||
fn forks(&self) -> bool {
|
||||
self.common.forks
|
||||
}
|
||||
|
||||
fn id(&self) -> GlobalNodeId {
|
||||
self.common.global_id
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Helper macro to generate `From` impl blocks for each builder.
|
||||
macro_rules! impl_builder_from_impls {
|
||||
($builder: ident => $detached_node: ident) => {
|
||||
impl<S: CommandSource> From<$builder<S>> for $detached_node<S> {
|
||||
fn from(value: $builder<S>) -> Self {
|
||||
value.build()
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: CommandSource> From<$builder<S>> for DetachedNode<S> {
|
||||
fn from(value: $builder<S>) -> Self {
|
||||
value.build().into()
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_builder_from_impls!(LiteralArgumentBuilder => LiteralDetachedNode);
|
||||
impl_builder_from_impls!(CommandArgumentBuilder => CommandDetachedNode);
|
||||
impl_builder_from_impls!(RequiredArgumentBuilder => ArgumentDetachedNode);
|
||||
|
||||
impl<S: CommandSource> LiteralArgumentBuilder<S> {
|
||||
/// Creates a new [`LiteralArgumentBuilder`] from a literal.
|
||||
pub fn new(literal: impl Into<Cow<'static, str>>) -> Self {
|
||||
Self {
|
||||
common: CommonArgumentBuilder::new(),
|
||||
literal: literal.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: CommandSource> CommandArgumentBuilder<S> {
|
||||
/// Creates a new [`CommandArgumentBuilder`] from a literal and a command description.
|
||||
pub fn new(
|
||||
literal: impl Into<Cow<'static, str>>,
|
||||
description: impl Into<Cow<'static, str>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
common: CommonArgumentBuilder::new(),
|
||||
literal: literal.into(),
|
||||
description: description.into(),
|
||||
source: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the source (e.g. plugin name) that registered this command.
|
||||
#[must_use]
|
||||
pub fn with_source(mut self, source: impl Into<String>) -> Self {
|
||||
self.source = Some(source.into());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: CommandSource> RequiredArgumentBuilder<S> {
|
||||
/// Creates a new [`RequiredArgumentBuilder`] from a name and an argument type.
|
||||
pub fn new(
|
||||
name: impl Into<Cow<'static, str>>,
|
||||
arg_type: impl AnyArgumentType<S> + 'static,
|
||||
) -> Self {
|
||||
Self {
|
||||
common: CommonArgumentBuilder::new(),
|
||||
name: name.into(),
|
||||
argument_type: Arc::new(arg_type),
|
||||
suggestion_provider: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the [`SuggestionProvider`] of this builder for the `ArgumentDetachedNode`.
|
||||
#[must_use]
|
||||
pub fn suggests(self, provider: impl SuggestionProvider<S> + 'static) -> Self {
|
||||
self.suggests_arc(Arc::new(provider))
|
||||
}
|
||||
|
||||
/// Sets the [`SuggestionProvider`] of this builder for the `ArgumentDetachedNode`.
|
||||
#[must_use]
|
||||
pub fn suggests_arc(mut self, provider: Arc<dyn SuggestionProvider<S>>) -> Self {
|
||||
self.suggestion_provider = Some(provider);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: CommandSource> ArgumentBuilder<S, LiteralDetachedNode<S>> for LiteralArgumentBuilder<S> {
|
||||
impl_boilerplate_argument_builder!(S);
|
||||
|
||||
fn build(self) -> LiteralDetachedNode<S> {
|
||||
let mut node = LiteralDetachedNode::new(
|
||||
self.common.global_id,
|
||||
self.literal,
|
||||
self.common.command,
|
||||
self.common.requirements,
|
||||
self.common.target,
|
||||
self.common.modifier,
|
||||
self.common.forks,
|
||||
);
|
||||
node.children = self.common.arguments;
|
||||
node
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: CommandSource> ArgumentBuilder<S, CommandDetachedNode<S>> for CommandArgumentBuilder<S> {
|
||||
impl_boilerplate_argument_builder!(S);
|
||||
|
||||
fn build(self) -> CommandDetachedNode<S> {
|
||||
let mut node = CommandDetachedNode::new(
|
||||
self.common.global_id,
|
||||
self.literal,
|
||||
self.description,
|
||||
self.common.command,
|
||||
self.common.requirements,
|
||||
self.common.target,
|
||||
self.common.modifier,
|
||||
self.common.forks,
|
||||
);
|
||||
node.meta.source = self.source;
|
||||
node.children = self.common.arguments;
|
||||
node
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: CommandSource> ArgumentBuilder<S, ArgumentDetachedNode<S>> for RequiredArgumentBuilder<S> {
|
||||
impl_boilerplate_argument_builder!(S);
|
||||
|
||||
fn build(self) -> ArgumentDetachedNode<S> {
|
||||
let mut node = ArgumentDetachedNode::new(
|
||||
self.common.global_id,
|
||||
self.name,
|
||||
self.argument_type,
|
||||
self.common.command,
|
||||
self.common.requirements,
|
||||
self.common.target,
|
||||
self.common.modifier,
|
||||
self.common.forks,
|
||||
self.suggestion_provider,
|
||||
);
|
||||
node.children = self.common.arguments;
|
||||
node
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::argument_builder::{ArgumentBuilder, argument, command, literal};
|
||||
use crate::argument_types::core::double::DoubleArgumentType;
|
||||
use crate::argument_types::core::integer::IntegerArgumentType;
|
||||
use crate::argument_types::core::string::StringArgumentType;
|
||||
use crate::errors::error_types;
|
||||
use crate::node::Redirection;
|
||||
use crate::node::attached::AttachedNode;
|
||||
use crate::node::tree::Tree;
|
||||
use crate::source::DummySource;
|
||||
use crate::string_reader::StringReader;
|
||||
|
||||
#[test]
|
||||
fn literal_one() {
|
||||
let builder = literal::<DummySource>("test");
|
||||
let node = builder.build();
|
||||
|
||||
assert_eq!(node.meta.literal, "test");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn required_one() {
|
||||
let builder = argument::<DummySource>("test", IntegerArgumentType::new(1, 10));
|
||||
let node = builder.build();
|
||||
|
||||
assert_eq!(node.meta.name, "test");
|
||||
|
||||
let mut reader1 = StringReader::new("5");
|
||||
let mut reader2 = StringReader::new("11");
|
||||
|
||||
let boxed_result = node
|
||||
.meta
|
||||
.argument_type
|
||||
.parse(&mut reader1)
|
||||
.expect("The parsing should not have errored");
|
||||
let result = boxed_result
|
||||
.downcast::<i32>()
|
||||
.expect("Downcasting shouldn't have failed");
|
||||
assert_eq!(result, Box::new(5));
|
||||
|
||||
let error = node
|
||||
.meta
|
||||
.argument_type
|
||||
.parse(&mut reader2)
|
||||
.expect_err("The parsing should have errored as 11 is outside the range");
|
||||
assert!(error.is(&error_types::INTEGER_TOO_HIGH));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn literal_multiple() {
|
||||
let mut builder = command::<DummySource>("letter", "A test command");
|
||||
for letter in 'a'..='z' {
|
||||
// Add a node per letter for the argument.
|
||||
let letter_string = letter.to_string();
|
||||
builder = builder.then(literal(letter_string));
|
||||
}
|
||||
|
||||
let node = builder.build();
|
||||
assert_eq!(node.children.len(), 26);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn required_multiple() {
|
||||
let builder = command::<DummySource>("test", "A test command")
|
||||
.then(argument("number", DoubleArgumentType::any()))
|
||||
.then(argument("word", StringArgumentType::SingleWord));
|
||||
|
||||
let node = builder.build();
|
||||
assert_eq!(node.children.len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn redirect() {
|
||||
let builder = command::<DummySource>("test", "A test command").redirect(Redirection::Root);
|
||||
|
||||
let mut tree = Tree::<DummySource>::new();
|
||||
let node_id = tree.add_child_to_root(builder);
|
||||
|
||||
let node = &tree[node_id];
|
||||
let redirect = node
|
||||
.redirect
|
||||
.expect("Redirection should exist as it was added before");
|
||||
|
||||
let target_id = tree
|
||||
.resolve(redirect)
|
||||
.expect("Target should have been resolved properly");
|
||||
let target = &tree[target_id];
|
||||
|
||||
assert!(matches!(target, AttachedNode::Root(_)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic = "Cannot forward a node with children. The node must have no children to redirect somewhere else"]
|
||||
fn redirect_after_child() {
|
||||
let _ = command::<DummySource>("test", "A test command")
|
||||
.then(literal("child"))
|
||||
.redirect(Redirection::Root);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic = "Cannot add children to a redirected node"]
|
||||
fn redirect_before_child() {
|
||||
let _ = command::<DummySource>("test", "A test command")
|
||||
.redirect(Redirection::Root)
|
||||
.then(literal("child"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic = "Cannot add a CommandDetachedNode as a child of a builder"]
|
||||
fn add_command_as_child() {
|
||||
let _ = command::<DummySource>("foo", "A test command")
|
||||
.then(command("bar", "Another test command"));
|
||||
}
|
||||
}
|
||||
@ -1,20 +1,17 @@
|
||||
use pumpkin_protocol::java::client::play::SuggestionProviders;
|
||||
|
||||
use crate::command::argument_types::argument_type::sealed::Sealed;
|
||||
use crate::command::context::command_context::CommandContext;
|
||||
use crate::command::context::command_source::CommandSource;
|
||||
use crate::command::suggestion::suggestions::Suggestions;
|
||||
use crate::command::suggestion::suggestions::SuggestionsBuilder;
|
||||
use crate::command::{
|
||||
errors::command_syntax_error::CommandSyntaxError, string_reader::StringReader,
|
||||
};
|
||||
use crate::context::command_context::CommandContext;
|
||||
use crate::source::{CommandSource, DummySource};
|
||||
use crate::suggestion::suggestions::Suggestions;
|
||||
use crate::suggestion::suggestions::SuggestionsBuilder;
|
||||
use crate::{errors::command_syntax_error::CommandSyntaxError, string_reader::StringReader};
|
||||
use std::any::Any;
|
||||
|
||||
pub type JavaClientArgumentType = pumpkin_protocol::java::client::play::ArgumentType;
|
||||
pub type ParseWithSourceAnyResult = Result<Box<dyn Any + Send + Sync>, CommandSyntaxError>;
|
||||
|
||||
/// Represents an argument type that parses a particular type `Item`.
|
||||
pub trait ArgumentType: Send + Sync {
|
||||
pub trait ArgumentType<S: CommandSource = DummySource>: Send + Sync {
|
||||
/// The data type that this argument type parses.
|
||||
type Item: Send + Sync;
|
||||
|
||||
@ -32,7 +29,7 @@ pub trait ArgumentType: Send + Sync {
|
||||
fn parse_with_source(
|
||||
&self,
|
||||
reader: &mut StringReader,
|
||||
_source: &CommandSource,
|
||||
_source: &S,
|
||||
) -> Result<Self::Item, CommandSyntaxError> {
|
||||
self.parse(reader)
|
||||
}
|
||||
@ -41,7 +38,7 @@ pub trait ArgumentType: Send + Sync {
|
||||
#[must_use]
|
||||
fn list_suggestions(
|
||||
&self,
|
||||
_context: &CommandContext,
|
||||
_context: &CommandContext<S>,
|
||||
_builder: SuggestionsBuilder,
|
||||
) -> Suggestions {
|
||||
Suggestions::empty()
|
||||
@ -73,11 +70,11 @@ pub trait ArgumentType: Send + Sync {
|
||||
// only for `AnyArgumentType`.
|
||||
mod sealed {
|
||||
/// Private trait to ensure only types implementing `ArgumentType` can implement `AnyArgumentType`.
|
||||
pub trait Sealed {}
|
||||
pub trait Sealed<S> {}
|
||||
}
|
||||
|
||||
/// Represents an argument type with any parsable type.
|
||||
pub trait AnyArgumentType: Sealed + Send + Sync {
|
||||
pub trait AnyArgumentType<S: CommandSource = DummySource>: sealed::Sealed<S> + Send + Sync {
|
||||
/// Parses a value by using a [`StringReader`]. Call this only if you have no source.
|
||||
///
|
||||
/// Errors should be propagated using the `?` operator, which will
|
||||
@ -91,17 +88,13 @@ pub trait AnyArgumentType: Sealed + Send + Sync {
|
||||
///
|
||||
/// Errors should be propagated using the `?` operator, which will
|
||||
/// replicate Brigadier's behavior of exceptions.
|
||||
fn parse_with_source(
|
||||
&self,
|
||||
reader: &mut StringReader,
|
||||
source: &CommandSource,
|
||||
) -> ParseWithSourceAnyResult;
|
||||
fn parse_with_source(&self, reader: &mut StringReader, source: &S) -> ParseWithSourceAnyResult;
|
||||
|
||||
/// Provides a list of suggestions from this argument type.
|
||||
#[must_use]
|
||||
fn list_suggestions(
|
||||
&self,
|
||||
context: &CommandContext,
|
||||
context: &CommandContext<S>,
|
||||
builder: SuggestionsBuilder,
|
||||
) -> Suggestions;
|
||||
|
||||
@ -129,9 +122,12 @@ pub trait AnyArgumentType: Sealed + Send + Sync {
|
||||
}
|
||||
|
||||
// Implement our private trait for all argument types.
|
||||
impl<U: ArgumentType<Item = T>, T: Send + Sync + 'static> Sealed for U {}
|
||||
impl<U: ArgumentType<S>, S: CommandSource> sealed::Sealed<S> for U {}
|
||||
|
||||
impl<U: ArgumentType<Item = T> + 'static, T: Send + Sync + 'static> AnyArgumentType for U {
|
||||
impl<U: ArgumentType<S> + 'static, S: CommandSource + 'static> AnyArgumentType<S> for U
|
||||
where
|
||||
U::Item: 'static,
|
||||
{
|
||||
fn parse(
|
||||
&self,
|
||||
reader: &mut StringReader,
|
||||
@ -142,11 +138,7 @@ impl<U: ArgumentType<Item = T> + 'static, T: Send + Sync + 'static> AnyArgumentT
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_with_source(
|
||||
&self,
|
||||
reader: &mut StringReader,
|
||||
source: &CommandSource,
|
||||
) -> ParseWithSourceAnyResult {
|
||||
fn parse_with_source(&self, reader: &mut StringReader, source: &S) -> ParseWithSourceAnyResult {
|
||||
match self.parse_with_source(reader, source) {
|
||||
Ok(value) => {
|
||||
let value: Box<dyn Any + Send + Sync> = Box::new(value);
|
||||
@ -158,7 +150,7 @@ impl<U: ArgumentType<Item = T> + 'static, T: Send + Sync + 'static> AnyArgumentT
|
||||
|
||||
fn list_suggestions(
|
||||
&self,
|
||||
context: &CommandContext,
|
||||
context: &CommandContext<S>,
|
||||
builder: SuggestionsBuilder,
|
||||
) -> Suggestions {
|
||||
self.list_suggestions(context, builder)
|
||||
@ -1,4 +1,4 @@
|
||||
use crate::command::{
|
||||
use crate::{
|
||||
argument_types::FromStringReader,
|
||||
argument_types::argument_type::{ArgumentType, JavaClientArgumentType},
|
||||
context::command_context::CommandContext,
|
||||
@ -20,7 +20,7 @@ pub const INVALID_ATTRIBUTE_ERROR_TYPE: CommandErrorType<1> = CommandErrorType::
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct AttributeArgumentType;
|
||||
|
||||
impl ArgumentType for AttributeArgumentType {
|
||||
impl<S: crate::source::CommandSource> ArgumentType<S> for AttributeArgumentType {
|
||||
type Item = Attributes;
|
||||
|
||||
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
|
||||
@ -46,7 +46,7 @@ impl ArgumentType for AttributeArgumentType {
|
||||
|
||||
fn list_suggestions(
|
||||
&self,
|
||||
_context: &CommandContext,
|
||||
_context: &CommandContext<S>,
|
||||
builder: SuggestionsBuilder,
|
||||
) -> Suggestions {
|
||||
builder
|
||||
@ -56,7 +56,10 @@ impl ArgumentType for AttributeArgumentType {
|
||||
}
|
||||
|
||||
impl AttributeArgumentType {
|
||||
pub fn get(context: &CommandContext, name: &str) -> Result<Attributes, CommandSyntaxError> {
|
||||
pub fn get<S: crate::source::CommandSource>(
|
||||
context: &CommandContext<S>,
|
||||
name: &str,
|
||||
) -> Result<Attributes, CommandSyntaxError> {
|
||||
context.get_argument::<Attributes>(name).cloned()
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
use crate::command::{
|
||||
use crate::{
|
||||
argument_types::argument_type::{ArgumentType, JavaClientArgumentType},
|
||||
context::command_context::CommandContext,
|
||||
errors::command_syntax_error::CommandSyntaxError,
|
||||
@ -17,7 +17,7 @@ pub const INVALID_BLOCK_ERROR_TYPE: CommandErrorType<1> = CommandErrorType::new(
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct BlockArgumentType;
|
||||
|
||||
impl ArgumentType for BlockArgumentType {
|
||||
impl<S: crate::source::CommandSource> ArgumentType<S> for BlockArgumentType {
|
||||
type Item = &'static Block;
|
||||
|
||||
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
|
||||
@ -46,7 +46,7 @@ impl ArgumentType for BlockArgumentType {
|
||||
|
||||
fn list_suggestions(
|
||||
&self,
|
||||
_context: &CommandContext,
|
||||
_context: &CommandContext<S>,
|
||||
builder: SuggestionsBuilder,
|
||||
) -> Suggestions {
|
||||
builder.build()
|
||||
@ -54,7 +54,10 @@ impl ArgumentType for BlockArgumentType {
|
||||
}
|
||||
|
||||
impl BlockArgumentType {
|
||||
pub fn get(context: &CommandContext, name: &str) -> Result<&'static Block, CommandSyntaxError> {
|
||||
pub fn get<S: crate::source::CommandSource>(
|
||||
context: &CommandContext<S>,
|
||||
name: &str,
|
||||
) -> Result<&'static Block, CommandSyntaxError> {
|
||||
context.get_argument::<&'static Block>(name).copied()
|
||||
}
|
||||
}
|
||||
@ -5,14 +5,14 @@ use pumpkin_data::{Block, translation};
|
||||
use pumpkin_nbt::compound::NbtCompound;
|
||||
use pumpkin_util::text::TextComponent;
|
||||
|
||||
use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
|
||||
use crate::command::argument_types::block::INVALID_BLOCK_ERROR_TYPE;
|
||||
use crate::command::argument_types::nbt::NbtCompoundArgumentType;
|
||||
use crate::command::context::command_context::CommandContext;
|
||||
use crate::command::errors::command_syntax_error::CommandSyntaxError;
|
||||
use crate::command::errors::error_types::CommandErrorType;
|
||||
use crate::command::string_reader::StringReader;
|
||||
use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
|
||||
use crate::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
|
||||
use crate::argument_types::block::INVALID_BLOCK_ERROR_TYPE;
|
||||
use crate::argument_types::nbt::NbtCompoundArgumentType;
|
||||
use crate::context::command_context::CommandContext;
|
||||
use crate::errors::command_syntax_error::CommandSyntaxError;
|
||||
use crate::errors::error_types::CommandErrorType;
|
||||
use crate::string_reader::StringReader;
|
||||
use crate::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
|
||||
|
||||
pub const ERROR_UNKNOWN_TAG: CommandErrorType<1> = CommandErrorType::new(
|
||||
translation::java::ARGUMENTS_BLOCK_TAG_UNKNOWN,
|
||||
@ -117,7 +117,8 @@ fn parse_properties(
|
||||
|
||||
fn parse_nbt(reader: &mut StringReader) -> Result<Option<NbtCompound>, CommandSyntaxError> {
|
||||
if reader.peek() == Some('{') {
|
||||
let tag = NbtCompoundArgumentType.parse(reader)?;
|
||||
let tag =
|
||||
ArgumentType::<crate::source::DummySource>::parse(&NbtCompoundArgumentType, reader)?;
|
||||
Ok(Some(tag))
|
||||
} else {
|
||||
Ok(None)
|
||||
@ -126,7 +127,7 @@ fn parse_nbt(reader: &mut StringReader) -> Result<Option<NbtCompound>, CommandSy
|
||||
|
||||
pub struct BlockPredicateArgumentType;
|
||||
|
||||
impl ArgumentType for BlockPredicateArgumentType {
|
||||
impl<S: crate::source::CommandSource> ArgumentType<S> for BlockPredicateArgumentType {
|
||||
type Item = BlockPredicate;
|
||||
|
||||
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
|
||||
@ -214,7 +215,7 @@ impl ArgumentType for BlockPredicateArgumentType {
|
||||
|
||||
fn list_suggestions(
|
||||
&self,
|
||||
_context: &CommandContext,
|
||||
_context: &CommandContext<S>,
|
||||
builder: SuggestionsBuilder,
|
||||
) -> Suggestions {
|
||||
builder.build()
|
||||
@ -222,7 +223,10 @@ impl ArgumentType for BlockPredicateArgumentType {
|
||||
}
|
||||
|
||||
impl BlockPredicateArgumentType {
|
||||
pub fn get(context: &CommandContext, name: &str) -> Result<BlockPredicate, CommandSyntaxError> {
|
||||
pub fn get<S: crate::source::CommandSource>(
|
||||
context: &CommandContext<S>,
|
||||
name: &str,
|
||||
) -> Result<BlockPredicate, CommandSyntaxError> {
|
||||
Ok(context.get_argument::<BlockPredicate>(name)?.clone())
|
||||
}
|
||||
}
|
||||
@ -1,12 +1,12 @@
|
||||
use pumpkin_data::translation;
|
||||
use pumpkin_util::text::TextComponent;
|
||||
|
||||
use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
|
||||
use crate::command::context::command_context::CommandContext;
|
||||
use crate::command::errors::command_syntax_error::CommandSyntaxError;
|
||||
use crate::command::errors::error_types::CommandErrorType;
|
||||
use crate::command::string_reader::StringReader;
|
||||
use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
|
||||
use crate::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
|
||||
use crate::context::command_context::CommandContext;
|
||||
use crate::errors::command_syntax_error::CommandSyntaxError;
|
||||
use crate::errors::error_types::CommandErrorType;
|
||||
use crate::string_reader::StringReader;
|
||||
use crate::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
|
||||
|
||||
pub const ERROR_INVALID_JSON: CommandErrorType<1> = CommandErrorType::new(
|
||||
translation::java::ARGUMENT_COMPONENT_INVALID,
|
||||
@ -16,7 +16,7 @@ pub const ERROR_INVALID_JSON: CommandErrorType<1> = CommandErrorType::new(
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct ComponentArgumentType;
|
||||
|
||||
impl ArgumentType for ComponentArgumentType {
|
||||
impl<S: crate::source::CommandSource> ArgumentType<S> for ComponentArgumentType {
|
||||
type Item = TextComponent;
|
||||
|
||||
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
|
||||
@ -40,7 +40,7 @@ impl ArgumentType for ComponentArgumentType {
|
||||
|
||||
fn list_suggestions(
|
||||
&self,
|
||||
_context: &CommandContext,
|
||||
_context: &CommandContext<S>,
|
||||
builder: SuggestionsBuilder,
|
||||
) -> Suggestions {
|
||||
builder.build()
|
||||
@ -48,7 +48,10 @@ impl ArgumentType for ComponentArgumentType {
|
||||
}
|
||||
|
||||
impl ComponentArgumentType {
|
||||
pub fn get(context: &CommandContext, name: &str) -> Result<TextComponent, CommandSyntaxError> {
|
||||
pub fn get<S: crate::source::CommandSource>(
|
||||
context: &CommandContext<S>,
|
||||
name: &str,
|
||||
) -> Result<TextComponent, CommandSyntaxError> {
|
||||
context.get_argument::<TextComponent>(name).cloned()
|
||||
}
|
||||
}
|
||||
@ -1,12 +1,12 @@
|
||||
use pumpkin_data::translation;
|
||||
use pumpkin_util::math::wrap_degrees;
|
||||
|
||||
use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
|
||||
use crate::command::context::command_context::CommandContext;
|
||||
use crate::command::context::command_source::CommandSource;
|
||||
use crate::command::errors::command_syntax_error::CommandSyntaxError;
|
||||
use crate::command::errors::error_types::CommandErrorType;
|
||||
use crate::command::string_reader::StringReader;
|
||||
use crate::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
|
||||
use crate::context::command_context::CommandContext;
|
||||
use crate::errors::command_syntax_error::CommandSyntaxError;
|
||||
use crate::errors::error_types::CommandErrorType;
|
||||
use crate::source::CommandSource;
|
||||
use crate::string_reader::StringReader;
|
||||
|
||||
pub const ERROR_NOT_COMPLETE: CommandErrorType<0> = CommandErrorType::new(
|
||||
translation::java::ARGUMENT_ANGLE_INCOMPLETE,
|
||||
@ -31,9 +31,9 @@ impl Angle {
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn get_angle(&self, source: &CommandSource) -> f32 {
|
||||
pub fn get_angle(&self, source: &impl CommandSource) -> f32 {
|
||||
let base = if self.is_relative {
|
||||
source.rotation.y
|
||||
source.rotation().y
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
@ -43,7 +43,7 @@ impl Angle {
|
||||
|
||||
pub struct AngleArgumentType;
|
||||
|
||||
impl ArgumentType for AngleArgumentType {
|
||||
impl<S: crate::source::CommandSource> ArgumentType<S> for AngleArgumentType {
|
||||
type Item = Angle;
|
||||
|
||||
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
|
||||
@ -82,7 +82,10 @@ impl ArgumentType for AngleArgumentType {
|
||||
}
|
||||
|
||||
impl AngleArgumentType {
|
||||
pub fn get(context: &CommandContext, name: &str) -> Result<Angle, CommandSyntaxError> {
|
||||
pub fn get<S: crate::source::CommandSource>(
|
||||
context: &CommandContext<S>,
|
||||
name: &str,
|
||||
) -> Result<Angle, CommandSyntaxError> {
|
||||
context.get_argument::<Angle>(name).copied()
|
||||
}
|
||||
}
|
||||
@ -1,11 +1,10 @@
|
||||
use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
|
||||
use crate::command::argument_types::coordinates::Coordinates;
|
||||
use crate::command::context::command_context::CommandContext;
|
||||
use crate::command::errors::command_syntax_error::CommandSyntaxError;
|
||||
use crate::command::errors::error_types::CommandErrorType;
|
||||
use crate::command::string_reader::StringReader;
|
||||
use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder, TextCoordinates};
|
||||
use crate::world::World;
|
||||
use crate::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
|
||||
use crate::argument_types::coordinates::Coordinates;
|
||||
use crate::context::command_context::CommandContext;
|
||||
use crate::errors::command_syntax_error::CommandSyntaxError;
|
||||
use crate::errors::error_types::CommandErrorType;
|
||||
use crate::string_reader::StringReader;
|
||||
use crate::suggestion::suggestions::{Suggestions, SuggestionsBuilder, TextCoordinates};
|
||||
use pumpkin_data::translation;
|
||||
use pumpkin_util::math::position::BlockPos;
|
||||
|
||||
@ -35,7 +34,7 @@ pub const OUT_OF_BOUNDS_ERROR_TYPE: CommandErrorType<0> = CommandErrorType::new(
|
||||
/// a player can spawn.
|
||||
pub struct BlockPosArgumentType;
|
||||
|
||||
impl ArgumentType for BlockPosArgumentType {
|
||||
impl<S: crate::source::CommandSource> ArgumentType<S> for BlockPosArgumentType {
|
||||
type Item = Coordinates;
|
||||
|
||||
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
|
||||
@ -56,7 +55,7 @@ impl ArgumentType for BlockPosArgumentType {
|
||||
|
||||
fn list_suggestions(
|
||||
&self,
|
||||
_context: &CommandContext,
|
||||
_context: &CommandContext<S>,
|
||||
builder: SuggestionsBuilder,
|
||||
) -> Suggestions {
|
||||
let remainder = builder.remaining();
|
||||
@ -68,7 +67,7 @@ impl ArgumentType for BlockPosArgumentType {
|
||||
};
|
||||
|
||||
builder.suggest_3d_coordinates(suggested_coordinates, |value| {
|
||||
self.parse(&mut StringReader::new(value)).is_ok()
|
||||
ArgumentType::<S>::parse(self, &mut StringReader::new(value)).is_ok()
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -83,8 +82,8 @@ impl BlockPosArgumentType {
|
||||
/// # Returns
|
||||
/// The loaded `BlockPos` containing the position represented by the parsed argument, wrapped in an `Ok`,
|
||||
/// or an `Err` with the appropriate [`CommandSyntaxError`] if it could not be resolved.
|
||||
pub fn get_block_pos(
|
||||
context: &CommandContext,
|
||||
pub fn get_block_pos<S: crate::source::CommandSource>(
|
||||
context: &CommandContext<S>,
|
||||
name: &str,
|
||||
) -> Result<BlockPos, CommandSyntaxError> {
|
||||
Ok(BlockPos::floored_v(
|
||||
@ -104,41 +103,13 @@ impl BlockPosArgumentType {
|
||||
/// # Returns
|
||||
/// The loaded `BlockPos` containing the position represented by the parsed argument, wrapped in an `Ok`,
|
||||
/// or an `Err` with the appropriate [`CommandSyntaxError`] if it could not be resolved.
|
||||
pub fn get_loaded_block_pos(
|
||||
context: &CommandContext,
|
||||
pub fn get_loaded_block_pos<S: crate::source::CommandSource>(
|
||||
context: &CommandContext<S>,
|
||||
name: &str,
|
||||
) -> Result<BlockPos, CommandSyntaxError> {
|
||||
Self::get_loaded_block_pos_in_world(context, name, context.source.world())
|
||||
}
|
||||
|
||||
/// Returns a [`CommandContext`]'s parsed coordinate argument in the form of a loaded [`BlockPos`]
|
||||
/// in the provided world.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `context` - The [`CommandContext`] that has the parsed `Coordinates` with the provided argument name.
|
||||
/// * `name` - The name of the argument that was parsed.
|
||||
/// * `world` - The world to check the load status in.
|
||||
///
|
||||
/// # Returns
|
||||
/// The loaded `BlockPos` containing the position represented by the parsed argument, wrapped in an `Ok`,
|
||||
/// or an `Err` with the appropriate [`CommandSyntaxError`] if it could not be resolved.
|
||||
pub fn get_loaded_block_pos_in_world(
|
||||
context: &CommandContext,
|
||||
name: &str,
|
||||
world: &World,
|
||||
) -> Result<BlockPos, CommandSyntaxError> {
|
||||
let pos = Self::get_block_pos(context, name)?;
|
||||
if world
|
||||
.level
|
||||
.read_chunk_sync(&pos.chunk_position(), |_| ())
|
||||
.is_none()
|
||||
{
|
||||
Err(NOT_LOADED_ERROR_TYPE.create_without_context())
|
||||
} else if !world.is_in_build_limit(pos) {
|
||||
Err(OUT_OF_WORLD_ERROR_TYPE.create_without_context())
|
||||
} else {
|
||||
Ok(pos)
|
||||
}
|
||||
context.source.check_block_loaded(&pos)?;
|
||||
Ok(pos)
|
||||
}
|
||||
|
||||
/// Returns a [`CommandContext`]'s parsed coordinate argument in the form of a [`BlockPos`]
|
||||
@ -151,15 +122,22 @@ impl BlockPosArgumentType {
|
||||
/// # Returns
|
||||
/// The loaded `BlockPos` containing the position represented by the parsed argument, wrapped in an `Ok`,
|
||||
/// or an `Err` with the appropriate [`CommandSyntaxError`] if it could not be resolved.
|
||||
pub fn get_spawnable_pos(
|
||||
context: &CommandContext,
|
||||
pub fn get_spawnable_pos<S: crate::source::CommandSource>(
|
||||
context: &CommandContext<S>,
|
||||
name: &str,
|
||||
) -> Result<BlockPos, CommandSyntaxError> {
|
||||
let pos = Self::get_block_pos(context, name)?;
|
||||
if World::is_valid(pos) {
|
||||
if is_valid_block_pos(pos) {
|
||||
Ok(pos)
|
||||
} else {
|
||||
Err(OUT_OF_BOUNDS_ERROR_TYPE.create_without_context())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn is_valid_block_pos(dest: BlockPos) -> bool {
|
||||
(-30_000_000..30_000_000).contains(&dest.0.x)
|
||||
&& (-30_000_000..30_000_000).contains(&dest.0.z)
|
||||
&& (-20_000_000..20_000_000).contains(&dest.0.y)
|
||||
}
|
||||
@ -3,8 +3,8 @@ use pumpkin_util::math::position::{BlockPos, ColumnPos};
|
||||
use pumpkin_util::math::vector2::Vector2;
|
||||
use pumpkin_util::math::vector3::Vector3;
|
||||
|
||||
use crate::command::suggestion::suggestions::TextCoordinates;
|
||||
use crate::command::{
|
||||
use crate::suggestion::suggestions::TextCoordinates;
|
||||
use crate::{
|
||||
argument_types::{
|
||||
argument_type::{ArgumentType, JavaClientArgumentType},
|
||||
coordinates::{Coordinates, WorldCoordinate},
|
||||
@ -22,7 +22,7 @@ pub const INCOMPLETE_ERROR_TYPE: CommandErrorType<0> = CommandErrorType::new(
|
||||
|
||||
pub struct ColumnPosArgumentType;
|
||||
|
||||
impl ArgumentType for ColumnPosArgumentType {
|
||||
impl<S: crate::source::CommandSource> ArgumentType<S> for ColumnPosArgumentType {
|
||||
type Item = Coordinates;
|
||||
|
||||
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
|
||||
@ -48,7 +48,7 @@ impl ArgumentType for ColumnPosArgumentType {
|
||||
|
||||
fn list_suggestions(
|
||||
&self,
|
||||
_context: &CommandContext,
|
||||
_context: &CommandContext<S>,
|
||||
builder: SuggestionsBuilder,
|
||||
) -> Suggestions {
|
||||
let remainder = builder.remaining();
|
||||
@ -60,7 +60,7 @@ impl ArgumentType for ColumnPosArgumentType {
|
||||
};
|
||||
|
||||
builder.suggest_2d_coordinates(suggested_coordinates, |value| {
|
||||
self.parse(&mut StringReader::new(value)).is_ok()
|
||||
ArgumentType::<S>::parse(self, &mut StringReader::new(value)).is_ok()
|
||||
})
|
||||
}
|
||||
|
||||
@ -83,8 +83,8 @@ impl ColumnPosArgumentType {
|
||||
/// # Returns
|
||||
/// The `ColumnPos` containing the position represented by the parsed argument, wrapped in an `Ok`,
|
||||
/// or an `Err` with the appropriate [`CommandSyntaxError`] if it could not be resolved.
|
||||
pub fn get_column_pos(
|
||||
context: &CommandContext,
|
||||
pub fn get_column_pos<S: crate::source::CommandSource>(
|
||||
context: &CommandContext<S>,
|
||||
name: &str,
|
||||
) -> Result<ColumnPos, CommandSyntaxError> {
|
||||
let block_pos = BlockPos::floored_v(
|
||||
@ -1,9 +1,7 @@
|
||||
use crate::command::context::command_source::CommandSource;
|
||||
use crate::command::errors::command_syntax_error::CommandSyntaxError;
|
||||
use crate::command::errors::error_types::{
|
||||
CommandErrorType, READER_EXPECTED_DOUBLE, READER_EXPECTED_INT,
|
||||
};
|
||||
use crate::command::string_reader::StringReader;
|
||||
use crate::errors::command_syntax_error::CommandSyntaxError;
|
||||
use crate::errors::error_types::{CommandErrorType, READER_EXPECTED_DOUBLE, READER_EXPECTED_INT};
|
||||
use crate::source::CommandSource;
|
||||
use crate::string_reader::StringReader;
|
||||
use pumpkin_data::translation;
|
||||
use pumpkin_util::math::vector2::Vector2;
|
||||
use pumpkin_util::math::vector3::{Axis, Vector3};
|
||||
@ -179,10 +177,10 @@ impl Coordinates {
|
||||
|
||||
/// Returns the physical position that these [`Coordinates`] represent.
|
||||
#[must_use]
|
||||
pub fn resolve(&self, source: &CommandSource) -> Vector3<f64> {
|
||||
pub fn resolve(&self, source: &impl CommandSource) -> Vector3<f64> {
|
||||
match self {
|
||||
Self::World(vector) => {
|
||||
let pos = source.position;
|
||||
let pos = source.position();
|
||||
Vector3::new(
|
||||
vector.x.resolve(pos.x),
|
||||
vector.y.resolve(pos.y),
|
||||
@ -190,8 +188,8 @@ impl Coordinates {
|
||||
)
|
||||
}
|
||||
Self::Local { left, up, forward } => {
|
||||
let start = source.entity_anchor.position_at_source(source);
|
||||
convert_local_coordinates(*left, *up, *forward, source.rotation).add(&start)
|
||||
let start = source.entity_anchor().position_at_source(source);
|
||||
convert_local_coordinates(*left, *up, *forward, source.rotation()).add(&start)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -201,10 +199,10 @@ impl Coordinates {
|
||||
/// - The returned *x*-component of the vector is the **pitch**.
|
||||
/// - The returned *y*-component of the vector is the **yaw**.
|
||||
#[must_use]
|
||||
pub const fn rotation(&self, source: &CommandSource) -> Vector2<f32> {
|
||||
pub fn rotation(&self, source: &impl CommandSource) -> Vector2<f32> {
|
||||
match self {
|
||||
Self::World(coords) => {
|
||||
let rotation = source.rotation;
|
||||
let rotation = source.rotation();
|
||||
Vector2::new(
|
||||
coords.x.resolve(rotation.x as f64) as f32,
|
||||
coords.y.resolve(rotation.y as f64) as f32,
|
||||
@ -1,9 +1,9 @@
|
||||
use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
|
||||
use crate::command::argument_types::coordinates::{Coordinates, WorldCoordinate};
|
||||
use crate::command::context::command_context::CommandContext;
|
||||
use crate::command::errors::command_syntax_error::CommandSyntaxError;
|
||||
use crate::command::errors::error_types::CommandErrorType;
|
||||
use crate::command::string_reader::StringReader;
|
||||
use crate::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
|
||||
use crate::argument_types::coordinates::{Coordinates, WorldCoordinate};
|
||||
use crate::context::command_context::CommandContext;
|
||||
use crate::errors::command_syntax_error::CommandSyntaxError;
|
||||
use crate::errors::error_types::CommandErrorType;
|
||||
use crate::string_reader::StringReader;
|
||||
use pumpkin_data::translation;
|
||||
use pumpkin_util::math::vector3::Vector3;
|
||||
|
||||
@ -14,7 +14,7 @@ pub const NOT_COMPLETE_ERROR_TYPE: CommandErrorType<0> = CommandErrorType::new(
|
||||
|
||||
pub struct RotationArgumentType;
|
||||
|
||||
impl ArgumentType for RotationArgumentType {
|
||||
impl<S: crate::source::CommandSource> ArgumentType<S> for RotationArgumentType {
|
||||
type Item = Coordinates;
|
||||
|
||||
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
|
||||
@ -60,7 +60,10 @@ impl RotationArgumentType {
|
||||
/// - The returned *x*-coordinate of the coordinates is the absolute/relative **pitch**.
|
||||
/// - The returned *y*-coordinate of the coordinates is the absolute/relative **yaw**.
|
||||
/// - The returned *z*-coordinate of the coordinates is always 0.
|
||||
pub fn get(context: &CommandContext, name: &str) -> Result<Coordinates, CommandSyntaxError> {
|
||||
pub fn get<S: crate::source::CommandSource>(
|
||||
context: &CommandContext<S>,
|
||||
name: &str,
|
||||
) -> Result<Coordinates, CommandSyntaxError> {
|
||||
context.get_argument::<Coordinates>(name).copied()
|
||||
}
|
||||
}
|
||||
@ -1,11 +1,11 @@
|
||||
use pumpkin_data::translation;
|
||||
use pumpkin_util::math::vector3::Vector3;
|
||||
|
||||
use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
|
||||
use crate::command::context::command_context::CommandContext;
|
||||
use crate::command::errors::command_syntax_error::CommandSyntaxError;
|
||||
use crate::command::errors::error_types::CommandErrorType;
|
||||
use crate::command::string_reader::StringReader;
|
||||
use crate::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
|
||||
use crate::context::command_context::CommandContext;
|
||||
use crate::errors::command_syntax_error::CommandSyntaxError;
|
||||
use crate::errors::error_types::CommandErrorType;
|
||||
use crate::string_reader::StringReader;
|
||||
|
||||
pub const ERROR_INVALID_SWIZZLE: CommandErrorType<0> = CommandErrorType::new(
|
||||
translation::java::ARGUMENTS_SWIZZLE_INVALID,
|
||||
@ -32,7 +32,7 @@ impl Swizzle {
|
||||
|
||||
pub struct SwizzleArgumentType;
|
||||
|
||||
impl ArgumentType for SwizzleArgumentType {
|
||||
impl<S: crate::source::CommandSource> ArgumentType<S> for SwizzleArgumentType {
|
||||
type Item = Swizzle;
|
||||
|
||||
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
|
||||
@ -82,7 +82,10 @@ impl ArgumentType for SwizzleArgumentType {
|
||||
}
|
||||
|
||||
impl SwizzleArgumentType {
|
||||
pub fn get(context: &CommandContext, name: &str) -> Result<Swizzle, CommandSyntaxError> {
|
||||
pub fn get<S: crate::source::CommandSource>(
|
||||
context: &CommandContext<S>,
|
||||
name: &str,
|
||||
) -> Result<Swizzle, CommandSyntaxError> {
|
||||
Ok(*context.get_argument::<Swizzle>(name)?)
|
||||
}
|
||||
}
|
||||
@ -1,10 +1,10 @@
|
||||
use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
|
||||
use crate::command::argument_types::coordinates::{Coordinates, WorldCoordinate};
|
||||
use crate::command::context::command_context::CommandContext;
|
||||
use crate::command::errors::command_syntax_error::CommandSyntaxError;
|
||||
use crate::command::errors::error_types::CommandErrorType;
|
||||
use crate::command::string_reader::StringReader;
|
||||
use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder, TextCoordinates};
|
||||
use crate::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
|
||||
use crate::argument_types::coordinates::{Coordinates, WorldCoordinate};
|
||||
use crate::context::command_context::CommandContext;
|
||||
use crate::errors::command_syntax_error::CommandSyntaxError;
|
||||
use crate::errors::error_types::CommandErrorType;
|
||||
use crate::string_reader::StringReader;
|
||||
use crate::suggestion::suggestions::{Suggestions, SuggestionsBuilder, TextCoordinates};
|
||||
use pumpkin_data::translation;
|
||||
use pumpkin_util::math::vector2::Vector2;
|
||||
use pumpkin_util::math::vector3::Vector3;
|
||||
@ -37,7 +37,7 @@ impl Vec2ArgumentType {
|
||||
}
|
||||
}
|
||||
|
||||
impl ArgumentType for Vec2ArgumentType {
|
||||
impl<S: crate::source::CommandSource> ArgumentType<S> for Vec2ArgumentType {
|
||||
type Item = Coordinates;
|
||||
|
||||
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
|
||||
@ -76,7 +76,7 @@ impl ArgumentType for Vec2ArgumentType {
|
||||
|
||||
fn list_suggestions(
|
||||
&self,
|
||||
_context: &CommandContext,
|
||||
_context: &CommandContext<S>,
|
||||
builder: SuggestionsBuilder,
|
||||
) -> Suggestions {
|
||||
let remainder = builder.remaining();
|
||||
@ -88,21 +88,21 @@ impl ArgumentType for Vec2ArgumentType {
|
||||
};
|
||||
|
||||
builder.suggest_2d_coordinates(suggested_coordinates, |value| {
|
||||
self.parse(&mut StringReader::new(value)).is_ok()
|
||||
ArgumentType::<S>::parse(self, &mut StringReader::new(value)).is_ok()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Vec2ArgumentType {
|
||||
pub fn get_coordinates(
|
||||
context: &CommandContext,
|
||||
pub fn get_coordinates<S: crate::source::CommandSource>(
|
||||
context: &CommandContext<S>,
|
||||
name: &str,
|
||||
) -> Result<Coordinates, CommandSyntaxError> {
|
||||
Ok(*context.get_argument(name)?)
|
||||
}
|
||||
|
||||
pub fn get_vector2(
|
||||
context: &CommandContext,
|
||||
pub fn get_vector2<S: crate::source::CommandSource>(
|
||||
context: &CommandContext<S>,
|
||||
name: &str,
|
||||
) -> Result<Vector2<f64>, CommandSyntaxError> {
|
||||
let resolved = Self::get_coordinates(context, name)?.resolve(context.source.as_ref());
|
||||
@ -1,10 +1,10 @@
|
||||
use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
|
||||
use crate::command::argument_types::coordinates::{Coordinates, MIXED_TYPE_ERROR_TYPE};
|
||||
use crate::command::context::command_context::CommandContext;
|
||||
use crate::command::errors::command_syntax_error::CommandSyntaxError;
|
||||
use crate::command::errors::error_types::CommandErrorType;
|
||||
use crate::command::string_reader::StringReader;
|
||||
use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder, TextCoordinates};
|
||||
use crate::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
|
||||
use crate::argument_types::coordinates::{Coordinates, MIXED_TYPE_ERROR_TYPE};
|
||||
use crate::context::command_context::CommandContext;
|
||||
use crate::errors::command_syntax_error::CommandSyntaxError;
|
||||
use crate::errors::error_types::CommandErrorType;
|
||||
use crate::string_reader::StringReader;
|
||||
use crate::suggestion::suggestions::{Suggestions, SuggestionsBuilder, TextCoordinates};
|
||||
use pumpkin_data::translation;
|
||||
use pumpkin_util::math::vector3::Vector3;
|
||||
|
||||
@ -51,7 +51,7 @@ impl Vec3ArgumentType {
|
||||
}
|
||||
}
|
||||
|
||||
impl ArgumentType for Vec3ArgumentType {
|
||||
impl<S: crate::source::CommandSource> ArgumentType<S> for Vec3ArgumentType {
|
||||
type Item = Coordinates;
|
||||
|
||||
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
|
||||
@ -79,7 +79,7 @@ impl ArgumentType for Vec3ArgumentType {
|
||||
|
||||
fn list_suggestions(
|
||||
&self,
|
||||
_context: &CommandContext,
|
||||
_context: &CommandContext<S>,
|
||||
builder: SuggestionsBuilder,
|
||||
) -> Suggestions {
|
||||
let remainder = builder.remaining();
|
||||
@ -91,23 +91,23 @@ impl ArgumentType for Vec3ArgumentType {
|
||||
};
|
||||
|
||||
builder.suggest_3d_coordinates(suggestioned_coordinates, |value| {
|
||||
self.parse(&mut StringReader::new(value)).is_ok()
|
||||
ArgumentType::<S>::parse(self, &mut StringReader::new(value)).is_ok()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Vec3ArgumentType {
|
||||
/// Returns a [`CommandContext`]'s parsed three-dimensional vector as a set of [`Coordinates`].
|
||||
pub fn get_coordinates(
|
||||
context: &CommandContext,
|
||||
pub fn get_coordinates<S: crate::source::CommandSource>(
|
||||
context: &CommandContext<S>,
|
||||
name: &str,
|
||||
) -> Result<Coordinates, CommandSyntaxError> {
|
||||
Ok(*context.get_argument(name)?)
|
||||
}
|
||||
|
||||
/// Returns a [`CommandContext`]'s parsed three-dimensional vector and resolves it to a [`Vector3`].
|
||||
pub fn get_vector3(
|
||||
context: &CommandContext,
|
||||
pub fn get_vector3<S: crate::source::CommandSource>(
|
||||
context: &CommandContext<S>,
|
||||
name: &str,
|
||||
) -> Result<Vector3<f64>, CommandSyntaxError> {
|
||||
Ok(Self::get_coordinates(context, name)?.resolve(context.source.as_ref()))
|
||||
@ -1,4 +1,4 @@
|
||||
use crate::command::{
|
||||
use crate::{
|
||||
argument_types::argument_type::{ArgumentType, JavaClientArgumentType},
|
||||
errors::command_syntax_error::CommandSyntaxError,
|
||||
string_reader::StringReader,
|
||||
@ -8,7 +8,7 @@ use crate::command::{
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct BoolArgumentType;
|
||||
|
||||
impl ArgumentType for BoolArgumentType {
|
||||
impl<S: crate::source::CommandSource> ArgumentType<S> for BoolArgumentType {
|
||||
type Item = bool;
|
||||
|
||||
fn parse(&self, reader: &mut StringReader) -> Result<bool, CommandSyntaxError> {
|
||||
@ -28,9 +28,8 @@ impl_copy_get!(BoolArgumentType, bool);
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::command::{
|
||||
argument_types::{argument_type::ArgumentType, core::bool::BoolArgumentType},
|
||||
errors::error_types,
|
||||
use crate::{
|
||||
argument_types::core::bool::BoolArgumentType, errors::error_types,
|
||||
string_reader::StringReader,
|
||||
};
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
use crate::command::{
|
||||
use crate::{
|
||||
argument_types::{
|
||||
argument_type::{ArgumentType, JavaClientArgumentType},
|
||||
core::within_or_err,
|
||||
@ -14,7 +14,7 @@ pub struct DoubleArgumentType {
|
||||
pub max: f64,
|
||||
}
|
||||
|
||||
impl ArgumentType for DoubleArgumentType {
|
||||
impl<S: crate::source::CommandSource> ArgumentType<S> for DoubleArgumentType {
|
||||
type Item = f64;
|
||||
|
||||
fn parse(&self, reader: &mut StringReader) -> Result<f64, CommandSyntaxError> {
|
||||
@ -76,9 +76,8 @@ impl DoubleArgumentType {
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::command::{
|
||||
argument_types::{argument_type::ArgumentType, core::double::DoubleArgumentType},
|
||||
errors::error_types,
|
||||
use crate::{
|
||||
argument_types::core::double::DoubleArgumentType, errors::error_types,
|
||||
string_reader::StringReader,
|
||||
};
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
use crate::command::{
|
||||
use crate::{
|
||||
argument_types::{
|
||||
argument_type::{ArgumentType, JavaClientArgumentType},
|
||||
core::within_or_err,
|
||||
@ -14,7 +14,7 @@ pub struct FloatArgumentType {
|
||||
pub max: f32,
|
||||
}
|
||||
|
||||
impl ArgumentType for FloatArgumentType {
|
||||
impl<S: crate::source::CommandSource> ArgumentType<S> for FloatArgumentType {
|
||||
type Item = f32;
|
||||
|
||||
fn parse(&self, reader: &mut StringReader) -> Result<f32, CommandSyntaxError> {
|
||||
@ -76,9 +76,8 @@ impl FloatArgumentType {
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::command::{
|
||||
argument_types::{argument_type::ArgumentType, core::float::FloatArgumentType},
|
||||
errors::error_types,
|
||||
use crate::{
|
||||
argument_types::core::float::FloatArgumentType, errors::error_types,
|
||||
string_reader::StringReader,
|
||||
};
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
use crate::command::{
|
||||
use crate::{
|
||||
argument_types::{
|
||||
argument_type::{ArgumentType, JavaClientArgumentType},
|
||||
core::within_or_err,
|
||||
@ -14,7 +14,7 @@ pub struct IntegerArgumentType {
|
||||
pub max: i32,
|
||||
}
|
||||
|
||||
impl ArgumentType for IntegerArgumentType {
|
||||
impl<S: crate::source::CommandSource> ArgumentType<S> for IntegerArgumentType {
|
||||
type Item = i32;
|
||||
|
||||
fn parse(&self, reader: &mut StringReader) -> Result<i32, CommandSyntaxError> {
|
||||
@ -76,9 +76,8 @@ impl IntegerArgumentType {
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::command::{
|
||||
argument_types::{argument_type::ArgumentType, core::integer::IntegerArgumentType},
|
||||
errors::error_types,
|
||||
use crate::{
|
||||
argument_types::core::integer::IntegerArgumentType, errors::error_types,
|
||||
string_reader::StringReader,
|
||||
};
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
use crate::command::{
|
||||
use crate::{
|
||||
argument_types::{
|
||||
argument_type::{ArgumentType, JavaClientArgumentType},
|
||||
core::within_or_err,
|
||||
@ -14,7 +14,7 @@ pub struct LongArgumentType {
|
||||
pub max: i64,
|
||||
}
|
||||
|
||||
impl ArgumentType for LongArgumentType {
|
||||
impl<S: crate::source::CommandSource> ArgumentType<S> for LongArgumentType {
|
||||
type Item = i64;
|
||||
|
||||
fn parse(&self, reader: &mut StringReader) -> Result<i64, CommandSyntaxError> {
|
||||
@ -76,9 +76,8 @@ impl LongArgumentType {
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::command::{
|
||||
argument_types::{argument_type::ArgumentType, core::long::LongArgumentType},
|
||||
errors::error_types,
|
||||
use crate::{
|
||||
argument_types::core::long::LongArgumentType, errors::error_types,
|
||||
string_reader::StringReader,
|
||||
};
|
||||
|
||||
@ -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,
|
||||
};
|
||||
@ -1,7 +1,7 @@
|
||||
use pumpkin_protocol::java::client::play::StringProtoArgBehavior;
|
||||
|
||||
use crate::command::context::command_context::CommandContext;
|
||||
use crate::command::{
|
||||
use crate::context::command_context::CommandContext;
|
||||
use crate::{
|
||||
argument_types::argument_type::{ArgumentType, JavaClientArgumentType},
|
||||
errors::command_syntax_error::CommandSyntaxError,
|
||||
string_reader::StringReader,
|
||||
@ -18,7 +18,7 @@ pub enum StringArgumentType {
|
||||
GreedyPhrase,
|
||||
}
|
||||
|
||||
impl ArgumentType for StringArgumentType {
|
||||
impl<S: crate::source::CommandSource> ArgumentType<S> for StringArgumentType {
|
||||
type Item = String;
|
||||
|
||||
fn parse(&self, reader: &mut StringReader) -> Result<String, CommandSyntaxError> {
|
||||
@ -52,16 +52,18 @@ impl ArgumentType for StringArgumentType {
|
||||
|
||||
impl StringArgumentType {
|
||||
/// Returns a [`CommandContext`]'s parsed `String` argument as a string slice.
|
||||
pub fn get<'a>(context: &'a CommandContext, name: &str) -> Result<&'a str, CommandSyntaxError> {
|
||||
pub fn get<'a, S: crate::source::CommandSource>(
|
||||
context: &'a CommandContext<S>,
|
||||
name: &str,
|
||||
) -> Result<&'a str, CommandSyntaxError> {
|
||||
Ok(context.get_argument::<String>(name)?.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::command::{
|
||||
argument_types::{argument_type::ArgumentType, core::string::StringArgumentType},
|
||||
errors::error_types,
|
||||
use crate::{
|
||||
argument_types::core::string::StringArgumentType, errors::error_types,
|
||||
string_reader::StringReader,
|
||||
};
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
use pumpkin_nbt::compound::NbtCompound;
|
||||
use pumpkin_util::identifier::Identifier;
|
||||
|
||||
use crate::command::{
|
||||
use crate::{
|
||||
argument_types::{
|
||||
FromStringReader,
|
||||
argument_type::{ArgumentType, JavaClientArgumentType},
|
||||
@ -20,12 +20,12 @@ pub enum DialogArg {
|
||||
|
||||
pub struct DialogArgumentType;
|
||||
|
||||
impl ArgumentType for DialogArgumentType {
|
||||
impl<S: crate::source::CommandSource> ArgumentType<S> for DialogArgumentType {
|
||||
type Item = DialogArg;
|
||||
|
||||
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
|
||||
if reader.peek() == Some('{') {
|
||||
let compound = NbtCompoundArgumentType.parse(reader)?;
|
||||
let compound = ArgumentType::<S>::parse(&NbtCompoundArgumentType, reader)?;
|
||||
Ok(DialogArg::Nbt(compound))
|
||||
} else {
|
||||
let id = Identifier::from_reader(reader)?;
|
||||
@ -48,8 +48,8 @@ impl ArgumentType for DialogArgumentType {
|
||||
|
||||
impl DialogArgumentType {
|
||||
/// Returns the parsed [`DialogArg`] from the name of the argument.
|
||||
pub fn get<'a>(
|
||||
context: &'a CommandContext,
|
||||
pub fn get<'a, S: crate::source::CommandSource>(
|
||||
context: &'a CommandContext<S>,
|
||||
name: &'_ str,
|
||||
) -> Result<&'a DialogArg, CommandSyntaxError> {
|
||||
context.get_argument(name)
|
||||
85
crates/pumpkin-command/src/argument_types/entity_anchor.rs
Normal file
85
crates/pumpkin-command/src/argument_types/entity_anchor.rs
Normal file
@ -0,0 +1,85 @@
|
||||
use crate::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
|
||||
use crate::context::command_context::CommandContext;
|
||||
use crate::errors::command_syntax_error::CommandSyntaxError;
|
||||
use crate::errors::error_types::CommandErrorType;
|
||||
use crate::source::CommandSource;
|
||||
use crate::string_reader::StringReader;
|
||||
use crate::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
|
||||
use pumpkin_data::translation;
|
||||
use pumpkin_util::math::vector3::Vector3;
|
||||
use pumpkin_util::text::TextComponent;
|
||||
|
||||
pub const INVALID_ERROR_TYPE: CommandErrorType<1> = CommandErrorType::new(
|
||||
translation::java::ARGUMENT_ANCHOR_INVALID,
|
||||
translation::java::ARGUMENT_ANCHOR_INVALID,
|
||||
);
|
||||
|
||||
pub struct EntityAnchorArgumentType;
|
||||
|
||||
impl<S: crate::source::CommandSource> ArgumentType<S> for EntityAnchorArgumentType {
|
||||
type Item = EntityAnchor;
|
||||
|
||||
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
|
||||
let i = reader.cursor();
|
||||
let anchor = reader.read_unquoted_string();
|
||||
EntityAnchor::from_id(anchor.as_str()).map_or_else(
|
||||
|| {
|
||||
reader.set_cursor(i);
|
||||
Err(INVALID_ERROR_TYPE.create(reader, TextComponent::text(anchor)))
|
||||
},
|
||||
Ok,
|
||||
)
|
||||
}
|
||||
|
||||
fn client_side_parser(&'_ self) -> JavaClientArgumentType {
|
||||
JavaClientArgumentType::EntityAnchor
|
||||
}
|
||||
|
||||
fn list_suggestions(
|
||||
&self,
|
||||
_context: &CommandContext<S>,
|
||||
builder: SuggestionsBuilder,
|
||||
) -> Suggestions {
|
||||
builder.filter_and_suggest(&["eyes", "feet"]).build()
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<String> {
|
||||
examples!("eyes", "feet")
|
||||
}
|
||||
}
|
||||
|
||||
impl_copy_get!(EntityAnchorArgumentType, EntityAnchor);
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, Default)]
|
||||
pub enum EntityAnchor {
|
||||
#[default]
|
||||
Feet,
|
||||
Eyes,
|
||||
}
|
||||
|
||||
impl EntityAnchor {
|
||||
/// Gets the [`EntityAnchor`] whose identity is the ID provided.
|
||||
#[must_use]
|
||||
pub fn from_id(id: &str) -> Option<Self> {
|
||||
match id {
|
||||
"feet" => Some(Self::Feet),
|
||||
"eyes" => Some(Self::Eyes),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the ID of this [`EntityAnchor`]
|
||||
#[must_use]
|
||||
pub const fn id(self) -> &'static str {
|
||||
match self {
|
||||
Self::Feet => "feet",
|
||||
Self::Eyes => "eyes",
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the position of a source with respect to this anchor.
|
||||
#[must_use]
|
||||
pub fn position_at_source(self, command_source: &impl CommandSource) -> Vector3<f64> {
|
||||
command_source.anchor_position(self)
|
||||
}
|
||||
}
|
||||
@ -1,9 +1,9 @@
|
||||
use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
|
||||
use crate::command::context::command_context::CommandContext;
|
||||
use crate::command::errors::command_syntax_error::CommandSyntaxError;
|
||||
use crate::command::errors::error_types::CommandErrorType;
|
||||
use crate::command::string_reader::StringReader;
|
||||
use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
|
||||
use crate::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
|
||||
use crate::context::command_context::CommandContext;
|
||||
use crate::errors::command_syntax_error::CommandSyntaxError;
|
||||
use crate::errors::error_types::CommandErrorType;
|
||||
use crate::string_reader::StringReader;
|
||||
use crate::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
|
||||
use pumpkin_data::translation;
|
||||
use pumpkin_util::GameMode;
|
||||
use pumpkin_util::text::TextComponent;
|
||||
@ -16,7 +16,7 @@ pub const INVALID_ERROR_TYPE: CommandErrorType<1> = CommandErrorType::new(
|
||||
|
||||
pub struct GameModeArgumentType;
|
||||
|
||||
impl ArgumentType for GameModeArgumentType {
|
||||
impl<S: crate::source::CommandSource> ArgumentType<S> for GameModeArgumentType {
|
||||
type Item = GameMode;
|
||||
|
||||
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
|
||||
@ -31,7 +31,7 @@ impl ArgumentType for GameModeArgumentType {
|
||||
|
||||
fn list_suggestions(
|
||||
&self,
|
||||
_context: &CommandContext,
|
||||
_context: &CommandContext<S>,
|
||||
mut builder: SuggestionsBuilder,
|
||||
) -> Suggestions {
|
||||
for gamemode in GameMode::VALUES {
|
||||
@ -1,9 +1,9 @@
|
||||
use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
|
||||
use crate::command::context::command_context::CommandContext;
|
||||
use crate::command::errors::command_syntax_error::CommandSyntaxError;
|
||||
use crate::command::errors::error_types::{CommandErrorType, DISPATCHER_PARSE_EXCEPTION};
|
||||
use crate::command::string_reader::StringReader;
|
||||
use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
|
||||
use crate::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
|
||||
use crate::context::command_context::CommandContext;
|
||||
use crate::errors::command_syntax_error::CommandSyntaxError;
|
||||
use crate::errors::error_types::{CommandErrorType, DISPATCHER_PARSE_EXCEPTION};
|
||||
use crate::string_reader::StringReader;
|
||||
use crate::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
|
||||
use pumpkin_data::translation::java::ARGUMENT_HEXCOLOR_INVALID;
|
||||
use pumpkin_util::text::TextComponent;
|
||||
use pumpkin_util::text::color::RGBColor;
|
||||
@ -13,7 +13,7 @@ pub const INVALID_HEX_ERROR_TYPE: CommandErrorType<1> =
|
||||
|
||||
pub struct HexColorArgumentType;
|
||||
|
||||
impl ArgumentType for HexColorArgumentType {
|
||||
impl<S: crate::source::CommandSource> ArgumentType<S> for HexColorArgumentType {
|
||||
type Item = RGBColor;
|
||||
|
||||
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
|
||||
@ -39,7 +39,7 @@ impl ArgumentType for HexColorArgumentType {
|
||||
|
||||
fn list_suggestions(
|
||||
&self,
|
||||
_context: &CommandContext,
|
||||
_context: &CommandContext<S>,
|
||||
builder: SuggestionsBuilder,
|
||||
) -> Suggestions {
|
||||
builder.filter_and_suggest(&["F00", "FF0000"]).build()
|
||||
@ -1,6 +1,6 @@
|
||||
use pumpkin_util::identifier::Identifier;
|
||||
|
||||
use crate::command::{
|
||||
use crate::{
|
||||
argument_types::{
|
||||
FromStringReader,
|
||||
argument_type::{ArgumentType, JavaClientArgumentType},
|
||||
@ -12,7 +12,7 @@ use crate::command::{
|
||||
/// An argument type that parses a generic [`Identifier`] with a namespace and path.
|
||||
pub struct IdentifierArgumentType;
|
||||
|
||||
impl ArgumentType for IdentifierArgumentType {
|
||||
impl<S: crate::source::CommandSource> ArgumentType<S> for IdentifierArgumentType {
|
||||
type Item = Identifier;
|
||||
|
||||
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
|
||||
@ -29,8 +29,8 @@ impl ArgumentType for IdentifierArgumentType {
|
||||
}
|
||||
|
||||
impl IdentifierArgumentType {
|
||||
pub fn get(
|
||||
context: &crate::command::context::command_context::CommandContext,
|
||||
pub fn get<S: crate::source::CommandSource>(
|
||||
context: &crate::context::command_context::CommandContext<S>,
|
||||
name: &str,
|
||||
) -> Result<Identifier, CommandSyntaxError> {
|
||||
Ok(context.get_argument::<Identifier>(name)?.clone())
|
||||
@ -4,13 +4,13 @@ use pumpkin_data::item_stack::ItemStack;
|
||||
use pumpkin_data::translation;
|
||||
use pumpkin_util::text::TextComponent;
|
||||
|
||||
use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
|
||||
use crate::command::context::command_context::CommandContext;
|
||||
use crate::command::errors::command_syntax_error::CommandSyntaxError;
|
||||
use crate::command::errors::error_types::CommandErrorType;
|
||||
use crate::command::snbt::SnbtParser;
|
||||
use crate::command::string_reader::StringReader;
|
||||
use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
|
||||
use crate::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
|
||||
use crate::context::command_context::CommandContext;
|
||||
use crate::errors::command_syntax_error::CommandSyntaxError;
|
||||
use crate::errors::error_types::CommandErrorType;
|
||||
use crate::snbt::SnbtParser;
|
||||
use crate::string_reader::StringReader;
|
||||
use crate::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
|
||||
|
||||
pub const ERROR_UNKNOWN_ITEM: CommandErrorType<1> = CommandErrorType::new(
|
||||
translation::java::ARGUMENT_ITEM_ID_INVALID,
|
||||
@ -20,7 +20,7 @@ pub const ERROR_UNKNOWN_ITEM: CommandErrorType<1> = CommandErrorType::new(
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct ItemStackArgumentType;
|
||||
|
||||
impl ArgumentType for ItemStackArgumentType {
|
||||
impl<S: crate::source::CommandSource> ArgumentType<S> for ItemStackArgumentType {
|
||||
type Item = ItemStack;
|
||||
|
||||
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
|
||||
@ -100,7 +100,7 @@ impl ArgumentType for ItemStackArgumentType {
|
||||
|
||||
fn list_suggestions(
|
||||
&self,
|
||||
_context: &CommandContext,
|
||||
_context: &CommandContext<S>,
|
||||
builder: SuggestionsBuilder,
|
||||
) -> Suggestions {
|
||||
builder.build()
|
||||
@ -108,7 +108,10 @@ impl ArgumentType for ItemStackArgumentType {
|
||||
}
|
||||
|
||||
impl ItemStackArgumentType {
|
||||
pub fn get(context: &CommandContext, name: &str) -> Result<ItemStack, CommandSyntaxError> {
|
||||
pub fn get<S: crate::source::CommandSource>(
|
||||
context: &CommandContext<S>,
|
||||
name: &str,
|
||||
) -> Result<ItemStack, CommandSyntaxError> {
|
||||
context.get_argument::<ItemStack>(name).cloned()
|
||||
}
|
||||
}
|
||||
@ -4,12 +4,12 @@ use pumpkin_data::tag::{RegistryKey, get_tag_ids};
|
||||
use pumpkin_data::translation;
|
||||
use pumpkin_util::text::TextComponent;
|
||||
|
||||
use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
|
||||
use crate::command::context::command_context::CommandContext;
|
||||
use crate::command::errors::command_syntax_error::CommandSyntaxError;
|
||||
use crate::command::errors::error_types::CommandErrorType;
|
||||
use crate::command::string_reader::StringReader;
|
||||
use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
|
||||
use crate::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
|
||||
use crate::context::command_context::CommandContext;
|
||||
use crate::errors::command_syntax_error::CommandSyntaxError;
|
||||
use crate::errors::error_types::CommandErrorType;
|
||||
use crate::string_reader::StringReader;
|
||||
use crate::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
|
||||
|
||||
pub const ERROR_UNKNOWN_ITEM: CommandErrorType<1> = CommandErrorType::new(
|
||||
translation::java::ARGUMENT_ITEM_ID_INVALID,
|
||||
@ -41,7 +41,7 @@ impl ItemPredicate {
|
||||
|
||||
pub struct ItemPredicateArgumentType;
|
||||
|
||||
impl ArgumentType for ItemPredicateArgumentType {
|
||||
impl<S: crate::source::CommandSource> ArgumentType<S> for ItemPredicateArgumentType {
|
||||
type Item = ItemPredicate;
|
||||
|
||||
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
|
||||
@ -131,7 +131,7 @@ impl ArgumentType for ItemPredicateArgumentType {
|
||||
|
||||
fn list_suggestions(
|
||||
&self,
|
||||
_context: &CommandContext,
|
||||
_context: &CommandContext<S>,
|
||||
builder: SuggestionsBuilder,
|
||||
) -> Suggestions {
|
||||
builder.build()
|
||||
@ -164,7 +164,10 @@ fn skip_components_and_nbt(reader: &mut StringReader) {
|
||||
}
|
||||
|
||||
impl ItemPredicateArgumentType {
|
||||
pub fn get(context: &CommandContext, name: &str) -> Result<ItemPredicate, CommandSyntaxError> {
|
||||
pub fn get<S: crate::source::CommandSource>(
|
||||
context: &CommandContext<S>,
|
||||
name: &str,
|
||||
) -> Result<ItemPredicate, CommandSyntaxError> {
|
||||
Ok(context.get_argument::<ItemPredicate>(name)?.clone())
|
||||
}
|
||||
}
|
||||
263
crates/pumpkin-command/src/argument_types/mod.rs
Normal file
263
crates/pumpkin-command/src/argument_types/mod.rs
Normal file
@ -0,0 +1,263 @@
|
||||
use crate::errors::command_syntax_error::CommandSyntaxError;
|
||||
use crate::errors::error_types::{
|
||||
CommandErrorType, READER_INVALID_DOUBLE, READER_INVALID_FLOAT, READER_INVALID_INT,
|
||||
};
|
||||
use crate::string_reader::StringReader;
|
||||
use pumpkin_data::translation;
|
||||
use pumpkin_util::identifier::Identifier;
|
||||
use pumpkin_util::math::bounds::{Bounds, DoubleBounds, FloatDegreeBounds, IntBounds};
|
||||
use pumpkin_util::text::TextComponent;
|
||||
use std::str::FromStr;
|
||||
|
||||
/// Creates a [`Vec<String>`] of examples from
|
||||
/// the given string literals.
|
||||
macro_rules! examples {
|
||||
( $( $example:literal ),* ) => {
|
||||
vec! [
|
||||
$( $example.to_string(), )*
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
// Helper methods for assertion with a `StringReader`:
|
||||
|
||||
/// Asserts that the result read by `reader` with the argument
|
||||
/// type `$argument_type` used to parse is equal to `Ok($value)`.
|
||||
/// Also resets the reader's cursor back to the start.
|
||||
#[cfg(test)]
|
||||
macro_rules! assert_parse_ok_reset {
|
||||
($reader: expr, $argument_type: expr, $value: expr) => {{
|
||||
assert_eq!(
|
||||
$crate::argument_types::argument_type::ArgumentType::<crate::source::DummySource>::parse(
|
||||
&$argument_type,
|
||||
&mut $reader
|
||||
),
|
||||
Ok($value)
|
||||
);
|
||||
$reader.set_cursor(0)
|
||||
}};
|
||||
($reader: expr, $argument_type: expr) => {{
|
||||
assert!(
|
||||
$crate::argument_types::argument_type::ArgumentType::<crate::source::DummySource>::parse(
|
||||
&$argument_type,
|
||||
&mut $reader
|
||||
)
|
||||
.is_ok()
|
||||
);
|
||||
$reader.set_cursor(0)
|
||||
}};
|
||||
}
|
||||
|
||||
/// Asserts that the result read by `reader` with the argument
|
||||
/// type `$argument_type` used to parse is an `Err` containing the type of error as `$error_type`.
|
||||
/// Also resets the reader's cursor back to the start.
|
||||
#[cfg(test)]
|
||||
macro_rules! assert_parse_err_reset {
|
||||
($reader: expr, $argument_type: expr, $error_type: expr) => {
|
||||
let error_type_dyn: &'static dyn crate::errors::error_types::AnyCommandErrorType =
|
||||
$error_type;
|
||||
assert_eq!(
|
||||
$crate::argument_types::argument_type::ArgumentType::<crate::source::DummySource>::parse(
|
||||
&$argument_type,
|
||||
&mut $reader
|
||||
)
|
||||
.map_err(|e| e.error_type),
|
||||
Err(error_type_dyn)
|
||||
);
|
||||
$reader.set_cursor(0)
|
||||
};
|
||||
}
|
||||
|
||||
/// Macro to implement a single `get()` function for an argument type whose `Item` is `Copy`.
|
||||
macro_rules! impl_copy_get {
|
||||
($ty:ty, $item:ty) => {
|
||||
impl $ty {
|
||||
#[doc = concat!("Returns a `CommandContext`'s parsed `", stringify!($item), "` argument.")]
|
||||
pub fn get<S: $crate::source::CommandSource>(context: &$crate::context::command_context::CommandContext<S>, name: &str) -> Result<$item, CommandSyntaxError> {
|
||||
Ok(*context.get_argument(name)?)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const EMPTY_BOUNDS_ERROR_TYPE: CommandErrorType<0> = CommandErrorType::new(
|
||||
translation::java::ARGUMENT_RANGE_EMPTY,
|
||||
translation::java::ARGUMENT_RANGE_EMPTY,
|
||||
);
|
||||
const SWAPPED_BOUNDS_ERROR_TYPE: CommandErrorType<0> = CommandErrorType::new(
|
||||
translation::java::ARGUMENT_RANGE_SWAPPED,
|
||||
translation::java::ARGUMENT_RANGE_SWAPPED,
|
||||
);
|
||||
const INVALID_IDENTIFIER_ERROR_TYPE: CommandErrorType<0> = CommandErrorType::new(
|
||||
translation::java::ARGUMENT_ID_INVALID,
|
||||
translation::java::ARGUMENT_ID_INVALID,
|
||||
);
|
||||
|
||||
/// A trait to try getting a value from a [`StringReader`].
|
||||
pub trait FromStringReader: Sized {
|
||||
/// Tries to parse a value of this type from a [`StringReader`].
|
||||
///
|
||||
/// If this fails, a [`CommandSyntaxError`] is returned with the erroring details.
|
||||
fn from_reader(reader: &mut StringReader) -> Result<Self, CommandSyntaxError>;
|
||||
}
|
||||
|
||||
fn bounds_from_reader<T: Copy + FromStr + PartialOrd>(
|
||||
reader: &mut StringReader,
|
||||
error_type: &'static CommandErrorType<1>,
|
||||
) -> Result<Bounds<T>, CommandSyntaxError> {
|
||||
if reader.can_read_char() {
|
||||
let i = reader.cursor();
|
||||
try_bounds_from_reader(reader, error_type).map_err(|e| {
|
||||
// On catching any error, set the reader's cursor position to be at where the range starts.
|
||||
if let Some(mut context) = e.context {
|
||||
context.cursor = i;
|
||||
CommandSyntaxError::create(e.error_type, e.message, &context)
|
||||
} else {
|
||||
CommandSyntaxError::create_without_context(e.error_type, e.message)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
Err(EMPTY_BOUNDS_ERROR_TYPE.create(reader))
|
||||
}
|
||||
}
|
||||
|
||||
fn try_bounds_from_reader<T: Copy + FromStr + PartialOrd>(
|
||||
reader: &mut StringReader,
|
||||
error_type: &'static CommandErrorType<1>,
|
||||
) -> Result<Bounds<T>, CommandSyntaxError> {
|
||||
let min = read_number_from_reader(reader, error_type)?;
|
||||
let max = if reader.peek() == Some('.') && reader.peek_with_offset(1) == Some('.') {
|
||||
reader.skip();
|
||||
reader.skip();
|
||||
read_number_from_reader(reader, error_type)?
|
||||
} else {
|
||||
min
|
||||
};
|
||||
if min.is_none() && max.is_none() {
|
||||
Err(EMPTY_BOUNDS_ERROR_TYPE.create(reader))
|
||||
} else {
|
||||
Ok(Bounds::<T>::new(min, max))
|
||||
}
|
||||
}
|
||||
|
||||
/// Tries to read a number of a type from a reader.
|
||||
fn read_number_from_reader<T: FromStr>(
|
||||
reader: &mut StringReader,
|
||||
error_type: &'static CommandErrorType<1>,
|
||||
) -> Result<Option<T>, CommandSyntaxError> {
|
||||
let i = reader.cursor();
|
||||
while has_allowed_peaked_character(reader) {
|
||||
reader.skip();
|
||||
}
|
||||
let string = &reader.string()[i..reader.cursor()].to_string();
|
||||
if string.is_empty() {
|
||||
Ok(None)
|
||||
} else {
|
||||
string.parse::<T>().map_or_else(
|
||||
|_| Err(error_type.create(reader, TextComponent::text(string.clone()))),
|
||||
|t| Ok(Some(t)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn has_allowed_peaked_character(reader: &StringReader) -> bool {
|
||||
let c = reader.peek();
|
||||
if matches!(c, Some('0'..='9' | '-')) {
|
||||
true
|
||||
} else {
|
||||
c.is_some_and(|c| {
|
||||
c == '.' && (!reader.can_read_chars(2) || reader.peek_with_offset(1) != Some('.'))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStringReader for IntBounds {
|
||||
fn from_reader(reader: &mut StringReader) -> Result<Self, CommandSyntaxError> {
|
||||
let i = reader.cursor();
|
||||
let bounds = bounds_from_reader(reader, &READER_INVALID_INT)?;
|
||||
if bounds.are_swapped() {
|
||||
reader.set_cursor(i);
|
||||
Err(SWAPPED_BOUNDS_ERROR_TYPE.create(reader))
|
||||
} else {
|
||||
Ok(Self::from_bounds(bounds))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStringReader for DoubleBounds {
|
||||
fn from_reader(reader: &mut StringReader) -> Result<Self, CommandSyntaxError> {
|
||||
let i = reader.cursor();
|
||||
let bounds = bounds_from_reader(reader, &READER_INVALID_DOUBLE)?;
|
||||
if bounds.are_swapped() {
|
||||
reader.set_cursor(i);
|
||||
Err(SWAPPED_BOUNDS_ERROR_TYPE.create(reader))
|
||||
} else {
|
||||
Ok(Self::from_bounds(bounds))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStringReader for FloatDegreeBounds {
|
||||
fn from_reader(reader: &mut StringReader) -> Result<Self, CommandSyntaxError> {
|
||||
let i = reader.cursor();
|
||||
let bounds = bounds_from_reader(reader, &READER_INVALID_FLOAT)?;
|
||||
if bounds.are_swapped() {
|
||||
reader.set_cursor(i);
|
||||
Err(SWAPPED_BOUNDS_ERROR_TYPE.create(reader))
|
||||
} else {
|
||||
Ok(Self::from_bounds(bounds))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStringReader for Identifier {
|
||||
fn from_reader(reader: &mut StringReader) -> Result<Self, CommandSyntaxError> {
|
||||
let start = reader.cursor();
|
||||
while let Some(c) = reader.peek()
|
||||
&& Self::is_valid_char(c)
|
||||
{
|
||||
reader.skip();
|
||||
}
|
||||
|
||||
let raw_identifier = &reader.string()[start..reader.cursor()];
|
||||
let identifier_result = Self::parse(raw_identifier);
|
||||
|
||||
identifier_result.map_or_else(
|
||||
|_| {
|
||||
reader.set_cursor(start);
|
||||
Err(INVALID_IDENTIFIER_ERROR_TYPE.create(reader))
|
||||
},
|
||||
Ok,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub mod argument_type;
|
||||
pub mod attribute;
|
||||
pub mod block;
|
||||
pub mod block_predicate;
|
||||
pub mod component;
|
||||
pub mod coordinates;
|
||||
pub mod core;
|
||||
pub mod dialog;
|
||||
pub mod entity_anchor;
|
||||
pub mod gamemode;
|
||||
pub mod hex_color;
|
||||
pub mod identifier;
|
||||
pub mod item;
|
||||
pub mod item_predicate;
|
||||
pub mod nbt;
|
||||
pub mod nbt_path;
|
||||
pub mod particle;
|
||||
pub mod placed_feature;
|
||||
pub mod pool;
|
||||
pub mod range;
|
||||
pub mod resource;
|
||||
pub mod resource_or_tag;
|
||||
pub mod slot;
|
||||
pub mod sound_category;
|
||||
pub mod structure;
|
||||
pub mod team_color;
|
||||
pub mod template;
|
||||
pub mod time;
|
||||
pub mod uuid;
|
||||
@ -1,6 +1,6 @@
|
||||
use pumpkin_nbt::tag::NbtTag;
|
||||
|
||||
use crate::command::{
|
||||
use crate::{
|
||||
argument_types::argument_type::{ArgumentType, JavaClientArgumentType},
|
||||
context::command_context::CommandContext,
|
||||
errors::{command_syntax_error::CommandSyntaxError, error_types::CommandErrorType},
|
||||
@ -15,7 +15,7 @@ use pumpkin_nbt::compound::NbtCompound;
|
||||
/// Parses any type of NBT tag from SNBT.
|
||||
pub struct NbtTagArgumentType;
|
||||
|
||||
impl ArgumentType for NbtTagArgumentType {
|
||||
impl<S: crate::source::CommandSource> ArgumentType<S> for NbtTagArgumentType {
|
||||
type Item = NbtTag;
|
||||
|
||||
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
|
||||
@ -28,7 +28,7 @@ impl ArgumentType for NbtTagArgumentType {
|
||||
|
||||
fn list_suggestions(
|
||||
&self,
|
||||
_context: &CommandContext,
|
||||
_context: &CommandContext<S>,
|
||||
builder: SuggestionsBuilder,
|
||||
) -> Suggestions {
|
||||
SnbtParser::parse_for_suggestions(builder)
|
||||
@ -41,8 +41,8 @@ impl ArgumentType for NbtTagArgumentType {
|
||||
|
||||
impl NbtTagArgumentType {
|
||||
/// Returns the parsed [`NbtTag`] from the name of the argument.
|
||||
pub fn get<'a>(
|
||||
context: &'a CommandContext,
|
||||
pub fn get<'a, S: crate::source::CommandSource>(
|
||||
context: &'a CommandContext<S>,
|
||||
name: &'_ str,
|
||||
) -> Result<&'a NbtTag, CommandSyntaxError> {
|
||||
context.get_argument(name)
|
||||
@ -57,7 +57,7 @@ pub const EXPECTED_COMPOUND_ERROR_TYPE: CommandErrorType<0> = CommandErrorType::
|
||||
/// Parses **only** compound NBT tags from SNBT.
|
||||
pub struct NbtCompoundArgumentType;
|
||||
|
||||
impl ArgumentType for NbtCompoundArgumentType {
|
||||
impl<S: crate::source::CommandSource> ArgumentType<S> for NbtCompoundArgumentType {
|
||||
type Item = NbtCompound;
|
||||
|
||||
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
|
||||
@ -81,8 +81,8 @@ impl ArgumentType for NbtCompoundArgumentType {
|
||||
|
||||
impl NbtCompoundArgumentType {
|
||||
/// Returns the parsed [`NbtCompound`] from the name of the argument.
|
||||
pub fn get<'a>(
|
||||
context: &'a CommandContext,
|
||||
pub fn get<'a, S: crate::source::CommandSource>(
|
||||
context: &'a CommandContext<S>,
|
||||
name: &'_ str,
|
||||
) -> Result<&'a NbtCompound, CommandSyntaxError> {
|
||||
context.get_argument(name)
|
||||
@ -21,12 +21,12 @@ use pumpkin_nbt::compound::NbtCompound;
|
||||
use pumpkin_nbt::tag::NbtTag;
|
||||
use pumpkin_util::text::TextComponent;
|
||||
|
||||
use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
|
||||
use crate::command::context::command_context::CommandContext;
|
||||
use crate::command::errors::command_syntax_error::CommandSyntaxError;
|
||||
use crate::command::errors::error_types::CommandErrorType;
|
||||
use crate::command::snbt::SnbtParser;
|
||||
use crate::command::string_reader::StringReader;
|
||||
use crate::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
|
||||
use crate::context::command_context::CommandContext;
|
||||
use crate::errors::command_syntax_error::CommandSyntaxError;
|
||||
use crate::errors::error_types::CommandErrorType;
|
||||
use crate::snbt::SnbtParser;
|
||||
use crate::string_reader::StringReader;
|
||||
|
||||
pub const ERROR_INVALID_NODE: CommandErrorType<0> = CommandErrorType::new(
|
||||
translation::java::ARGUMENTS_NBTPATH_NODE_INVALID,
|
||||
@ -666,7 +666,7 @@ impl std::fmt::Display for NbtPath {
|
||||
/// Argument type for parsing NBT Paths.
|
||||
pub struct NbtPathArgumentType;
|
||||
|
||||
impl ArgumentType for NbtPathArgumentType {
|
||||
impl<S: crate::source::CommandSource> ArgumentType<S> for NbtPathArgumentType {
|
||||
type Item = NbtPath;
|
||||
|
||||
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
|
||||
@ -706,8 +706,8 @@ impl ArgumentType for NbtPathArgumentType {
|
||||
}
|
||||
|
||||
impl NbtPathArgumentType {
|
||||
pub fn get<'a>(
|
||||
context: &'a CommandContext,
|
||||
pub fn get<'a, S: crate::source::CommandSource>(
|
||||
context: &'a CommandContext<S>,
|
||||
name: &'_ str,
|
||||
) -> Result<&'a NbtPath, CommandSyntaxError> {
|
||||
context.get_argument(name)
|
||||
@ -1,10 +1,10 @@
|
||||
use crate::command::argument_types::FromStringReader;
|
||||
use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
|
||||
use crate::command::context::command_context::CommandContext;
|
||||
use crate::command::errors::command_syntax_error::CommandSyntaxError;
|
||||
use crate::command::errors::error_types::CommandErrorType;
|
||||
use crate::command::string_reader::StringReader;
|
||||
use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
|
||||
use crate::argument_types::FromStringReader;
|
||||
use crate::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
|
||||
use crate::context::command_context::CommandContext;
|
||||
use crate::errors::command_syntax_error::CommandSyntaxError;
|
||||
use crate::errors::error_types::CommandErrorType;
|
||||
use crate::string_reader::StringReader;
|
||||
use crate::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
|
||||
use pumpkin_data::particle::Particle;
|
||||
use pumpkin_data::translation;
|
||||
use pumpkin_util::identifier::Identifier;
|
||||
@ -17,7 +17,7 @@ pub const ERROR_UNKNOWN_PARTICLE: CommandErrorType<1> = CommandErrorType::new(
|
||||
|
||||
pub struct ParticleArgumentType;
|
||||
|
||||
impl ArgumentType for ParticleArgumentType {
|
||||
impl<S: crate::source::CommandSource> ArgumentType<S> for ParticleArgumentType {
|
||||
type Item = Particle;
|
||||
|
||||
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
|
||||
@ -43,7 +43,7 @@ impl ArgumentType for ParticleArgumentType {
|
||||
|
||||
fn list_suggestions(
|
||||
&self,
|
||||
_context: &CommandContext,
|
||||
_context: &CommandContext<S>,
|
||||
builder: SuggestionsBuilder,
|
||||
) -> Suggestions {
|
||||
let particles = (0..=124u16)
|
||||
@ -55,7 +55,10 @@ impl ArgumentType for ParticleArgumentType {
|
||||
}
|
||||
|
||||
impl ParticleArgumentType {
|
||||
pub fn get(context: &CommandContext, name: &str) -> Result<Particle, CommandSyntaxError> {
|
||||
pub fn get<S: crate::source::CommandSource>(
|
||||
context: &CommandContext<S>,
|
||||
name: &str,
|
||||
) -> Result<Particle, CommandSyntaxError> {
|
||||
Ok(*context.get_argument::<Particle>(name)?)
|
||||
}
|
||||
}
|
||||
@ -1,16 +1,16 @@
|
||||
use pumpkin_protocol::java::client::play::SuggestionProviders;
|
||||
use pumpkin_util::identifier::Identifier;
|
||||
|
||||
use crate::command::argument_types::FromStringReader;
|
||||
use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
|
||||
use crate::command::context::command_context::CommandContext;
|
||||
use crate::command::errors::command_syntax_error::CommandSyntaxError;
|
||||
use crate::command::string_reader::StringReader;
|
||||
use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
|
||||
use crate::argument_types::FromStringReader;
|
||||
use crate::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
|
||||
use crate::context::command_context::CommandContext;
|
||||
use crate::errors::command_syntax_error::CommandSyntaxError;
|
||||
use crate::string_reader::StringReader;
|
||||
use crate::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
|
||||
|
||||
pub struct PlacedFeatureNameArgumentType;
|
||||
|
||||
impl ArgumentType for PlacedFeatureNameArgumentType {
|
||||
impl<S: crate::source::CommandSource> ArgumentType<S> for PlacedFeatureNameArgumentType {
|
||||
type Item = Identifier;
|
||||
|
||||
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
|
||||
@ -19,7 +19,7 @@ impl ArgumentType for PlacedFeatureNameArgumentType {
|
||||
|
||||
fn list_suggestions(
|
||||
&self,
|
||||
_context: &CommandContext,
|
||||
_context: &CommandContext<S>,
|
||||
builder: SuggestionsBuilder,
|
||||
) -> Suggestions {
|
||||
let names = pumpkin_world::generation::feature::placed_features::all_placed_feature_names();
|
||||
@ -1,15 +1,15 @@
|
||||
use crate::command::argument_types::FromStringReader;
|
||||
use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
|
||||
use crate::command::context::command_context::CommandContext;
|
||||
use crate::command::errors::command_syntax_error::CommandSyntaxError;
|
||||
use crate::command::string_reader::StringReader;
|
||||
use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
|
||||
use crate::argument_types::FromStringReader;
|
||||
use crate::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
|
||||
use crate::context::command_context::CommandContext;
|
||||
use crate::errors::command_syntax_error::CommandSyntaxError;
|
||||
use crate::string_reader::StringReader;
|
||||
use crate::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
|
||||
use pumpkin_protocol::java::client::play::SuggestionProviders;
|
||||
use pumpkin_util::identifier::Identifier;
|
||||
|
||||
pub struct PoolNameArgumentType;
|
||||
|
||||
impl ArgumentType for PoolNameArgumentType {
|
||||
impl<S: crate::source::CommandSource> ArgumentType<S> for PoolNameArgumentType {
|
||||
type Item = Identifier;
|
||||
|
||||
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
|
||||
@ -18,7 +18,7 @@ impl ArgumentType for PoolNameArgumentType {
|
||||
|
||||
fn list_suggestions(
|
||||
&self,
|
||||
_context: &CommandContext,
|
||||
_context: &CommandContext<S>,
|
||||
builder: SuggestionsBuilder,
|
||||
) -> Suggestions {
|
||||
let names = pumpkin_world::generation::structure::template::all_pool_names();
|
||||
@ -1,6 +1,6 @@
|
||||
use pumpkin_util::math::bounds::{DoubleBounds, IntBounds};
|
||||
|
||||
use crate::command::{
|
||||
use crate::{
|
||||
argument_types::{
|
||||
FromStringReader,
|
||||
argument_type::{ArgumentType, JavaClientArgumentType},
|
||||
@ -16,7 +16,7 @@ use crate::command::{
|
||||
/// - `min..max`: All integers that are between `min` and `max`, inclusive. Mathematically representable as `[min, max]`.
|
||||
pub struct IntRangeArgumentType;
|
||||
|
||||
impl ArgumentType for IntRangeArgumentType {
|
||||
impl<S: crate::source::CommandSource> ArgumentType<S> for IntRangeArgumentType {
|
||||
type Item = IntBounds;
|
||||
|
||||
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
|
||||
@ -41,7 +41,7 @@ impl_copy_get!(IntRangeArgumentType, IntBounds);
|
||||
/// - `min..max`: All numbers that are between `min` and `max`, inclusive. Mathematically representable as `[min, max]`.
|
||||
pub struct FloatRangeArgumentType;
|
||||
|
||||
impl ArgumentType for FloatRangeArgumentType {
|
||||
impl<S: crate::source::CommandSource> ArgumentType<S> for FloatRangeArgumentType {
|
||||
type Item = DoubleBounds;
|
||||
|
||||
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
|
||||
@ -63,10 +63,9 @@ impl_copy_get!(FloatRangeArgumentType, DoubleBounds);
|
||||
mod test {
|
||||
use pumpkin_util::math::bounds::{DoubleBounds, IntBounds};
|
||||
|
||||
use crate::command::{
|
||||
use crate::{
|
||||
argument_types::{
|
||||
SWAPPED_BOUNDS_ERROR_TYPE,
|
||||
argument_type::ArgumentType,
|
||||
range::{FloatRangeArgumentType, IntRangeArgumentType},
|
||||
},
|
||||
string_reader::StringReader,
|
||||
@ -1,11 +1,11 @@
|
||||
use crate::command::argument_types::FromStringReader;
|
||||
use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
|
||||
use crate::command::context::command_context::CommandContext;
|
||||
use crate::command::errors::command_syntax_error::CommandSyntaxError;
|
||||
use crate::command::errors::error_types::{CommandErrorType, DISPATCHER_PARSE_EXCEPTION};
|
||||
use crate::command::node::attached::AttachedNode;
|
||||
use crate::command::string_reader::StringReader;
|
||||
use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
|
||||
use crate::argument_types::FromStringReader;
|
||||
use crate::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
|
||||
use crate::context::command_context::CommandContext;
|
||||
use crate::errors::command_syntax_error::CommandSyntaxError;
|
||||
use crate::errors::error_types::{CommandErrorType, DISPATCHER_PARSE_EXCEPTION};
|
||||
use crate::node::attached::AttachedNode;
|
||||
use crate::string_reader::StringReader;
|
||||
use crate::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
|
||||
use pumpkin_data::entity::EntityType;
|
||||
use pumpkin_data::translation;
|
||||
use pumpkin_protocol::java::client::play::SuggestionProviders;
|
||||
@ -123,7 +123,7 @@ pub struct ResourceArgument(
|
||||
pub &'static (dyn Fn(Identifier) -> Option<&'static (dyn Any + Send + Sync)> + Send + Sync),
|
||||
);
|
||||
|
||||
impl ArgumentType for ResourceArgument {
|
||||
impl<S: crate::source::CommandSource> ArgumentType<S> for ResourceArgument {
|
||||
type Item = &'static (dyn Any + Send + Sync);
|
||||
|
||||
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
|
||||
@ -139,7 +139,7 @@ impl ArgumentType for ResourceArgument {
|
||||
|
||||
fn list_suggestions(
|
||||
&self,
|
||||
_context: &CommandContext,
|
||||
_context: &CommandContext<S>,
|
||||
suggestions_builder: SuggestionsBuilder,
|
||||
) -> Suggestions {
|
||||
if self.0 == ENTITY_TYPE_REGISTRY {
|
||||
@ -225,8 +225,8 @@ impl ArgumentType for ResourceArgument {
|
||||
}
|
||||
|
||||
impl ResourceArgument {
|
||||
pub fn get_resource<T: 'static>(
|
||||
context: &CommandContext,
|
||||
pub fn get_resource<T: 'static, S: crate::source::CommandSource>(
|
||||
context: &CommandContext<S>,
|
||||
name: &str,
|
||||
registry_key: &Identifier,
|
||||
) -> Result<&'static T, CommandSyntaxError> {
|
||||
@ -291,36 +291,36 @@ impl ResourceArgument {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_entity_type(
|
||||
context: &CommandContext,
|
||||
pub fn get_entity_type<S: crate::source::CommandSource>(
|
||||
context: &CommandContext<S>,
|
||||
name: &str,
|
||||
) -> Result<&'static EntityType, CommandSyntaxError> {
|
||||
Self::get_resource(context, name, ENTITY_TYPE_REGISTRY)
|
||||
}
|
||||
|
||||
pub fn get_enchantment(
|
||||
context: &CommandContext,
|
||||
pub fn get_enchantment<S: crate::source::CommandSource>(
|
||||
context: &CommandContext<S>,
|
||||
name: &str,
|
||||
) -> Result<&'static pumpkin_data::Enchantment, CommandSyntaxError> {
|
||||
Self::get_resource(context, name, ENCHANTMENT_REGISTRY)
|
||||
}
|
||||
|
||||
pub fn get_mob_effect(
|
||||
context: &CommandContext,
|
||||
pub fn get_mob_effect<S: crate::source::CommandSource>(
|
||||
context: &CommandContext<S>,
|
||||
name: &str,
|
||||
) -> Result<&'static pumpkin_data::effect::StatusEffect, CommandSyntaxError> {
|
||||
Self::get_resource(context, name, MOB_EFFECT_REGISTRY)
|
||||
}
|
||||
|
||||
pub fn get_damage_type(
|
||||
context: &CommandContext,
|
||||
pub fn get_damage_type<S: crate::source::CommandSource>(
|
||||
context: &CommandContext<S>,
|
||||
name: &str,
|
||||
) -> Result<&'static pumpkin_data::damage::DamageType, CommandSyntaxError> {
|
||||
Self::get_resource(context, name, DAMAGE_TYPE_REGISTRY)
|
||||
}
|
||||
|
||||
pub fn get_summonable_entity_type(
|
||||
context: &CommandContext,
|
||||
pub fn get_summonable_entity_type<S: crate::source::CommandSource>(
|
||||
context: &CommandContext<S>,
|
||||
name: &str,
|
||||
) -> Result<&'static EntityType, CommandSyntaxError> {
|
||||
let val: &'static EntityType = Self::get_resource(context, name, ENTITY_TYPE_REGISTRY)?;
|
||||
@ -1,21 +1,18 @@
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use pumpkin_data::structures::StructureSet;
|
||||
use pumpkin_data::tag::{self, RegistryKey};
|
||||
use pumpkin_data::translation;
|
||||
use pumpkin_util::identifier::Identifier;
|
||||
use pumpkin_util::text::TextComponent;
|
||||
use pumpkin_world::poi::POI_TYPE_NETHER_PORTAL;
|
||||
|
||||
use crate::command::argument_types::FromStringReader;
|
||||
use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
|
||||
use crate::command::argument_types::resource_key::BIOME_REGISTRY;
|
||||
use crate::command::context::command_context::CommandContext;
|
||||
use crate::command::errors::command_syntax_error::CommandSyntaxError;
|
||||
use crate::command::errors::error_types::CommandErrorType;
|
||||
use crate::command::string_reader::StringReader;
|
||||
use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
|
||||
use crate::argument_types::FromStringReader;
|
||||
use crate::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
|
||||
use crate::context::command_context::CommandContext;
|
||||
use crate::errors::command_syntax_error::CommandSyntaxError;
|
||||
use crate::errors::error_types::CommandErrorType;
|
||||
use crate::string_reader::StringReader;
|
||||
use crate::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
|
||||
|
||||
pub static BIOME_REGISTRY: Identifier = Identifier::vanilla_static("worldgen/biome");
|
||||
pub static STRUCTURE_REGISTRY: Identifier = Identifier::vanilla_static("worldgen/structure");
|
||||
pub static POI_REGISTRY: Identifier = Identifier::vanilla_static("point_of_interest_type");
|
||||
|
||||
@ -78,7 +75,7 @@ fn suggest_for_registry(registry: &Identifier, builder: SuggestionsBuilder) -> S
|
||||
.map(|name| format!("minecraft:{name}")),
|
||||
)
|
||||
.build()
|
||||
} else if *registry == *BIOME_REGISTRY {
|
||||
} else if *registry == BIOME_REGISTRY {
|
||||
let biomes = pumpkin_data::biome::Biome::ALL
|
||||
.iter()
|
||||
.map(|biome| format!("minecraft:{}", biome.registry_id));
|
||||
@ -88,21 +85,38 @@ fn suggest_for_registry(registry: &Identifier, builder: SuggestionsBuilder) -> S
|
||||
} else if *registry == POI_REGISTRY {
|
||||
// There is no generated POI type registry (yet), so offer the types
|
||||
// known from tag data plus the ones the server actually creates.
|
||||
let mut names: BTreeSet<String> = tag::get_latest_map(RegistryKey::PointOfInterestType)
|
||||
.into_iter()
|
||||
.flat_map(|map| map.values().flat_map(|tag| tag.0.iter()))
|
||||
.map(|name| format!("minecraft:{name}"))
|
||||
.collect();
|
||||
names.insert(POI_TYPE_NETHER_PORTAL.to_string());
|
||||
let poi_types = [
|
||||
"minecraft:armorer",
|
||||
"minecraft:butcher",
|
||||
"minecraft:cartographer",
|
||||
"minecraft:cleric",
|
||||
"minecraft:farmer",
|
||||
"minecraft:fisherman",
|
||||
"minecraft:fletcher",
|
||||
"minecraft:leatherworker",
|
||||
"minecraft:librarian",
|
||||
"minecraft:mason",
|
||||
"minecraft:shepherd",
|
||||
"minecraft:toolsmith",
|
||||
"minecraft:weaponsmith",
|
||||
"minecraft:home",
|
||||
"minecraft:meeting",
|
||||
"minecraft:beehive",
|
||||
"minecraft:bee_nest",
|
||||
"minecraft:nether_portal",
|
||||
"minecraft:lodestone",
|
||||
"minecraft:lightning_rod",
|
||||
];
|
||||
builder
|
||||
.filter_and_suggest_iter(
|
||||
names
|
||||
.into_iter()
|
||||
poi_types
|
||||
.iter()
|
||||
.map(|&s| s.to_string())
|
||||
.chain(tag_names(RegistryKey::PointOfInterestType)),
|
||||
)
|
||||
.build()
|
||||
} else {
|
||||
Suggestions::empty()
|
||||
builder.build()
|
||||
}
|
||||
}
|
||||
|
||||
@ -113,7 +127,7 @@ fn suggest_for_registry(registry: &Identifier, builder: SuggestionsBuilder) -> S
|
||||
/// the command.
|
||||
pub struct ResourceOrTagKeyArgument(pub Identifier);
|
||||
|
||||
impl ArgumentType for ResourceOrTagKeyArgument {
|
||||
impl<S: crate::source::CommandSource> ArgumentType<S> for ResourceOrTagKeyArgument {
|
||||
type Item = ResourceOrTag;
|
||||
|
||||
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
|
||||
@ -122,13 +136,13 @@ impl ArgumentType for ResourceOrTagKeyArgument {
|
||||
|
||||
fn list_suggestions(
|
||||
&self,
|
||||
_context: &CommandContext,
|
||||
_context: &CommandContext<S>,
|
||||
builder: SuggestionsBuilder,
|
||||
) -> Suggestions {
|
||||
suggest_for_registry(&self.0, builder)
|
||||
}
|
||||
|
||||
fn client_side_parser(&'_ self) -> JavaClientArgumentType {
|
||||
fn client_side_parser(&self) -> JavaClientArgumentType {
|
||||
JavaClientArgumentType::ResourceOrTagKey {
|
||||
identifier: self.0.clone(),
|
||||
}
|
||||
@ -148,7 +162,7 @@ impl ArgumentType for ResourceOrTagKeyArgument {
|
||||
/// generated entry data (such as POI types) are accepted as-is.
|
||||
pub struct ResourceOrTagArgument(pub Identifier);
|
||||
|
||||
impl ArgumentType for ResourceOrTagArgument {
|
||||
impl<S: crate::source::CommandSource> ArgumentType<S> for ResourceOrTagArgument {
|
||||
type Item = ResourceOrTag;
|
||||
|
||||
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
|
||||
@ -156,7 +170,7 @@ impl ArgumentType for ResourceOrTagArgument {
|
||||
|
||||
match &value {
|
||||
ResourceOrTag::Resource(id) => {
|
||||
if self.0 == *BIOME_REGISTRY
|
||||
if self.0 == BIOME_REGISTRY
|
||||
&& !(id.is_vanilla()
|
||||
&& pumpkin_data::biome::Biome::from_name(id.path()).is_some())
|
||||
{
|
||||
@ -167,7 +181,7 @@ impl ArgumentType for ResourceOrTagArgument {
|
||||
}
|
||||
}
|
||||
ResourceOrTag::Tag(id) => {
|
||||
let registry_key = if self.0 == *BIOME_REGISTRY {
|
||||
let registry_key = if self.0 == BIOME_REGISTRY {
|
||||
Some(RegistryKey::WorldgenBiome)
|
||||
} else if self.0 == POI_REGISTRY {
|
||||
Some(RegistryKey::PointOfInterestType)
|
||||
@ -191,7 +205,7 @@ impl ArgumentType for ResourceOrTagArgument {
|
||||
|
||||
fn list_suggestions(
|
||||
&self,
|
||||
_context: &CommandContext,
|
||||
_context: &CommandContext<S>,
|
||||
builder: SuggestionsBuilder,
|
||||
) -> Suggestions {
|
||||
suggest_for_registry(&self.0, builder)
|
||||
@ -1,9 +1,9 @@
|
||||
use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
|
||||
use crate::command::context::command_context::CommandContext;
|
||||
use crate::command::errors::command_syntax_error::CommandSyntaxError;
|
||||
use crate::command::errors::error_types::CommandErrorType;
|
||||
use crate::command::string_reader::StringReader;
|
||||
use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
|
||||
use crate::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
|
||||
use crate::context::command_context::CommandContext;
|
||||
use crate::errors::command_syntax_error::CommandSyntaxError;
|
||||
use crate::errors::error_types::CommandErrorType;
|
||||
use crate::string_reader::StringReader;
|
||||
use crate::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
|
||||
use pumpkin_data::slot_ranges::{
|
||||
SLOT_RANGE_ALL_NAMES, SLOT_RANGE_SINGLE_SLOT_NAMES, get_slot_range,
|
||||
};
|
||||
@ -22,7 +22,7 @@ pub const ONLY_SINGLE_SLOT_ALLOWED_ERROR_TYPE: CommandErrorType<1> = CommandErro
|
||||
|
||||
pub struct SlotArgumentType;
|
||||
|
||||
impl ArgumentType for SlotArgumentType {
|
||||
impl<S: crate::source::CommandSource> ArgumentType<S> for SlotArgumentType {
|
||||
type Item = usize;
|
||||
|
||||
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
|
||||
@ -46,7 +46,7 @@ impl ArgumentType for SlotArgumentType {
|
||||
|
||||
fn list_suggestions(
|
||||
&self,
|
||||
_context: &CommandContext,
|
||||
_context: &CommandContext<S>,
|
||||
builder: SuggestionsBuilder,
|
||||
) -> Suggestions {
|
||||
builder
|
||||
@ -63,7 +63,7 @@ impl_copy_get!(SlotArgumentType, usize);
|
||||
|
||||
pub struct SlotsArgumentType;
|
||||
|
||||
impl ArgumentType for SlotsArgumentType {
|
||||
impl<S: crate::source::CommandSource> ArgumentType<S> for SlotsArgumentType {
|
||||
type Item = &'static [usize];
|
||||
|
||||
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
|
||||
@ -83,7 +83,7 @@ impl ArgumentType for SlotsArgumentType {
|
||||
|
||||
fn list_suggestions(
|
||||
&self,
|
||||
_context: &CommandContext,
|
||||
_context: &CommandContext<S>,
|
||||
builder: SuggestionsBuilder,
|
||||
) -> Suggestions {
|
||||
builder.filter_and_suggest(&SLOT_RANGE_ALL_NAMES).build()
|
||||
@ -2,12 +2,12 @@ use pumpkin_data::sound::SoundCategory;
|
||||
use pumpkin_data::translation;
|
||||
use pumpkin_util::text::TextComponent;
|
||||
|
||||
use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
|
||||
use crate::command::context::command_context::CommandContext;
|
||||
use crate::command::errors::command_syntax_error::CommandSyntaxError;
|
||||
use crate::command::errors::error_types::CommandErrorType;
|
||||
use crate::command::string_reader::StringReader;
|
||||
use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
|
||||
use crate::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
|
||||
use crate::context::command_context::CommandContext;
|
||||
use crate::errors::command_syntax_error::CommandSyntaxError;
|
||||
use crate::errors::error_types::CommandErrorType;
|
||||
use crate::string_reader::StringReader;
|
||||
use crate::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
|
||||
|
||||
pub const ERROR_INVALID_SOURCE: CommandErrorType<1> = CommandErrorType::new(
|
||||
translation::java::ARGUMENT_ENUM_INVALID,
|
||||
@ -22,7 +22,7 @@ const CATEGORIES: [&str; 10] = [
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct SoundCategoryArgumentType;
|
||||
|
||||
impl ArgumentType for SoundCategoryArgumentType {
|
||||
impl<S: crate::source::CommandSource> ArgumentType<S> for SoundCategoryArgumentType {
|
||||
type Item = SoundCategory;
|
||||
|
||||
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
|
||||
@ -54,7 +54,7 @@ impl ArgumentType for SoundCategoryArgumentType {
|
||||
|
||||
fn list_suggestions(
|
||||
&self,
|
||||
_context: &CommandContext,
|
||||
_context: &CommandContext<S>,
|
||||
builder: SuggestionsBuilder,
|
||||
) -> Suggestions {
|
||||
builder.filter_and_suggest(&CATEGORIES).build()
|
||||
@ -62,7 +62,10 @@ impl ArgumentType for SoundCategoryArgumentType {
|
||||
}
|
||||
|
||||
impl SoundCategoryArgumentType {
|
||||
pub fn get(context: &CommandContext, name: &str) -> Result<SoundCategory, CommandSyntaxError> {
|
||||
pub fn get<S: crate::source::CommandSource>(
|
||||
context: &CommandContext<S>,
|
||||
name: &str,
|
||||
) -> Result<SoundCategory, CommandSyntaxError> {
|
||||
context.get_argument::<SoundCategory>(name).copied()
|
||||
}
|
||||
}
|
||||
@ -1,15 +1,15 @@
|
||||
use crate::command::argument_types::FromStringReader;
|
||||
use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
|
||||
use crate::command::context::command_context::CommandContext;
|
||||
use crate::command::errors::command_syntax_error::CommandSyntaxError;
|
||||
use crate::command::string_reader::StringReader;
|
||||
use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
|
||||
use crate::argument_types::FromStringReader;
|
||||
use crate::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
|
||||
use crate::context::command_context::CommandContext;
|
||||
use crate::errors::command_syntax_error::CommandSyntaxError;
|
||||
use crate::string_reader::StringReader;
|
||||
use crate::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
|
||||
use pumpkin_protocol::java::client::play::SuggestionProviders;
|
||||
use pumpkin_util::identifier::Identifier;
|
||||
|
||||
pub struct StructureNameArgumentType;
|
||||
|
||||
impl ArgumentType for StructureNameArgumentType {
|
||||
impl<S: crate::source::CommandSource> ArgumentType<S> for StructureNameArgumentType {
|
||||
type Item = Identifier;
|
||||
|
||||
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
|
||||
@ -18,7 +18,7 @@ impl ArgumentType for StructureNameArgumentType {
|
||||
|
||||
fn list_suggestions(
|
||||
&self,
|
||||
_context: &CommandContext,
|
||||
_context: &CommandContext<S>,
|
||||
builder: SuggestionsBuilder,
|
||||
) -> Suggestions {
|
||||
let names = pumpkin_world::generation::structure::template::all_structure_names();
|
||||
@ -1,9 +1,9 @@
|
||||
use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
|
||||
use crate::command::context::command_context::CommandContext;
|
||||
use crate::command::errors::command_syntax_error::CommandSyntaxError;
|
||||
use crate::command::errors::error_types::CommandErrorType;
|
||||
use crate::command::string_reader::StringReader;
|
||||
use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
|
||||
use crate::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
|
||||
use crate::context::command_context::CommandContext;
|
||||
use crate::errors::command_syntax_error::CommandSyntaxError;
|
||||
use crate::errors::error_types::CommandErrorType;
|
||||
use crate::string_reader::StringReader;
|
||||
use crate::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
|
||||
use pumpkin_data::translation::java::ARGUMENT_COLOR_INVALID;
|
||||
use pumpkin_util::text::TextComponent;
|
||||
use pumpkin_util::text::color::NamedColor;
|
||||
@ -13,7 +13,7 @@ pub const INVALID_VALUE_ERROR_TYPE: CommandErrorType<1> =
|
||||
|
||||
pub struct TeamColorArgumentType;
|
||||
|
||||
impl ArgumentType for TeamColorArgumentType {
|
||||
impl<S: crate::source::CommandSource> ArgumentType<S> for TeamColorArgumentType {
|
||||
type Item = NamedColor;
|
||||
|
||||
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
|
||||
@ -31,7 +31,7 @@ impl ArgumentType for TeamColorArgumentType {
|
||||
|
||||
fn list_suggestions(
|
||||
&self,
|
||||
_context: &CommandContext,
|
||||
_context: &CommandContext<S>,
|
||||
builder: SuggestionsBuilder,
|
||||
) -> Suggestions {
|
||||
builder
|
||||
@ -1,15 +1,15 @@
|
||||
use crate::command::argument_types::FromStringReader;
|
||||
use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
|
||||
use crate::command::context::command_context::CommandContext;
|
||||
use crate::command::errors::command_syntax_error::CommandSyntaxError;
|
||||
use crate::command::string_reader::StringReader;
|
||||
use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
|
||||
use crate::argument_types::FromStringReader;
|
||||
use crate::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
|
||||
use crate::context::command_context::CommandContext;
|
||||
use crate::errors::command_syntax_error::CommandSyntaxError;
|
||||
use crate::string_reader::StringReader;
|
||||
use crate::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
|
||||
use pumpkin_protocol::java::client::play::SuggestionProviders;
|
||||
use pumpkin_util::identifier::Identifier;
|
||||
|
||||
pub struct TemplateNameArgumentType;
|
||||
|
||||
impl ArgumentType for TemplateNameArgumentType {
|
||||
impl<S: crate::source::CommandSource> ArgumentType<S> for TemplateNameArgumentType {
|
||||
type Item = String;
|
||||
|
||||
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
|
||||
@ -19,7 +19,7 @@ impl ArgumentType for TemplateNameArgumentType {
|
||||
|
||||
fn list_suggestions(
|
||||
&self,
|
||||
_context: &CommandContext,
|
||||
_context: &CommandContext<S>,
|
||||
builder: SuggestionsBuilder,
|
||||
) -> Suggestions {
|
||||
let names = pumpkin_world::generation::structure::template::all_template_names();
|
||||
@ -1,9 +1,9 @@
|
||||
use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
|
||||
use crate::command::context::command_context::CommandContext;
|
||||
use crate::command::errors::command_syntax_error::CommandSyntaxError;
|
||||
use crate::command::errors::error_types::CommandErrorType;
|
||||
use crate::command::string_reader::StringReader;
|
||||
use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
|
||||
use crate::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
|
||||
use crate::context::command_context::CommandContext;
|
||||
use crate::errors::command_syntax_error::CommandSyntaxError;
|
||||
use crate::errors::error_types::CommandErrorType;
|
||||
use crate::string_reader::StringReader;
|
||||
use crate::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
|
||||
use pumpkin_data::translation;
|
||||
use pumpkin_util::text::TextComponent;
|
||||
|
||||
@ -43,7 +43,7 @@ impl TimeArgumentType {
|
||||
}
|
||||
}
|
||||
|
||||
impl ArgumentType for TimeArgumentType {
|
||||
impl<S: crate::source::CommandSource> ArgumentType<S> for TimeArgumentType {
|
||||
type Item = i32;
|
||||
|
||||
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
|
||||
@ -74,7 +74,7 @@ impl ArgumentType for TimeArgumentType {
|
||||
|
||||
fn list_suggestions(
|
||||
&self,
|
||||
_context: &CommandContext,
|
||||
_context: &CommandContext<S>,
|
||||
suggestions_builder: SuggestionsBuilder,
|
||||
) -> Suggestions {
|
||||
let mut reader = StringReader::new(suggestions_builder.remaining());
|
||||
@ -95,15 +95,18 @@ impl ArgumentType for TimeArgumentType {
|
||||
impl TimeArgumentType {
|
||||
/// Returns a [`CommandContext`]'s parsed time argument in the form
|
||||
/// of its duration, in ticks.
|
||||
pub fn get(context: &CommandContext, name: &str) -> Result<i32, CommandSyntaxError> {
|
||||
pub fn get<S: crate::source::CommandSource>(
|
||||
context: &CommandContext<S>,
|
||||
name: &str,
|
||||
) -> Result<i32, CommandSyntaxError> {
|
||||
Ok(*context.get_argument(name)?)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::command::{
|
||||
argument_types::{argument_type::ArgumentType, time, time::TimeArgumentType},
|
||||
use crate::{
|
||||
argument_types::{time, time::TimeArgumentType},
|
||||
string_reader::StringReader,
|
||||
};
|
||||
|
||||
@ -2,7 +2,7 @@ use pumpkin_data::translation::java::ARGUMENT_UUID_INVALID;
|
||||
use pumpkin_util::uuid::parse_uuid;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::command::{
|
||||
use crate::{
|
||||
argument_types::argument_type::{ArgumentType, JavaClientArgumentType},
|
||||
errors::{command_syntax_error::CommandSyntaxError, error_types::CommandErrorType},
|
||||
string_reader::StringReader,
|
||||
@ -15,7 +15,7 @@ pub const INVALID_UUID_ERROR_TYPE: CommandErrorType<0> =
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct UuidArgumentType;
|
||||
|
||||
impl ArgumentType for UuidArgumentType {
|
||||
impl<S: crate::source::CommandSource> ArgumentType<S> for UuidArgumentType {
|
||||
type Item = Uuid;
|
||||
|
||||
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
|
||||
@ -1,13 +1,11 @@
|
||||
use crate::command::context::command_source::{CommandSource, ReturnValue};
|
||||
use crate::command::context::string_range::StringRange;
|
||||
use crate::command::errors::command_syntax_error::CommandSyntaxError;
|
||||
use crate::command::errors::error_types::DISPATCHER_PARSE_EXCEPTION;
|
||||
use crate::command::node::attached::NodeId;
|
||||
use crate::command::node::dispatcher::{CommandDispatcher, ResultConsumer};
|
||||
use crate::command::node::tree::Tree;
|
||||
use crate::command::node::{Command, RedirectModifier};
|
||||
use crate::server::Server;
|
||||
use crate::world::World;
|
||||
use crate::context::string_range::StringRange;
|
||||
use crate::errors::command_syntax_error::CommandSyntaxError;
|
||||
use crate::errors::error_types::DISPATCHER_PARSE_EXCEPTION;
|
||||
use crate::node::attached::NodeId;
|
||||
use crate::node::dispatcher::{CommandDispatcher, ResultConsumer};
|
||||
use crate::node::tree::Tree;
|
||||
use crate::node::{Command, RedirectModifier};
|
||||
use crate::source::{CommandSource, DummySource, ReturnValue};
|
||||
use pumpkin_util::text::TextComponent;
|
||||
use rustc_hash::FxHashMap;
|
||||
use std::any::Any;
|
||||
@ -53,9 +51,9 @@ impl ParsedArgument {
|
||||
|
||||
/// Represents the context used when commands are run.
|
||||
#[derive(Clone)]
|
||||
pub struct CommandContext<'a> {
|
||||
pub struct CommandContext<'a, S: CommandSource = DummySource> {
|
||||
/// The source running the commands.
|
||||
pub source: Arc<CommandSource>,
|
||||
pub source: Arc<S>,
|
||||
|
||||
/// The input string ran as the command.
|
||||
pub input: String,
|
||||
@ -65,7 +63,7 @@ pub struct CommandContext<'a> {
|
||||
pub arguments: FxHashMap<String, Arc<ParsedArgument>>,
|
||||
|
||||
/// The tree this context is related to.
|
||||
pub tree: &'a Tree,
|
||||
pub tree: &'a Tree<S>,
|
||||
|
||||
/// The root that this context will use, bound to the tree.
|
||||
/// Not necessarily the root node of the tree, however.
|
||||
@ -81,20 +79,28 @@ pub struct CommandContext<'a> {
|
||||
pub child: Option<Arc<Self>>,
|
||||
|
||||
/// The redirect modifier of this context.
|
||||
pub modifier: RedirectModifier,
|
||||
pub modifier: RedirectModifier<S>,
|
||||
|
||||
/// Whether this context forks or not.
|
||||
pub forks: bool,
|
||||
|
||||
/// The command stored in this context which
|
||||
/// is run to get a command result.
|
||||
pub command: Option<Command>,
|
||||
pub command: Option<Command<S>>,
|
||||
}
|
||||
|
||||
impl CommandContext<'_> {
|
||||
impl<S: CommandSource> std::ops::Deref for CommandContext<'_, S> {
|
||||
type Target = S;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.source
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: CommandSource> CommandContext<'_, S> {
|
||||
/// Copies this context with the source provided.
|
||||
#[must_use]
|
||||
pub fn with_source(&self, source: Arc<CommandSource>) -> Self {
|
||||
pub fn with_source(&self, source: Arc<S>) -> Self {
|
||||
Self {
|
||||
source,
|
||||
input: self.input.clone(),
|
||||
@ -136,8 +142,8 @@ impl CommandContext<'_> {
|
||||
/// A simple example that takes two arguments specified from the node
|
||||
/// and returns their sum as the status output of the `Executor`:
|
||||
/// ```
|
||||
/// use pumpkin::command::context::command_context::CommandContext;
|
||||
/// use pumpkin::command::node::{CommandExecutor, CommandExecutorResult};
|
||||
/// use pumpkin_command::context::command_context::CommandContext;
|
||||
/// use pumpkin_command::node::{CommandExecutor, CommandExecutorResult};
|
||||
///
|
||||
/// struct Executor;
|
||||
/// impl CommandExecutor for Executor {
|
||||
@ -170,38 +176,29 @@ impl CommandContext<'_> {
|
||||
)))
|
||||
})
|
||||
}
|
||||
|
||||
/// Gets the server of this context.
|
||||
#[must_use]
|
||||
pub fn server(&self) -> &Arc<Server> {
|
||||
self.source.server()
|
||||
}
|
||||
|
||||
/// Gets the world of this context.
|
||||
#[must_use]
|
||||
pub fn world(&self) -> &Arc<World> {
|
||||
self.source.world()
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a linked chain of [`CommandContext`]s, where the previous links to the next as a child.
|
||||
#[derive(Clone)]
|
||||
pub struct ContextChain<'a> {
|
||||
pub struct ContextChain<'a, S: CommandSource = DummySource> {
|
||||
/// The modifiers of this context chain.
|
||||
modifiers: Vec<Arc<CommandContext<'a>>>,
|
||||
modifiers: Vec<Arc<CommandContext<'a, S>>>,
|
||||
|
||||
/// That specific [`CommandContext`] to execute.
|
||||
execute: Arc<CommandContext<'a>>,
|
||||
execute: Arc<CommandContext<'a, S>>,
|
||||
}
|
||||
|
||||
impl<'a> ContextChain<'a> {
|
||||
impl<'a, S: CommandSource> ContextChain<'a, S> {
|
||||
/// Creates a new chain of contexts from a vector of them and one to execute.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the `execute` given is non-executable.
|
||||
#[must_use]
|
||||
pub fn new(modifiers: Vec<Arc<CommandContext<'a>>>, execute: Arc<CommandContext<'a>>) -> Self {
|
||||
pub fn new(
|
||||
modifiers: Vec<Arc<CommandContext<'a, S>>>,
|
||||
execute: Arc<CommandContext<'a, S>>,
|
||||
) -> Self {
|
||||
assert!(
|
||||
execute.command.is_some(),
|
||||
"Expected last command in chain to be executable"
|
||||
@ -212,7 +209,7 @@ impl<'a> ContextChain<'a> {
|
||||
/// Tries to flatten a [`CommandContext`]. If no command
|
||||
/// is available at the end of chain, [`None`] is returned.
|
||||
#[must_use]
|
||||
pub fn try_flatten(root: &CommandContext<'a>) -> Option<Self> {
|
||||
pub fn try_flatten(root: &CommandContext<'a, S>) -> Option<Self> {
|
||||
let mut modifiers = Vec::new();
|
||||
let mut current = Arc::new(root.clone());
|
||||
loop {
|
||||
@ -231,11 +228,11 @@ impl<'a> ContextChain<'a> {
|
||||
|
||||
/// Runs the given modifier with provided details.
|
||||
pub fn run_modifier(
|
||||
modifier: &CommandContext<'a>,
|
||||
source: &Arc<CommandSource>,
|
||||
result_consumer: &dyn ResultConsumer,
|
||||
modifier: &CommandContext<'a, S>,
|
||||
source: &Arc<S>,
|
||||
result_consumer: &dyn ResultConsumer<S>,
|
||||
forked_mode: bool,
|
||||
) -> Result<Vec<Arc<CommandSource>>, CommandSyntaxError> {
|
||||
) -> Result<Vec<Arc<S>>, CommandSyntaxError> {
|
||||
let source_modifier = &modifier.modifier;
|
||||
|
||||
if matches!(source_modifier, RedirectModifier::KeepSource) {
|
||||
@ -261,9 +258,9 @@ impl<'a> ContextChain<'a> {
|
||||
///
|
||||
/// Panics if the `executable` provided cannot be executed.
|
||||
pub fn run_executable(
|
||||
executable: &CommandContext<'a>,
|
||||
source: &Arc<CommandSource>,
|
||||
result_consumer: &dyn ResultConsumer,
|
||||
executable: &CommandContext<'a, S>,
|
||||
source: &Arc<S>,
|
||||
result_consumer: &dyn ResultConsumer<S>,
|
||||
forked_mode: bool,
|
||||
) -> Result<i32, CommandSyntaxError> {
|
||||
let context_to_use = executable.with_source(source.clone());
|
||||
@ -288,15 +285,15 @@ impl<'a> ContextChain<'a> {
|
||||
/// Executes all contexts in the chain, returning the ultimate result.
|
||||
pub fn execute_all(
|
||||
&self,
|
||||
source: &Arc<CommandSource>,
|
||||
result_consumer: &dyn ResultConsumer,
|
||||
source: &Arc<S>,
|
||||
result_consumer: &dyn ResultConsumer<S>,
|
||||
) -> Result<i32, CommandSyntaxError> {
|
||||
if self.modifiers.is_empty() {
|
||||
return Self::run_executable(&self.execute, source, result_consumer, false);
|
||||
}
|
||||
|
||||
let mut forked_mode = false;
|
||||
let mut current_sources: Vec<Arc<CommandSource>> = vec![source.clone()];
|
||||
let mut current_sources: Vec<Arc<S>> = vec![source.clone()];
|
||||
|
||||
for modifier in &self.modifiers {
|
||||
forked_mode |= modifier.forks;
|
||||
@ -338,7 +335,7 @@ impl<'a> ContextChain<'a> {
|
||||
|
||||
/// Gets a reference to the top context of this chain.
|
||||
#[must_use]
|
||||
pub fn get_top_context(&'_ self) -> &'_ Arc<CommandContext<'_>> {
|
||||
pub fn get_top_context(&'_ self) -> &'_ Arc<CommandContext<'a, S>> {
|
||||
if self.modifiers.is_empty() {
|
||||
&self.execute
|
||||
} else {
|
||||
@ -347,7 +344,7 @@ impl<'a> ContextChain<'a> {
|
||||
}
|
||||
|
||||
/// Gets a mutable reference to the top context of this chain.
|
||||
pub fn get_top_context_mut(&'_ mut self) -> &mut Arc<CommandContext<'a>> {
|
||||
pub fn get_top_context_mut(&'_ mut self) -> &mut Arc<CommandContext<'a, S>> {
|
||||
if self.modifiers.is_empty() {
|
||||
&mut self.execute
|
||||
} else {
|
||||
@ -373,12 +370,12 @@ impl<'a> ContextChain<'a> {
|
||||
///
|
||||
/// This builder's lifetime is bound to the dispatcher provided to it.
|
||||
#[derive(Clone)]
|
||||
pub struct CommandContextBuilder<'a> {
|
||||
pub struct CommandContextBuilder<'a, S: CommandSource = DummySource> {
|
||||
/// The dispatcher this builder is related to.
|
||||
pub dispatcher: &'a CommandDispatcher,
|
||||
pub dispatcher: &'a CommandDispatcher<S>,
|
||||
|
||||
/// The source running the commands.
|
||||
pub source: Arc<CommandSource>,
|
||||
pub source: Arc<S>,
|
||||
|
||||
/// Arguments which have been parsed and
|
||||
/// can be fetched for command execution.
|
||||
@ -398,24 +395,24 @@ pub struct CommandContextBuilder<'a> {
|
||||
pub child: Option<Box<Self>>,
|
||||
|
||||
/// The redirect modifier of this context.
|
||||
pub modifier: RedirectModifier,
|
||||
pub modifier: RedirectModifier<S>,
|
||||
|
||||
/// Whether this context forks or not.
|
||||
pub forks: bool,
|
||||
|
||||
/// The command stored in this context which
|
||||
/// is run to get a command result.
|
||||
pub command: Option<Command>,
|
||||
pub command: Option<Command<S>>,
|
||||
}
|
||||
|
||||
impl<'a> CommandContextBuilder<'a> {
|
||||
impl<'a, S: CommandSource> CommandContextBuilder<'a, S> {
|
||||
/// Creates a new [`CommandContextBuilder`] from the properties required to initialize one.
|
||||
///
|
||||
/// Note that builder's lifetime is bound to the dispatcher provided to it.
|
||||
#[must_use]
|
||||
pub fn new(
|
||||
dispatcher: &'a CommandDispatcher,
|
||||
source: Arc<CommandSource>,
|
||||
dispatcher: &'a CommandDispatcher<S>,
|
||||
source: Arc<S>,
|
||||
root: NodeId,
|
||||
start: usize,
|
||||
) -> Self {
|
||||
@ -435,7 +432,7 @@ impl<'a> CommandContextBuilder<'a> {
|
||||
|
||||
/// Builds the required [`CommandContext`], consuming itself in the process.
|
||||
#[must_use]
|
||||
pub fn build(self, input: &str) -> CommandContext<'a> {
|
||||
pub fn build(self, input: &str) -> CommandContext<'a, S> {
|
||||
CommandContext {
|
||||
source: self.source,
|
||||
input: input.to_string(),
|
||||
@ -452,7 +449,7 @@ impl<'a> CommandContextBuilder<'a> {
|
||||
}
|
||||
|
||||
/// Mutates itself with the new source set.
|
||||
pub fn with_source(&mut self, source: Arc<CommandSource>) {
|
||||
pub fn with_source(&mut self, source: Arc<S>) {
|
||||
self.source = source;
|
||||
}
|
||||
|
||||
@ -462,7 +459,7 @@ impl<'a> CommandContextBuilder<'a> {
|
||||
}
|
||||
|
||||
/// Mutates itself with the new command set.
|
||||
pub fn with_command(&mut self, command: Option<Command>) {
|
||||
pub fn with_command(&mut self, command: Option<Command<S>>) {
|
||||
self.command = command;
|
||||
}
|
||||
|
||||
@ -538,18 +535,16 @@ impl<'a> CommandContextBuilder<'a> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::command::argument_builder::{ArgumentBuilder, CommandArgumentBuilder};
|
||||
use crate::command::context::command_context::{
|
||||
use crate::argument_builder::{ArgumentBuilder, CommandArgumentBuilder};
|
||||
use crate::context::command_context::{
|
||||
CommandContext, CommandContextBuilder, ContextChain, ParsedArgument, Stage,
|
||||
};
|
||||
use crate::command::context::command_source::CommandSource;
|
||||
use crate::command::context::string_range::StringRange;
|
||||
use crate::command::errors::command_syntax_error::CommandSyntaxError;
|
||||
use crate::command::node::dispatcher::{CommandDispatcher, EmptyResultConsumer};
|
||||
use crate::command::node::tree::ROOT_NODE_ID;
|
||||
use crate::command::node::{
|
||||
CommandExecutor, CommandExecutorResult, RedirectModifier, Redirection,
|
||||
};
|
||||
use crate::context::string_range::StringRange;
|
||||
use crate::errors::command_syntax_error::CommandSyntaxError;
|
||||
use crate::node::dispatcher::{CommandDispatcher, EmptyResultConsumer};
|
||||
use crate::node::tree::ROOT_NODE_ID;
|
||||
use crate::node::{CommandExecutor, CommandExecutorResult, RedirectModifier, Redirection};
|
||||
use crate::source::DummySource;
|
||||
use pumpkin_util::math::vector3::Vector3;
|
||||
use std::sync::Arc;
|
||||
|
||||
@ -562,12 +557,8 @@ mod test {
|
||||
|
||||
// For testing purposes
|
||||
fn builder(dispatcher: &'_ CommandDispatcher) -> CommandContextBuilder<'_> {
|
||||
let mut builder = CommandContextBuilder::new(
|
||||
dispatcher,
|
||||
Arc::new(CommandSource::dummy()),
|
||||
ROOT_NODE_ID,
|
||||
0,
|
||||
);
|
||||
let mut builder =
|
||||
CommandContextBuilder::new(dispatcher, Arc::new(DummySource::dummy()), ROOT_NODE_ID, 0);
|
||||
|
||||
let parsed_argument = ParsedArgument::new(StringRange::between(0, 1), Box::new(6789i32));
|
||||
|
||||
@ -611,7 +602,7 @@ mod test {
|
||||
dispatcher
|
||||
.register(CommandArgumentBuilder::new("foo", "A test command").executes(TenExecutor));
|
||||
|
||||
let source = Arc::new(CommandSource::dummy());
|
||||
let source = Arc::new(DummySource::dummy());
|
||||
let result = dispatcher.parse_input("foo", &source);
|
||||
let top_context = result.context.build("foo");
|
||||
let chain = ContextChain::try_flatten(&top_context)
|
||||
@ -629,7 +620,7 @@ mod test {
|
||||
CommandArgumentBuilder::new("bar", "Another test command").redirect(Redirection::Root),
|
||||
);
|
||||
|
||||
let source = Arc::new(CommandSource::dummy());
|
||||
let source = Arc::new(DummySource::dummy());
|
||||
let result = dispatcher.parse_input("bar foo", &source);
|
||||
let top_context = result.context.build("bar foo");
|
||||
let chain = ContextChain::try_flatten(&top_context)
|
||||
@ -644,7 +635,7 @@ mod test {
|
||||
dispatcher
|
||||
.register(CommandArgumentBuilder::new("foo", "A test command").executes(TenExecutor));
|
||||
|
||||
let source = Arc::new(CommandSource::dummy());
|
||||
let source = Arc::new(DummySource::dummy());
|
||||
let result = dispatcher.parse_input("foo", &source);
|
||||
let top_context = result.context.build("foo");
|
||||
let chain = ContextChain::try_flatten(&top_context)
|
||||
@ -667,7 +658,7 @@ mod test {
|
||||
.redirect(Redirection::Root),
|
||||
);
|
||||
|
||||
let source = Arc::new(CommandSource::dummy());
|
||||
let source = Arc::new(DummySource::dummy());
|
||||
let result = dispatcher.parse_input("bar qux foo", &source);
|
||||
let top_context = result.context.build("bar qux foo");
|
||||
let chain = ContextChain::try_flatten(&top_context)
|
||||
@ -691,7 +682,7 @@ mod test {
|
||||
let mut dispatcher = CommandDispatcher::new();
|
||||
dispatcher.register(CommandArgumentBuilder::new("foo", "A test command"));
|
||||
|
||||
let source = Arc::new(CommandSource::dummy());
|
||||
let source = DummySource::dummy();
|
||||
let result = dispatcher.parse_input("foo", &source);
|
||||
let top_context = result.context.build("foo");
|
||||
|
||||
@ -716,15 +707,15 @@ mod test {
|
||||
dispatcher.register(
|
||||
CommandArgumentBuilder::new("bar", "Another test command").redirect_with_modifier(
|
||||
Redirection::Root,
|
||||
RedirectModifier::Custom(Arc::new(|context| {
|
||||
RedirectModifier::Custom(Arc::new(|context: &CommandContext<DummySource>| {
|
||||
let mut new_source = context.source.as_ref().clone();
|
||||
new_source.position = Vector3::new(0f64, 10f64, 0f64);
|
||||
Ok(vec![Arc::new(new_source)])
|
||||
})),
|
||||
),
|
||||
);
|
||||
let source = Arc::new(CommandSource::dummy());
|
||||
let result = dispatcher.parse_input("bar foo", &source);
|
||||
let source = Arc::new(DummySource::dummy());
|
||||
let result = dispatcher.parse_input("bar foo", source.as_ref());
|
||||
let top_context = result.context.build("bar foo");
|
||||
let chain = ContextChain::try_flatten(&top_context)
|
||||
.expect("The context should have properly flattened, as it has a command to execute");
|
||||
5
crates/pumpkin-command/src/context/mod.rs
Normal file
5
crates/pumpkin-command/src/context/mod.rs
Normal file
@ -0,0 +1,5 @@
|
||||
pub mod command_context;
|
||||
pub mod string_range;
|
||||
|
||||
pub use command_context::*;
|
||||
pub use string_range::*;
|
||||
76
crates/pumpkin-command/src/context/string_range.rs
Normal file
76
crates/pumpkin-command/src/context/string_range.rs
Normal file
@ -0,0 +1,76 @@
|
||||
use std::ops::Range;
|
||||
|
||||
use crate::string_reader::StringReader;
|
||||
|
||||
/// Indicates a range that is effectively
|
||||
/// a substring of a string from its `start`
|
||||
/// and `end` byte-indices.
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct StringRange {
|
||||
pub start: usize,
|
||||
pub end: usize,
|
||||
}
|
||||
|
||||
impl StringRange {
|
||||
/// Constructs a new substring range, with indices
|
||||
/// inclusive to `start`, but exclusive to `end`.
|
||||
#[must_use]
|
||||
pub const fn between(start: usize, end: usize) -> Self {
|
||||
Self { start, end }
|
||||
}
|
||||
|
||||
/// Constructs an empty range on the left side of
|
||||
/// one character with index `pos`.
|
||||
#[must_use]
|
||||
pub const fn at(pos: usize) -> Self {
|
||||
Self::between(pos, pos)
|
||||
}
|
||||
|
||||
/// Constructs a new substring range encompassing two
|
||||
/// [`StringRange`]s, returning a new one that covers
|
||||
/// both the required ranges.
|
||||
#[must_use]
|
||||
pub fn encompass(a: Self, b: Self) -> Self {
|
||||
Self::between(a.start.min(b.start), a.end.max(b.end))
|
||||
}
|
||||
|
||||
/// Gets a [`str`] substring slice bound
|
||||
/// to a [`StringReader`] from this range.
|
||||
#[must_use]
|
||||
pub fn slice_from_reader<'a>(&self, reader: &'a StringReader) -> &'a str {
|
||||
&reader.string()[self.start..self.end]
|
||||
}
|
||||
|
||||
/// Gets a [`str`] substring slice bound
|
||||
/// to a [`String`] from this range.
|
||||
#[must_use]
|
||||
pub fn substring_slice<'a>(&self, string: &'a str) -> &'a str {
|
||||
&string[self.start..self.end]
|
||||
}
|
||||
|
||||
/// Returns if the length of this range is zero.
|
||||
#[must_use]
|
||||
#[inline]
|
||||
pub const fn is_empty(&self) -> bool {
|
||||
self.start == self.end
|
||||
}
|
||||
|
||||
/// Returns the length of this range.
|
||||
#[must_use]
|
||||
#[inline]
|
||||
pub const fn len(&self) -> usize {
|
||||
self.end - self.start
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Range<usize>> for StringRange {
|
||||
fn from(value: Range<usize>) -> Self {
|
||||
Self::between(value.start, value.end)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<StringRange> for Range<usize> {
|
||||
fn from(value: StringRange) -> Self {
|
||||
value.start..value.end
|
||||
}
|
||||
}
|
||||
1
crates/pumpkin-command/src/dispatcher.rs
Normal file
1
crates/pumpkin-command/src/dispatcher.rs
Normal file
@ -0,0 +1 @@
|
||||
pub use crate::node::dispatcher::*;
|
||||
@ -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)]
|
||||
@ -113,7 +113,7 @@ pub const DISPATCHER_PARSE_EXCEPTION: CommandErrorType<1> = CommandErrorType::ne
|
||||
translation::bedrock::COMMANDS_GENERIC_SYNTAX,
|
||||
);
|
||||
|
||||
use crate::command::errors::{
|
||||
use crate::errors::{
|
||||
command_syntax_error::{CommandSyntaxError, ContextProvider},
|
||||
error_types::sealed::Sealed,
|
||||
};
|
||||
@ -163,6 +163,7 @@ impl<const N: usize> CommandErrorType<N> {
|
||||
|
||||
/// Creates an error without context from itself and from a slice of `N` arguments.
|
||||
#[must_use]
|
||||
#[expect(deprecated)]
|
||||
pub fn create_without_context_args_slice(
|
||||
&'static self,
|
||||
args: &[TextComponent; N],
|
||||
@ -178,6 +179,7 @@ impl<const N: usize> CommandErrorType<N> {
|
||||
}
|
||||
|
||||
/// Creates an error with context from itself and from a slice of `N` arguments.
|
||||
#[expect(deprecated)]
|
||||
pub fn create_args_slice<C>(
|
||||
&'static self,
|
||||
context_provider: &C,
|
||||
@ -344,8 +346,8 @@ error_type_no_arg_slice_impl!(N = 4 => arg1, arg2, arg3, arg4 | "by taking 4 tra
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::command::errors::error_types::{CommandErrorType, LiteralCommandErrorType};
|
||||
use crate::command::string_reader::StringReader;
|
||||
use crate::errors::error_types::{CommandErrorType, LiteralCommandErrorType};
|
||||
use crate::string_reader::StringReader;
|
||||
use pumpkin_util::text::TextComponent;
|
||||
|
||||
const TEST_LITERAL_ERROR_TYPE: LiteralCommandErrorType =
|
||||
@ -382,6 +384,7 @@ mod test {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[expect(deprecated)]
|
||||
fn create_translatable_error() {
|
||||
let mut reader = StringReader::new("foo bar");
|
||||
reader.set_cursor(4);
|
||||
20
crates/pumpkin-command/src/lib.rs
Normal file
20
crates/pumpkin-command/src/lib.rs
Normal file
@ -0,0 +1,20 @@
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||
|
||||
pub mod argument_builder;
|
||||
pub mod argument_types;
|
||||
pub mod context;
|
||||
pub mod dispatcher;
|
||||
pub mod errors;
|
||||
pub mod node;
|
||||
pub mod parser;
|
||||
pub mod snbt;
|
||||
pub mod source;
|
||||
pub mod string_reader;
|
||||
pub mod suggestion;
|
||||
|
||||
pub use argument_builder::{ArgumentBuilder, CommandArgumentBuilder, LiteralArgumentBuilder};
|
||||
pub use argument_types::argument_type::{AnyArgumentType, ArgumentType};
|
||||
pub use context::command_context::CommandContext;
|
||||
pub use node::dispatcher::CommandDispatcher;
|
||||
pub use node::{Command, CommandExecutor, CommandExecutorResult, Requirement};
|
||||
pub use source::CommandSource;
|
||||
@ -1,13 +1,14 @@
|
||||
use crate::command::context::string_range::StringRange;
|
||||
use crate::command::errors::command_syntax_error::CommandSyntaxError;
|
||||
use crate::command::errors::error_types::LITERAL_INCORRECT;
|
||||
use crate::command::node::detached::GlobalNodeId;
|
||||
use crate::command::node::tree::ROOT_NODE_ID;
|
||||
use crate::command::node::{
|
||||
use crate::context::string_range::StringRange;
|
||||
use crate::errors::command_syntax_error::CommandSyntaxError;
|
||||
use crate::errors::error_types::LITERAL_INCORRECT;
|
||||
use crate::node::detached::GlobalNodeId;
|
||||
use crate::node::tree::ROOT_NODE_ID;
|
||||
use crate::node::{
|
||||
ArgumentNodeMetadata, Command, CommandNodeMetadata, LiteralNodeMetadata, NodeMetadata,
|
||||
OwnedNodeData, RedirectModifier, Redirection, Requirements,
|
||||
};
|
||||
use crate::command::string_reader::StringReader;
|
||||
use crate::source::{CommandSource, DummySource};
|
||||
use crate::string_reader::StringReader;
|
||||
use pumpkin_util::text::TextComponent;
|
||||
use rustc_hash::FxHashMap;
|
||||
use std::num::NonZero;
|
||||
@ -70,18 +71,18 @@ impl From<ArgumentNodeId> for NodeId {
|
||||
|
||||
/// Represents a node which has been attached as the root of a `Tree`.
|
||||
#[derive(Clone)]
|
||||
pub struct RootAttachedNode {
|
||||
pub owned: OwnedNodeData,
|
||||
pub struct RootAttachedNode<S: CommandSource = DummySource> {
|
||||
pub owned: OwnedNodeData<S>,
|
||||
pub children: FxHashMap<String, NodeId>,
|
||||
}
|
||||
|
||||
impl Default for RootAttachedNode {
|
||||
impl<S: CommandSource> Default for RootAttachedNode<S> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl RootAttachedNode {
|
||||
impl<S: CommandSource> RootAttachedNode<S> {
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
@ -100,8 +101,8 @@ impl RootAttachedNode {
|
||||
/// Represents a literal, non-command node that has already been attached
|
||||
/// to a `Tree`.
|
||||
#[derive(Clone)]
|
||||
pub struct LiteralAttachedNode {
|
||||
pub owned: OwnedNodeData,
|
||||
pub struct LiteralAttachedNode<S: CommandSource = DummySource> {
|
||||
pub owned: OwnedNodeData<S>,
|
||||
pub children: FxHashMap<String, NodeId>,
|
||||
pub redirect: Option<Redirection>,
|
||||
pub meta: LiteralNodeMetadata,
|
||||
@ -110,8 +111,8 @@ pub struct LiteralAttachedNode {
|
||||
/// Represents a literal, command node that has already been attached
|
||||
/// to a `Tree`.
|
||||
#[derive(Clone)]
|
||||
pub struct CommandAttachedNode {
|
||||
pub owned: OwnedNodeData,
|
||||
pub struct CommandAttachedNode<S: CommandSource = DummySource> {
|
||||
pub owned: OwnedNodeData<S>,
|
||||
pub children: FxHashMap<String, NodeId>,
|
||||
pub redirect: Option<Redirection>,
|
||||
pub meta: CommandNodeMetadata,
|
||||
@ -120,11 +121,11 @@ pub struct CommandAttachedNode {
|
||||
/// Represents a node that accepts a specific type of argument that has already been attached
|
||||
/// to a `Tree`.
|
||||
#[derive(Clone)]
|
||||
pub struct ArgumentAttachedNode {
|
||||
pub owned: OwnedNodeData,
|
||||
pub struct ArgumentAttachedNode<S: CommandSource = DummySource> {
|
||||
pub owned: OwnedNodeData<S>,
|
||||
pub children: FxHashMap<String, NodeId>,
|
||||
pub redirect: Option<Redirection>,
|
||||
pub meta: ArgumentNodeMetadata,
|
||||
pub meta: ArgumentNodeMetadata<S>,
|
||||
}
|
||||
|
||||
/// Allows a way to store the kind of node
|
||||
@ -139,24 +140,24 @@ pub enum NodeClassification {
|
||||
|
||||
/// Represents a node not attached to a `Tree` yet.
|
||||
#[derive(Clone)]
|
||||
pub enum AttachedNode {
|
||||
Root(RootAttachedNode),
|
||||
Literal(LiteralAttachedNode),
|
||||
Command(CommandAttachedNode),
|
||||
Argument(ArgumentAttachedNode),
|
||||
pub enum AttachedNode<S: CommandSource = DummySource> {
|
||||
Root(RootAttachedNode<S>),
|
||||
Literal(LiteralAttachedNode<S>),
|
||||
Command(CommandAttachedNode<S>),
|
||||
Argument(ArgumentAttachedNode<S>),
|
||||
}
|
||||
|
||||
/// Returned when a literal could not be parsed.
|
||||
pub struct CouldNotParseLiteral;
|
||||
|
||||
impl AttachedNode {
|
||||
impl<S: CommandSource> AttachedNode<S> {
|
||||
/// Creates an [`AttachedNode`] from its properties allowing any [`NodeMetadata`].
|
||||
#[must_use]
|
||||
pub fn from_parts(
|
||||
owned: OwnedNodeData,
|
||||
owned: OwnedNodeData<S>,
|
||||
children: FxHashMap<String, NodeId>,
|
||||
redirect: Option<Redirection>,
|
||||
meta: NodeMetadata,
|
||||
meta: NodeMetadata<S>,
|
||||
) -> Self {
|
||||
match meta {
|
||||
NodeMetadata::Root => Self::Root(RootAttachedNode { owned, children }),
|
||||
@ -201,7 +202,7 @@ impl AttachedNode {
|
||||
|
||||
/// Gets a reference to the owned data of this node.
|
||||
#[must_use]
|
||||
pub const fn owned_node_data_ref(&self) -> &OwnedNodeData {
|
||||
pub const fn owned_node_data_ref(&self) -> &OwnedNodeData<S> {
|
||||
match self {
|
||||
Self::Root(node) => &node.owned,
|
||||
Self::Literal(node) => &node.owned,
|
||||
@ -211,7 +212,7 @@ impl AttachedNode {
|
||||
}
|
||||
|
||||
/// Gets a mutable reference to the owned data of this node.
|
||||
pub const fn owned_node_data_mut_ref(&mut self) -> &mut OwnedNodeData {
|
||||
pub const fn owned_node_data_mut_ref(&mut self) -> &mut OwnedNodeData<S> {
|
||||
match self {
|
||||
Self::Root(node) => &mut node.owned,
|
||||
Self::Literal(node) => &mut node.owned,
|
||||
@ -285,23 +286,23 @@ impl AttachedNode {
|
||||
|
||||
/// Gets all the requirements required for this node to be run.
|
||||
#[must_use]
|
||||
pub const fn requirements(&self) -> &Requirements {
|
||||
pub const fn requirements(&self) -> &Requirements<S> {
|
||||
&self.owned_node_data_ref().requirements
|
||||
}
|
||||
|
||||
/// Overrides all the requirements for this node to be run to a value.
|
||||
pub fn set_requirement(&mut self, requirements: Requirements) {
|
||||
pub fn set_requirement(&mut self, requirements: Requirements<S>) {
|
||||
self.owned_node_data_mut_ref().requirements = requirements;
|
||||
}
|
||||
|
||||
/// Gets the modifier for this node to be run.
|
||||
#[must_use]
|
||||
pub const fn modifier(&self) -> &RedirectModifier {
|
||||
pub const fn modifier(&self) -> &RedirectModifier<S> {
|
||||
&self.owned_node_data_ref().modifier
|
||||
}
|
||||
|
||||
/// Sets the modifier for this node to a value.
|
||||
pub fn set_modifier(&mut self, modifier: RedirectModifier) {
|
||||
pub fn set_modifier(&mut self, modifier: RedirectModifier<S>) {
|
||||
self.owned_node_data_mut_ref().modifier = modifier;
|
||||
}
|
||||
|
||||
@ -318,12 +319,12 @@ impl AttachedNode {
|
||||
|
||||
/// Gets the executable command for this node.
|
||||
#[must_use]
|
||||
pub fn command(&self) -> &Option<Command> {
|
||||
pub fn command(&self) -> &Option<Command<S>> {
|
||||
&self.owned_node_data_ref().command
|
||||
}
|
||||
|
||||
/// Sets the executable command for this node.
|
||||
pub fn set_command(&mut self, command: Option<Command>) {
|
||||
pub fn set_command(&mut self, command: Option<Command<S>>) {
|
||||
self.owned_node_data_mut_ref().command = command;
|
||||
}
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
use crate::command::argument_types::argument_type::AnyArgumentType;
|
||||
use crate::command::node::{
|
||||
use crate::argument_types::argument_type::AnyArgumentType;
|
||||
use crate::node::{
|
||||
ArgumentNodeMetadata, Command, CommandNodeMetadata, LiteralNodeMetadata, NodeMetadata,
|
||||
OwnedNodeData, RedirectModifier, Redirection, Requirements,
|
||||
};
|
||||
use crate::command::suggestion::provider::SuggestionProvider;
|
||||
use crate::source::{CommandSource, DummySource};
|
||||
use crate::suggestion::provider::SuggestionProvider;
|
||||
use rustc_hash::FxHashMap;
|
||||
use std::borrow::Cow;
|
||||
use std::num::NonZero;
|
||||
@ -44,26 +45,26 @@ impl Default for GlobalNodeId {
|
||||
///
|
||||
/// To be of any utility, this must be attached to a tree later.
|
||||
#[derive(Clone)]
|
||||
pub struct LiteralDetachedNode {
|
||||
pub owned: OwnedNodeData,
|
||||
pub children: FxHashMap<String, DetachedNode>,
|
||||
pub struct LiteralDetachedNode<S: CommandSource = DummySource> {
|
||||
pub owned: OwnedNodeData<S>,
|
||||
pub children: FxHashMap<String, DetachedNode<S>>,
|
||||
pub redirect: Option<Redirection>,
|
||||
pub meta: LiteralNodeMetadata,
|
||||
}
|
||||
|
||||
impl LiteralDetachedNode {
|
||||
impl<S: CommandSource> LiteralDetachedNode<S> {
|
||||
/// Creates a detached literal node from its properties,
|
||||
/// without any children.
|
||||
///
|
||||
/// # Note
|
||||
/// Prefer using the [`literal`](crate::command::argument_builder::literal) function over this one.
|
||||
/// Prefer using the [`literal`](crate::argument_builder::literal) function over this one.
|
||||
pub fn new(
|
||||
global_id: GlobalNodeId,
|
||||
literal: impl Into<Cow<'static, str>>,
|
||||
command: Option<Command>,
|
||||
requirements: Requirements,
|
||||
command: Option<Command<S>>,
|
||||
requirements: Requirements<S>,
|
||||
redirect: Option<Redirection>,
|
||||
modifier: RedirectModifier,
|
||||
modifier: RedirectModifier<S>,
|
||||
forks: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
@ -88,28 +89,28 @@ impl LiteralDetachedNode {
|
||||
///
|
||||
/// To be of any utility, this must be attached to a tree later.
|
||||
#[derive(Clone)]
|
||||
pub struct CommandDetachedNode {
|
||||
pub owned: OwnedNodeData,
|
||||
pub children: FxHashMap<String, DetachedNode>,
|
||||
pub struct CommandDetachedNode<S: CommandSource = DummySource> {
|
||||
pub owned: OwnedNodeData<S>,
|
||||
pub children: FxHashMap<String, DetachedNode<S>>,
|
||||
pub redirect: Option<Redirection>,
|
||||
pub meta: CommandNodeMetadata,
|
||||
}
|
||||
|
||||
impl CommandDetachedNode {
|
||||
impl<S: CommandSource> CommandDetachedNode<S> {
|
||||
/// Creates a detached literal node from its properties,
|
||||
/// without any children.
|
||||
///
|
||||
/// # Note
|
||||
/// Prefer using the [`command`](crate::command::argument_builder::command) function over this one.
|
||||
/// Prefer using the [`command`](crate::argument_builder::command) function over this one.
|
||||
#[expect(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
global_id: GlobalNodeId,
|
||||
literal: impl Into<Cow<'static, str>>,
|
||||
description: impl Into<Cow<'static, str>>,
|
||||
command: Option<Command>,
|
||||
requirements: Requirements,
|
||||
command: Option<Command<S>>,
|
||||
requirements: Requirements<S>,
|
||||
redirect: Option<Redirection>,
|
||||
modifier: RedirectModifier,
|
||||
modifier: RedirectModifier<S>,
|
||||
forks: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
@ -131,30 +132,30 @@ impl CommandDetachedNode {
|
||||
///
|
||||
/// To be of any utility, this must be attached to a tree later.
|
||||
#[derive(Clone)]
|
||||
pub struct ArgumentDetachedNode {
|
||||
pub owned: OwnedNodeData,
|
||||
pub children: FxHashMap<String, DetachedNode>,
|
||||
pub struct ArgumentDetachedNode<S: CommandSource = DummySource> {
|
||||
pub owned: OwnedNodeData<S>,
|
||||
pub children: FxHashMap<String, DetachedNode<S>>,
|
||||
pub redirect: Option<Redirection>,
|
||||
pub meta: ArgumentNodeMetadata,
|
||||
pub meta: ArgumentNodeMetadata<S>,
|
||||
}
|
||||
|
||||
impl ArgumentDetachedNode {
|
||||
impl<S: CommandSource> ArgumentDetachedNode<S> {
|
||||
/// Creates a detached argument node from its properties,
|
||||
/// without any children.
|
||||
///
|
||||
/// # Note
|
||||
/// Prefer using the [`argument`](crate::command::argument_builder::argument) function over this one.
|
||||
/// Prefer using the [`argument`](crate::argument_builder::argument) function over this one.
|
||||
#[expect(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
global_id: GlobalNodeId,
|
||||
name: impl Into<Cow<'static, str>>,
|
||||
argument_type: Arc<dyn AnyArgumentType>,
|
||||
command: Option<Command>,
|
||||
requirements: Requirements,
|
||||
argument_type: Arc<dyn AnyArgumentType<S>>,
|
||||
command: Option<Command<S>>,
|
||||
requirements: Requirements<S>,
|
||||
redirect: Option<Redirection>,
|
||||
modifier: RedirectModifier,
|
||||
modifier: RedirectModifier<S>,
|
||||
forks: bool,
|
||||
suggestion_provider: Option<Arc<dyn SuggestionProvider>>,
|
||||
suggestion_provider: Option<Arc<dyn SuggestionProvider<S>>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
owned: OwnedNodeData {
|
||||
@ -173,45 +174,45 @@ impl ArgumentDetachedNode {
|
||||
|
||||
/// Represents a node not attached to a `Tree` yet.
|
||||
#[derive(Clone)]
|
||||
pub enum DetachedNode {
|
||||
Literal(LiteralDetachedNode),
|
||||
Command(CommandDetachedNode),
|
||||
Argument(ArgumentDetachedNode),
|
||||
pub enum DetachedNode<S: CommandSource = DummySource> {
|
||||
Literal(LiteralDetachedNode<S>),
|
||||
Command(CommandDetachedNode<S>),
|
||||
Argument(ArgumentDetachedNode<S>),
|
||||
}
|
||||
|
||||
/// Represents a [`DetachedNode`] that has been irreversibly
|
||||
/// decomposed into its elements so that it can be recast
|
||||
/// into a new `AttachedNode`.
|
||||
pub struct DecomposedNode {
|
||||
pub owned: OwnedNodeData,
|
||||
pub children: FxHashMap<String, DetachedNode>,
|
||||
pub struct DecomposedNode<S: CommandSource = DummySource> {
|
||||
pub owned: OwnedNodeData<S>,
|
||||
pub children: FxHashMap<String, DetachedNode<S>>,
|
||||
pub redirect: Option<Redirection>,
|
||||
pub meta: NodeMetadata,
|
||||
pub meta: NodeMetadata<S>,
|
||||
}
|
||||
|
||||
impl From<LiteralDetachedNode> for DetachedNode {
|
||||
fn from(node: LiteralDetachedNode) -> Self {
|
||||
impl<S: CommandSource> From<LiteralDetachedNode<S>> for DetachedNode<S> {
|
||||
fn from(node: LiteralDetachedNode<S>) -> Self {
|
||||
Self::Literal(node)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CommandDetachedNode> for DetachedNode {
|
||||
fn from(node: CommandDetachedNode) -> Self {
|
||||
impl<S: CommandSource> From<CommandDetachedNode<S>> for DetachedNode<S> {
|
||||
fn from(node: CommandDetachedNode<S>) -> Self {
|
||||
Self::Command(node)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ArgumentDetachedNode> for DetachedNode {
|
||||
fn from(node: ArgumentDetachedNode) -> Self {
|
||||
impl<S: CommandSource> From<ArgumentDetachedNode<S>> for DetachedNode<S> {
|
||||
fn from(node: ArgumentDetachedNode<S>) -> Self {
|
||||
Self::Argument(node)
|
||||
}
|
||||
}
|
||||
|
||||
impl DetachedNode {
|
||||
impl<S: CommandSource> DetachedNode<S> {
|
||||
/// Irreversibly decomposes this [`DetachedNode`] into its constituent elements.
|
||||
/// This allows it to then be recast into a new `AttachedNode`.
|
||||
#[must_use]
|
||||
pub fn decompose(self) -> DecomposedNode {
|
||||
pub fn decompose(self) -> DecomposedNode<S> {
|
||||
match self {
|
||||
Self::Literal(node) => DecomposedNode {
|
||||
owned: node.owned,
|
||||
@ -1,19 +1,17 @@
|
||||
use crate::command::argument_builder::{ArgumentBuilder, CommandArgumentBuilder};
|
||||
use crate::command::context::command_context::{
|
||||
CommandContext, CommandContextBuilder, ContextChain,
|
||||
};
|
||||
use crate::command::context::command_source::{CommandSource, ReturnValue};
|
||||
use crate::command::errors::command_syntax_error::CommandSyntaxError;
|
||||
use crate::command::errors::error_types::{
|
||||
use crate::argument_builder::{ArgumentBuilder, CommandArgumentBuilder};
|
||||
use crate::context::command_context::{CommandContext, CommandContextBuilder, ContextChain};
|
||||
use crate::errors::command_syntax_error::CommandSyntaxError;
|
||||
use crate::errors::error_types::{
|
||||
DISPATCHER_EXPECTED_ARGUMENT_SEPARATOR, DISPATCHER_UNKNOWN_ARGUMENT,
|
||||
DISPATCHER_UNKNOWN_COMMAND, LiteralCommandErrorType,
|
||||
};
|
||||
use crate::command::node::Redirection;
|
||||
use crate::command::node::attached::{CommandNodeId, NodeId};
|
||||
use crate::command::node::detached::CommandDetachedNode;
|
||||
use crate::command::node::tree::{NodeIdClassification, ROOT_NODE_ID, Tree};
|
||||
use crate::command::string_reader::StringReader;
|
||||
use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
|
||||
use crate::node::Redirection;
|
||||
use crate::node::attached::{CommandNodeId, NodeId};
|
||||
use crate::node::detached::CommandDetachedNode;
|
||||
use crate::node::tree::{NodeIdClassification, ROOT_NODE_ID, Tree};
|
||||
use crate::source::{CommandSource, DummySource, ReturnValue};
|
||||
use crate::string_reader::StringReader;
|
||||
use crate::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
|
||||
use pumpkin_data::translation::java::COMMAND_CONTEXT_HERE;
|
||||
use pumpkin_protocol::java::client::play::CommandSuggestion;
|
||||
use pumpkin_util::text::TextComponent;
|
||||
@ -21,7 +19,7 @@ use pumpkin_util::text::click::ClickEvent;
|
||||
use pumpkin_util::text::color::{Color, NamedColor};
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use std::collections::BTreeMap;
|
||||
use std::sync::{Arc, LazyLock};
|
||||
use std::sync::Arc;
|
||||
|
||||
pub const ARG_SEPARATOR: &str = " ";
|
||||
pub const ARG_SEPARATOR_CHAR: char = ' ';
|
||||
@ -38,47 +36,41 @@ pub const UNRESOLVED_REDIRECT: LiteralCommandErrorType =
|
||||
LiteralCommandErrorType::new("Could not resolve redirect to node");
|
||||
|
||||
/// Represents the result of parsing.
|
||||
pub struct ParsingResult<'a> {
|
||||
pub context: CommandContextBuilder<'a>,
|
||||
pub struct ParsingResult<'a, S: CommandSource = DummySource> {
|
||||
pub context: CommandContextBuilder<'a, S>,
|
||||
pub errors: FxHashMap<NodeId, CommandSyntaxError>,
|
||||
pub reader: StringReader<'static>,
|
||||
}
|
||||
|
||||
/// Structs implementing this trait are able to execute upon command completion.
|
||||
pub trait ResultConsumer: Sync + Send {
|
||||
fn on_command_completion(&self, context: &CommandContext, result: ReturnValue);
|
||||
pub trait ResultConsumer<S: CommandSource = DummySource>: Sync + Send {
|
||||
fn on_command_completion(&self, context: &CommandContext<S>, result: ReturnValue);
|
||||
}
|
||||
|
||||
/// A [`ResultConsumer`] which does nothing.
|
||||
pub struct EmptyResultConsumer;
|
||||
|
||||
impl ResultConsumer for EmptyResultConsumer {
|
||||
fn on_command_completion(&self, _context: &CommandContext, _result: ReturnValue) {}
|
||||
impl<S: CommandSource> ResultConsumer<S> for EmptyResultConsumer {
|
||||
fn on_command_completion(&self, _context: &CommandContext<S>, _result: ReturnValue) {}
|
||||
}
|
||||
|
||||
pub static EMPTY_CONSUMER: LazyLock<Arc<EmptyResultConsumer>> =
|
||||
LazyLock::new(|| Arc::new(EmptyResultConsumer));
|
||||
|
||||
/// A [`ResultConsumer`] which defers the given result to the source provided.
|
||||
pub struct ResultDeferrer;
|
||||
|
||||
impl ResultConsumer for ResultDeferrer {
|
||||
fn on_command_completion(&self, context: &CommandContext, result: ReturnValue) {
|
||||
context.source.command_result_taker.call(result);
|
||||
impl<S: CommandSource> ResultConsumer<S> for ResultDeferrer {
|
||||
fn on_command_completion(&self, context: &CommandContext<S>, result: ReturnValue) {
|
||||
context.source.call_result(result);
|
||||
}
|
||||
}
|
||||
|
||||
pub static RESULT_DEFERRER: LazyLock<Arc<ResultDeferrer>> =
|
||||
LazyLock::new(|| Arc::new(ResultDeferrer));
|
||||
|
||||
/// The core command dispatcher, used to register, parse and execute commands.
|
||||
///
|
||||
/// Internally, this dispatcher stores a [`Tree`]. Refer to its documentation
|
||||
/// for more information about nodes.
|
||||
#[derive(Clone)]
|
||||
pub struct CommandDispatcher {
|
||||
pub tree: Tree,
|
||||
pub consumer: Arc<dyn ResultConsumer>,
|
||||
pub struct CommandDispatcher<S: CommandSource = DummySource> {
|
||||
pub tree: Tree<S>,
|
||||
pub consumer: Arc<dyn ResultConsumer<S>>,
|
||||
|
||||
/// Primary names of commands that have been turned off through the server
|
||||
/// configuration. A disabled command behaves as if it does not exist: it
|
||||
@ -91,13 +83,13 @@ pub struct CommandDispatcher {
|
||||
inactive_plugin_commands: FxHashSet<String>,
|
||||
}
|
||||
|
||||
impl Default for CommandDispatcher {
|
||||
impl<S: CommandSource> Default for CommandDispatcher<S> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl CommandDispatcher {
|
||||
impl<S: CommandSource> CommandDispatcher<S> {
|
||||
/// Creates a new [`CommandDispatcher`] with a new [`Tree`].
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
@ -105,10 +97,11 @@ impl CommandDispatcher {
|
||||
}
|
||||
|
||||
/// Creates this [`CommandDispatcher`] from a pre-existing tree.
|
||||
pub fn from_existing_tree(tree: Tree) -> Self {
|
||||
#[must_use]
|
||||
pub fn from_existing_tree(tree: Tree<S>) -> Self {
|
||||
Self {
|
||||
tree,
|
||||
consumer: RESULT_DEFERRER.clone(),
|
||||
consumer: Arc::new(ResultDeferrer),
|
||||
disabled: FxHashSet::default(),
|
||||
inactive_plugin_commands: FxHashSet::default(),
|
||||
}
|
||||
@ -133,7 +126,7 @@ impl CommandDispatcher {
|
||||
}
|
||||
|
||||
/// Makes a plugin command and its aliases unavailable until registration.
|
||||
pub(crate) fn deactivate_plugin_command_and_aliases(&mut self, name: &str) {
|
||||
pub fn deactivate_plugin_command_and_aliases(&mut self, name: &str) {
|
||||
let primary_name = self.primary_command_name(name);
|
||||
for alias in self.tree_alias_names(&primary_name) {
|
||||
self.deactivate_plugin_command(alias);
|
||||
@ -142,7 +135,7 @@ impl CommandDispatcher {
|
||||
}
|
||||
|
||||
/// Makes every root command registered by a plugin source unavailable.
|
||||
pub(crate) fn deactivate_commands_from_source(&mut self, source: &str) {
|
||||
pub fn deactivate_commands_from_source(&mut self, source: &str) {
|
||||
let primary_names = self
|
||||
.tree
|
||||
.get_root_children()
|
||||
@ -253,7 +246,7 @@ impl CommandDispatcher {
|
||||
/// Note that, at least for now with this system, there is no way to
|
||||
/// unregister a command. This is due to redirection to
|
||||
/// potentially unregistered (freed) nodes.
|
||||
pub fn register(&mut self, command_node: impl Into<CommandDetachedNode>) -> CommandNodeId {
|
||||
pub fn register(&mut self, command_node: impl Into<CommandDetachedNode<S>>) -> CommandNodeId {
|
||||
let mut node = command_node.into();
|
||||
let name = Self::normalize_command_name(&node.meta.literal);
|
||||
node.meta.literal = name.clone().into();
|
||||
@ -268,17 +261,13 @@ impl CommandDispatcher {
|
||||
if self.tree.get(&single_slash).is_none() {
|
||||
let main_node = &self.tree[main_node_id];
|
||||
let description = main_node.meta.description.clone();
|
||||
let mut alias = crate::command::argument_builder::CommandArgumentBuilder::new(
|
||||
single_slash,
|
||||
description,
|
||||
);
|
||||
let mut alias =
|
||||
crate::argument_builder::CommandArgumentBuilder::new(single_slash, description);
|
||||
if let Some(executor) = &main_node.owned.command {
|
||||
alias = alias.executes_arc(executor.clone());
|
||||
alias = alias.overwrite_requirements(main_node.owned.requirements.clone());
|
||||
}
|
||||
alias = alias.redirect(crate::command::node::Redirection::Local(
|
||||
main_node_id.into(),
|
||||
));
|
||||
alias = alias.redirect(crate::node::Redirection::Local(main_node_id.into()));
|
||||
self.tree.add_child_to_root(alias.build());
|
||||
}
|
||||
} else if let Some(stripped) = name.strip_prefix('/') {
|
||||
@ -286,17 +275,13 @@ impl CommandDispatcher {
|
||||
if self.tree.get(&double_slash).is_none() {
|
||||
let main_node = &self.tree[main_node_id];
|
||||
let description = main_node.meta.description.clone();
|
||||
let mut alias = crate::command::argument_builder::CommandArgumentBuilder::new(
|
||||
double_slash,
|
||||
description,
|
||||
);
|
||||
let mut alias =
|
||||
crate::argument_builder::CommandArgumentBuilder::new(double_slash, description);
|
||||
if let Some(executor) = &main_node.owned.command {
|
||||
alias = alias.executes_arc(executor.clone());
|
||||
alias = alias.overwrite_requirements(main_node.owned.requirements.clone());
|
||||
}
|
||||
alias = alias.redirect(crate::command::node::Redirection::Local(
|
||||
main_node_id.into(),
|
||||
));
|
||||
alias = alias.redirect(crate::node::Redirection::Local(main_node_id.into()));
|
||||
self.tree.add_child_to_root(alias.build());
|
||||
}
|
||||
}
|
||||
@ -313,10 +298,10 @@ impl CommandDispatcher {
|
||||
/// Note that, at least for now with this system, there is no way to
|
||||
/// unregister a command. This is due to redirection to
|
||||
/// potentially unregistered (freed) nodes.
|
||||
pub fn register_with_aliases<S: AsRef<str>>(
|
||||
pub fn register_with_aliases<Str: AsRef<str>>(
|
||||
&mut self,
|
||||
command_node: impl Into<CommandDetachedNode>,
|
||||
aliases: &[S],
|
||||
command_node: impl Into<CommandDetachedNode<S>>,
|
||||
aliases: &[Str],
|
||||
) -> CommandNodeId {
|
||||
let main_node_id = self.register(command_node);
|
||||
|
||||
@ -365,11 +350,7 @@ impl CommandDispatcher {
|
||||
///
|
||||
/// # Note
|
||||
/// This does not cache parsed input.
|
||||
pub fn execute_input(
|
||||
&self,
|
||||
input: &str,
|
||||
source: &CommandSource,
|
||||
) -> Result<i32, CommandSyntaxError> {
|
||||
pub fn execute_input(&self, input: &str, source: &S) -> Result<i32, CommandSyntaxError> {
|
||||
let mut reader = StringReader::new(input);
|
||||
|
||||
// A disabled command must behave as if it does not exist from every
|
||||
@ -390,14 +371,14 @@ impl CommandDispatcher {
|
||||
pub fn execute_reader(
|
||||
&self,
|
||||
reader: &mut StringReader<'_>,
|
||||
source: &CommandSource,
|
||||
source: &S,
|
||||
) -> Result<i32, CommandSyntaxError> {
|
||||
let parsed = self.parse(reader, source);
|
||||
self.execute(parsed)
|
||||
}
|
||||
|
||||
/// Executes a given result that has already been parsed from an input.
|
||||
pub fn execute(&self, parsed: ParsingResult<'_>) -> Result<i32, CommandSyntaxError> {
|
||||
pub fn execute(&self, parsed: ParsingResult<'_, S>) -> Result<i32, CommandSyntaxError> {
|
||||
if parsed.reader.peek().is_some() {
|
||||
return if let Some(err) = parsed.errors.values().next() {
|
||||
Err(err.clone())
|
||||
@ -425,17 +406,13 @@ impl CommandDispatcher {
|
||||
|
||||
/// Only parses a given source with the specified source.
|
||||
#[must_use]
|
||||
pub fn parse_input(&self, command: &str, source: &CommandSource) -> ParsingResult<'_> {
|
||||
pub fn parse_input(&self, command: &str, source: &S) -> ParsingResult<'_, S> {
|
||||
let mut reader = StringReader::new(command);
|
||||
self.parse(&mut reader, source)
|
||||
}
|
||||
|
||||
/// Parses a command owned by a [`StringReader`] with the provided source.
|
||||
pub fn parse(
|
||||
&self,
|
||||
reader: &mut StringReader<'_>,
|
||||
source: &CommandSource,
|
||||
) -> ParsingResult<'_> {
|
||||
pub fn parse(&self, reader: &mut StringReader<'_>, source: &S) -> ParsingResult<'_, S> {
|
||||
let context = CommandContextBuilder::new(
|
||||
self,
|
||||
Arc::new(source.clone()),
|
||||
@ -449,11 +426,11 @@ impl CommandDispatcher {
|
||||
&'a self,
|
||||
node: NodeId,
|
||||
original_reader: &mut StringReader<'_>,
|
||||
context_so_far: &CommandContextBuilder<'a>,
|
||||
) -> ParsingResult<'a> {
|
||||
context_so_far: &CommandContextBuilder<'a, S>,
|
||||
) -> ParsingResult<'a, S> {
|
||||
let source = context_so_far.source.clone();
|
||||
let mut errors: FxHashMap<NodeId, CommandSyntaxError> = FxHashMap::default();
|
||||
let mut potentials: Vec<ParsingResult> = Vec::new();
|
||||
let mut potentials: Vec<ParsingResult<'a, S>> = Vec::new();
|
||||
let cursor = original_reader.cursor();
|
||||
|
||||
for child in self.tree.get_relevant_nodes(original_reader, node) {
|
||||
@ -539,16 +516,7 @@ impl CommandDispatcher {
|
||||
///
|
||||
/// If the input starts with one slash (`/`), it is removed
|
||||
/// inside the call itself.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the source given to it is a dummy one.
|
||||
pub fn handle_command<'a>(&'a self, source: &CommandSource, mut input: &'a str) {
|
||||
assert!(
|
||||
source.server.is_some(),
|
||||
"Source provided to this command was a dummy source"
|
||||
);
|
||||
|
||||
pub fn handle_command<'a>(&'a self, source: &S, mut input: &'a str) {
|
||||
// If input starts with '/', but the command with that leading slash is NOT
|
||||
// registered, while the stripped command IS registered (or if it's an unknown command
|
||||
// starting with a single slash, e.g. from console input), strip one leading slash.
|
||||
@ -583,7 +551,12 @@ impl CommandDispatcher {
|
||||
/// Sends a command error to the provided source.
|
||||
/// This also shows the contextual information
|
||||
/// leading up to the error if necessary.
|
||||
pub fn send_error_to_source(source: &CommandSource, error: CommandSyntaxError, command: &str) {
|
||||
#[expect(deprecated)]
|
||||
pub fn send_error_to_source(
|
||||
source: &impl CommandSource,
|
||||
error: CommandSyntaxError,
|
||||
command: &str,
|
||||
) {
|
||||
source.send_message(error.message.color(Color::Named(NamedColor::Red)));
|
||||
|
||||
if let Some(context) = error.context {
|
||||
@ -631,7 +604,7 @@ impl CommandDispatcher {
|
||||
#[must_use]
|
||||
pub fn get_completion_suggestions_at_end(
|
||||
&self,
|
||||
parsing_result: ParsingResult<'_>,
|
||||
parsing_result: ParsingResult<'_, S>,
|
||||
) -> Suggestions {
|
||||
let length = parsing_result.reader.total_length();
|
||||
self.get_completion_suggestions(parsing_result, length)
|
||||
@ -644,7 +617,7 @@ impl CommandDispatcher {
|
||||
#[must_use]
|
||||
pub fn get_completion_suggestions(
|
||||
&self,
|
||||
parsing_result: ParsingResult<'_>,
|
||||
parsing_result: ParsingResult<'_, S>,
|
||||
cursor: usize,
|
||||
) -> Suggestions {
|
||||
let context = parsing_result.context;
|
||||
@ -711,7 +684,7 @@ impl CommandDispatcher {
|
||||
/// This function currently panics if the source provided was a dummy source.
|
||||
/// This is subject to change in the future.
|
||||
#[must_use]
|
||||
pub fn suggest(&self, input: &str, source: &CommandSource) -> Vec<CommandSuggestion> {
|
||||
pub fn suggest(&self, input: &str, source: &S) -> Vec<CommandSuggestion> {
|
||||
self.suggest_with_range(input, source)
|
||||
.suggestions
|
||||
.into_iter()
|
||||
@ -723,7 +696,7 @@ impl CommandDispatcher {
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn suggest_with_range(&self, input: &str, source: &CommandSource) -> Suggestions {
|
||||
pub fn suggest_with_range(&self, input: &str, source: &S) -> Suggestions {
|
||||
// Never suggest arguments for a command that has been turned off.
|
||||
if self.is_disabled(Self::command_name(input)) {
|
||||
return Suggestions::empty();
|
||||
@ -756,7 +729,7 @@ impl CommandDispatcher {
|
||||
/// The map returned has the key as the command name
|
||||
/// and the value as the command's description.
|
||||
#[must_use]
|
||||
pub fn get_all_permitted_commands(&self, source: &CommandSource) -> BTreeMap<&str, &str> {
|
||||
pub fn get_all_permitted_commands(&self, source: &S) -> BTreeMap<&str, &str> {
|
||||
let mut commands: BTreeMap<&str, &str> = BTreeMap::new();
|
||||
|
||||
for command in self.tree.get_root_children() {
|
||||
@ -777,10 +750,7 @@ impl CommandDispatcher {
|
||||
/// The key is the command identifier,
|
||||
/// and the value is a tuple of `(description, usage)`.
|
||||
#[must_use]
|
||||
pub fn get_all_permitted_commands_usage(
|
||||
&self,
|
||||
source: &CommandSource,
|
||||
) -> BTreeMap<&str, (&str, Box<str>)> {
|
||||
pub fn get_all_permitted_commands_usage(&self, source: &S) -> BTreeMap<&str, (&str, Box<str>)> {
|
||||
let mut commands: BTreeMap<&str, (&str, Box<str>)> = BTreeMap::new();
|
||||
|
||||
for (command_node_id, usage) in self.get_usage_of_commands(source) {
|
||||
@ -801,7 +771,7 @@ impl CommandDispatcher {
|
||||
#[must_use]
|
||||
pub fn get_all_permitted_commands_usage_by_plugin(
|
||||
&self,
|
||||
source: &CommandSource,
|
||||
source: &S,
|
||||
plugin_name: &str,
|
||||
) -> BTreeMap<&str, (&str, Box<str>)> {
|
||||
let mut commands: BTreeMap<&str, (&str, Box<str>)> = BTreeMap::new();
|
||||
@ -831,7 +801,7 @@ impl CommandDispatcher {
|
||||
#[must_use]
|
||||
pub fn get_permitted_command_usage(
|
||||
&self,
|
||||
source: &CommandSource,
|
||||
source: &S,
|
||||
command: &str,
|
||||
) -> Option<(&str, Box<str>)> {
|
||||
let command_node_id = self.tree.get(command)?;
|
||||
@ -846,11 +816,7 @@ impl CommandDispatcher {
|
||||
|
||||
/// Returns the usage of the given command node.
|
||||
#[must_use]
|
||||
pub fn get_usage_of_command(
|
||||
&self,
|
||||
command_node: CommandNodeId,
|
||||
source: &CommandSource,
|
||||
) -> Option<String> {
|
||||
pub fn get_usage_of_command(&self, command_node: CommandNodeId, source: &S) -> Option<String> {
|
||||
// We know the root DOES NOT have an executor, so we pass false to `is_optional`.
|
||||
self.get_usage_recursive(command_node.into(), source, false, false, None)
|
||||
.map(|mut usage| {
|
||||
@ -862,11 +828,7 @@ impl CommandDispatcher {
|
||||
|
||||
/// Returns the usage of each child of the given node (permitted for the given source).
|
||||
#[must_use]
|
||||
pub fn get_usage_of_children(
|
||||
&self,
|
||||
node: NodeId,
|
||||
source: &CommandSource,
|
||||
) -> FxHashMap<NodeId, String> {
|
||||
pub fn get_usage_of_children(&self, node: NodeId, source: &S) -> FxHashMap<NodeId, String> {
|
||||
let mut map = FxHashMap::default();
|
||||
|
||||
let is_optional = self.tree[node].command().is_some();
|
||||
@ -881,10 +843,7 @@ impl CommandDispatcher {
|
||||
|
||||
/// Returns the usage of each command (permitted for the given source).
|
||||
#[must_use]
|
||||
pub fn get_usage_of_commands(
|
||||
&self,
|
||||
source: &CommandSource,
|
||||
) -> FxHashMap<CommandNodeId, String> {
|
||||
pub fn get_usage_of_commands(&self, source: &S) -> FxHashMap<CommandNodeId, String> {
|
||||
self.get_usage_of_children(ROOT_NODE_ID, source)
|
||||
.into_iter()
|
||||
// This is safe because every child of the root child is a command node.
|
||||
@ -900,7 +859,7 @@ impl CommandDispatcher {
|
||||
fn get_usage_recursive(
|
||||
&self,
|
||||
node: NodeId,
|
||||
source: &CommandSource,
|
||||
source: &S,
|
||||
is_optional: bool,
|
||||
deep: bool,
|
||||
redirector_usage_text: Option<String>,
|
||||
@ -1007,15 +966,15 @@ impl CommandDispatcher {
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::command::argument_builder::{
|
||||
use crate::argument_builder::{
|
||||
ArgumentBuilder, CommandArgumentBuilder, LiteralArgumentBuilder, RequiredArgumentBuilder,
|
||||
};
|
||||
use crate::command::argument_types::core::integer::IntegerArgumentType;
|
||||
use crate::command::context::command_context::CommandContext;
|
||||
use crate::command::context::command_source::CommandSource;
|
||||
use crate::command::errors::error_types::DISPATCHER_UNKNOWN_COMMAND;
|
||||
use crate::command::node::dispatcher::CommandDispatcher;
|
||||
use crate::command::node::{CommandExecutor, CommandExecutorResult};
|
||||
use crate::argument_types::core::integer::IntegerArgumentType;
|
||||
use crate::context::command_context::CommandContext;
|
||||
use crate::errors::error_types::DISPATCHER_UNKNOWN_COMMAND;
|
||||
use crate::node::dispatcher::CommandDispatcher;
|
||||
use crate::node::{CommandExecutor, CommandExecutorResult};
|
||||
use crate::source::DummySource;
|
||||
|
||||
#[test]
|
||||
fn unknown_command() {
|
||||
@ -1023,7 +982,7 @@ mod test {
|
||||
dispatcher.register(
|
||||
CommandArgumentBuilder::new("unknown", "A command without an executor").build(),
|
||||
);
|
||||
let source = CommandSource::dummy();
|
||||
let source = DummySource::dummy();
|
||||
let result = dispatcher.execute_input("unknown", &source);
|
||||
assert!(result.is_err_and(|error| error.error_type == &DISPATCHER_UNKNOWN_COMMAND));
|
||||
}
|
||||
@ -1034,7 +993,7 @@ mod test {
|
||||
let executor: fn(&CommandContext) -> CommandExecutorResult = |_| Ok(1);
|
||||
dispatcher
|
||||
.register(CommandArgumentBuilder::new("simple", "A simple command").executes(executor));
|
||||
let source = CommandSource::dummy();
|
||||
let source = DummySource::dummy();
|
||||
let result = dispatcher.execute_input("simple", &source);
|
||||
assert_eq!(result, Ok(1));
|
||||
}
|
||||
@ -1050,7 +1009,7 @@ mod test {
|
||||
.register(CommandArgumentBuilder::new("simple", "A simple command").executes(executor));
|
||||
dispatcher.disable_command("simple");
|
||||
|
||||
let source = CommandSource::dummy();
|
||||
let source = DummySource::dummy();
|
||||
let result = dispatcher.execute_input("simple", &source);
|
||||
assert!(result.is_err_and(|error| error.error_type == &DISPATCHER_UNKNOWN_COMMAND));
|
||||
}
|
||||
@ -1066,7 +1025,7 @@ mod test {
|
||||
};
|
||||
|
||||
dispatcher.register_with_aliases(command(), &["Plugin-Alias"]);
|
||||
let source = CommandSource::dummy();
|
||||
let source = DummySource::dummy();
|
||||
assert_eq!(dispatcher.execute_input("plugin-command", &source), Ok(1));
|
||||
assert_eq!(dispatcher.execute_input("plugin-alias", &source), Ok(1));
|
||||
|
||||
@ -1170,7 +1129,7 @@ mod test {
|
||||
),
|
||||
),
|
||||
);
|
||||
let source = CommandSource::dummy();
|
||||
let source = DummySource::dummy();
|
||||
assert_eq!(
|
||||
dispatcher.execute_input("arithmetic 3 + -7", &source),
|
||||
Ok(-4)
|
||||
@ -1194,7 +1153,7 @@ mod test {
|
||||
// Note that we CANNOT use redirect here as node itself needs to execute the command,
|
||||
// not its 'children'.
|
||||
dispatcher.register(CommandArgumentBuilder::new("b", "An alias for /a").executes(executor));
|
||||
let source = CommandSource::dummy();
|
||||
let source = DummySource::dummy();
|
||||
assert_eq!(dispatcher.execute_input("a", &source), Ok(1));
|
||||
assert_eq!(dispatcher.execute_input("b", &source), Ok(1));
|
||||
}
|
||||
@ -1215,7 +1174,7 @@ mod test {
|
||||
));
|
||||
// Note that this time, we SHOULD use redirect - it is leading to another node having `command`.
|
||||
dispatcher.register(CommandArgumentBuilder::new("b", "An alias for /a").redirect(a));
|
||||
let source = CommandSource::dummy();
|
||||
let source = DummySource::dummy();
|
||||
assert_eq!(dispatcher.execute_input("a 5", &source), Ok(5));
|
||||
assert_eq!(dispatcher.execute_input("b 7", &source), Ok(7));
|
||||
}
|
||||
@ -1246,7 +1205,7 @@ mod test {
|
||||
|
||||
dispatcher.register(builder);
|
||||
|
||||
let source = CommandSource::dummy();
|
||||
let source = DummySource::dummy();
|
||||
assert_eq!(dispatcher.execute_input("recurse", &source), Ok(1));
|
||||
assert_eq!(dispatcher.execute_input("recurse 4", &source), Ok(1));
|
||||
assert_eq!(dispatcher.execute_input("recurse 9 -1", &source), Ok(1));
|
||||
@ -1269,7 +1228,7 @@ mod test {
|
||||
CommandArgumentBuilder::new("//set", "WorldEdit set command").executes(executor),
|
||||
);
|
||||
|
||||
let source = CommandSource::dummy();
|
||||
let source = DummySource::dummy();
|
||||
// Direct execution with //set
|
||||
assert_eq!(dispatcher.execute_input("//set", &source), Ok(42));
|
||||
// Execution via /set alias (as sent by Java client for //set)
|
||||
267
crates/pumpkin-command/src/node/mod.rs
Normal file
267
crates/pumpkin-command/src/node/mod.rs
Normal file
@ -0,0 +1,267 @@
|
||||
pub mod attached;
|
||||
pub mod detached;
|
||||
pub mod dispatcher;
|
||||
pub mod tree;
|
||||
|
||||
use crate::argument_types::argument_type::AnyArgumentType;
|
||||
use crate::context::command_context::CommandContext;
|
||||
use crate::errors::command_syntax_error::CommandSyntaxError;
|
||||
use crate::node::attached::NodeId;
|
||||
use crate::node::detached::GlobalNodeId;
|
||||
use crate::source::{CommandSource, DummySource};
|
||||
use crate::suggestion::provider::SuggestionProvider;
|
||||
use std::borrow::Cow;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Represents a [`CommandExecutor`]'s result.
|
||||
///
|
||||
/// If the command **ran successfully**, an [`Ok`] is returned containing an [`i32`].
|
||||
/// This represents the 'output value' of the command, which is *homologous* to the
|
||||
/// `int` that command executors in vanilla return **upon success**.
|
||||
///
|
||||
/// **You should choose the successful result as `1` if**:
|
||||
/// - you don't know what value to use for a success for your
|
||||
/// own commands, or
|
||||
/// - you don't understand what this value means, or
|
||||
/// - you just simply don't care about this value at all
|
||||
///
|
||||
/// If the command **fails**, an [`Err`] is returned, containing the [`CommandSyntaxError`]
|
||||
/// that led to this result.
|
||||
pub type CommandExecutorResult = Result<i32, CommandSyntaxError>;
|
||||
|
||||
/// A struct implementing this trait is able to run with a given context.
|
||||
pub trait CommandExecutor<S: CommandSource = DummySource>: Sync + Send {
|
||||
/// Executes this executor for a command.
|
||||
fn execute(&self, context: &CommandContext<S>) -> CommandExecutorResult;
|
||||
}
|
||||
|
||||
impl<F, S: CommandSource> CommandExecutor<S> for F
|
||||
where
|
||||
F: Fn(&CommandContext<S>) -> CommandExecutorResult + Send + Sync,
|
||||
{
|
||||
fn execute(&self, context: &CommandContext<S>) -> CommandExecutorResult {
|
||||
self(context)
|
||||
}
|
||||
}
|
||||
|
||||
/// A function that takes a context and returns a command result.
|
||||
pub type Command<S = DummySource> = Arc<dyn CommandExecutor<S>>;
|
||||
|
||||
/// Represents the result of [`Arc<S>`]s from a [`CommandContext`].
|
||||
pub type RedirectModifierResult<S = DummySource> = Result<Vec<Arc<S>>, CommandSyntaxError>;
|
||||
|
||||
/// A function that performs the required modification.
|
||||
pub type RedirectModifierExecutor<S = DummySource> =
|
||||
dyn Fn(&CommandContext<S>) -> RedirectModifierResult<S> + Send + Sync;
|
||||
|
||||
/// A function that returns a new collection of sources from a given context.
|
||||
#[derive(Clone)]
|
||||
pub enum RedirectModifier<S: CommandSource = DummySource> {
|
||||
/// Always returns only the source from the given context.
|
||||
KeepSource,
|
||||
|
||||
/// Returns multiple [`CommandSource`]s from one context via
|
||||
/// custom behavior.
|
||||
Custom(Arc<RedirectModifierExecutor<S>>),
|
||||
}
|
||||
|
||||
impl<S: CommandSource> RedirectModifier<S> {
|
||||
/// Tries to provide a [`Vec`] of [`Arc<S>`] from a
|
||||
/// given [`CommandContext`].
|
||||
pub fn sources(&self, command_context: &CommandContext<S>) -> RedirectModifierResult<S> {
|
||||
match self {
|
||||
Self::KeepSource => Ok(vec![command_context.source.clone()]),
|
||||
Self::Custom(function) => function(command_context),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the result of a node requirement.
|
||||
pub type RequirementResult = bool;
|
||||
|
||||
/// A predicate that returns if the provided source satisfies it.
|
||||
#[derive(Clone)]
|
||||
pub struct Requirement<S: CommandSource = DummySource>(
|
||||
pub Arc<dyn Fn(&S) -> RequirementResult + Send + Sync>,
|
||||
);
|
||||
|
||||
impl<S: CommandSource> Requirement<S> {
|
||||
/// Evaluates the given condition, returning whether the
|
||||
/// given [`CommandSource`] satisfies this requirement.
|
||||
#[must_use]
|
||||
pub fn evaluate(&self, command_source: &S) -> RequirementResult {
|
||||
self.0(command_source)
|
||||
}
|
||||
}
|
||||
|
||||
impl<F, S: CommandSource> From<F> for Requirement<S>
|
||||
where
|
||||
F: Fn(&S) -> RequirementResult + Send + Sync + 'static,
|
||||
{
|
||||
fn from(value: F) -> Self {
|
||||
Self(Arc::new(value))
|
||||
}
|
||||
}
|
||||
|
||||
// Permissions
|
||||
impl<S: CommandSource> From<String> for Requirement<S> {
|
||||
fn from(value: String) -> Self {
|
||||
Self(Arc::new({
|
||||
let permission = Arc::new(value);
|
||||
|
||||
move |source: &S| source.has_permission(&permission)
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: CommandSource> From<&'static str> for Requirement<S> {
|
||||
fn from(value: &'static str) -> Self {
|
||||
Self(Arc::new(move |source: &S| source.has_permission(value)))
|
||||
}
|
||||
}
|
||||
|
||||
/// A structure that returns if the source is qualified enough to run the command.
|
||||
#[derive(Clone)]
|
||||
pub struct Requirements<S: CommandSource = DummySource>(pub Vec<Requirement<S>>);
|
||||
|
||||
impl<S: CommandSource> Requirements<S> {
|
||||
/// Creates a new `Requirements` with no requirements in it.
|
||||
/// If used, this will always return `true` when evaluated.
|
||||
#[must_use]
|
||||
pub const fn new() -> Self {
|
||||
Self(Vec::new())
|
||||
}
|
||||
|
||||
/// Evaluates the given condition, returning whether the
|
||||
/// given [`CommandSource`] satisfies all contained requirements.
|
||||
#[must_use]
|
||||
pub fn evaluate(&self, command_source: &S) -> RequirementResult {
|
||||
for predicate in &self.0 {
|
||||
if !predicate.evaluate(command_source) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: CommandSource> Default for Requirements<S> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// Stores common owned data for a node.
|
||||
#[derive(Clone)]
|
||||
pub struct OwnedNodeData<S: CommandSource = DummySource> {
|
||||
pub global_id: GlobalNodeId,
|
||||
pub requirements: Requirements<S>,
|
||||
pub modifier: RedirectModifier<S>,
|
||||
pub forks: bool,
|
||||
pub command: Option<Command<S>>,
|
||||
}
|
||||
|
||||
/// Represents the extra metadata of a node storing a literal.
|
||||
#[derive(Clone)]
|
||||
pub struct LiteralNodeMetadata {
|
||||
pub literal: Cow<'static, str>,
|
||||
pub literal_lowercase: String,
|
||||
}
|
||||
|
||||
impl LiteralNodeMetadata {
|
||||
pub fn new(literal: impl Into<Cow<'static, str>>) -> Self {
|
||||
let literal = literal.into();
|
||||
Self {
|
||||
literal: literal.clone(),
|
||||
literal_lowercase: literal.to_lowercase(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A special type of [`LiteralNodeMetadata`], containing
|
||||
/// a description for the command as well.
|
||||
#[derive(Clone)]
|
||||
pub struct CommandNodeMetadata {
|
||||
pub literal: Cow<'static, str>,
|
||||
pub literal_lowercase: String,
|
||||
pub description: Cow<'static, str>,
|
||||
pub source: Option<String>,
|
||||
}
|
||||
|
||||
impl CommandNodeMetadata {
|
||||
pub fn new(
|
||||
literal: impl Into<Cow<'static, str>>,
|
||||
description: impl Into<Cow<'static, str>>,
|
||||
) -> Self {
|
||||
let literal = literal.into();
|
||||
Self {
|
||||
literal: literal.clone(),
|
||||
literal_lowercase: literal.to_lowercase(),
|
||||
description: description.into(),
|
||||
source: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the extra metadata of an argument of any type.
|
||||
#[derive(Clone)]
|
||||
pub struct ArgumentNodeMetadata<S: CommandSource = DummySource> {
|
||||
pub name: Cow<'static, str>,
|
||||
pub argument_type: Arc<dyn AnyArgumentType<S>>,
|
||||
pub suggestion_provider: Option<Arc<dyn SuggestionProvider<S>>>,
|
||||
}
|
||||
|
||||
impl<S: CommandSource> ArgumentNodeMetadata<S> {
|
||||
pub fn new(
|
||||
name: impl Into<Cow<'static, str>>,
|
||||
argument_type: Arc<dyn AnyArgumentType<S>>,
|
||||
suggestion_provider: Option<Arc<dyn SuggestionProvider<S>>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
name: name.into(),
|
||||
argument_type,
|
||||
suggestion_provider,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the extra metadata for nodes of different types. Can be of the root, a literal, command or an argument.
|
||||
pub enum NodeMetadata<S: CommandSource = DummySource> {
|
||||
/// Metadata of the root node.
|
||||
Root,
|
||||
|
||||
/// Metadata of a literal node that doesn't start a command.
|
||||
Literal(LiteralNodeMetadata),
|
||||
|
||||
/// Metadata of a literal node that starts a command.
|
||||
Command(CommandNodeMetadata),
|
||||
|
||||
/// Metadata of an argument node.
|
||||
Argument(ArgumentNodeMetadata<S>),
|
||||
}
|
||||
|
||||
/// Stores where this redirection would lead to.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
|
||||
pub enum Redirection {
|
||||
/// Leads to the root of the tree.
|
||||
Root,
|
||||
|
||||
/// Leads to a node in the tree from its tree-local ID.
|
||||
Local(NodeId),
|
||||
|
||||
/// Leads to a node in the tree from its global ID.
|
||||
Global(GlobalNodeId),
|
||||
}
|
||||
|
||||
impl<T: Into<NodeId>> From<T> for Redirection {
|
||||
fn from(value: T) -> Self {
|
||||
Self::Local(value.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<GlobalNodeId> for Redirection {
|
||||
fn from(value: GlobalNodeId) -> Self {
|
||||
Self::Global(value)
|
||||
}
|
||||
}
|
||||
@ -1,15 +1,15 @@
|
||||
use crate::command::context::command_context::{CommandContextBuilder, ParsedArgument};
|
||||
use crate::command::context::command_source::CommandSource;
|
||||
use crate::command::context::string_range::StringRange;
|
||||
use crate::command::errors::command_syntax_error::CommandSyntaxError;
|
||||
use crate::command::errors::error_types::LITERAL_INCORRECT;
|
||||
use crate::command::node::Redirection;
|
||||
use crate::command::node::attached::{
|
||||
use crate::context::command_context::{CommandContextBuilder, ParsedArgument};
|
||||
use crate::context::string_range::StringRange;
|
||||
use crate::errors::command_syntax_error::CommandSyntaxError;
|
||||
use crate::errors::error_types::LITERAL_INCORRECT;
|
||||
use crate::node::Redirection;
|
||||
use crate::node::attached::{
|
||||
ArgumentAttachedNode, ArgumentNodeId, AttachedNode, CommandAttachedNode, CommandNodeId,
|
||||
LiteralAttachedNode, LiteralNodeId, NodeClassification, NodeId, RootAttachedNode,
|
||||
};
|
||||
use crate::command::node::detached::{CommandDetachedNode, DetachedNode, GlobalNodeId};
|
||||
use crate::command::string_reader::StringReader;
|
||||
use crate::node::detached::{CommandDetachedNode, DetachedNode, GlobalNodeId};
|
||||
use crate::source::{CommandSource, DummySource};
|
||||
use crate::string_reader::StringReader;
|
||||
use pumpkin_util::text::TextComponent;
|
||||
use rustc_hash::{FxBuildHasher, FxHashMap, FxHashSet};
|
||||
use std::num::NonZero;
|
||||
@ -20,10 +20,10 @@ use std::sync::Arc;
|
||||
pub const ROOT_NODE_ID: NodeId = NodeId(NonZero::new(1).expect("1 is non-zero"));
|
||||
|
||||
/// A consumer which takes ambiguity of input (when two or more nodes are satisfied)
|
||||
pub trait AmbiguityConsumer {
|
||||
pub trait AmbiguityConsumer<S: CommandSource = DummySource> {
|
||||
fn ambiguous(
|
||||
&mut self,
|
||||
tree: &Tree,
|
||||
tree: &Tree<S>,
|
||||
parent: NodeId,
|
||||
child: NodeId,
|
||||
sibling: NodeId,
|
||||
@ -76,12 +76,12 @@ pub enum NodeIdClassification {
|
||||
/// - **Argument**:
|
||||
/// Parses and accepts a specific type of value. This is very dynamic.
|
||||
#[derive(Clone)]
|
||||
pub struct Tree {
|
||||
pub struct Tree<S: CommandSource = DummySource> {
|
||||
/// All the nodes stored in this tree.
|
||||
///
|
||||
/// In this vector, indices starting at 0 indicates the first node (ID = 1),
|
||||
/// 1 indicates the second node (ID = 2) and so on.
|
||||
nodes: Vec<AttachedNode>,
|
||||
nodes: Vec<AttachedNode<S>>,
|
||||
|
||||
/// Keys linking [`GlobalNodeId`] to the [`NodeId`] for this tree.
|
||||
/// Useful for redirecting.
|
||||
@ -91,13 +91,13 @@ pub struct Tree {
|
||||
command_node_mappings: FxHashMap<String, CommandNodeId>,
|
||||
}
|
||||
|
||||
impl Default for Tree {
|
||||
impl<S: CommandSource> Default for Tree<S> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Tree {
|
||||
impl<S: CommandSource> Tree<S> {
|
||||
/// Constructs a new tree, containing a new root node without children.
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
@ -130,7 +130,7 @@ impl Tree {
|
||||
|
||||
/// Helper to attach a given [`AttachedNode`], returning
|
||||
/// its [`NodeId`].
|
||||
fn add(&mut self, node: AttachedNode) -> NodeId {
|
||||
fn add(&mut self, node: AttachedNode<S>) -> NodeId {
|
||||
let global_id = node.global_id();
|
||||
let local_id = self.alloc();
|
||||
|
||||
@ -144,7 +144,7 @@ impl Tree {
|
||||
/// Helper to attach a [`DetachedNode`] irreversibly
|
||||
/// into this [`Tree`], returning the ID of the now attached
|
||||
/// node.
|
||||
fn attach(&mut self, node: DetachedNode) -> NodeId {
|
||||
fn attach(&mut self, node: DetachedNode<S>) -> NodeId {
|
||||
// First, we decompose this node.
|
||||
let node = node.decompose();
|
||||
|
||||
@ -177,7 +177,7 @@ impl Tree {
|
||||
}
|
||||
|
||||
/// Adds a [`CommandDetachedNode`] to the root node of this tree.
|
||||
pub fn add_child_to_root(&mut self, node: impl Into<CommandDetachedNode>) -> CommandNodeId {
|
||||
pub fn add_child_to_root(&mut self, node: impl Into<CommandDetachedNode<S>>) -> CommandNodeId {
|
||||
// First, attach the node to this tree.
|
||||
let node = node.into();
|
||||
let name = node.meta.literal.to_string();
|
||||
@ -199,7 +199,7 @@ impl Tree {
|
||||
///
|
||||
/// Essentially, this means that a [`CommandDetachedNode`] must have the root node
|
||||
/// of the tree *as its parent*, to be attached to the tree.
|
||||
pub fn add_child(&mut self, parent: NodeId, node: impl Into<DetachedNode>) -> NodeId {
|
||||
pub fn add_child(&mut self, parent: NodeId, node: impl Into<DetachedNode<S>>) -> NodeId {
|
||||
let node = node.into();
|
||||
assert!(
|
||||
parent == ROOT_NODE_ID || !matches!(node, DetachedNode::Command(_)),
|
||||
@ -269,12 +269,12 @@ impl Tree {
|
||||
|
||||
/// Returns whether the given node is able to be used by a given source.
|
||||
#[must_use]
|
||||
pub fn can_use(&self, node: NodeId, source: &CommandSource) -> bool {
|
||||
pub fn can_use(&self, node: NodeId, source: &S) -> bool {
|
||||
self[node].requirements().evaluate(source)
|
||||
}
|
||||
|
||||
/// Finds ambiguities of input and gives them to the [`AmbiguityConsumer`].
|
||||
pub fn find_ambiguities(&self, node: NodeId, consumer: &mut impl AmbiguityConsumer) {
|
||||
pub fn find_ambiguities(&self, node: NodeId, consumer: &mut impl AmbiguityConsumer<S>) {
|
||||
let mut matches: FxHashSet<String> = FxHashSet::default();
|
||||
|
||||
for child in self.get_children(node) {
|
||||
@ -363,13 +363,13 @@ impl Tree {
|
||||
&self,
|
||||
node_id: NodeId,
|
||||
reader: &'a mut StringReader<'_>,
|
||||
command_context_builder: &'a mut CommandContextBuilder<'_>,
|
||||
command_context_builder: &'a mut CommandContextBuilder<'_, S>,
|
||||
) -> Result<(), CommandSyntaxError> {
|
||||
match &self[node_id] {
|
||||
AttachedNode::Root(_) => {}
|
||||
AttachedNode::Literal(node) => {
|
||||
let start = reader.cursor();
|
||||
let Ok(end) = AttachedNode::parse_literal(reader, &node.meta.literal) else {
|
||||
let Ok(end) = AttachedNode::<S>::parse_literal(reader, &node.meta.literal) else {
|
||||
return Err(LITERAL_INCORRECT
|
||||
.create(reader, TextComponent::text(node.meta.literal.to_string())));
|
||||
};
|
||||
@ -377,7 +377,7 @@ impl Tree {
|
||||
}
|
||||
AttachedNode::Command(node) => {
|
||||
let start = reader.cursor();
|
||||
let Ok(end) = AttachedNode::parse_literal(reader, &node.meta.literal) else {
|
||||
let Ok(end) = AttachedNode::<S>::parse_literal(reader, &node.meta.literal) else {
|
||||
return Err(LITERAL_INCORRECT
|
||||
.create(reader, TextComponent::text(node.meta.literal.to_string())));
|
||||
};
|
||||
@ -419,20 +419,20 @@ impl Tree {
|
||||
}
|
||||
|
||||
/// Returns an iterator to all the nodes of this tree.
|
||||
pub fn iter(&self) -> std::slice::Iter<'_, AttachedNode> {
|
||||
pub fn iter(&self) -> std::slice::Iter<'_, AttachedNode<S>> {
|
||||
self.nodes.iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl Index<NodeId> for Tree {
|
||||
type Output = AttachedNode;
|
||||
impl<S: CommandSource> Index<NodeId> for Tree<S> {
|
||||
type Output = AttachedNode<S>;
|
||||
|
||||
fn index(&self, index: NodeId) -> &Self::Output {
|
||||
&self.nodes[index.0.get() - 1]
|
||||
}
|
||||
}
|
||||
|
||||
impl IndexMut<NodeId> for Tree {
|
||||
impl<S: CommandSource> IndexMut<NodeId> for Tree<S> {
|
||||
fn index_mut(&mut self, index: NodeId) -> &mut Self::Output {
|
||||
&mut self.nodes[index.0.get() - 1]
|
||||
}
|
||||
@ -441,8 +441,8 @@ impl IndexMut<NodeId> for Tree {
|
||||
/// Macro helper to create [`Index`] and [`IndexMut`] for [`Tree`] with typed IDs.
|
||||
macro_rules! impl_index_index_mut {
|
||||
($node_id: ident -> AttachedNode::$attached_node_enum: ident($attached_node: ident)) => {
|
||||
impl Index<$node_id> for Tree {
|
||||
type Output = $attached_node;
|
||||
impl<S: CommandSource> Index<$node_id> for Tree<S> {
|
||||
type Output = $attached_node<S>;
|
||||
|
||||
fn index(&self, index: $node_id) -> &Self::Output {
|
||||
if let AttachedNode::$attached_node_enum(node) = &self.nodes[index.0.get() - 1] {
|
||||
@ -456,7 +456,7 @@ macro_rules! impl_index_index_mut {
|
||||
}
|
||||
}
|
||||
|
||||
impl IndexMut<$node_id> for Tree {
|
||||
impl<S: CommandSource> IndexMut<$node_id> for Tree<S> {
|
||||
fn index_mut(&mut self, index: $node_id) -> &mut Self::Output {
|
||||
if let AttachedNode::$attached_node_enum(node) = &mut self.nodes[index.0.get() - 1]
|
||||
{
|
||||
@ -476,9 +476,9 @@ impl_index_index_mut!(LiteralNodeId -> AttachedNode::Literal(LiteralAttachedNode
|
||||
impl_index_index_mut!(CommandNodeId -> AttachedNode::Command(CommandAttachedNode));
|
||||
impl_index_index_mut!(ArgumentNodeId -> AttachedNode::Argument(ArgumentAttachedNode));
|
||||
|
||||
impl<'a> IntoIterator for &'a Tree {
|
||||
type Item = &'a AttachedNode;
|
||||
type IntoIter = std::slice::Iter<'a, AttachedNode>;
|
||||
impl<'a, S: CommandSource> IntoIterator for &'a Tree<S> {
|
||||
type Item = &'a AttachedNode<S>;
|
||||
type IntoIter = std::slice::Iter<'a, AttachedNode<S>>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.iter()
|
||||
@ -487,17 +487,18 @@ impl<'a> IntoIterator for &'a Tree {
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::command::argument_builder::{
|
||||
use crate::argument_builder::{
|
||||
ArgumentBuilder, CommandArgumentBuilder, LiteralArgumentBuilder, RequiredArgumentBuilder,
|
||||
};
|
||||
use crate::command::argument_types::core::string::StringArgumentType;
|
||||
use crate::command::node::attached::NodeId;
|
||||
use crate::command::node::tree::{AmbiguityConsumer, Tree};
|
||||
use crate::argument_types::core::string::StringArgumentType;
|
||||
use crate::node::attached::NodeId;
|
||||
use crate::node::tree::{AmbiguityConsumer, Tree};
|
||||
use crate::source::DummySource;
|
||||
|
||||
#[test]
|
||||
fn adding_nodes() {
|
||||
// New tree (containing only one root node)
|
||||
let mut tree = Tree::new();
|
||||
let mut tree = Tree::<DummySource>::new();
|
||||
assert_eq!(tree.size(), 1);
|
||||
|
||||
// Adding one node.
|
||||
@ -516,7 +517,7 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn adding_children_to_attached_node() {
|
||||
let mut tree = Tree::new();
|
||||
let mut tree = Tree::<DummySource>::new();
|
||||
|
||||
let parent: NodeId = tree
|
||||
.add_child_to_root(CommandArgumentBuilder::new("foo", "A test command"))
|
||||
@ -532,7 +533,7 @@ mod test {
|
||||
#[test]
|
||||
#[should_panic = "Cannot add a CommandDetachedNode as a child of a non-root node"]
|
||||
fn adding_command_node_to_non_root_node() {
|
||||
let mut tree = Tree::new();
|
||||
let mut tree = Tree::<DummySource>::new();
|
||||
|
||||
let parent: NodeId = tree
|
||||
.add_child_to_root(CommandArgumentBuilder::new("foo", "A test command"))
|
||||
@ -1,6 +1,6 @@
|
||||
use std::borrow::Cow;
|
||||
|
||||
use crate::command::{
|
||||
use crate::{
|
||||
errors::error_types::{AnyCommandErrorType, CommandErrorType},
|
||||
string_reader::StringReader,
|
||||
};
|
||||
@ -1,4 +1,4 @@
|
||||
use crate::command::{
|
||||
use crate::{
|
||||
errors::error_types::CommandErrorType,
|
||||
snbt::rules::{EXPECTED_BINARY_NUMERAL, EXPECTED_DECIMAL_NUMERAL, EXPECTED_HEX_NUMERAL},
|
||||
};
|
||||
@ -5,14 +5,12 @@ mod rules;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
use crate::command::errors::command_syntax_error::{CommandSyntaxError, CommandSyntaxErrorContext};
|
||||
use crate::command::errors::error_types::{CommandErrorType, LITERAL_INCORRECT};
|
||||
use crate::command::parser::{Parser, ParserErrors};
|
||||
use crate::command::snbt::markers::{
|
||||
ArrayPrefix, Base, IntegerLiteral, Sign, SignedPrefix, TypeSuffix,
|
||||
};
|
||||
use crate::command::string_reader::StringReader;
|
||||
use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
|
||||
use crate::errors::command_syntax_error::{CommandSyntaxError, CommandSyntaxErrorContext};
|
||||
use crate::errors::error_types::{CommandErrorType, LITERAL_INCORRECT};
|
||||
use crate::parser::{Parser, ParserErrors};
|
||||
use crate::snbt::markers::{ArrayPrefix, Base, IntegerLiteral, Sign, SignedPrefix, TypeSuffix};
|
||||
use crate::string_reader::StringReader;
|
||||
use crate::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
|
||||
use pumpkin_codecs::Number;
|
||||
use pumpkin_data::translation;
|
||||
use pumpkin_nbt::tag::NbtTag;
|
||||
@ -62,6 +60,7 @@ pub struct SnbtParser<'r, 's> {
|
||||
//
|
||||
impl SnbtParser<'_, '_> {
|
||||
/// Parses SNBT with a given [`StringReader`], giving the result or error from parsing.
|
||||
#[expect(deprecated)]
|
||||
pub fn parse_for_commands(reader: &mut StringReader) -> Result<NbtTag, CommandSyntaxError> {
|
||||
let (result, errors) = {
|
||||
let mut parser = SnbtParser {
|
||||
@ -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(
|
||||
@ -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;
|
||||
@ -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,
|
||||
};
|
||||
189
crates/pumpkin-command/src/source.rs
Normal file
189
crates/pumpkin-command/src/source.rs
Normal file
@ -0,0 +1,189 @@
|
||||
use pumpkin_util::math::position::BlockPos;
|
||||
use pumpkin_util::math::vector2::Vector2;
|
||||
use pumpkin_util::math::vector3::Vector3;
|
||||
use pumpkin_util::text::TextComponent;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::argument_types::entity_anchor::EntityAnchor;
|
||||
use crate::errors::command_syntax_error::CommandSyntaxError;
|
||||
|
||||
pub trait ReturnValueCallable: Send + Sync {
|
||||
fn call(&self, value: ReturnValue);
|
||||
}
|
||||
|
||||
pub type ReturnValueCallback = Arc<dyn ReturnValueCallable>;
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct ResultValueTaker(pub Vec<ReturnValueCallback>);
|
||||
|
||||
impl ResultValueTaker {
|
||||
#[must_use]
|
||||
pub fn merge(taker_1: &Self, taker_2: &Self) -> Self {
|
||||
let mut takers = Vec::with_capacity(taker_1.0.len() + taker_2.0.len());
|
||||
for taker in &taker_1.0 {
|
||||
takers.push(taker.clone());
|
||||
}
|
||||
for taker in &taker_2.0 {
|
||||
takers.push(taker.clone());
|
||||
}
|
||||
Self(takers)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
Self(Vec::new())
|
||||
}
|
||||
|
||||
pub fn call(&self, return_value: ReturnValue) {
|
||||
for callback in &self.0 {
|
||||
callback.call(return_value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||
pub enum ReturnValue {
|
||||
Success(i32),
|
||||
Failure,
|
||||
}
|
||||
|
||||
impl ReturnValue {
|
||||
#[must_use]
|
||||
pub const fn success_value(self) -> bool {
|
||||
match self {
|
||||
Self::Success(_) => true,
|
||||
Self::Failure => false,
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn result_value(self) -> i32 {
|
||||
match self {
|
||||
Self::Success(value) => value,
|
||||
Self::Failure => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Abstract command source trait for command dispatch and execution.
|
||||
pub trait CommandSource: Clone + Send + Sync + 'static {
|
||||
/// Sends a feedback message to the command sender.
|
||||
fn send_message(&self, message: TextComponent);
|
||||
|
||||
/// Sends an error message to the command sender.
|
||||
fn send_error(&self, error: TextComponent) {
|
||||
self.send_message(error);
|
||||
}
|
||||
|
||||
/// Invokes callbacks for the command's return value.
|
||||
fn call_result(&self, _result: ReturnValue) {}
|
||||
|
||||
/// Checks if this command source has the specified permission.
|
||||
fn has_permission(&self, _permission: &str) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
/// Returns the position of this command source in the world.
|
||||
fn position(&self) -> Vector3<f64> {
|
||||
Vector3::default()
|
||||
}
|
||||
|
||||
/// Returns the rotation (pitch, yaw) of this command source.
|
||||
fn rotation(&self) -> Vector2<f32> {
|
||||
Vector2::default()
|
||||
}
|
||||
|
||||
/// Checks if the block at the given position is loaded.
|
||||
fn check_block_loaded(&self, _pos: &BlockPos) -> Result<(), CommandSyntaxError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns the anchor position for the given entity anchor.
|
||||
fn anchor_position(&self, _anchor: EntityAnchor) -> Vector3<f64> {
|
||||
self.position()
|
||||
}
|
||||
|
||||
/// Returns the current entity anchor of this command source.
|
||||
fn entity_anchor(&self) -> EntityAnchor {
|
||||
EntityAnchor::Feet
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct DummySource {
|
||||
pub position: Vector3<f64>,
|
||||
pub rotation: Vector2<f32>,
|
||||
pub entity_anchor: EntityAnchor,
|
||||
pub command_result_taker: ResultValueTaker,
|
||||
}
|
||||
|
||||
impl DummySource {
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn dummy() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl CommandSource for DummySource {
|
||||
fn send_message(&self, _message: TextComponent) {}
|
||||
fn send_error(&self, _error: TextComponent) {}
|
||||
fn position(&self) -> Vector3<f64> {
|
||||
self.position
|
||||
}
|
||||
fn rotation(&self) -> Vector2<f32> {
|
||||
self.rotation
|
||||
}
|
||||
fn entity_anchor(&self) -> EntityAnchor {
|
||||
self.entity_anchor
|
||||
}
|
||||
fn call_result(&self, result: ReturnValue) {
|
||||
self.command_result_taker.call(result);
|
||||
}
|
||||
}
|
||||
|
||||
impl CommandSource for () {
|
||||
fn send_message(&self, _message: TextComponent) {}
|
||||
}
|
||||
|
||||
impl<S: CommandSource> CommandSource for Arc<S> {
|
||||
fn send_message(&self, message: TextComponent) {
|
||||
(**self).send_message(message);
|
||||
}
|
||||
|
||||
fn send_error(&self, error: TextComponent) {
|
||||
(**self).send_error(error);
|
||||
}
|
||||
|
||||
fn has_permission(&self, permission: &str) -> bool {
|
||||
(**self).has_permission(permission)
|
||||
}
|
||||
|
||||
fn position(&self) -> Vector3<f64> {
|
||||
(**self).position()
|
||||
}
|
||||
|
||||
fn rotation(&self) -> Vector2<f32> {
|
||||
(**self).rotation()
|
||||
}
|
||||
|
||||
fn entity_anchor(&self) -> EntityAnchor {
|
||||
(**self).entity_anchor()
|
||||
}
|
||||
|
||||
fn anchor_position(&self, anchor: EntityAnchor) -> Vector3<f64> {
|
||||
(**self).anchor_position(anchor)
|
||||
}
|
||||
|
||||
fn call_result(&self, result: ReturnValue) {
|
||||
(**self).call_result(result);
|
||||
}
|
||||
|
||||
fn check_block_loaded(&self, pos: &BlockPos) -> Result<(), CommandSyntaxError> {
|
||||
(**self).check_block_loaded(pos)
|
||||
}
|
||||
}
|
||||
@ -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() {
|
||||
@ -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]
|
||||
@ -1,12 +1,13 @@
|
||||
use crate::command::context::command_context::CommandContext;
|
||||
use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
|
||||
use crate::context::command_context::CommandContext;
|
||||
use crate::source::{CommandSource, DummySource};
|
||||
use crate::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
|
||||
|
||||
/// The [`Suggestions`] given by a [`SuggestionProvider`].
|
||||
pub type SuggestionProviderResult = Suggestions;
|
||||
|
||||
/// A trait allowing an object to provide suggestions using a
|
||||
/// [`CommandContext`] and [`SuggestionsBuilder`].
|
||||
pub trait SuggestionProvider: Send + Sync {
|
||||
pub trait SuggestionProvider<S: CommandSource = DummySource>: Send + Sync {
|
||||
/// Uses a [`CommandContext`] and [`SuggestionsBuilder`] to suggest.
|
||||
///
|
||||
/// # Arguments
|
||||
@ -17,7 +18,7 @@ pub trait SuggestionProvider: Send + Sync {
|
||||
/// The [`Suggestions`] representing the suggested items.
|
||||
fn suggest(
|
||||
&self,
|
||||
context: &CommandContext,
|
||||
context: &CommandContext<S>,
|
||||
builder: SuggestionsBuilder,
|
||||
) -> SuggestionProviderResult;
|
||||
}
|
||||
@ -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;
|
||||
@ -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"] }
|
||||
|
||||
@ -1,165 +1,69 @@
|
||||
use crate::command::argument_builder::private::Sealed;
|
||||
use crate::command::argument_types::argument_type::AnyArgumentType;
|
||||
use crate::command::node::detached::{
|
||||
ArgumentDetachedNode, CommandDetachedNode, DetachedNode, GlobalNodeId, LiteralDetachedNode,
|
||||
};
|
||||
use crate::command::node::{
|
||||
Command, CommandExecutor, RedirectModifier, Redirection, Requirement, Requirements,
|
||||
};
|
||||
use crate::command::suggestion::provider::SuggestionProvider;
|
||||
use rustc_hash::FxHashMap;
|
||||
use std::borrow::Cow;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Represents an intermediate struct for
|
||||
/// building arguments for commands.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// This is an implementation detail.
|
||||
struct CommonArgumentBuilder {
|
||||
pub global_id: GlobalNodeId,
|
||||
pub arguments: FxHashMap<String, DetachedNode>,
|
||||
pub command: Option<Command>,
|
||||
pub requirements: Requirements,
|
||||
pub target: Option<Redirection>,
|
||||
pub modifier: RedirectModifier,
|
||||
pub forks: bool,
|
||||
}
|
||||
use crate::command::context::command_source::CommandSource;
|
||||
use crate::command::node::detached::DetachedNode;
|
||||
use crate::command::node::{
|
||||
ArcCommandExecutorAdapter, Command, CommandExecutor, CommandExecutorAdapter, RedirectModifier,
|
||||
Requirement,
|
||||
};
|
||||
use crate::command::suggestion::provider::{SuggestionProvider, SuggestionProviderAdapter};
|
||||
|
||||
impl CommonArgumentBuilder {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
global_id: GlobalNodeId::new(),
|
||||
arguments: FxHashMap::default(),
|
||||
command: None,
|
||||
requirements: Requirements::new(),
|
||||
target: None,
|
||||
modifier: RedirectModifier::KeepSource,
|
||||
forks: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
use pumpkin_command::argument_builder::ArgumentBuilder as CoreArgumentBuilder;
|
||||
pub use pumpkin_command::node::Redirection;
|
||||
|
||||
impl Default for CommonArgumentBuilder {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
pub struct CommandArgumentBuilder(
|
||||
pub pumpkin_command::argument_builder::CommandArgumentBuilder<CommandSource>,
|
||||
);
|
||||
pub struct LiteralArgumentBuilder(
|
||||
pub pumpkin_command::argument_builder::LiteralArgumentBuilder<CommandSource>,
|
||||
);
|
||||
pub struct RequiredArgumentBuilder(
|
||||
pub pumpkin_command::argument_builder::RequiredArgumentBuilder<CommandSource>,
|
||||
);
|
||||
|
||||
/// A short-form way to create a new [`CommandArgumentBuilder`]
|
||||
/// from a literal and a command description.
|
||||
///
|
||||
/// This can be imported directly without a prefix, or imported with the `argument_builder::` prefix. Here's an example of usage:
|
||||
/// ```
|
||||
/// use pumpkin::command::argument_builder::command;
|
||||
///
|
||||
/// let builder = command("foo", "A test command");
|
||||
/// ```
|
||||
///
|
||||
/// The builder returned will eventually construct a [`CommandDetachedNode`].
|
||||
/// This node can then be registered into a dispatcher.
|
||||
pub fn command(
|
||||
literal: impl Into<Cow<'static, str>>,
|
||||
description: impl Into<Cow<'static, str>>,
|
||||
) -> CommandArgumentBuilder {
|
||||
CommandArgumentBuilder::new(literal, description)
|
||||
CommandArgumentBuilder(pumpkin_command::argument_builder::command(
|
||||
literal,
|
||||
description,
|
||||
))
|
||||
}
|
||||
|
||||
/// A short-form way to create a new [`LiteralArgumentBuilder`]
|
||||
/// from a literal.
|
||||
///
|
||||
/// This can be imported directly without a prefix, or imported with the `argument_builder::` prefix. Here's an example of usage:
|
||||
/// ```
|
||||
/// use pumpkin::command::argument_builder::literal;
|
||||
///
|
||||
/// let builder = literal("bar");
|
||||
/// ```
|
||||
///
|
||||
/// The builder returned will eventually construct a [`LiteralDetachedNode`].
|
||||
pub fn literal(literal: impl Into<Cow<'static, str>>) -> LiteralArgumentBuilder {
|
||||
LiteralArgumentBuilder::new(literal)
|
||||
LiteralArgumentBuilder(pumpkin_command::argument_builder::literal(literal))
|
||||
}
|
||||
|
||||
/// A short-form way to create a new [`RequiredArgumentBuilder`]
|
||||
/// from an argument type and name.
|
||||
///
|
||||
/// This can be imported directly without a prefix, or imported with the `argument_builder::` prefix. Here's an example of usage:
|
||||
/// ```
|
||||
/// use pumpkin::command::{
|
||||
/// argument_builder::argument,
|
||||
/// argument_types::core::integer::IntegerArgumentType
|
||||
/// };
|
||||
///
|
||||
/// let argument_builder = argument("bar", IntegerArgumentType::new(1, 10));
|
||||
/// ```
|
||||
///
|
||||
/// The builder returned will eventually construct a [`ArgumentDetachedNode`].
|
||||
pub fn argument(
|
||||
name: impl Into<Cow<'static, str>>,
|
||||
arg_type: impl AnyArgumentType + 'static,
|
||||
arg_type: impl pumpkin_command::argument_types::argument_type::AnyArgumentType<CommandSource>
|
||||
+ 'static,
|
||||
) -> RequiredArgumentBuilder {
|
||||
RequiredArgumentBuilder::new(name, arg_type)
|
||||
RequiredArgumentBuilder(pumpkin_command::argument_builder::argument(name, arg_type))
|
||||
}
|
||||
|
||||
/// A builder that builds a literal, non-command [`DetachedNode`].
|
||||
pub struct LiteralArgumentBuilder {
|
||||
common: CommonArgumentBuilder,
|
||||
literal: Cow<'static, str>,
|
||||
}
|
||||
pub trait ArgumentBuilder: Sized {
|
||||
type Node;
|
||||
|
||||
/// A builder that builds a command [`DetachedNode`].
|
||||
pub struct CommandArgumentBuilder {
|
||||
common: CommonArgumentBuilder,
|
||||
literal: Cow<'static, str>,
|
||||
description: Cow<'static, str>,
|
||||
source: Option<String>,
|
||||
}
|
||||
|
||||
/// A builder that builds an argument [`DetachedNode`].
|
||||
pub struct RequiredArgumentBuilder {
|
||||
common: CommonArgumentBuilder,
|
||||
name: Cow<'static, str>,
|
||||
argument_type: Arc<dyn AnyArgumentType>,
|
||||
suggestion_provider: Option<Arc<dyn SuggestionProvider>>,
|
||||
}
|
||||
|
||||
mod private {
|
||||
// We want to make this trait private so that
|
||||
// we can implement it for only our
|
||||
// argument builders defined here.
|
||||
pub trait Sealed {}
|
||||
}
|
||||
|
||||
pub trait ArgumentBuilder<N: Into<DetachedNode>>: Sized + Sealed {
|
||||
/// Puts an argument to be specified, right after this one is specified.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if this node is redirected to another node, or the child
|
||||
/// provided is of the type [`CommandDetachedNode`].
|
||||
#[must_use]
|
||||
fn then(self, child: impl Into<DetachedNode>) -> Self;
|
||||
|
||||
/// Gets the command to execute for the node being built.
|
||||
#[must_use]
|
||||
fn command(&self) -> Option<Command>;
|
||||
|
||||
/// Sets the command to execute for the node being built.
|
||||
#[must_use]
|
||||
fn executes(self, command: impl CommandExecutor + 'static) -> Self {
|
||||
self.executes_arc(Arc::new(command))
|
||||
}
|
||||
|
||||
/// Sets the command to execute for the node being built.
|
||||
#[must_use]
|
||||
fn executes_arc(self, command: Arc<dyn CommandExecutor + 'static>) -> Self;
|
||||
|
||||
/// Sets the redirect target of the node being built to another, without a modifier.
|
||||
#[must_use]
|
||||
fn redirect(self, redirection: impl Into<Redirection>) -> Self;
|
||||
|
||||
/// Sets the redirect target of the node being built to another, with a given modifier.
|
||||
#[must_use]
|
||||
fn redirect_with_modifier(
|
||||
self,
|
||||
@ -167,392 +71,218 @@ pub trait ArgumentBuilder<N: Into<DetachedNode>>: Sized + Sealed {
|
||||
redirect_modifier: RedirectModifier,
|
||||
) -> Self;
|
||||
|
||||
/// Forks the given context, using multiple for later.
|
||||
#[must_use]
|
||||
fn fork(self, redirection: impl Into<Redirection>, redirect_modifier: RedirectModifier)
|
||||
-> Self;
|
||||
|
||||
/// Forwards the given context, with the given `fork` flag.
|
||||
#[must_use]
|
||||
fn forward(
|
||||
self,
|
||||
redirection: impl Into<Redirection>,
|
||||
redirect_modifier: RedirectModifier,
|
||||
fork: bool,
|
||||
) -> Self;
|
||||
|
||||
/// Gets a reference to the arguments of the node to be built.
|
||||
#[must_use]
|
||||
fn arguments(&self) -> &FxHashMap<String, DetachedNode>;
|
||||
|
||||
/// Gets the node to which the node being built by this [`ArgumentBuilder`] redirects.
|
||||
#[must_use]
|
||||
fn target(&self) -> Option<Redirection>;
|
||||
|
||||
/// Adds a given predicate to this list of requirements of the node being built.
|
||||
///
|
||||
/// This means that it is possible to chain multiple predicates together, which must all
|
||||
/// be satisfied.
|
||||
///
|
||||
/// Permissions can also be inserted directly into this method as a `requirement`.
|
||||
#[must_use]
|
||||
fn requires(self, requirement: impl Into<Requirement>) -> Self;
|
||||
|
||||
/// Overwrites the current requirements of this node to a new value.
|
||||
#[must_use]
|
||||
fn overwrite_requirements(self, requirements: Requirements) -> Self;
|
||||
|
||||
/// Gets the redirect modifier of the node this [`ArgumentBuilder`] is building.
|
||||
#[must_use]
|
||||
fn redirect_modifier(&self) -> RedirectModifier;
|
||||
|
||||
/// Whether this builder forks.
|
||||
#[must_use]
|
||||
fn forks(&self) -> bool;
|
||||
|
||||
/// Returns the 'future [`GlobalNodeId`]' of the node that will be produced by this Builder.
|
||||
/// Very useful for redirects.
|
||||
#[must_use]
|
||||
fn id(&self) -> GlobalNodeId;
|
||||
|
||||
/// Builds the node represented by this builder, consuming itself in the process.
|
||||
#[must_use]
|
||||
fn build(self) -> N;
|
||||
}
|
||||
|
||||
// Implement the private trait for our builders!
|
||||
impl Sealed for LiteralArgumentBuilder {}
|
||||
impl Sealed for CommandArgumentBuilder {}
|
||||
impl Sealed for RequiredArgumentBuilder {}
|
||||
|
||||
/// Helper macro to implement repeated code of `ArgumentBuilder` for our types.
|
||||
macro_rules! impl_boilerplate_argument_builder {
|
||||
() => {
|
||||
fn then(mut self, argument: impl Into<DetachedNode>) -> Self {
|
||||
assert!(
|
||||
self.target().is_none(),
|
||||
"Cannot add children to a redirected node"
|
||||
);
|
||||
let node = argument.into();
|
||||
assert!(
|
||||
!matches!(node, DetachedNode::Command(_)),
|
||||
"Cannot add a CommandDetachedNode as a child of a builder"
|
||||
);
|
||||
|
||||
self.common.arguments.insert(node.name(), node);
|
||||
self
|
||||
}
|
||||
|
||||
fn command(&self) -> Option<Command> {
|
||||
self.common.command.clone()
|
||||
}
|
||||
|
||||
fn executes_arc(mut self, command: Arc<dyn CommandExecutor + 'static>) -> Self {
|
||||
self.common.command = Some(command);
|
||||
self
|
||||
}
|
||||
|
||||
fn requires(mut self, requirement: impl Into<Requirement>) -> Self {
|
||||
self.common.requirements.0.push(requirement.into());
|
||||
self
|
||||
}
|
||||
|
||||
fn overwrite_requirements(mut self, requirements: Requirements) -> Self {
|
||||
self.common.requirements = requirements;
|
||||
self
|
||||
}
|
||||
|
||||
fn redirect(self, redirection: impl Into<Redirection>) -> Self {
|
||||
self.forward(redirection.into(), RedirectModifier::KeepSource, false)
|
||||
}
|
||||
|
||||
fn redirect_with_modifier(self, redirection: impl Into<Redirection>, redirect_modifier: RedirectModifier) -> Self {
|
||||
self.forward(redirection.into(), redirect_modifier, false)
|
||||
}
|
||||
|
||||
fn fork(self, redirection: impl Into<Redirection>, redirect_modifier: RedirectModifier) -> Self {
|
||||
self.forward(redirection.into(), redirect_modifier, true)
|
||||
}
|
||||
|
||||
fn forward(mut self, redirection: impl Into<Redirection>, redirect_modifier: RedirectModifier, fork: bool) -> Self {
|
||||
assert!(self.common.arguments.is_empty(), "Cannot forward a node with children. The node must have no children to redirect somewhere else");
|
||||
self.common.target = Some(redirection.into());
|
||||
self.common.modifier = redirect_modifier;
|
||||
self.common.forks = fork;
|
||||
self
|
||||
}
|
||||
|
||||
fn arguments(&self) -> &FxHashMap<String, DetachedNode> {
|
||||
&self.common.arguments
|
||||
}
|
||||
|
||||
fn target(&self) -> Option<Redirection> {
|
||||
self.common.target.clone()
|
||||
}
|
||||
|
||||
fn redirect_modifier(&self) -> RedirectModifier {
|
||||
self.common.modifier.clone()
|
||||
}
|
||||
|
||||
fn forks(&self) -> bool {
|
||||
self.common.forks
|
||||
}
|
||||
|
||||
fn id(&self) -> GlobalNodeId {
|
||||
self.common.global_id
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Helper macro to generate `From` impl blocks for each builder.
|
||||
macro_rules! impl_builder_from_impls {
|
||||
($builder: ty => $detached_node: ty) => {
|
||||
impl From<$builder> for $detached_node {
|
||||
fn from(value: $builder) -> Self {
|
||||
value.build()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<$builder> for DetachedNode {
|
||||
fn from(value: $builder) -> Self {
|
||||
value.build().into()
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_builder_from_impls!(LiteralArgumentBuilder => LiteralDetachedNode);
|
||||
impl_builder_from_impls!(CommandArgumentBuilder => CommandDetachedNode);
|
||||
impl_builder_from_impls!(RequiredArgumentBuilder => ArgumentDetachedNode);
|
||||
|
||||
impl LiteralArgumentBuilder {
|
||||
/// Creates a new [`LiteralArgumentBuilder`] from a literal.
|
||||
pub fn new(literal: impl Into<Cow<'static, str>>) -> Self {
|
||||
Self {
|
||||
common: CommonArgumentBuilder::new(),
|
||||
literal: literal.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CommandArgumentBuilder {
|
||||
/// Creates a new [`CommandArgumentBuilder`] from a literal and a command description.
|
||||
pub fn new(
|
||||
literal: impl Into<Cow<'static, str>>,
|
||||
description: impl Into<Cow<'static, str>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
common: CommonArgumentBuilder::new(),
|
||||
literal: literal.into(),
|
||||
description: description.into(),
|
||||
source: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the source (e.g. plugin name) that registered this command.
|
||||
#[must_use]
|
||||
pub fn with_source(mut self, source: impl Into<String>) -> Self {
|
||||
self.source = Some(source.into());
|
||||
self
|
||||
}
|
||||
fn build(self) -> Self::Node;
|
||||
}
|
||||
|
||||
impl RequiredArgumentBuilder {
|
||||
/// Creates a new [`RequiredArgumentBuilder`] from a name and an argument type.
|
||||
pub fn new(
|
||||
name: impl Into<Cow<'static, str>>,
|
||||
arg_type: impl AnyArgumentType + 'static,
|
||||
) -> Self {
|
||||
Self {
|
||||
common: CommonArgumentBuilder::new(),
|
||||
name: name.into(),
|
||||
argument_type: Arc::new(arg_type),
|
||||
suggestion_provider: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the [`SuggestionProvider`] of this builder for the `ArgumentDetachedNode`.
|
||||
#[must_use]
|
||||
pub fn suggests(self, provider: impl SuggestionProvider + 'static) -> Self {
|
||||
self.suggests_arc(Arc::new(provider))
|
||||
}
|
||||
|
||||
/// Sets the [`SuggestionProvider`] of this builder for the `ArgumentDetachedNode`.
|
||||
#[must_use]
|
||||
pub fn suggests_arc(mut self, provider: Arc<dyn SuggestionProvider>) -> Self {
|
||||
self.suggestion_provider = Some(provider);
|
||||
self
|
||||
Self(self.0.suggests(SuggestionProviderAdapter(provider)))
|
||||
}
|
||||
}
|
||||
|
||||
impl ArgumentBuilder<LiteralDetachedNode> for LiteralArgumentBuilder {
|
||||
impl_boilerplate_argument_builder!();
|
||||
impl ArgumentBuilder for CommandArgumentBuilder {
|
||||
type Node = pumpkin_command::node::detached::CommandDetachedNode<CommandSource>;
|
||||
|
||||
fn build(self) -> LiteralDetachedNode {
|
||||
let mut node = LiteralDetachedNode::new(
|
||||
self.common.global_id,
|
||||
self.literal,
|
||||
self.common.command,
|
||||
self.common.requirements,
|
||||
self.common.target,
|
||||
self.common.modifier,
|
||||
self.common.forks,
|
||||
);
|
||||
node.children = self.common.arguments;
|
||||
node
|
||||
fn then(self, child: impl Into<DetachedNode>) -> Self {
|
||||
Self(self.0.then(child.into()))
|
||||
}
|
||||
|
||||
fn command(&self) -> Option<Command> {
|
||||
self.0.command()
|
||||
}
|
||||
|
||||
fn executes(self, command: impl CommandExecutor + 'static) -> Self {
|
||||
Self(self.0.executes(CommandExecutorAdapter(command)))
|
||||
}
|
||||
|
||||
fn executes_arc(self, command: Arc<dyn CommandExecutor + 'static>) -> Self {
|
||||
Self(
|
||||
self.0
|
||||
.executes_arc(Arc::new(ArcCommandExecutorAdapter(command))),
|
||||
)
|
||||
}
|
||||
|
||||
fn redirect(self, redirection: impl Into<Redirection>) -> Self {
|
||||
Self(self.0.redirect(redirection))
|
||||
}
|
||||
|
||||
fn redirect_with_modifier(
|
||||
self,
|
||||
redirection: impl Into<Redirection>,
|
||||
redirect_modifier: RedirectModifier,
|
||||
) -> Self {
|
||||
Self(
|
||||
self.0
|
||||
.redirect_with_modifier(redirection, redirect_modifier),
|
||||
)
|
||||
}
|
||||
|
||||
fn fork(
|
||||
self,
|
||||
redirection: impl Into<Redirection>,
|
||||
redirect_modifier: RedirectModifier,
|
||||
) -> Self {
|
||||
Self(self.0.fork(redirection, redirect_modifier))
|
||||
}
|
||||
|
||||
fn requires(self, requirement: impl Into<Requirement>) -> Self {
|
||||
Self(self.0.requires(requirement))
|
||||
}
|
||||
|
||||
fn build(self) -> Self::Node {
|
||||
self.0.build()
|
||||
}
|
||||
}
|
||||
|
||||
impl ArgumentBuilder<CommandDetachedNode> for CommandArgumentBuilder {
|
||||
impl_boilerplate_argument_builder!();
|
||||
impl ArgumentBuilder for LiteralArgumentBuilder {
|
||||
type Node = pumpkin_command::node::detached::LiteralDetachedNode<CommandSource>;
|
||||
|
||||
fn build(self) -> CommandDetachedNode {
|
||||
let mut node = CommandDetachedNode::new(
|
||||
self.common.global_id,
|
||||
self.literal,
|
||||
self.description,
|
||||
self.common.command,
|
||||
self.common.requirements,
|
||||
self.common.target,
|
||||
self.common.modifier,
|
||||
self.common.forks,
|
||||
);
|
||||
node.meta.source = self.source;
|
||||
node.children = self.common.arguments;
|
||||
node
|
||||
fn then(self, child: impl Into<DetachedNode>) -> Self {
|
||||
Self(self.0.then(child.into()))
|
||||
}
|
||||
|
||||
fn command(&self) -> Option<Command> {
|
||||
self.0.command()
|
||||
}
|
||||
|
||||
fn executes(self, command: impl CommandExecutor + 'static) -> Self {
|
||||
Self(self.0.executes(CommandExecutorAdapter(command)))
|
||||
}
|
||||
|
||||
fn executes_arc(self, command: Arc<dyn CommandExecutor + 'static>) -> Self {
|
||||
Self(
|
||||
self.0
|
||||
.executes_arc(Arc::new(ArcCommandExecutorAdapter(command))),
|
||||
)
|
||||
}
|
||||
|
||||
fn redirect(self, redirection: impl Into<Redirection>) -> Self {
|
||||
Self(self.0.redirect(redirection))
|
||||
}
|
||||
|
||||
fn redirect_with_modifier(
|
||||
self,
|
||||
redirection: impl Into<Redirection>,
|
||||
redirect_modifier: RedirectModifier,
|
||||
) -> Self {
|
||||
Self(
|
||||
self.0
|
||||
.redirect_with_modifier(redirection, redirect_modifier),
|
||||
)
|
||||
}
|
||||
|
||||
fn fork(
|
||||
self,
|
||||
redirection: impl Into<Redirection>,
|
||||
redirect_modifier: RedirectModifier,
|
||||
) -> Self {
|
||||
Self(self.0.fork(redirection, redirect_modifier))
|
||||
}
|
||||
|
||||
fn requires(self, requirement: impl Into<Requirement>) -> Self {
|
||||
Self(self.0.requires(requirement))
|
||||
}
|
||||
|
||||
fn build(self) -> Self::Node {
|
||||
self.0.build()
|
||||
}
|
||||
}
|
||||
|
||||
impl ArgumentBuilder<ArgumentDetachedNode> for RequiredArgumentBuilder {
|
||||
impl_boilerplate_argument_builder!();
|
||||
impl ArgumentBuilder for RequiredArgumentBuilder {
|
||||
type Node = pumpkin_command::node::detached::ArgumentDetachedNode<CommandSource>;
|
||||
|
||||
fn build(self) -> ArgumentDetachedNode {
|
||||
let mut node = ArgumentDetachedNode::new(
|
||||
self.common.global_id,
|
||||
self.name,
|
||||
self.argument_type,
|
||||
self.common.command,
|
||||
self.common.requirements,
|
||||
self.common.target,
|
||||
self.common.modifier,
|
||||
self.common.forks,
|
||||
self.suggestion_provider,
|
||||
);
|
||||
node.children = self.common.arguments;
|
||||
node
|
||||
fn then(self, child: impl Into<DetachedNode>) -> Self {
|
||||
Self(self.0.then(child.into()))
|
||||
}
|
||||
|
||||
fn command(&self) -> Option<Command> {
|
||||
self.0.command()
|
||||
}
|
||||
|
||||
fn executes(self, command: impl CommandExecutor + 'static) -> Self {
|
||||
Self(self.0.executes(CommandExecutorAdapter(command)))
|
||||
}
|
||||
|
||||
fn executes_arc(self, command: Arc<dyn CommandExecutor + 'static>) -> Self {
|
||||
Self(
|
||||
self.0
|
||||
.executes_arc(Arc::new(ArcCommandExecutorAdapter(command))),
|
||||
)
|
||||
}
|
||||
|
||||
fn redirect(self, redirection: impl Into<Redirection>) -> Self {
|
||||
Self(self.0.redirect(redirection))
|
||||
}
|
||||
|
||||
fn redirect_with_modifier(
|
||||
self,
|
||||
redirection: impl Into<Redirection>,
|
||||
redirect_modifier: RedirectModifier,
|
||||
) -> Self {
|
||||
Self(
|
||||
self.0
|
||||
.redirect_with_modifier(redirection, redirect_modifier),
|
||||
)
|
||||
}
|
||||
|
||||
fn fork(
|
||||
self,
|
||||
redirection: impl Into<Redirection>,
|
||||
redirect_modifier: RedirectModifier,
|
||||
) -> Self {
|
||||
Self(self.0.fork(redirection, redirect_modifier))
|
||||
}
|
||||
|
||||
fn requires(self, requirement: impl Into<Requirement>) -> Self {
|
||||
Self(self.0.requires(requirement))
|
||||
}
|
||||
|
||||
fn build(self) -> Self::Node {
|
||||
self.0.build()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::command::argument_builder::{ArgumentBuilder, argument, command, literal};
|
||||
use crate::command::argument_types::core::double::DoubleArgumentType;
|
||||
use crate::command::argument_types::core::integer::IntegerArgumentType;
|
||||
use crate::command::argument_types::core::string::StringArgumentType;
|
||||
use crate::command::errors::error_types;
|
||||
use crate::command::node::Redirection;
|
||||
use crate::command::node::attached::AttachedNode;
|
||||
use crate::command::node::tree::Tree;
|
||||
use crate::command::string_reader::StringReader;
|
||||
|
||||
#[test]
|
||||
fn literal_one() {
|
||||
let builder = literal("test");
|
||||
let node = builder.build();
|
||||
|
||||
assert_eq!(node.meta.literal, "test");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn required_one() {
|
||||
let builder = argument("test", IntegerArgumentType::new(1, 10));
|
||||
let node = builder.build();
|
||||
|
||||
assert_eq!(node.meta.name, "test");
|
||||
|
||||
let mut reader1 = StringReader::new("5");
|
||||
let mut reader2 = StringReader::new("11");
|
||||
|
||||
let boxed_result = node
|
||||
.meta
|
||||
.argument_type
|
||||
.parse(&mut reader1)
|
||||
.expect("The parsing should not have errored");
|
||||
let result = boxed_result
|
||||
.downcast::<i32>()
|
||||
.expect("Downcasting shouldn't have failed");
|
||||
assert_eq!(result, Box::new(5));
|
||||
|
||||
let error = node
|
||||
.meta
|
||||
.argument_type
|
||||
.parse(&mut reader2)
|
||||
.expect_err("The parsing should have errored as 11 is outside the range");
|
||||
assert!(error.is(&error_types::INTEGER_TOO_HIGH));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn literal_multiple() {
|
||||
let mut builder = command("letter", "A test command");
|
||||
for letter in 'a'..='z' {
|
||||
// Add a node per letter for the argument.
|
||||
let letter_string = letter.to_string();
|
||||
builder = builder.then(literal(letter_string));
|
||||
}
|
||||
|
||||
let node = builder.build();
|
||||
assert_eq!(node.children.len(), 26);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn required_multiple() {
|
||||
let builder = command("test", "A test command")
|
||||
.then(argument("number", DoubleArgumentType::any()))
|
||||
.then(argument("word", StringArgumentType::SingleWord));
|
||||
|
||||
let node = builder.build();
|
||||
assert_eq!(node.children.len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn redirect() {
|
||||
let builder = command("test", "A test command").redirect(Redirection::Root);
|
||||
|
||||
let mut tree = Tree::new();
|
||||
let node_id = tree.add_child_to_root(builder);
|
||||
|
||||
let node = &tree[node_id];
|
||||
let redirect = node
|
||||
.redirect
|
||||
.expect("Redirection should exist as it was added before");
|
||||
|
||||
let target_id = tree
|
||||
.resolve(redirect)
|
||||
.expect("Target should have been resolved properly");
|
||||
let target = &tree[target_id];
|
||||
|
||||
assert!(matches!(target, AttachedNode::Root(_)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic = "Cannot forward a node with children. The node must have no children to redirect somewhere else"]
|
||||
fn redirect_after_child() {
|
||||
let _ = command("test", "A test command")
|
||||
.then(literal("child"))
|
||||
.redirect(Redirection::Root);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic = "Cannot add children to a redirected node"]
|
||||
fn redirect_before_child() {
|
||||
let _ = command("test", "A test command")
|
||||
.redirect(Redirection::Root)
|
||||
.then(literal("child"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic = "Cannot add a CommandDetachedNode as a child of a builder"]
|
||||
fn add_command_as_child() {
|
||||
let _ = command("foo", "A test command").then(command("bar", "Another test command"));
|
||||
impl From<CommandArgumentBuilder>
|
||||
for pumpkin_command::node::detached::CommandDetachedNode<CommandSource>
|
||||
{
|
||||
fn from(b: CommandArgumentBuilder) -> Self {
|
||||
b.0.build()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LiteralArgumentBuilder> for DetachedNode {
|
||||
fn from(b: LiteralArgumentBuilder) -> Self {
|
||||
Self::Literal(b.0.build())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RequiredArgumentBuilder> for DetachedNode {
|
||||
fn from(b: RequiredArgumentBuilder) -> Self {
|
||||
Self::Argument(b.0.build())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LiteralArgumentBuilder>
|
||||
for pumpkin_command::node::detached::LiteralDetachedNode<CommandSource>
|
||||
{
|
||||
fn from(b: LiteralArgumentBuilder) -> Self {
|
||||
b.0.build()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RequiredArgumentBuilder>
|
||||
for pumpkin_command::node::detached::ArgumentDetachedNode<CommandSource>
|
||||
{
|
||||
fn from(b: RequiredArgumentBuilder) -> Self {
|
||||
b.0.build()
|
||||
}
|
||||
}
|
||||
|
||||
@ -79,7 +79,7 @@ impl EntityArgumentType {
|
||||
}
|
||||
}
|
||||
|
||||
impl ArgumentType for EntityArgumentType {
|
||||
impl ArgumentType<CommandSource> for EntityArgumentType {
|
||||
type Item = EntitySelector;
|
||||
|
||||
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
|
||||
@ -126,7 +126,7 @@ impl EntityArgumentType {
|
||||
self,
|
||||
reader: &mut StringReader,
|
||||
allow_selectors: bool,
|
||||
) -> Result<<Self as ArgumentType>::Item, CommandSyntaxError> {
|
||||
) -> Result<<Self as ArgumentType<CommandSource>>::Item, CommandSyntaxError> {
|
||||
let selector = {
|
||||
let parser = EntitySelectorParser::new(reader, allow_selectors);
|
||||
parser.parse_and_consume()?
|
||||
|
||||
@ -1,82 +1,13 @@
|
||||
use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
|
||||
use crate::command::context::command_context::CommandContext;
|
||||
use crate::command::context::command_source::CommandSource;
|
||||
use crate::command::errors::command_syntax_error::CommandSyntaxError;
|
||||
use crate::command::errors::error_types::CommandErrorType;
|
||||
use crate::command::string_reader::StringReader;
|
||||
use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
|
||||
use crate::entity::Entity;
|
||||
use pumpkin_data::translation;
|
||||
pub use pumpkin_command::argument_types::entity_anchor::*;
|
||||
use pumpkin_util::math::vector3::Vector3;
|
||||
use pumpkin_util::text::TextComponent;
|
||||
|
||||
pub const INVALID_ERROR_TYPE: CommandErrorType<1> = CommandErrorType::new(
|
||||
translation::java::ARGUMENT_ANCHOR_INVALID,
|
||||
translation::java::ARGUMENT_ANCHOR_INVALID,
|
||||
);
|
||||
|
||||
pub struct EntityAnchorArgumentType;
|
||||
|
||||
impl ArgumentType for EntityAnchorArgumentType {
|
||||
type Item = EntityAnchor;
|
||||
|
||||
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
|
||||
let i = reader.cursor();
|
||||
let anchor = reader.read_unquoted_string();
|
||||
EntityAnchor::from_id(anchor.as_str()).map_or_else(
|
||||
|| {
|
||||
reader.set_cursor(i);
|
||||
Err(INVALID_ERROR_TYPE.create(reader, TextComponent::text(anchor)))
|
||||
},
|
||||
Ok,
|
||||
)
|
||||
}
|
||||
|
||||
fn client_side_parser(&'_ self) -> JavaClientArgumentType {
|
||||
JavaClientArgumentType::EntityAnchor
|
||||
}
|
||||
|
||||
fn list_suggestions(
|
||||
&self,
|
||||
_context: &CommandContext,
|
||||
builder: SuggestionsBuilder,
|
||||
) -> Suggestions {
|
||||
builder.filter_and_suggest(&["eyes", "feet"]).build()
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<String> {
|
||||
examples!("eyes", "feet")
|
||||
}
|
||||
pub trait EntityAnchorExt {
|
||||
fn transform_position(self, position: Vector3<f64>, entity: &Entity) -> Vector3<f64>;
|
||||
fn position_at_entity(self, entity: &Entity) -> Vector3<f64>;
|
||||
}
|
||||
|
||||
impl_copy_get!(EntityAnchorArgumentType, EntityAnchor);
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||
pub enum EntityAnchor {
|
||||
Feet,
|
||||
Eyes,
|
||||
}
|
||||
|
||||
impl EntityAnchor {
|
||||
/// Gets the [`EntityAnchor`] whose identity is the ID provided.
|
||||
#[must_use]
|
||||
pub fn from_id(id: &str) -> Option<Self> {
|
||||
match id {
|
||||
"feet" => Some(Self::Feet),
|
||||
"eyes" => Some(Self::Eyes),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the ID of this [`EntityAnchor`]
|
||||
#[must_use]
|
||||
pub const fn id(self) -> &'static str {
|
||||
match self {
|
||||
Self::Feet => "feet",
|
||||
Self::Eyes => "eyes",
|
||||
}
|
||||
}
|
||||
|
||||
impl EntityAnchorExt for EntityAnchor {
|
||||
fn transform_position(self, position: Vector3<f64>, entity: &Entity) -> Vector3<f64> {
|
||||
match self {
|
||||
Self::Feet => position,
|
||||
@ -84,18 +15,7 @@ impl EntityAnchor {
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the position of an entity with respect to this anchor.
|
||||
pub fn position_at_entity(self, entity: &Entity) -> Vector3<f64> {
|
||||
fn position_at_entity(self, entity: &Entity) -> Vector3<f64> {
|
||||
self.transform_position(entity.pos.load(), entity)
|
||||
}
|
||||
|
||||
/// Gets the position of a source with respect to this anchor.
|
||||
#[must_use]
|
||||
pub fn position_at_source(self, command_source: &CommandSource) -> Vector3<f64> {
|
||||
let pos = command_source.position;
|
||||
command_source
|
||||
.entity
|
||||
.as_ref()
|
||||
.map_or_else(|| pos, |e| self.position_at_entity(e.get_entity()))
|
||||
}
|
||||
}
|
||||
|
||||
@ -228,7 +228,7 @@ impl GameProfileResult {
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct GameProfileArgumentType;
|
||||
|
||||
impl ArgumentType for GameProfileArgumentType {
|
||||
impl ArgumentType<CommandSource> for GameProfileArgumentType {
|
||||
type Item = GameProfileResult;
|
||||
|
||||
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
|
||||
@ -256,7 +256,7 @@ impl GameProfileArgumentType {
|
||||
fn parse_with_allow_selectors(
|
||||
reader: &mut StringReader,
|
||||
allow_selectors: bool,
|
||||
) -> Result<<Self as ArgumentType>::Item, CommandSyntaxError> {
|
||||
) -> Result<<Self as ArgumentType<CommandSource>>::Item, CommandSyntaxError> {
|
||||
if reader.peek() == Some('@') {
|
||||
// We read a selector variable.
|
||||
let parser = EntitySelectorParser::new(reader, allow_selectors);
|
||||
|
||||
@ -1,16 +1,6 @@
|
||||
use crate::command::errors::command_syntax_error::CommandSyntaxError;
|
||||
use crate::command::errors::error_types::{
|
||||
CommandErrorType, READER_INVALID_DOUBLE, READER_INVALID_FLOAT, READER_INVALID_INT,
|
||||
};
|
||||
use crate::command::string_reader::StringReader;
|
||||
use pumpkin_data::translation;
|
||||
use pumpkin_util::identifier::Identifier;
|
||||
use pumpkin_util::math::bounds::{Bounds, DoubleBounds, FloatDegreeBounds, IntBounds};
|
||||
use pumpkin_util::text::TextComponent;
|
||||
use std::str::FromStr;
|
||||
|
||||
/// Creates a [`Vec<String>`] of examples from
|
||||
/// the given string literals.
|
||||
#[macro_export]
|
||||
macro_rules! examples {
|
||||
( $( $example:literal ),* ) => {
|
||||
vec! [
|
||||
@ -19,235 +9,12 @@ macro_rules! examples {
|
||||
};
|
||||
}
|
||||
|
||||
// Helper methods for assertion with a `StringReader`:
|
||||
pub use pumpkin_command::argument_types::*;
|
||||
|
||||
/// Asserts that the result read by `reader` with the argument
|
||||
/// type `$argument_type` used to parse is equal to `Ok($value)`.
|
||||
/// Also resets the reader's cursor back to the start.
|
||||
#[cfg(test)]
|
||||
macro_rules! assert_parse_ok_reset {
|
||||
($reader: expr, $argument_type: expr, $value: expr) => {{
|
||||
assert_eq!($argument_type.parse(&mut $reader), Ok($value));
|
||||
$reader.set_cursor(0)
|
||||
}};
|
||||
($reader: expr, $argument_type: expr) => {{
|
||||
assert!($argument_type.parse(&mut $reader).is_ok());
|
||||
$reader.set_cursor(0)
|
||||
}};
|
||||
}
|
||||
|
||||
/// Asserts that the result read by `reader` with the argument
|
||||
/// type `$argument_type` used to parse is an `Err` containing the type of error as `$error_type`.
|
||||
/// Also resets the reader's cursor back to the start.
|
||||
#[cfg(test)]
|
||||
macro_rules! assert_parse_err_reset {
|
||||
($reader: expr, $argument_type: expr, $error_type: expr) => {
|
||||
let error_type_dyn: &'static dyn crate::command::errors::error_types::AnyCommandErrorType =
|
||||
$error_type;
|
||||
assert_eq!(
|
||||
$argument_type.parse(&mut $reader).map_err(|e| e.error_type),
|
||||
Err(error_type_dyn)
|
||||
);
|
||||
$reader.set_cursor(0)
|
||||
};
|
||||
}
|
||||
|
||||
/// Macro to implement a single `get()` function for an argument type whose `Item` is `Copy`.
|
||||
macro_rules! impl_copy_get {
|
||||
($ty:ty, $item:ty) => {
|
||||
impl $ty {
|
||||
#[doc = concat!("Returns a `CommandContext`'s parsed `", stringify!($item), "` argument.")]
|
||||
pub fn get(context: &$crate::command::context::command_context::CommandContext, name: &str) -> Result<$item, CommandSyntaxError> {
|
||||
Ok(*context.get_argument(name)?)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const EMPTY_BOUNDS_ERROR_TYPE: CommandErrorType<0> = CommandErrorType::new(
|
||||
translation::java::ARGUMENT_RANGE_EMPTY,
|
||||
translation::java::ARGUMENT_RANGE_EMPTY,
|
||||
);
|
||||
const SWAPPED_BOUNDS_ERROR_TYPE: CommandErrorType<0> = CommandErrorType::new(
|
||||
translation::java::ARGUMENT_RANGE_SWAPPED,
|
||||
translation::java::ARGUMENT_RANGE_SWAPPED,
|
||||
);
|
||||
const INVALID_IDENTIFIER_ERROR_TYPE: CommandErrorType<0> = CommandErrorType::new(
|
||||
translation::java::ARGUMENT_ID_INVALID,
|
||||
translation::java::ARGUMENT_ID_INVALID,
|
||||
);
|
||||
|
||||
/// A trait to try getting a value from a [`StringReader`].
|
||||
pub trait FromStringReader: Sized {
|
||||
/// Tries to parse a value of this type from a [`StringReader`].
|
||||
///
|
||||
/// If this fails, a [`CommandSyntaxError`] is returned with the erroring details.
|
||||
fn from_reader(reader: &mut StringReader) -> Result<Self, CommandSyntaxError>;
|
||||
}
|
||||
|
||||
fn bounds_from_reader<T: Copy + FromStr + PartialOrd>(
|
||||
reader: &mut StringReader,
|
||||
error_type: &'static CommandErrorType<1>,
|
||||
) -> Result<Bounds<T>, CommandSyntaxError> {
|
||||
if reader.can_read_char() {
|
||||
let i = reader.cursor();
|
||||
try_bounds_from_reader(reader, error_type).map_err(|e| {
|
||||
// On catching any error, set the reader's cursor position to be at where the range starts.
|
||||
if let Some(mut context) = e.context {
|
||||
context.cursor = i;
|
||||
CommandSyntaxError::create(e.error_type, e.message, &context)
|
||||
} else {
|
||||
CommandSyntaxError::create_without_context(e.error_type, e.message)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
Err(EMPTY_BOUNDS_ERROR_TYPE.create(reader))
|
||||
}
|
||||
}
|
||||
|
||||
fn try_bounds_from_reader<T: Copy + FromStr + PartialOrd>(
|
||||
reader: &mut StringReader,
|
||||
error_type: &'static CommandErrorType<1>,
|
||||
) -> Result<Bounds<T>, CommandSyntaxError> {
|
||||
let min = read_number_from_reader(reader, error_type)?;
|
||||
let max = if reader.peek() == Some('.') && reader.peek_with_offset(1) == Some('.') {
|
||||
reader.skip();
|
||||
reader.skip();
|
||||
read_number_from_reader(reader, error_type)?
|
||||
} else {
|
||||
min
|
||||
};
|
||||
if min.is_none() && max.is_none() {
|
||||
Err(EMPTY_BOUNDS_ERROR_TYPE.create(reader))
|
||||
} else {
|
||||
Ok(Bounds::<T>::new(min, max))
|
||||
}
|
||||
}
|
||||
|
||||
/// Tries to read a number of a type from a reader.
|
||||
fn read_number_from_reader<T: FromStr>(
|
||||
reader: &mut StringReader,
|
||||
error_type: &'static CommandErrorType<1>,
|
||||
) -> Result<Option<T>, CommandSyntaxError> {
|
||||
let i = reader.cursor();
|
||||
while has_allowed_peaked_character(reader) {
|
||||
reader.skip();
|
||||
}
|
||||
let string = &reader.string()[i..reader.cursor()].to_string();
|
||||
if string.is_empty() {
|
||||
Ok(None)
|
||||
} else {
|
||||
string.parse::<T>().map_or_else(
|
||||
|_| Err(error_type.create(reader, TextComponent::text(string.clone()))),
|
||||
|t| Ok(Some(t)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn has_allowed_peaked_character(reader: &StringReader) -> bool {
|
||||
let c = reader.peek();
|
||||
if matches!(c, Some('0'..='9' | '-')) {
|
||||
true
|
||||
} else {
|
||||
c.is_some_and(|c| {
|
||||
c == '.' && (!reader.can_read_chars(2) || reader.peek_with_offset(1) != Some('.'))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStringReader for IntBounds {
|
||||
fn from_reader(reader: &mut StringReader) -> Result<Self, CommandSyntaxError> {
|
||||
let i = reader.cursor();
|
||||
let bounds = bounds_from_reader(reader, &READER_INVALID_INT)?;
|
||||
if bounds.are_swapped() {
|
||||
reader.set_cursor(i);
|
||||
Err(SWAPPED_BOUNDS_ERROR_TYPE.create(reader))
|
||||
} else {
|
||||
Ok(Self::from_bounds(bounds))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStringReader for DoubleBounds {
|
||||
fn from_reader(reader: &mut StringReader) -> Result<Self, CommandSyntaxError> {
|
||||
let i = reader.cursor();
|
||||
let bounds = bounds_from_reader(reader, &READER_INVALID_DOUBLE)?;
|
||||
if bounds.are_swapped() {
|
||||
reader.set_cursor(i);
|
||||
Err(SWAPPED_BOUNDS_ERROR_TYPE.create(reader))
|
||||
} else {
|
||||
Ok(Self::from_bounds(bounds))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStringReader for FloatDegreeBounds {
|
||||
fn from_reader(reader: &mut StringReader) -> Result<Self, CommandSyntaxError> {
|
||||
let i = reader.cursor();
|
||||
let bounds = bounds_from_reader(reader, &READER_INVALID_FLOAT)?;
|
||||
if bounds.are_swapped() {
|
||||
reader.set_cursor(i);
|
||||
Err(SWAPPED_BOUNDS_ERROR_TYPE.create(reader))
|
||||
} else {
|
||||
Ok(Self::from_bounds(bounds))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStringReader for Identifier {
|
||||
fn from_reader(reader: &mut StringReader) -> Result<Self, CommandSyntaxError> {
|
||||
let start = reader.cursor();
|
||||
while let Some(c) = reader.peek()
|
||||
&& Self::is_valid_char(c)
|
||||
{
|
||||
reader.skip();
|
||||
}
|
||||
|
||||
let raw_identifier = &reader.string()[start..reader.cursor()];
|
||||
let identifier_result = Self::parse(raw_identifier);
|
||||
|
||||
identifier_result.map_or_else(
|
||||
|_| {
|
||||
reader.set_cursor(start);
|
||||
Err(INVALID_IDENTIFIER_ERROR_TYPE.create(reader))
|
||||
},
|
||||
Ok,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub mod argument_type;
|
||||
pub mod attribute;
|
||||
pub mod block;
|
||||
pub mod block_predicate;
|
||||
pub mod component;
|
||||
pub mod coordinates;
|
||||
pub mod core;
|
||||
pub mod dialog;
|
||||
pub mod entity;
|
||||
pub mod entity_anchor;
|
||||
pub mod entity_selector;
|
||||
pub mod game_profile;
|
||||
pub mod gamemode;
|
||||
pub mod hex_color;
|
||||
pub mod identifier;
|
||||
pub mod item;
|
||||
pub mod item_predicate;
|
||||
pub mod nbt;
|
||||
pub mod nbt_path;
|
||||
pub mod objective;
|
||||
pub mod particle;
|
||||
pub mod placed_feature;
|
||||
pub mod pool;
|
||||
pub mod range;
|
||||
pub mod resource;
|
||||
pub mod resource_key;
|
||||
pub mod resource_or_tag;
|
||||
pub mod slot;
|
||||
pub mod sound_category;
|
||||
pub mod structure;
|
||||
pub mod team;
|
||||
pub mod team_color;
|
||||
pub mod template;
|
||||
pub mod time;
|
||||
pub mod uuid;
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
use crate::command::{
|
||||
CommandSource,
|
||||
argument_types::argument_type::{ArgumentType, JavaClientArgumentType},
|
||||
context::command_context::CommandContext,
|
||||
errors::command_syntax_error::CommandSyntaxError,
|
||||
@ -10,7 +11,7 @@ use crate::command::{
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct ObjectiveArgumentType;
|
||||
|
||||
impl ArgumentType for ObjectiveArgumentType {
|
||||
impl ArgumentType<CommandSource> for ObjectiveArgumentType {
|
||||
type Item = String;
|
||||
|
||||
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
use crate::command::CommandSource;
|
||||
use crate::command::argument_types::FromStringReader;
|
||||
use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
|
||||
use crate::command::context::command_context::CommandContext;
|
||||
@ -42,7 +43,7 @@ pub static ERROR_INVALID: CommandErrorType<0> = CommandErrorType::new(
|
||||
translation::java::ARGUMENT_ID_INVALID,
|
||||
);
|
||||
|
||||
impl ArgumentType for ResourceKeyArgument {
|
||||
impl ArgumentType<CommandSource> for ResourceKeyArgument {
|
||||
type Item = ResourceKey;
|
||||
|
||||
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
use crate::command::{
|
||||
CommandSource,
|
||||
argument_types::argument_type::{ArgumentType, JavaClientArgumentType},
|
||||
context::command_context::CommandContext,
|
||||
errors::command_syntax_error::CommandSyntaxError,
|
||||
@ -10,7 +11,7 @@ use crate::command::{
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct TeamArgumentType;
|
||||
|
||||
impl ArgumentType for TeamArgumentType {
|
||||
impl ArgumentType<CommandSource> for TeamArgumentType {
|
||||
type Item = String;
|
||||
|
||||
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
|
||||
|
||||
@ -6,6 +6,7 @@ use pumpkin_util::permission::{Permission, PermissionDefault, PermissionRegistry
|
||||
use pumpkin_util::text::TextComponent;
|
||||
use pumpkin_util::text::hover::HoverEvent;
|
||||
|
||||
use crate::command::CommandSource;
|
||||
use crate::command::argument_builder::{ArgumentBuilder, argument, command, literal};
|
||||
use crate::command::argument_types::FromStringReader;
|
||||
use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
|
||||
@ -50,7 +51,7 @@ const ERROR_CREATE_FAILED: CommandErrorType<1> = CommandErrorType::new(
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct BossbarIdArgumentType;
|
||||
|
||||
impl ArgumentType for BossbarIdArgumentType {
|
||||
impl ArgumentType<CommandSource> for BossbarIdArgumentType {
|
||||
type Item = String;
|
||||
|
||||
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
|
||||
@ -85,7 +86,7 @@ pub struct BossbarColorArgumentType;
|
||||
|
||||
const COLORS: [&str; 7] = ["blue", "green", "pink", "purple", "red", "white", "yellow"];
|
||||
|
||||
impl ArgumentType for BossbarColorArgumentType {
|
||||
impl ArgumentType<CommandSource> for BossbarColorArgumentType {
|
||||
type Item = BossbarColor;
|
||||
|
||||
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
|
||||
@ -138,7 +139,7 @@ const STYLES: [&str; 5] = [
|
||||
"progress",
|
||||
];
|
||||
|
||||
impl ArgumentType for BossbarStyleArgumentType {
|
||||
impl ArgumentType<CommandSource> for BossbarStyleArgumentType {
|
||||
type Item = BossbarDivisions;
|
||||
|
||||
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
use crate::command::CommandSource;
|
||||
use crate::command::argument_builder::{ArgumentBuilder, argument, command};
|
||||
use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
|
||||
use crate::command::argument_types::core::string::StringArgumentType;
|
||||
@ -35,7 +36,7 @@ enum HelpArgument {
|
||||
}
|
||||
|
||||
struct HelpArgumentType;
|
||||
impl ArgumentType for HelpArgumentType {
|
||||
impl ArgumentType<CommandSource> for HelpArgumentType {
|
||||
type Item = HelpArgument;
|
||||
|
||||
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
|
||||
@ -68,7 +69,10 @@ impl ArgumentType for HelpArgumentType {
|
||||
// We use greedy phrases for now
|
||||
// as the `?` command as the argument
|
||||
// doesn't work for the unquoted word one.
|
||||
let mut text = StringArgumentType::GreedyPhrase.parse(reader)?;
|
||||
let mut text = ArgumentType::<CommandSource>::parse(
|
||||
&StringArgumentType::GreedyPhrase,
|
||||
reader,
|
||||
)?;
|
||||
|
||||
{
|
||||
let mut integer_text = text.as_str();
|
||||
|
||||
@ -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};
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -11,6 +11,7 @@ use pumpkin_util::text::TextComponent;
|
||||
use tracing::info;
|
||||
|
||||
use crate::command::CommandSender;
|
||||
use crate::command::CommandSource;
|
||||
use crate::command::argument_builder::{ArgumentBuilder, argument, command, literal};
|
||||
use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
|
||||
use crate::command::argument_types::core::bool::BoolArgumentType;
|
||||
@ -59,7 +60,7 @@ impl GameTestReporter for CommandTestReporter {
|
||||
/// matching the behavior of the old command implementation.
|
||||
struct TestInstanceArgumentType;
|
||||
|
||||
impl ArgumentType for TestInstanceArgumentType {
|
||||
impl ArgumentType<CommandSource> for TestInstanceArgumentType {
|
||||
type Item = String;
|
||||
|
||||
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
|
||||
|
||||
@ -1,12 +1,16 @@
|
||||
use crate::command::CommandSender;
|
||||
use crate::command::argument_types::entity_anchor::EntityAnchor;
|
||||
use crate::command::errors::command_syntax_error::CommandSyntaxError;
|
||||
use crate::command::errors::error_types::CommandErrorType;
|
||||
use crate::command::argument_types::entity_anchor::{EntityAnchor, EntityAnchorExt};
|
||||
use crate::entity::EntityBase;
|
||||
use crate::entity::player::Player;
|
||||
use crate::server::Server;
|
||||
use crate::world::World;
|
||||
use pumpkin_command::errors::command_syntax_error::CommandSyntaxError;
|
||||
use pumpkin_command::errors::error_types::CommandErrorType;
|
||||
pub use pumpkin_command::source::{
|
||||
ResultValueTaker, ReturnValue, ReturnValueCallable, ReturnValueCallback,
|
||||
};
|
||||
use pumpkin_data::translation;
|
||||
use pumpkin_util::math::position::BlockPos;
|
||||
use pumpkin_util::math::vector2::Vector2;
|
||||
use pumpkin_util::math::vector3::Vector3;
|
||||
use pumpkin_util::math::wrap_degrees;
|
||||
@ -23,50 +27,6 @@ pub const REQUIRES_ENTITY: CommandErrorType<0> = CommandErrorType::new(
|
||||
translation::java::PERMISSIONS_REQUIRES_ENTITY,
|
||||
);
|
||||
|
||||
pub trait ReturnValueCallable: Send + Sync {
|
||||
fn call(&self, value: ReturnValue);
|
||||
}
|
||||
|
||||
pub type ReturnValueCallback = Arc<dyn ReturnValueCallable>;
|
||||
|
||||
/// Represents a collection of 'return value callbacks'.
|
||||
#[derive(Clone)]
|
||||
pub struct ResultValueTaker(pub Vec<ReturnValueCallback>);
|
||||
|
||||
impl ResultValueTaker {
|
||||
/// Merges two takers, returning one.
|
||||
#[must_use]
|
||||
pub fn merge(taker_1: &Self, taker_2: &Self) -> Self {
|
||||
let mut takers = Vec::with_capacity(taker_1.0.len() + taker_2.0.len());
|
||||
for taker in &taker_1.0 {
|
||||
takers.push(taker.clone());
|
||||
}
|
||||
for taker in &taker_2.0 {
|
||||
takers.push(taker.clone());
|
||||
}
|
||||
Self(takers)
|
||||
}
|
||||
|
||||
/// Constructs a new, empty result value taker.
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
Self(Vec::new())
|
||||
}
|
||||
|
||||
/// Calls all the contained callbacks of this taker with the returned result.
|
||||
pub fn call(&self, return_value: ReturnValue) {
|
||||
for callback in &self.0 {
|
||||
callback.call(return_value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ResultValueTaker {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a source of a command, which
|
||||
/// contains its own state, which could keep track of its:
|
||||
/// - position
|
||||
@ -505,28 +465,60 @@ impl CommandSource {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||
pub enum ReturnValue {
|
||||
Success(i32),
|
||||
Failure,
|
||||
}
|
||||
impl pumpkin_command::source::CommandSource for CommandSource {
|
||||
fn send_message(&self, message: TextComponent) {
|
||||
self.send_message(message);
|
||||
}
|
||||
|
||||
impl ReturnValue {
|
||||
/// Get the success value of this return value.
|
||||
#[must_use]
|
||||
pub const fn success_value(self) -> bool {
|
||||
match self {
|
||||
Self::Success(_) => true,
|
||||
Self::Failure => false,
|
||||
fn send_error(&self, error: TextComponent) {
|
||||
self.send_error(error);
|
||||
}
|
||||
|
||||
fn call_result(&self, result: ReturnValue) {
|
||||
self.command_result_taker.call(result);
|
||||
}
|
||||
|
||||
fn has_permission(&self, permission: &str) -> bool {
|
||||
self.has_permission(permission)
|
||||
}
|
||||
|
||||
fn position(&self) -> Vector3<f64> {
|
||||
self.position
|
||||
}
|
||||
|
||||
fn rotation(&self) -> Vector2<f32> {
|
||||
self.rotation
|
||||
}
|
||||
|
||||
fn check_block_loaded(&self, pos: &BlockPos) -> Result<(), CommandSyntaxError> {
|
||||
let world = self.world();
|
||||
if world
|
||||
.level
|
||||
.read_chunk_sync(&pos.chunk_position(), |_| ())
|
||||
.is_none()
|
||||
{
|
||||
Err(
|
||||
pumpkin_command::argument_types::coordinates::block_pos::NOT_LOADED_ERROR_TYPE
|
||||
.create_without_context(),
|
||||
)
|
||||
} else if !world.is_in_build_limit(*pos) {
|
||||
Err(
|
||||
pumpkin_command::argument_types::coordinates::block_pos::OUT_OF_WORLD_ERROR_TYPE
|
||||
.create_without_context(),
|
||||
)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the result integral value of this return value.
|
||||
#[must_use]
|
||||
pub const fn result_value(self) -> i32 {
|
||||
match self {
|
||||
Self::Success(value) => value,
|
||||
Self::Failure => 0,
|
||||
}
|
||||
fn entity_anchor(&self) -> EntityAnchor {
|
||||
self.entity_anchor
|
||||
}
|
||||
|
||||
fn anchor_position(&self, anchor: EntityAnchor) -> Vector3<f64> {
|
||||
let pos = self.position;
|
||||
self.entity
|
||||
.as_ref()
|
||||
.map_or_else(|| pos, |e| anchor.position_at_entity(e.get_entity()))
|
||||
}
|
||||
}
|
||||
|
||||
@ -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::*;
|
||||
|
||||
@ -1 +0,0 @@
|
||||
pub use crate::command::node::dispatcher::*;
|
||||
@ -7,7 +7,7 @@ use std::sync::atomic::AtomicBool;
|
||||
|
||||
use crate::block::entities::BlockEntity;
|
||||
use crate::block::entities::command_block::CommandBlockEntity;
|
||||
use crate::command::context::command_source::CommandSource;
|
||||
pub use crate::command::context::command_source::CommandSource;
|
||||
use crate::entity::EntityBase;
|
||||
use crate::entity::player::Player;
|
||||
use crate::server::Server;
|
||||
@ -23,18 +23,69 @@ use pumpkin_util::permission::{PermissionDefault, PermissionLvl};
|
||||
use pumpkin_util::text::TextComponent;
|
||||
use pumpkin_util::translation::Locale;
|
||||
|
||||
pub use pumpkin_command::*;
|
||||
|
||||
pub mod argument_builder;
|
||||
pub mod argument_types;
|
||||
pub mod client_suggestions;
|
||||
pub mod commands;
|
||||
pub mod context;
|
||||
pub mod dispatcher;
|
||||
pub mod errors;
|
||||
pub mod node;
|
||||
pub mod parser;
|
||||
pub mod snbt;
|
||||
pub mod string_reader;
|
||||
pub mod suggestion;
|
||||
|
||||
pub mod dispatcher {
|
||||
pub use pumpkin_command::dispatcher::*;
|
||||
pub type CommandDispatcher =
|
||||
pumpkin_command::dispatcher::CommandDispatcher<crate::command::CommandSource>;
|
||||
}
|
||||
pub mod errors {
|
||||
pub use pumpkin_command::errors::*;
|
||||
}
|
||||
pub mod parser {
|
||||
pub use pumpkin_command::parser::*;
|
||||
}
|
||||
pub mod snbt {
|
||||
pub use pumpkin_command::snbt::*;
|
||||
}
|
||||
pub mod string_reader {
|
||||
pub use pumpkin_command::string_reader::*;
|
||||
}
|
||||
pub mod suggestion {
|
||||
pub use pumpkin_command::suggestion::*;
|
||||
|
||||
pub mod provider {
|
||||
use crate::command::context::command_context::CommandContext;
|
||||
use crate::command::context::command_source::CommandSource;
|
||||
use pumpkin_command::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
|
||||
|
||||
pub type SuggestionProviderResult = Suggestions;
|
||||
|
||||
pub trait SuggestionProvider: Send + Sync {
|
||||
fn suggest(
|
||||
&self,
|
||||
context: &CommandContext,
|
||||
builder: SuggestionsBuilder,
|
||||
) -> SuggestionProviderResult;
|
||||
}
|
||||
|
||||
pub struct SuggestionProviderAdapter<T>(pub T);
|
||||
|
||||
impl<T: SuggestionProvider>
|
||||
pumpkin_command::suggestion::provider::SuggestionProvider<CommandSource>
|
||||
for SuggestionProviderAdapter<T>
|
||||
{
|
||||
fn suggest(
|
||||
&self,
|
||||
context: &pumpkin_command::context::command_context::CommandContext<
|
||||
'_,
|
||||
CommandSource,
|
||||
>,
|
||||
builder: SuggestionsBuilder,
|
||||
) -> SuggestionProviderResult {
|
||||
self.0.suggest(context, builder)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether console and RCON command output is broadcast to online operators.
|
||||
///
|
||||
@ -397,5 +448,6 @@ const fn command_block_y_rot(facing: Facing) -> f32 {
|
||||
}
|
||||
}
|
||||
|
||||
pub use context::command_context::CommandContext;
|
||||
pub use node::dispatcher::CommandDispatcher;
|
||||
pub use node::{Command, CommandExecutor, CommandExecutorResult};
|
||||
|
||||
@ -1,263 +1,78 @@
|
||||
pub mod attached;
|
||||
pub mod detached;
|
||||
pub mod dispatcher;
|
||||
pub mod tree;
|
||||
|
||||
use crate::command::argument_types::argument_type::AnyArgumentType;
|
||||
use crate::command::context::command_context::CommandContext;
|
||||
use crate::command::context::command_source::CommandSource;
|
||||
use crate::command::errors::command_syntax_error::CommandSyntaxError;
|
||||
use crate::command::node::attached::NodeId;
|
||||
use crate::command::node::detached::GlobalNodeId;
|
||||
use crate::command::suggestion::provider::SuggestionProvider;
|
||||
use std::borrow::Cow;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Represents a [`CommandExecutor`]'s result.
|
||||
///
|
||||
/// If the command **ran successfully**, an [`Ok`] is returned containing an [`i32`].
|
||||
/// This represents the 'output value' of the command, which is *homologous* to the
|
||||
/// `int` that command executors in vanilla return **upon success**.
|
||||
///
|
||||
/// **You should choose the successful result as `1` if**:
|
||||
/// - you don't know what value to use for a success for your
|
||||
/// own commands, or
|
||||
/// - you don't understand what this value means, or
|
||||
/// - you just simply don't care about this value at all
|
||||
///
|
||||
/// If the command **fails**, an [`Err`] is returned, containing the [`CommandSyntaxError`]
|
||||
/// that led to this result.
|
||||
pub type CommandExecutorResult = Result<i32, CommandSyntaxError>;
|
||||
pub use pumpkin_command::node::*;
|
||||
|
||||
pub mod attached {
|
||||
pub use pumpkin_command::node::attached::*;
|
||||
pub type AttachedNode =
|
||||
pumpkin_command::node::attached::AttachedNode<crate::command::CommandSource>;
|
||||
pub type RootAttachedNode =
|
||||
pumpkin_command::node::attached::RootAttachedNode<crate::command::CommandSource>;
|
||||
pub type LiteralAttachedNode =
|
||||
pumpkin_command::node::attached::LiteralAttachedNode<crate::command::CommandSource>;
|
||||
pub type CommandAttachedNode =
|
||||
pumpkin_command::node::attached::CommandAttachedNode<crate::command::CommandSource>;
|
||||
pub type ArgumentAttachedNode =
|
||||
pumpkin_command::node::attached::ArgumentAttachedNode<crate::command::CommandSource>;
|
||||
}
|
||||
|
||||
pub mod detached {
|
||||
pub use pumpkin_command::node::detached::*;
|
||||
pub type DetachedNode =
|
||||
pumpkin_command::node::detached::DetachedNode<crate::command::CommandSource>;
|
||||
pub type LiteralDetachedNode =
|
||||
pumpkin_command::node::detached::LiteralDetachedNode<crate::command::CommandSource>;
|
||||
pub type CommandDetachedNode =
|
||||
pumpkin_command::node::detached::CommandDetachedNode<crate::command::CommandSource>;
|
||||
pub type ArgumentDetachedNode =
|
||||
pumpkin_command::node::detached::ArgumentDetachedNode<crate::command::CommandSource>;
|
||||
}
|
||||
|
||||
pub mod tree {
|
||||
pub use pumpkin_command::node::tree::*;
|
||||
pub type Tree = pumpkin_command::node::tree::Tree<crate::command::CommandSource>;
|
||||
}
|
||||
|
||||
pub mod dispatcher {
|
||||
pub use pumpkin_command::dispatcher::*;
|
||||
pub type CommandDispatcher =
|
||||
pumpkin_command::dispatcher::CommandDispatcher<crate::command::CommandSource>;
|
||||
}
|
||||
|
||||
pub type Command = pumpkin_command::node::Command<CommandSource>;
|
||||
pub type Requirement = pumpkin_command::node::Requirement<CommandSource>;
|
||||
pub type Requirements = pumpkin_command::node::Requirements<CommandSource>;
|
||||
pub type RedirectModifier = pumpkin_command::node::RedirectModifier<CommandSource>;
|
||||
pub type RedirectModifierResult = pumpkin_command::node::RedirectModifierResult<CommandSource>;
|
||||
pub type RedirectModifierExecutor = pumpkin_command::node::RedirectModifierExecutor<CommandSource>;
|
||||
|
||||
/// A struct implementing this trait is able to run with a given context.
|
||||
pub trait CommandExecutor: Sync + Send {
|
||||
/// Executes this executor for a command.
|
||||
fn execute(&self, context: &CommandContext) -> CommandExecutorResult;
|
||||
fn execute(&self, context: &CommandContext) -> pumpkin_command::node::CommandExecutorResult;
|
||||
}
|
||||
|
||||
impl<F> CommandExecutor for F
|
||||
where
|
||||
F: Fn(&CommandContext) -> CommandExecutorResult + Send + Sync,
|
||||
pub struct CommandExecutorAdapter<T>(pub T);
|
||||
|
||||
impl<T: CommandExecutor> pumpkin_command::node::CommandExecutor<CommandSource>
|
||||
for CommandExecutorAdapter<T>
|
||||
{
|
||||
fn execute(&self, context: &CommandContext) -> CommandExecutorResult {
|
||||
self(context)
|
||||
fn execute(
|
||||
&self,
|
||||
context: &pumpkin_command::context::command_context::CommandContext<'_, CommandSource>,
|
||||
) -> pumpkin_command::node::CommandExecutorResult {
|
||||
self.0.execute(context)
|
||||
}
|
||||
}
|
||||
|
||||
/// A function that takes a context and returns a command result.
|
||||
pub type Command = Arc<dyn CommandExecutor>;
|
||||
pub struct ArcCommandExecutorAdapter(pub std::sync::Arc<dyn CommandExecutor>);
|
||||
|
||||
/// Represents the result of [`Arc<CommandSource>`]s from a [`CommandContext`].
|
||||
pub type RedirectModifierResult = Result<Vec<Arc<CommandSource>>, CommandSyntaxError>;
|
||||
|
||||
/// A function that performs the required modification.
|
||||
pub type RedirectModifierExecutor = dyn Fn(&CommandContext) -> RedirectModifierResult + Send + Sync;
|
||||
|
||||
/// A function that returns a new collection of sources from a given context.
|
||||
#[derive(Clone)]
|
||||
pub enum RedirectModifier {
|
||||
/// Always returns only the source from the given context.
|
||||
KeepSource,
|
||||
|
||||
/// Returns multiple [`CommandSource`]s from one context via
|
||||
/// custom behavior.
|
||||
Custom(Arc<RedirectModifierExecutor>),
|
||||
}
|
||||
|
||||
impl RedirectModifier {
|
||||
/// Tries to provide a [`Vec`] of [`Arc<CommandSource>`] from a
|
||||
/// given [`CommandContext`].
|
||||
pub fn sources(&self, command_context: &CommandContext) -> RedirectModifierResult {
|
||||
match self {
|
||||
Self::KeepSource => Ok(vec![command_context.source.clone()]),
|
||||
Self::Custom(function) => function(command_context),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the result of a node requirement.
|
||||
pub type RequirementResult = bool;
|
||||
|
||||
/// A predicate that returns if the provided source satisfies it.
|
||||
#[derive(Clone)]
|
||||
pub struct Requirement(pub Arc<dyn Fn(&CommandSource) -> RequirementResult + Send + Sync>);
|
||||
|
||||
impl Requirement {
|
||||
/// Evaluates the given condition, returning whether the
|
||||
/// given [`CommandSource`] satisfies this requirement.
|
||||
#[must_use]
|
||||
pub fn evaluate(&self, command_source: &CommandSource) -> RequirementResult {
|
||||
self.0(command_source)
|
||||
}
|
||||
}
|
||||
|
||||
impl<F> From<F> for Requirement
|
||||
where
|
||||
F: Fn(&CommandSource) -> RequirementResult + Send + Sync + 'static,
|
||||
{
|
||||
fn from(value: F) -> Self {
|
||||
Self(Arc::new(value))
|
||||
}
|
||||
}
|
||||
|
||||
// Permissions
|
||||
impl From<String> for Requirement {
|
||||
fn from(value: String) -> Self {
|
||||
Self(Arc::new({
|
||||
let permission = Arc::new(value);
|
||||
|
||||
move |source| source.has_permission(&permission)
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&'static str> for Requirement {
|
||||
fn from(value: &'static str) -> Self {
|
||||
Self(Arc::new(move |source| source.has_permission(value)))
|
||||
}
|
||||
}
|
||||
|
||||
/// A structure that returns if the source is qualified enough to run the command.
|
||||
#[derive(Clone)]
|
||||
pub struct Requirements(pub Vec<Requirement>);
|
||||
|
||||
impl Requirements {
|
||||
/// Creates a new `Requirements` with no requirements in it.
|
||||
/// If used, this will always return `true` when evaluated.
|
||||
#[must_use]
|
||||
pub const fn new() -> Self {
|
||||
Self(Vec::new())
|
||||
}
|
||||
|
||||
/// Evaluates the given condition, returning whether the
|
||||
/// given [`CommandSource`] satisfies all contained requirements.
|
||||
#[must_use]
|
||||
pub fn evaluate(&self, command_source: &CommandSource) -> RequirementResult {
|
||||
for predicate in &self.0 {
|
||||
if !predicate.evaluate(command_source) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Requirements {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// Stores common owned data for a node.
|
||||
#[derive(Clone)]
|
||||
pub struct OwnedNodeData {
|
||||
pub global_id: GlobalNodeId,
|
||||
pub requirements: Requirements,
|
||||
pub modifier: RedirectModifier,
|
||||
pub forks: bool,
|
||||
pub command: Option<Command>,
|
||||
}
|
||||
|
||||
/// Represents the extra metadata of a node storing a literal.
|
||||
#[derive(Clone)]
|
||||
pub struct LiteralNodeMetadata {
|
||||
pub literal: Cow<'static, str>,
|
||||
pub literal_lowercase: String,
|
||||
}
|
||||
|
||||
impl LiteralNodeMetadata {
|
||||
pub fn new(literal: impl Into<Cow<'static, str>>) -> Self {
|
||||
let literal = literal.into();
|
||||
Self {
|
||||
literal: literal.clone(),
|
||||
literal_lowercase: literal.to_lowercase(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A special type of [`LiteralNodeMetadata`], containing
|
||||
/// a description for the command as well.
|
||||
#[derive(Clone)]
|
||||
pub struct CommandNodeMetadata {
|
||||
pub literal: Cow<'static, str>,
|
||||
pub literal_lowercase: String,
|
||||
pub description: Cow<'static, str>,
|
||||
pub source: Option<String>,
|
||||
}
|
||||
|
||||
impl CommandNodeMetadata {
|
||||
pub fn new(
|
||||
literal: impl Into<Cow<'static, str>>,
|
||||
description: impl Into<Cow<'static, str>>,
|
||||
) -> Self {
|
||||
let literal = literal.into();
|
||||
Self {
|
||||
literal: literal.clone(),
|
||||
literal_lowercase: literal.to_lowercase(),
|
||||
description: description.into(),
|
||||
source: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the extra metadata of an argument of any type.
|
||||
#[derive(Clone)]
|
||||
pub struct ArgumentNodeMetadata {
|
||||
pub name: Cow<'static, str>,
|
||||
pub argument_type: Arc<dyn AnyArgumentType>,
|
||||
pub suggestion_provider: Option<Arc<dyn SuggestionProvider>>,
|
||||
}
|
||||
|
||||
impl ArgumentNodeMetadata {
|
||||
pub fn new(
|
||||
name: impl Into<Cow<'static, str>>,
|
||||
argument_type: Arc<dyn AnyArgumentType>,
|
||||
suggestion_provider: Option<Arc<dyn SuggestionProvider>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
name: name.into(),
|
||||
argument_type,
|
||||
suggestion_provider,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the extra metadata for nodes of different types. Can be of the root, a literal, command or an argument.
|
||||
pub enum NodeMetadata {
|
||||
/// Metadata of the root node.
|
||||
Root,
|
||||
|
||||
/// Metadata of a literal node that doesn't start a command.
|
||||
Literal(LiteralNodeMetadata),
|
||||
|
||||
/// Metadata of a literal node that starts a command.
|
||||
Command(CommandNodeMetadata),
|
||||
|
||||
/// Metadata of an argument node.
|
||||
Argument(ArgumentNodeMetadata),
|
||||
}
|
||||
|
||||
/// Stores where this redirection would lead to.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
|
||||
pub enum Redirection {
|
||||
/// Leads to the root of the tree.
|
||||
Root,
|
||||
|
||||
/// Leads to a node in the tree from its tree-local ID.
|
||||
Local(NodeId),
|
||||
|
||||
/// Leads to a node in the tree from its global ID.
|
||||
Global(GlobalNodeId),
|
||||
}
|
||||
|
||||
impl<T: Into<NodeId>> From<T> for Redirection {
|
||||
fn from(value: T) -> Self {
|
||||
Self::Local(value.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<GlobalNodeId> for Redirection {
|
||||
fn from(value: GlobalNodeId) -> Self {
|
||||
Self::Global(value)
|
||||
impl pumpkin_command::node::CommandExecutor<CommandSource> for ArcCommandExecutorAdapter {
|
||||
fn execute(
|
||||
&self,
|
||||
context: &pumpkin_command::context::command_context::CommandContext<'_, CommandSource>,
|
||||
) -> pumpkin_command::node::CommandExecutorResult {
|
||||
self.0.execute(context)
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,7 +8,7 @@ use std::{
|
||||
net::SocketAddr,
|
||||
sync::{
|
||||
Arc,
|
||||
atomic::{AtomicBool, AtomicU32, Ordering},
|
||||
atomic::{AtomicBool, AtomicU32, AtomicUsize, Ordering},
|
||||
},
|
||||
};
|
||||
|
||||
@ -47,7 +47,7 @@ use pumpkin_protocol::{
|
||||
serial::{PacketRead, PacketReadSlice},
|
||||
};
|
||||
use tokio::{
|
||||
sync::mpsc::{Receiver, Sender},
|
||||
sync::mpsc::{Receiver, Sender, UnboundedReceiver, UnboundedSender, error::TryRecvError},
|
||||
sync::{Mutex, RwLock, oneshot},
|
||||
task::JoinHandle,
|
||||
};
|
||||
@ -58,7 +58,10 @@ pub mod login;
|
||||
use self::nethernet::NetherNetSession;
|
||||
use crate::{
|
||||
entity::player::Player,
|
||||
net::{DisconnectReason, PacketHandlerResult, PacketRateLimiter},
|
||||
net::{
|
||||
DisconnectReason, MAX_PENDING_BYTES, PacketHandlerResult, PacketRateLimiter,
|
||||
decrement_pending_bytes,
|
||||
},
|
||||
plugin::api::events::world::chunk_send::ChunkSend,
|
||||
server::Server,
|
||||
};
|
||||
@ -101,12 +104,15 @@ pub struct BedrockClient {
|
||||
|
||||
tasks: TaskTracker,
|
||||
rt_handle: tokio::runtime::Handle,
|
||||
outgoing_packet_queue_send: Sender<OutgoingPacket>,
|
||||
outgoing_packet_queue_send: UnboundedSender<OutgoingPacket>,
|
||||
/// A queue of serialized packets to send to the network
|
||||
outgoing_packet_queue_recv: Mutex<Option<Receiver<OutgoingPacket>>>,
|
||||
outgoing_packet_queue_recv: Mutex<Option<UnboundedReceiver<OutgoingPacket>>>,
|
||||
|
||||
outgoing_packet_priority_send: Sender<OutgoingPacket>,
|
||||
outgoing_packet_priority_recv: Mutex<Option<Receiver<OutgoingPacket>>>,
|
||||
outgoing_packet_priority_send: UnboundedSender<OutgoingPacket>,
|
||||
outgoing_packet_priority_recv: Mutex<Option<UnboundedReceiver<OutgoingPacket>>>,
|
||||
|
||||
/// Tracks total buffered payload bytes in the outgoing queues.
|
||||
pub pending_bytes: Arc<AtomicUsize>,
|
||||
|
||||
/// The packet encoder for outgoing packets.
|
||||
network_writer: Arc<RwLock<BedrockBatchEncoder>>,
|
||||
@ -135,8 +141,8 @@ impl BedrockClient {
|
||||
be_clients: Arc<Mutex<HashMap<SocketAddr, Arc<Self>>>>,
|
||||
packet_limiter: PacketRateLimiter,
|
||||
) -> Self {
|
||||
let (send, recv) = tokio::sync::mpsc::channel(4096);
|
||||
let (priority_send, priority_recv) = tokio::sync::mpsc::channel(4096);
|
||||
let (send, recv) = tokio::sync::mpsc::unbounded_channel();
|
||||
let (priority_send, priority_recv) = tokio::sync::mpsc::unbounded_channel();
|
||||
let (incoming_send, incoming_recv) = tokio::sync::mpsc::channel(4096);
|
||||
let rt_handle = tokio::runtime::Handle::current();
|
||||
Self {
|
||||
@ -154,6 +160,7 @@ impl BedrockClient {
|
||||
outgoing_packet_queue_recv: Mutex::new(Some(recv)),
|
||||
outgoing_packet_priority_send: priority_send,
|
||||
outgoing_packet_priority_recv: Mutex::new(Some(priority_recv)),
|
||||
pending_bytes: Arc::new(AtomicUsize::new(0)),
|
||||
next_form_id: AtomicU32::new(0),
|
||||
inventory_opened: AtomicBool::new(false),
|
||||
client_cache_supported: AtomicBool::new(false),
|
||||
@ -176,6 +183,8 @@ impl BedrockClient {
|
||||
}
|
||||
|
||||
pub fn start_outgoing_packet_task(self: &Arc<Self>) {
|
||||
const MAX_BATCH_SIZE: usize = 64;
|
||||
|
||||
let client = self.clone();
|
||||
self.spawn_task(async move {
|
||||
let Some(mut packet_receiver) = client.outgoing_packet_queue_recv.lock().await.take()
|
||||
@ -189,41 +198,70 @@ impl BedrockClient {
|
||||
};
|
||||
let mut interval = tokio::time::interval(std::time::Duration::from_millis(100));
|
||||
|
||||
while !client.close_token.is_cancelled() {
|
||||
let packet = tokio::select! {
|
||||
loop {
|
||||
let recv_result = tokio::select! {
|
||||
biased;
|
||||
() = client.close_token.cancelled() => break,
|
||||
res = priority_packet_receiver.recv() => match res {
|
||||
Some(p) => p,
|
||||
None => break,
|
||||
},
|
||||
res = priority_packet_receiver.recv() => res,
|
||||
res = packet_receiver.recv() => res,
|
||||
_ = interval.tick() => {
|
||||
if !client.tick_connection().await {
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
res = packet_receiver.recv() => match res {
|
||||
Some(p) => p,
|
||||
None => break,
|
||||
},
|
||||
() = client.close_token.cancelled() => {
|
||||
priority_packet_receiver
|
||||
.try_recv()
|
||||
.ok()
|
||||
.or_else(|| packet_receiver.try_recv().ok())
|
||||
}
|
||||
};
|
||||
|
||||
let data = packet.data.strip_prefix(&[BEDROCK_GAME_PACKET]);
|
||||
let Some(data) = data else {
|
||||
warn!("Refusing to send a non-game packet over NetherNet");
|
||||
continue;
|
||||
let Some(packet) = recv_result else {
|
||||
break;
|
||||
};
|
||||
if let Err(error) = client.session.send(Bytes::copy_from_slice(data)).await {
|
||||
warn!(
|
||||
"Failed to send NetherNet packet to {}: {error}",
|
||||
client.address
|
||||
);
|
||||
client.close().await;
|
||||
|
||||
let mut packet_batch = Vec::with_capacity(MAX_BATCH_SIZE);
|
||||
packet_batch.push(packet);
|
||||
|
||||
while packet_batch.len() < MAX_BATCH_SIZE {
|
||||
match priority_packet_receiver.try_recv() {
|
||||
Ok(packet) => {
|
||||
packet_batch.push(packet);
|
||||
continue;
|
||||
}
|
||||
Err(TryRecvError::Disconnected | TryRecvError::Empty) => {}
|
||||
}
|
||||
|
||||
match packet_receiver.try_recv() {
|
||||
Ok(packet) => packet_batch.push(packet),
|
||||
Err(TryRecvError::Disconnected | TryRecvError::Empty) => break,
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(completion) = packet.completion {
|
||||
let _ = completion.send(());
|
||||
for packet in packet_batch {
|
||||
let packet_len = packet.data.len();
|
||||
let data = packet.data.strip_prefix(&[BEDROCK_GAME_PACKET]);
|
||||
let Some(data) = data else {
|
||||
warn!("Refusing to send a non-game packet over NetherNet");
|
||||
decrement_pending_bytes(&client.pending_bytes, packet_len);
|
||||
continue;
|
||||
};
|
||||
if let Err(error) = client.session.send(Bytes::copy_from_slice(data)).await {
|
||||
warn!(
|
||||
"Failed to send NetherNet packet to {}: {error}",
|
||||
client.address
|
||||
);
|
||||
decrement_pending_bytes(&client.pending_bytes, packet_len);
|
||||
client.close().await;
|
||||
return;
|
||||
}
|
||||
|
||||
decrement_pending_bytes(&client.pending_bytes, packet_len);
|
||||
|
||||
if let Some(completion) = packet.completion {
|
||||
let _ = completion.send(());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -270,9 +308,14 @@ impl BedrockClient {
|
||||
}
|
||||
|
||||
pub fn try_kick(&self, reason: DisconnectReason, message: String) {
|
||||
warn!("Closing connection for {}: {message}", self.address);
|
||||
let packet = CDisconnect::new(reason as i32, message);
|
||||
if let Ok(data) = self.serialize_packet(&packet) {
|
||||
self.try_enqueue_packet(data);
|
||||
let packet_len = data.len();
|
||||
let _ = self.pending_bytes.fetch_add(packet_len, Ordering::AcqRel);
|
||||
let _ = self
|
||||
.outgoing_packet_priority_send
|
||||
.send(OutgoingPacket::normal(data));
|
||||
}
|
||||
if !self.close_token.is_cancelled() {
|
||||
self.close_token.cancel();
|
||||
@ -280,6 +323,7 @@ impl BedrockClient {
|
||||
}
|
||||
|
||||
pub async fn kick(&self, reason: DisconnectReason, message: String) {
|
||||
warn!("Closing connection for {}: {message}", self.address);
|
||||
self.send_packet(&CDisconnect::new(reason as i32, message))
|
||||
.await;
|
||||
self.close().await;
|
||||
@ -293,6 +337,7 @@ impl BedrockClient {
|
||||
filtered_message: String,
|
||||
send_packet: bool,
|
||||
) {
|
||||
warn!("Closing connection for {}: {message}", self.address);
|
||||
if send_packet {
|
||||
self.send_packet(&CDisconnect {
|
||||
reason: pumpkin_protocol::codec::var_int::VarInt(reason as i32),
|
||||
@ -406,8 +451,9 @@ impl BedrockClient {
|
||||
self.player.store(Arc::new(Some(player)));
|
||||
}
|
||||
|
||||
#[allow(clippy::unused_async)]
|
||||
pub async fn enqueue_packet(&self, packet_data: Bytes) {
|
||||
self.enqueue_packet_data(packet_data).await;
|
||||
self.try_enqueue_packet_data(packet_data);
|
||||
}
|
||||
|
||||
pub fn try_enqueue_packet(&self, packet_data: Bytes) {
|
||||
@ -420,37 +466,40 @@ impl BedrockClient {
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `packet_data`: A `Bytes` payload representing the encoded packet.
|
||||
#[allow(clippy::unused_async)]
|
||||
pub async fn enqueue_packet_data(&self, packet_data: Bytes) {
|
||||
if let Err(err) = self
|
||||
.outgoing_packet_queue_send
|
||||
.send(OutgoingPacket::normal(packet_data))
|
||||
.await
|
||||
{
|
||||
// This is expected to fail if we are closed
|
||||
if !self.is_closed() {
|
||||
error!("Failed to add packet to the outgoing packet queue for client: {err}");
|
||||
}
|
||||
}
|
||||
self.try_enqueue_packet_data(packet_data);
|
||||
}
|
||||
|
||||
pub fn try_enqueue_packet_data(&self, packet_data: Bytes) {
|
||||
if self.is_closed() {
|
||||
return;
|
||||
}
|
||||
|
||||
let packet_len = packet_data.len();
|
||||
let prev_bytes = self.pending_bytes.fetch_add(packet_len, Ordering::AcqRel);
|
||||
let new_bytes = prev_bytes.saturating_add(packet_len);
|
||||
|
||||
if new_bytes > MAX_PENDING_BYTES {
|
||||
decrement_pending_bytes(&self.pending_bytes, packet_len);
|
||||
if !self.is_closed() {
|
||||
warn!(
|
||||
"Bedrock client {} outbound packet buffer overflow ({} bytes > {} bytes). Closing connection.",
|
||||
self.address, new_bytes, MAX_PENDING_BYTES
|
||||
);
|
||||
self.close_token.cancel();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if let Err(err) = self
|
||||
.outgoing_packet_queue_send
|
||||
.try_send(OutgoingPacket::normal(packet_data))
|
||||
.send(OutgoingPacket::normal(packet_data))
|
||||
{
|
||||
match err {
|
||||
tokio::sync::mpsc::error::TrySendError::Full(_) => {
|
||||
debug!(
|
||||
"Failed to add packet to the outgoing packet queue for client: channel full"
|
||||
);
|
||||
}
|
||||
tokio::sync::mpsc::error::TrySendError::Closed(_) => {
|
||||
if !self.is_closed() {
|
||||
error!(
|
||||
"Failed to add packet to the outgoing packet queue for client: channel closed"
|
||||
);
|
||||
}
|
||||
}
|
||||
decrement_pending_bytes(&self.pending_bytes, packet_len);
|
||||
// This is expected to fail if we are closed
|
||||
if !self.is_closed() {
|
||||
error!("Failed to add packet to the outgoing packet queue for client: {err}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -512,12 +561,32 @@ impl BedrockClient {
|
||||
}
|
||||
|
||||
pub async fn send_game_packet(&self, packet_data: Bytes) {
|
||||
if self.is_closed() {
|
||||
return;
|
||||
}
|
||||
|
||||
let packet_len = packet_data.len();
|
||||
let prev_bytes = self.pending_bytes.fetch_add(packet_len, Ordering::AcqRel);
|
||||
let new_bytes = prev_bytes.saturating_add(packet_len);
|
||||
|
||||
if new_bytes > MAX_PENDING_BYTES {
|
||||
decrement_pending_bytes(&self.pending_bytes, packet_len);
|
||||
if !self.is_closed() {
|
||||
warn!(
|
||||
"Bedrock client {} outbound packet buffer overflow ({} bytes > {} bytes). Closing connection.",
|
||||
self.address, new_bytes, MAX_PENDING_BYTES
|
||||
);
|
||||
self.close_token.cancel();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let (tx, rx) = oneshot::channel();
|
||||
if let Err(err) = self
|
||||
.outgoing_packet_priority_send
|
||||
.send(OutgoingPacket::priority(packet_data, tx))
|
||||
.await
|
||||
{
|
||||
decrement_pending_bytes(&self.pending_bytes, packet_len);
|
||||
if !self.is_closed() {
|
||||
error!("Failed to add priority packet to the outgoing packet queue: {err}");
|
||||
}
|
||||
|
||||
@ -3,7 +3,7 @@ use pumpkin_protocol::java::client::play::{
|
||||
};
|
||||
use pumpkin_world::level::SyncChunk;
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::atomic::{AtomicBool, AtomicI32, Ordering};
|
||||
use std::sync::atomic::{AtomicBool, AtomicI32, AtomicUsize, Ordering};
|
||||
use std::time::{Instant, SystemTime, UNIX_EPOCH};
|
||||
use std::{collections::VecDeque, io::Write, sync::Arc};
|
||||
|
||||
@ -46,7 +46,7 @@ use tokio::{
|
||||
sync::oneshot,
|
||||
};
|
||||
use tokio::{
|
||||
sync::mpsc::{Receiver, Sender, error::TryRecvError},
|
||||
sync::mpsc::{UnboundedReceiver, UnboundedSender, error::TryRecvError},
|
||||
task::JoinHandle,
|
||||
};
|
||||
use tokio_util::sync::CancellationToken;
|
||||
@ -66,7 +66,8 @@ use pending::PendingConnection;
|
||||
|
||||
use crate::entity::player::Player;
|
||||
use crate::net::{
|
||||
ClientPlatform, GameProfile, PacketHandlerResult, PacketRateLimiter, PlayerConfig,
|
||||
ClientPlatform, GameProfile, MAX_PENDING_BYTES, PacketHandlerResult, PacketRateLimiter,
|
||||
PlayerConfig, decrement_pending_bytes,
|
||||
};
|
||||
use crate::plugin::api::events::world::chunk_send::ChunkSend;
|
||||
use crate::plugin::player::player_custom_payload::PlayerCustomPayloadEvent;
|
||||
@ -95,13 +96,15 @@ pub struct JavaClient {
|
||||
/// An notifier that is triggered when this client is closed.
|
||||
close_token: CancellationToken,
|
||||
/// A normal-priority queue of serialized packets to send to the network.
|
||||
outgoing_packet_queue_send: Sender<OutgoingPacket>,
|
||||
outgoing_packet_queue_send: UnboundedSender<OutgoingPacket>,
|
||||
/// A normal-priority queue of serialized packets to send to the network.
|
||||
outgoing_packet_queue_recv: Option<Receiver<OutgoingPacket>>,
|
||||
outgoing_packet_queue_recv: Option<UnboundedReceiver<OutgoingPacket>>,
|
||||
/// A high-priority queue of serialized packets to send to the network.
|
||||
outgoing_packet_priority_send: Sender<OutgoingPacket>,
|
||||
outgoing_packet_priority_send: UnboundedSender<OutgoingPacket>,
|
||||
/// A high-priority queue of serialized packets to send to the network.
|
||||
outgoing_packet_priority_recv: Option<Receiver<OutgoingPacket>>,
|
||||
outgoing_packet_priority_recv: Option<UnboundedReceiver<OutgoingPacket>>,
|
||||
/// Tracks total buffered payload bytes in the outgoing queues.
|
||||
pub pending_bytes: Arc<AtomicUsize>,
|
||||
/// The packet encoder for outgoing packets.
|
||||
network_writer: std::sync::Mutex<Option<TCPNetworkEncoder<BufWriter<OwnedWriteHalf>>>>,
|
||||
/// The packet decoder for incoming packets.
|
||||
@ -224,8 +227,8 @@ impl JavaClient {
|
||||
gameprofile: GameProfile,
|
||||
config: PlayerConfig,
|
||||
) -> Self {
|
||||
let (send, recv) = tokio::sync::mpsc::channel(4096);
|
||||
let (priority_send, priority_recv) = tokio::sync::mpsc::channel(4096);
|
||||
let (send, recv) = tokio::sync::mpsc::unbounded_channel();
|
||||
let (priority_send, priority_recv) = tokio::sync::mpsc::unbounded_channel();
|
||||
|
||||
Self {
|
||||
id: pending.id,
|
||||
@ -241,6 +244,7 @@ impl JavaClient {
|
||||
outgoing_packet_queue_recv: Some(recv),
|
||||
outgoing_packet_priority_send: priority_send,
|
||||
outgoing_packet_priority_recv: Some(priority_recv),
|
||||
pending_bytes: Arc::new(AtomicUsize::new(0)),
|
||||
version: pending.version,
|
||||
network_writer: std::sync::Mutex::new(Some(pending.network_writer)),
|
||||
network_reader: std::sync::Mutex::new(Some(pending.network_reader)),
|
||||
@ -480,16 +484,46 @@ impl JavaClient {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::unused_async)]
|
||||
pub async fn enqueue_packet(&self, packet_data: Bytes) {
|
||||
self.enqueue_packet_data(packet_data).await;
|
||||
self.try_enqueue_packet_data(packet_data);
|
||||
}
|
||||
|
||||
#[allow(clippy::unused_async)]
|
||||
pub async fn enqueue_packet_data(&self, packet_data: Bytes) {
|
||||
self.try_enqueue_packet_data(packet_data);
|
||||
}
|
||||
|
||||
pub fn try_enqueue_packet(&self, packet_data: Bytes) {
|
||||
self.try_enqueue_packet_data(packet_data);
|
||||
}
|
||||
|
||||
pub fn try_enqueue_packet_data(&self, packet_data: Bytes) {
|
||||
if self.close_token.is_cancelled() {
|
||||
return;
|
||||
}
|
||||
|
||||
let packet_len = packet_data.len();
|
||||
let prev_bytes = self.pending_bytes.fetch_add(packet_len, Ordering::AcqRel);
|
||||
let new_bytes = prev_bytes.saturating_add(packet_len);
|
||||
|
||||
if new_bytes > MAX_PENDING_BYTES {
|
||||
decrement_pending_bytes(&self.pending_bytes, packet_len);
|
||||
if !self.close_token.is_cancelled() {
|
||||
warn!(
|
||||
"Client {} outbound packet buffer overflow ({} bytes > {} bytes). Closing connection.",
|
||||
self.id, new_bytes, MAX_PENDING_BYTES
|
||||
);
|
||||
self.close();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if let Err(err) = self
|
||||
.outgoing_packet_queue_send
|
||||
.send(OutgoingPacket::normal(packet_data))
|
||||
.await
|
||||
{
|
||||
decrement_pending_bytes(&self.pending_bytes, packet_len);
|
||||
// This is expected to fail if we are closed
|
||||
if !self.close_token.is_cancelled() {
|
||||
warn!(
|
||||
@ -503,35 +537,6 @@ impl JavaClient {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_enqueue_packet(&self, packet_data: Bytes) {
|
||||
self.try_enqueue_packet_data(packet_data);
|
||||
}
|
||||
|
||||
pub fn try_enqueue_packet_data(&self, packet_data: Bytes) {
|
||||
if let Err(err) = self
|
||||
.outgoing_packet_queue_send
|
||||
.try_send(OutgoingPacket::normal(packet_data))
|
||||
{
|
||||
match err {
|
||||
tokio::sync::mpsc::error::TrySendError::Full(_) => {
|
||||
debug!(
|
||||
"Failed to add packet to the outgoing packet queue for client {}: channel full",
|
||||
self.id
|
||||
);
|
||||
}
|
||||
tokio::sync::mpsc::error::TrySendError::Closed(_) => {
|
||||
if !self.close_token.is_cancelled() {
|
||||
warn!(
|
||||
"Failed to add packet to the outgoing packet queue for client {}: channel closed",
|
||||
self.id
|
||||
);
|
||||
self.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn await_close_interrupt(&self) {
|
||||
self.close_token.cancelled().await;
|
||||
}
|
||||
@ -562,31 +567,34 @@ impl JavaClient {
|
||||
}
|
||||
|
||||
pub fn try_kick(&self, reason: &TextComponent) {
|
||||
match self.connection_state.load() {
|
||||
let serialized = match self.connection_state.load() {
|
||||
ConnectionState::Login => {
|
||||
let packet = CLoginDisconnect::new(
|
||||
serde_json::to_string(&reason.0).unwrap_or_else(|_| String::new()),
|
||||
);
|
||||
if let Ok(data) = self.serialize_packet(&packet) {
|
||||
self.try_enqueue_packet(data);
|
||||
}
|
||||
self.serialize_packet(&packet).ok()
|
||||
}
|
||||
ConnectionState::Config => {
|
||||
let reason_text = reason.clone().get_text();
|
||||
let packet = CConfigDisconnect::new(&reason_text);
|
||||
if let Ok(data) = self.serialize_packet(&packet) {
|
||||
self.try_enqueue_packet(data);
|
||||
}
|
||||
self.serialize_packet(&packet).ok()
|
||||
}
|
||||
ConnectionState::Play => {
|
||||
let packet = CPlayDisconnect::new(reason);
|
||||
if let Ok(data) = self.serialize_packet(&packet) {
|
||||
self.try_enqueue_packet(data);
|
||||
}
|
||||
self.serialize_packet(&packet).ok()
|
||||
}
|
||||
_ => {}
|
||||
_ => None,
|
||||
};
|
||||
|
||||
if let Some(data) = serialized {
|
||||
let packet_len = data.len();
|
||||
let _ = self.pending_bytes.fetch_add(packet_len, Ordering::AcqRel);
|
||||
let _ = self
|
||||
.outgoing_packet_priority_send
|
||||
.send(OutgoingPacket::normal(data));
|
||||
}
|
||||
debug!("Closing connection for {}", self.id);
|
||||
let reason_text = reason.clone().get_text();
|
||||
warn!("Closing connection for {}: {reason_text}", self.id);
|
||||
self.close();
|
||||
}
|
||||
|
||||
@ -612,7 +620,8 @@ impl JavaClient {
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
debug!("Closing connection for {}", self.id);
|
||||
let reason_text = reason.clone().get_text();
|
||||
warn!("Closing connection for {}: {reason_text}", self.id);
|
||||
self.close();
|
||||
}
|
||||
|
||||
@ -621,13 +630,33 @@ impl JavaClient {
|
||||
}
|
||||
|
||||
pub async fn send_packet_now_data(&self, packet: Bytes) {
|
||||
if self.close_token.is_cancelled() {
|
||||
return;
|
||||
}
|
||||
|
||||
let packet_len = packet.len();
|
||||
let prev_bytes = self.pending_bytes.fetch_add(packet_len, Ordering::AcqRel);
|
||||
let new_bytes = prev_bytes.saturating_add(packet_len);
|
||||
|
||||
if new_bytes > MAX_PENDING_BYTES {
|
||||
decrement_pending_bytes(&self.pending_bytes, packet_len);
|
||||
if !self.close_token.is_cancelled() {
|
||||
warn!(
|
||||
"Client {} outbound packet buffer overflow ({} bytes > {} bytes). Closing connection.",
|
||||
self.id, new_bytes, MAX_PENDING_BYTES
|
||||
);
|
||||
self.close();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let (completion_tx, completion_rx) = oneshot::channel();
|
||||
|
||||
if let Err(err) = self
|
||||
.outgoing_packet_priority_send
|
||||
.send(OutgoingPacket::high_priority(packet, completion_tx))
|
||||
.await
|
||||
{
|
||||
decrement_pending_bytes(&self.pending_bytes, packet_len);
|
||||
// It is expected that the packet will fail if we are closed
|
||||
if !self.close_token.is_cancelled() {
|
||||
warn!(
|
||||
@ -701,6 +730,7 @@ impl JavaClient {
|
||||
/// - **Status:** Handles status request and ping packets.
|
||||
/// - **Login/Transfer:** Handles login and transfer packets.
|
||||
/// - **Config:** Handles configuration packets.
|
||||
#[expect(clippy::too_many_lines)]
|
||||
pub fn start_outgoing_packet_task(&mut self) {
|
||||
const MAX_BATCH_SIZE: usize = 64;
|
||||
|
||||
@ -711,6 +741,7 @@ impl JavaClient {
|
||||
return;
|
||||
};
|
||||
let close_token = self.close_token.clone();
|
||||
let pending_bytes = self.pending_bytes.clone();
|
||||
let Some(mut writer) = self
|
||||
.network_writer
|
||||
.lock()
|
||||
@ -721,12 +752,17 @@ impl JavaClient {
|
||||
};
|
||||
let id = self.id;
|
||||
self.spawn_task(async move {
|
||||
while !close_token.is_cancelled() {
|
||||
loop {
|
||||
let recv_result = tokio::select! {
|
||||
biased;
|
||||
() = close_token.cancelled() => None,
|
||||
res = priority_packet_receiver.recv() => res,
|
||||
res = packet_receiver.recv() => res,
|
||||
() = close_token.cancelled() => {
|
||||
priority_packet_receiver
|
||||
.try_recv()
|
||||
.ok()
|
||||
.or_else(|| packet_receiver.try_recv().ok())
|
||||
}
|
||||
};
|
||||
|
||||
let Some(packet_data) = recv_result else {
|
||||
@ -796,6 +832,9 @@ impl JavaClient {
|
||||
send_failed = true;
|
||||
}
|
||||
|
||||
let flushed_bytes: usize = written_packets.iter().map(|p| p.data.len()).sum();
|
||||
decrement_pending_bytes(&pending_bytes, flushed_bytes);
|
||||
|
||||
if send_failed {
|
||||
// We now need to close the connection to the client since the stream is in an unknown state.
|
||||
close_token.cancel();
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user