From a1ae0c5be6400fbffda9ac4bb571bf35e741a1ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nils=20Kr=C3=BCger?= Date: Sun, 29 Dec 2024 12:51:24 +0100 Subject: [PATCH] feat(commands): vanilla arg types and props, fix some graph issues --- src/lib/commands/src/arg/parser/int.rs | 15 ++- src/lib/commands/src/arg/parser/mod.rs | 2 + src/lib/commands/src/arg/parser/string.rs | 107 +++++++++++------ .../commands/src/arg/parser/vanilla/float.rs | 42 +++++++ .../commands/src/arg/parser/vanilla/int.rs | 42 +++++++ .../commands/src/arg/parser/vanilla/long.rs | 42 +++++++ .../commands/src/arg/parser/vanilla/mod.rs | 112 ++++++++++++++++++ .../commands/src/arg/parser/vanilla/string.rs | 27 +++++ src/lib/commands/src/ctx.rs | 15 +++ src/lib/commands/src/graph/mod.rs | 43 ++++--- src/lib/commands/src/graph/node.rs | 71 +---------- src/lib/default_commands/Cargo.toml | 2 - src/lib/default_commands/src/echo.rs | 46 ++----- src/lib/default_commands/src/nested.rs | 23 +++- 14 files changed, 425 insertions(+), 164 deletions(-) create mode 100644 src/lib/commands/src/arg/parser/vanilla/float.rs create mode 100644 src/lib/commands/src/arg/parser/vanilla/int.rs create mode 100644 src/lib/commands/src/arg/parser/vanilla/long.rs create mode 100644 src/lib/commands/src/arg/parser/vanilla/mod.rs create mode 100644 src/lib/commands/src/arg/parser/vanilla/string.rs diff --git a/src/lib/commands/src/arg/parser/int.rs b/src/lib/commands/src/arg/parser/int.rs index 8d4a1854..f22074f5 100644 --- a/src/lib/commands/src/arg/parser/int.rs +++ b/src/lib/commands/src/arg/parser/int.rs @@ -2,7 +2,13 @@ use std::sync::{Arc, Mutex}; use crate::{ctx::CommandContext, input::CommandInput, ParserResult}; -use super::{utils::error, ArgumentParser}; +use super::{ + utils::error, + vanilla::{ + int::IntParserFlags, MinecraftArgument, MinecraftArgumentProperties, MinecraftArgumentType, + }, + ArgumentParser, +}; pub struct IntParser; @@ -22,4 +28,11 @@ impl ArgumentParser for IntParser { { IntParser } + + fn vanilla(&self) -> MinecraftArgument { + MinecraftArgument { + argument_type: MinecraftArgumentType::Int, + props: MinecraftArgumentProperties::Int(IntParserFlags::default()), + } + } } diff --git a/src/lib/commands/src/arg/parser/mod.rs b/src/lib/commands/src/arg/parser/mod.rs index 1f2d62fd..192c713f 100644 --- a/src/lib/commands/src/arg/parser/mod.rs +++ b/src/lib/commands/src/arg/parser/mod.rs @@ -5,10 +5,12 @@ use crate::{ctx::CommandContext, input::CommandInput, ParserResult}; pub mod int; pub mod string; pub mod utils; +pub mod vanilla; pub trait ArgumentParser: Send + Sync { fn parse(&self, context: Arc, input: Arc>) -> ParserResult; fn new() -> Self where Self: Sized; + fn vanilla(&self) -> vanilla::MinecraftArgument; } diff --git a/src/lib/commands/src/arg/parser/string.rs b/src/lib/commands/src/arg/parser/string.rs index d01dbaff..983425c8 100644 --- a/src/lib/commands/src/arg/parser/string.rs +++ b/src/lib/commands/src/arg/parser/string.rs @@ -2,7 +2,14 @@ use std::sync::{Arc, Mutex}; use crate::{ctx::CommandContext, input::CommandInput, ParserResult}; -use super::{utils::parser_error, ArgumentParser}; +use super::{ + utils::parser_error, + vanilla::{ + string::StringParsingBehavior, MinecraftArgument, MinecraftArgumentProperties, + MinecraftArgumentType, + }, + ArgumentParser, +}; pub struct SingleStringParser; @@ -22,6 +29,13 @@ impl ArgumentParser for SingleStringParser { { SingleStringParser } + + fn vanilla(&self) -> MinecraftArgument { + MinecraftArgument { + argument_type: MinecraftArgumentType::String, + props: MinecraftArgumentProperties::String(StringParsingBehavior::default()), + } + } } pub struct GreedyStringParser; @@ -57,56 +71,68 @@ impl ArgumentParser for GreedyStringParser { { GreedyStringParser } + + fn vanilla(&self) -> MinecraftArgument { + MinecraftArgument { + argument_type: MinecraftArgumentType::String, + props: MinecraftArgumentProperties::String(StringParsingBehavior::Greedy), + } + } } pub struct QuotedStringParser; impl ArgumentParser for QuotedStringParser { fn parse(&self, _ctx: Arc, input: Arc>) -> ParserResult { let mut input = input.lock().unwrap(); - input.skip_whitespace(u32::MAX, false); - if input.peek() != Some('"') { - return Err(parser_error("expected opening quote")); - } - - input.read(1); - - let mut result = String::new(); - let mut escaped = false; - - while input.has_remaining_input() { - let current = input.peek(); - - match current { - None => return Err(parser_error("unterminated quoted string")), - Some(c) => { - input.read(1); - - if escaped { - match c { - '"' | '\\' => result.push(c), - 'n' => result.push('\n'), - 'r' => result.push('\r'), - 't' => result.push('\t'), - _ => { - result.push('\\'); - result.push(c); + // If it starts with a quote, use quoted string parsing + if input.peek() == Some('"') { + input.read(1); // consume the opening quote + + let mut result = String::new(); + let mut escaped = false; + + while input.has_remaining_input() { + let current = input.peek(); + + match current { + None => return Err(parser_error("unterminated quoted string")), + Some(c) => { + input.read(1); + + if escaped { + match c { + '"' | '\\' => result.push(c), + 'n' => result.push('\n'), + 'r' => result.push('\r'), + 't' => result.push('\t'), + _ => { + result.push('\\'); + result.push(c); + } + } + escaped = false; + } else { + match c { + '"' => return Ok(Box::new(result)), + '\\' => escaped = true, + _ => result.push(c), } - } - escaped = false; - } else { - match c { - '"' => return Ok(Box::new(result)), - '\\' => escaped = true, - _ => result.push(c), } } } } - } - Err(parser_error("unterminated quoted string")) + Err(parser_error("unterminated quoted string")) + } else { + // If no quotes, parse as single word + if input.peek_string().is_empty() { + return Err(parser_error("input cannot be empty")); + } + + Ok(Box::new(input.read_string())) + } } fn new() -> Self @@ -115,4 +141,11 @@ impl ArgumentParser for QuotedStringParser { { QuotedStringParser } + + fn vanilla(&self) -> MinecraftArgument { + MinecraftArgument { + argument_type: MinecraftArgumentType::String, + props: MinecraftArgumentProperties::String(StringParsingBehavior::Quotable), + } + } } diff --git a/src/lib/commands/src/arg/parser/vanilla/float.rs b/src/lib/commands/src/arg/parser/vanilla/float.rs new file mode 100644 index 00000000..0c23f336 --- /dev/null +++ b/src/lib/commands/src/arg/parser/vanilla/float.rs @@ -0,0 +1,42 @@ +use std::io::Write; + +use ferrumc_net_codec::encode::{NetEncode, NetEncodeOpts, NetEncodeResult}; +use tokio::io::AsyncWrite; + +#[derive(Clone, Debug, PartialEq, Default)] +pub struct FloatParserFlags { + pub min: Option, + pub max: Option, +} + +impl NetEncode for FloatParserFlags { + fn encode(&self, writer: &mut W, opts: &NetEncodeOpts) -> NetEncodeResult<()> { + let mut flags = 0u8; + if self.min.is_some() { + flags |= 0x01; + } + if self.max.is_some() { + flags |= 0x02; + } + flags.encode(writer, opts)?; + self.min.encode(writer, opts)?; + self.max.encode(writer, opts) + } + + async fn encode_async( + &self, + writer: &mut W, + opts: &NetEncodeOpts, + ) -> NetEncodeResult<()> { + let mut flags = 0u8; + if self.min.is_some() { + flags |= 0x01; + } + if self.max.is_some() { + flags |= 0x02; + } + flags.encode_async(writer, opts).await?; + self.min.encode_async(writer, opts).await?; + self.max.encode_async(writer, opts).await + } +} diff --git a/src/lib/commands/src/arg/parser/vanilla/int.rs b/src/lib/commands/src/arg/parser/vanilla/int.rs new file mode 100644 index 00000000..11dd7020 --- /dev/null +++ b/src/lib/commands/src/arg/parser/vanilla/int.rs @@ -0,0 +1,42 @@ +use std::io::Write; + +use ferrumc_net_codec::encode::{NetEncode, NetEncodeOpts, NetEncodeResult}; +use tokio::io::AsyncWrite; + +#[derive(Clone, Debug, PartialEq, Default)] +pub struct IntParserFlags { + pub min: Option, + pub max: Option, +} + +impl NetEncode for IntParserFlags { + fn encode(&self, writer: &mut W, opts: &NetEncodeOpts) -> NetEncodeResult<()> { + let mut flags = 0u8; + if self.min.is_some() { + flags |= 0x01; + } + if self.max.is_some() { + flags |= 0x02; + } + flags.encode(writer, opts)?; + self.min.encode(writer, opts)?; + self.max.encode(writer, opts) + } + + async fn encode_async( + &self, + writer: &mut W, + opts: &NetEncodeOpts, + ) -> NetEncodeResult<()> { + let mut flags = 0u8; + if self.min.is_some() { + flags |= 0x01; + } + if self.max.is_some() { + flags |= 0x02; + } + flags.encode_async(writer, opts).await?; + self.min.encode_async(writer, opts).await?; + self.max.encode_async(writer, opts).await + } +} diff --git a/src/lib/commands/src/arg/parser/vanilla/long.rs b/src/lib/commands/src/arg/parser/vanilla/long.rs new file mode 100644 index 00000000..504f99d6 --- /dev/null +++ b/src/lib/commands/src/arg/parser/vanilla/long.rs @@ -0,0 +1,42 @@ +use std::io::Write; + +use ferrumc_net_codec::encode::{NetEncode, NetEncodeOpts, NetEncodeResult}; +use tokio::io::AsyncWrite; + +#[derive(Clone, Debug, PartialEq, Default)] +pub struct LongParserFlags { + pub min: Option, + pub max: Option, +} + +impl NetEncode for LongParserFlags { + fn encode(&self, writer: &mut W, opts: &NetEncodeOpts) -> NetEncodeResult<()> { + let mut flags = 0u8; + if self.min.is_some() { + flags |= 0x01; + } + if self.max.is_some() { + flags |= 0x02; + } + flags.encode(writer, opts)?; + self.min.encode(writer, opts)?; + self.max.encode(writer, opts) + } + + async fn encode_async( + &self, + writer: &mut W, + opts: &NetEncodeOpts, + ) -> NetEncodeResult<()> { + let mut flags = 0u8; + if self.min.is_some() { + flags |= 0x01; + } + if self.max.is_some() { + flags |= 0x02; + } + flags.encode_async(writer, opts).await?; + self.min.encode_async(writer, opts).await?; + self.max.encode_async(writer, opts).await + } +} diff --git a/src/lib/commands/src/arg/parser/vanilla/mod.rs b/src/lib/commands/src/arg/parser/vanilla/mod.rs new file mode 100644 index 00000000..e462fa70 --- /dev/null +++ b/src/lib/commands/src/arg/parser/vanilla/mod.rs @@ -0,0 +1,112 @@ +//! TODO: +//! * Entity +//! * Double (does rust even have a double type?) +//! * Score Holder +//! * Time +//! * Resource or Tag +//! * Resource or Tag Key +//! * Resource +//! * Resource Key + +use std::io::Write; + +use enum_ordinalize::Ordinalize; +use ferrumc_macros::NetEncode; +use ferrumc_net_codec::{ + encode::{NetEncode, NetEncodeOpts, NetEncodeResult}, + net_types::var_int::VarInt, +}; +use float::FloatParserFlags; +use int::IntParserFlags; +use long::LongParserFlags; +use string::StringParsingBehavior; +use tokio::io::AsyncWrite; + +pub mod float; +pub mod int; +pub mod long; +pub mod string; + +#[derive(Clone, Debug, PartialEq)] +pub struct MinecraftArgument { + pub argument_type: MinecraftArgumentType, + pub props: MinecraftArgumentProperties, +} + +#[derive(Clone, Debug, PartialEq, NetEncode)] +pub enum MinecraftArgumentProperties { + Float(FloatParserFlags), + Int(IntParserFlags), + Long(LongParserFlags), + String(StringParsingBehavior), +} + +#[derive(Clone, Debug, PartialEq, Ordinalize)] +pub enum MinecraftArgumentType { + Bool, + Float, + Double, + Int, + Long, + String, + Entity, + GameProfile, + BlockPos, + ColumnPos, + Vec3, + Vec2, + BlockState, + BlockPredicate, + ItemStack, + ItemPredicate, + Color, + Component, + Style, + Message, + Nbt, + NbtTag, + NbtPath, + Objective, + ObjectiveCriteria, + Operator, + Particle, + Angle, + Rotation, + ScoreboardDisplaySlot, + ScoreHolder, + UpTo3Axes, + Team, + ItemSlot, + ResourceLocation, + Function, + EntityAnchor, + IntRange, + FloatRange, + Dimension, + GameMode, + Time, + ResourceOrTag, + ResourceOrTagKey, + Resource, + ResourceKey, + TemplateMirror, + TemplateRotation, + Heightmap, + UUID, +} + +impl NetEncode for MinecraftArgumentType { + fn encode(&self, writer: &mut W, opts: &NetEncodeOpts) -> NetEncodeResult<()> { + VarInt::new(self.ordinal() as i32).encode(writer, opts) + } + + async fn encode_async( + &self, + writer: &mut W, + opts: &NetEncodeOpts, + ) -> NetEncodeResult<()> { + VarInt::new(self.ordinal() as i32) + .encode_async(writer, opts) + .await + } +} diff --git a/src/lib/commands/src/arg/parser/vanilla/string.rs b/src/lib/commands/src/arg/parser/vanilla/string.rs new file mode 100644 index 00000000..e8bed53c --- /dev/null +++ b/src/lib/commands/src/arg/parser/vanilla/string.rs @@ -0,0 +1,27 @@ +use std::io::Write; + +use enum_ordinalize::Ordinalize; +use ferrumc_net_codec::{encode::{NetEncode, NetEncodeOpts, NetEncodeResult}, net_types::var_int::VarInt}; +use tokio::io::AsyncWrite; + +#[derive(Clone, Debug, PartialEq, Ordinalize, Default)] +pub enum StringParsingBehavior { + #[default] + SingleWord, + Quotable, + Greedy +} + +impl NetEncode for StringParsingBehavior { + fn encode(&self, writer: &mut W, opts: &NetEncodeOpts) -> NetEncodeResult<()> { + VarInt::new(self.ordinal() as i32).encode(writer, opts) + } + + async fn encode_async( + &self, + writer: &mut W, + opts: &NetEncodeOpts, + ) -> NetEncodeResult<()> { + VarInt::new(self.ordinal() as i32).encode_async(writer, opts).await + } +} diff --git a/src/lib/commands/src/ctx.rs b/src/lib/commands/src/ctx.rs index fa71a3db..bd2f8058 100644 --- a/src/lib/commands/src/ctx.rs +++ b/src/lib/commands/src/ctx.rs @@ -3,7 +3,10 @@ use std::{ sync::{Arc, Mutex}, }; +use ferrumc_net::{connection::StreamWriter, packets::outgoing::system_message::SystemMessagePacket, NetResult}; +use ferrumc_net_codec::encode::NetEncodeOpts; use ferrumc_state::GlobalState; +use ferrumc_text::TextComponent; use crate::{input::CommandInput, Command}; @@ -47,4 +50,16 @@ impl CommandContext { todo!(); } } + + pub async fn reply(&self, text: TextComponent) -> NetResult<()> { + let mut stream_writer = self + .state + .universe + .get_mut::(self.connection_id)?; + + stream_writer.send_packet( + &SystemMessagePacket::new(text, false), + &NetEncodeOpts::WithLength, + ).await + } } diff --git a/src/lib/commands/src/graph/mod.rs b/src/lib/commands/src/graph/mod.rs index 0ebb6271..0c51e3fa 100644 --- a/src/lib/commands/src/graph/mod.rs +++ b/src/lib/commands/src/graph/mod.rs @@ -1,11 +1,10 @@ use std::sync::Arc; use std::{collections::HashMap, io::Write}; -use enum_ordinalize::Ordinalize; use ferrumc_macros::{packet, NetEncode}; use ferrumc_net_codec::net_types::length_prefixed_vec::LengthPrefixedVec; use ferrumc_net_codec::net_types::var_int::VarInt; -use node::{CommandNode, CommandNodeFlag, CommandNodeType, MinecraftCommandParser}; +use node::{CommandNode, CommandNodeFlag, CommandNodeType}; use crate::infrastructure::get_graph; use crate::Command; @@ -50,8 +49,7 @@ impl CommandGraph { current_node_index = child_index; } else { let mut node = CommandNode { - flags: CommandNodeFlag::NodeType(CommandNodeType::Literal).bitmask() - | CommandNodeFlag::Executable.bitmask(), + flags: CommandNodeFlag::NodeType(CommandNodeType::Literal).bitmask(), children: LengthPrefixedVec::new(Vec::new()), redirect_node: None, name: Some(part.to_string()), @@ -61,8 +59,7 @@ impl CommandGraph { }; if is_last - && !command.args.is_empty() - && command.args.first().is_some_and(|arg| !arg.required) + && (command.args.is_empty() || command.args.first().is_some_and(|arg| !arg.required)) { node.flags |= CommandNodeFlag::Executable.bitmask(); } @@ -70,34 +67,46 @@ impl CommandGraph { let node_index = self.nodes.len() as u32; self.nodes.push(node); self.node_to_indices.insert(part.to_string(), node_index); - let node_index_varint = VarInt::new(node_index as i32); - self.root_node.children.push(node_index_varint.clone()); + if i == 0 { + self.nodes[0].children.push(VarInt::new(node_index as i32)); + } else { + let parent_node = self.nodes.get_mut(current_node_index as usize).unwrap(); + parent_node.children.push(VarInt::new(node_index as i32)); + } - let node = self.nodes.get_mut(current_node_index as usize).unwrap(); - node.children.push(node_index_varint); current_node_index = node_index; } } - for arg in &command.args { - let arg_node = CommandNode { + let mut prev_node_index = current_node_index; + + for (i, arg) in command.args.iter().enumerate() { + let vanilla = arg.parser.vanilla(); + let is_last = i == command.args.len() - 1; + + let mut arg_node = CommandNode { flags: CommandNodeFlag::NodeType(CommandNodeType::Argument).bitmask(), children: LengthPrefixedVec::new(Vec::new()), redirect_node: None, name: Some(arg.name.clone()), - parser_id: Some(VarInt::new(MinecraftCommandParser::String.ordinal() as i32)), - properties: Some(node::CommandNodeProperties::String { - behavior: VarInt::new(2), - }), + parser_id: Some(vanilla.argument_type), + properties: Some(vanilla.props), suggestions_type: None, }; + if is_last { + arg_node.flags |= CommandNodeFlag::Executable.bitmask(); + } + let arg_node_index = self.nodes.len() as u32; self.nodes.push(arg_node); - self.nodes[current_node_index as usize] + + self.nodes[prev_node_index as usize] .children .push(VarInt::new(arg_node_index as i32)); + + prev_node_index = arg_node_index; } } diff --git a/src/lib/commands/src/graph/node.rs b/src/lib/commands/src/graph/node.rs index fd3b101b..e3204efe 100644 --- a/src/lib/commands/src/graph/node.rs +++ b/src/lib/commands/src/graph/node.rs @@ -1,9 +1,10 @@ -use std::{collections::HashMap, fmt, io::Write}; +use std::{fmt, io::Write}; -use enum_ordinalize::Ordinalize; use ferrumc_macros::NetEncode; use ferrumc_net_codec::net_types::{length_prefixed_vec::LengthPrefixedVec, var_int::VarInt}; +use crate::arg::parser::vanilla::{MinecraftArgumentProperties, MinecraftArgumentType}; + #[derive(Clone, Debug, PartialEq)] pub enum CommandNodeType { Root, @@ -42,22 +43,14 @@ impl CommandNodeFlag { } } -#[derive(Clone, Debug, PartialEq, NetEncode)] -pub enum CommandNodeProperties { - IntRange { min: i32, max: i32 }, - FloatRange { min: f32, max: f32 }, - String { behavior: VarInt }, - Other(HashMap), -} - #[derive(Clone, NetEncode)] pub struct CommandNode { pub flags: u8, pub children: LengthPrefixedVec, pub redirect_node: Option, pub name: Option, - pub parser_id: Option, - pub properties: Option, + pub parser_id: Option, + pub properties: Option, pub suggestions_type: Option, } @@ -113,57 +106,3 @@ impl CommandNode { self.flags & 0x10 != 0 } } - -#[derive(Clone, Debug, PartialEq, Ordinalize)] -pub enum MinecraftCommandParser { - Bool, - Float, - Double, - Int, - Long, - String, - Entity, - GameProfile, - BlockPos, - ColumnPos, - Vec3, - Vec2, - BlockState, - BlockPredicate, - ItemStack, - ItemPredicate, - Color, - Component, - Style, - Message, - Nbt, - NbtTag, - NbtPath, - Objective, - ObjectiveCriteria, - Operator, - Particle, - Angle, - Rotation, - ScoreboardDisplaySlot, - ScoreHolder, - UpTo3Axes, - Team, - ItemSlot, - ResourceLocation, - Function, - EntityAnchor, - IntRange, - FloatRange, - Dimension, - GameMode, - Time, - ResourceOrTag, - ResourceOrTagKey, - Resource, - ResourceKey, - TemplateMirror, - TemplateRotation, - Heightmap, - UUID, -} diff --git a/src/lib/default_commands/Cargo.toml b/src/lib/default_commands/Cargo.toml index 50515c4c..7796c8ff 100644 --- a/src/lib/default_commands/Cargo.toml +++ b/src/lib/default_commands/Cargo.toml @@ -5,9 +5,7 @@ edition = "2021" [dependencies] ferrumc-commands = { workspace = true } -ferrumc-net = { workspace = true } ferrumc-ecs = { workspace = true } -ferrumc-net-codec = { workspace = true } ferrumc-macros = { workspace = true } ferrumc-text = { workspace = true } ctor = { workspace = true } diff --git a/src/lib/default_commands/src/echo.rs b/src/lib/default_commands/src/echo.rs index 131c8dcd..0326aec6 100644 --- a/src/lib/default_commands/src/echo.rs +++ b/src/lib/default_commands/src/echo.rs @@ -5,46 +5,20 @@ use ferrumc_commands::{ infrastructure::register_command, Command, CommandResult, }; use ferrumc_macros::{arg, command}; -use ferrumc_net::connection::StreamWriter; -use ferrumc_net::packets::outgoing::system_message::SystemMessagePacket; -use ferrumc_net_codec::encode::NetEncodeOpts; use ferrumc_text::{NamedColor, TextComponentBuilder}; -use tracing::error; #[arg("message", GreedyStringParser)] #[command("echo")] async fn echo(ctx: Arc) -> CommandResult { let message = ctx.arg::("message"); - let mut writer = match ctx - .state - .universe - .get_mut::(ctx.connection_id) - { - Ok(writer) => writer, - Err(err) => { - error!( - "failed retrieving stream writer for conn id {}: {err}", - ctx.connection_id - ); - return Ok(()); - } - }; - - if let Err(err) = writer - .send_packet( - &SystemMessagePacket::new( - TextComponentBuilder::new(message) - .color(NamedColor::Green) - .build(), - false, - ), - &NetEncodeOpts::WithLength, - ) - .await - { - error!("failed sending packet: {err}"); - return Ok(()); - } - - Ok(()) + ctx.reply( + TextComponentBuilder::new(message) + .color(NamedColor::Green) + .build() + ).await + .map_err(|err| { + TextComponentBuilder::new(err.to_string()) + .color(NamedColor::Red) + .build() + }) } diff --git a/src/lib/default_commands/src/nested.rs b/src/lib/default_commands/src/nested.rs index 4e4cfab3..bdf044eb 100644 --- a/src/lib/default_commands/src/nested.rs +++ b/src/lib/default_commands/src/nested.rs @@ -1,23 +1,36 @@ use std::sync::Arc; use ferrumc_commands::{ - arg::{parser::string::GreedyStringParser, CommandArgument}, + arg::{parser::{string::{QuotedStringParser, SingleStringParser}, int::IntParser}, CommandArgument}, ctx::CommandContext, executor, infrastructure::register_command, Command, CommandResult, }; use ferrumc_macros::{arg, command}; +use ferrumc_text::TextComponentBuilder; #[command("nested")] -async fn root(_ctx: Arc) -> CommandResult { - println!("Executed root"); +async fn root(ctx: Arc) -> CommandResult { + ctx.reply(TextComponentBuilder::new("Executed /nested").build()).await.unwrap(); Ok(()) } -#[arg("message", GreedyStringParser)] +#[arg("message", QuotedStringParser)] +#[arg("word", SingleStringParser)] +#[arg("number", IntParser)] #[command("nested abc")] async fn abc(ctx: Arc) -> CommandResult { - println!("Executed abc with message {}", ctx.arg::("message")); + let message = ctx.arg::("message"); + let word = ctx.arg::("word"); + let number = ctx.arg::("number"); + + ctx.reply( + TextComponentBuilder::new( + format!("Message: {message:?}, Word: {word:?}, Message: {number}") + ) + .build() + ).await.unwrap(); + Ok(()) }