From 5f466cca28793169680d9c9d1e351ebb39259f9b Mon Sep 17 00:00:00 2001 From: user622628252416 <76960354+user622628252416@users.noreply.github.com> Date: Fri, 8 Nov 2024 22:29:06 +0100 Subject: [PATCH] add client side command argument suggestions --- .../src/client/play/c_commands.rs | 297 +++++++++++++++--- pumpkin/src/client/player_packet.rs | 51 +-- pumpkin/src/command/args/arg_bounded_num.rs | 81 ++++- pumpkin/src/command/args/arg_command.rs | 15 +- pumpkin/src/command/args/arg_entities.rs | 13 +- pumpkin/src/command/args/arg_entity.rs | 15 +- pumpkin/src/command/args/arg_gamemode.rs | 15 +- pumpkin/src/command/args/arg_item.rs | 13 +- pumpkin/src/command/args/arg_message.rs | 15 +- pumpkin/src/command/args/arg_players.rs | 15 +- pumpkin/src/command/args/arg_position_2d.rs | 13 +- pumpkin/src/command/args/arg_position_3d.rs | 13 +- pumpkin/src/command/args/arg_rotation.rs | 13 +- pumpkin/src/command/args/arg_simple.rs | 15 +- pumpkin/src/command/args/mod.rs | 10 +- pumpkin/src/command/client_cmd_suggestions.rs | 6 +- pumpkin/src/command/commands/cmd_give.rs | 2 +- pumpkin/src/entity/player.rs | 4 +- 18 files changed, 522 insertions(+), 84 deletions(-) diff --git a/pumpkin-protocol/src/client/play/c_commands.rs b/pumpkin-protocol/src/client/play/c_commands.rs index 21f9f68b..a5066924 100644 --- a/pumpkin-protocol/src/client/play/c_commands.rs +++ b/pumpkin-protocol/src/client/play/c_commands.rs @@ -2,7 +2,6 @@ use pumpkin_macros::client_packet; use crate::{bytebuf::ByteBuffer, ClientPacket, VarInt}; -/// this is a bit ugly, but ClientPacket depends on CommandTree in pumpkin(bin), from where ClientPacket cannot be implemented #[client_packet("play:commands")] pub struct CCommands<'a> { pub nodes: Vec>, @@ -32,10 +31,19 @@ pub struct ProtoNode<'a> { pub node_type: ProtoNodeType<'a>, } +#[derive(Debug)] pub enum ProtoNodeType<'a> { Root, - Literal { name: &'a str, is_executable: bool }, - Argument { name: &'a str, is_executable: bool }, + Literal { + name: &'a str, + is_executable: bool, + }, + Argument { + name: &'a str, + is_executable: bool, + parser: ProtoCmdArgParser<'a>, + override_suggestion_type: Option, + }, } impl<'a> ProtoNode<'a> { @@ -43,33 +51,6 @@ impl<'a> ProtoNode<'a> { const FLAG_HAS_REDIRECT: i8 = 8; const FLAG_HAS_SUGGESTION_TYPE: i8 = 16; - pub fn new_root(children: Vec) -> Self { - Self { - children, - node_type: ProtoNodeType::Root, - } - } - - pub fn new_literal(children: Vec, name: &'a str) -> Self { - Self { - children, - node_type: ProtoNodeType::Literal { - name, - is_executable: true, - }, - } - } - - pub fn new_argument(children: Vec, name: &'a str) -> Self { - Self { - children, - node_type: ProtoNodeType::Argument { - name, - is_executable: true, // todo - }, - } - } - /// https://wiki.vg/Command_Data pub fn write_to(&self, bytebuf: &mut ByteBuffer) { // flags @@ -88,8 +69,13 @@ impl<'a> ProtoNode<'a> { ProtoNodeType::Argument { name: _, is_executable, + parser: _, + override_suggestion_type: override_suggestion_tpye, } => { - let mut n = 2 | Self::FLAG_HAS_SUGGESTION_TYPE; + let mut n = 2; + if override_suggestion_tpye.is_some() { + n |= Self::FLAG_HAS_SUGGESTION_TYPE + } if is_executable { n |= Self::FLAG_IS_EXECUTABLE } @@ -115,14 +101,253 @@ impl<'a> ProtoNode<'a> { } // parser id + properties - if let ProtoNodeType::Argument { .. } = self.node_type { - bytebuf.put_var_int(&5.into()); // string arg type has id 5 - bytebuf.put_var_int(&0.into()); // match single word only + if let ProtoNodeType::Argument { + name: _, + is_executable: _, + parser, + override_suggestion_type: _, + } = &self.node_type + { + parser.write_to_buffer(bytebuf) } - // suggestion type if flags & Self::FLAG_HAS_SUGGESTION_TYPE != 0 { - bytebuf.put_string("minecraft:ask_server"); + match &self.node_type { + ProtoNodeType::Argument { name: _, is_executable: _, parser: _, override_suggestion_type } => { + // suggestion type + let suggestion_type = &override_suggestion_type.expect("ProtoNode::FLAG_HAS_SUGGESTION_TYPE should only be set if override_suggestion_type is not None."); + bytebuf.put_string(suggestion_type.identifier()); + }, + _ => unimplemented!("ProtoNode::FLAG_HAS_SUGGESTION_TYPE is only implemented for ProtoNodeType::Argument"), + } + } + } +} + +/// see https://wiki.vg/Command_Data for meaning of flags/enums/etc. +#[derive(Debug)] +pub enum ProtoCmdArgParser<'a> { + Bool, + Float { min: Option, max: Option }, + Double { min: Option, max: Option }, + Integer { min: Option, max: Option }, + Long { min: Option, max: Option }, + String(StringProtoArgBehavior), + Entity { flags: i8 }, + GameProfile, + BlockPos, + ColumnPos, + Vec3, + Vec2, + BlockState, + BlockPredicate, + ItemStack, + ItemPredicate, + Color, + Component, + Style, + Message, + Nbt, + NbtTag, + NbtPath, + Objective, + ObjectiveCriteria, + Operation, + Particle, + Angle, + Rotation, + ScoreboardSlot, + ScoreHolder { flags: i8 }, + Swizzle, + Team, + ItemSlot, + ResourceLocation, + Function, + EntityAnchor, + IntRange, + FloatRange, + Dimension, + Gamemode, + Time { min: i32 }, + ResourceOrTag { identifier: &'a str }, + ResourceOrTagKey { identifier: &'a str }, + Resource { identifier: &'a str }, + ResourceKey { identifier: &'a str }, + TemplateMirror, + TemplateRotation, + Heightmap, + Uuid, +} + +impl<'a> ProtoCmdArgParser<'a> { + pub const ENTITY_FLAG_ONLY_SINGLE: i8 = 1; + pub const ENTITY_FLAG_PLAYERS_ONLY: i8 = 2; + + pub const SCORE_HOLDER_FLAG_ALLOW_MULTIPLE: i8 = 1; + + pub fn write_to_buffer(&self, bytebuf: &mut ByteBuffer) { + match self { + Self::Bool => bytebuf.put_var_int(&0.into()), + Self::Float { min, max } => Self::write_number_arg(&1.into(), *min, *max, bytebuf), + Self::Double { min, max } => Self::write_number_arg(&2.into(), *min, *max, bytebuf), + Self::Integer { min, max } => Self::write_number_arg(&3.into(), *min, *max, bytebuf), + Self::Long { min, max } => Self::write_number_arg(&4.into(), *min, *max, bytebuf), + Self::String(behavior) => { + bytebuf.put_var_int(&5.into()); + let i = match behavior { + StringProtoArgBehavior::SingleWord => 0, + StringProtoArgBehavior::QuotablePhrase => 1, + StringProtoArgBehavior::GreedyPhrase => 2, + }; + bytebuf.put_var_int(&i.into()); + } + Self::Entity { flags } => Self::write_with_flags(&6.into(), *flags, bytebuf), + Self::GameProfile => bytebuf.put_var_int(&7.into()), + Self::BlockPos => bytebuf.put_var_int(&8.into()), + Self::ColumnPos => bytebuf.put_var_int(&9.into()), + Self::Vec3 => bytebuf.put_var_int(&10.into()), + Self::Vec2 => bytebuf.put_var_int(&11.into()), + Self::BlockState => bytebuf.put_var_int(&12.into()), + Self::BlockPredicate => bytebuf.put_var_int(&13.into()), + Self::ItemStack => bytebuf.put_var_int(&14.into()), + Self::ItemPredicate => bytebuf.put_var_int(&15.into()), + Self::Color => bytebuf.put_var_int(&16.into()), + Self::Component => bytebuf.put_var_int(&17.into()), + Self::Style => bytebuf.put_var_int(&18.into()), + Self::Message => bytebuf.put_var_int(&19.into()), + Self::Nbt => bytebuf.put_var_int(&20.into()), + Self::NbtTag => bytebuf.put_var_int(&21.into()), + Self::NbtPath => bytebuf.put_var_int(&22.into()), + Self::Objective => bytebuf.put_var_int(&23.into()), + Self::ObjectiveCriteria => bytebuf.put_var_int(&24.into()), + Self::Operation => bytebuf.put_var_int(&25.into()), + Self::Particle => bytebuf.put_var_int(&26.into()), + Self::Angle => bytebuf.put_var_int(&27.into()), + Self::Rotation => bytebuf.put_var_int(&28.into()), + Self::ScoreboardSlot => bytebuf.put_var_int(&29.into()), + Self::ScoreHolder { flags } => Self::write_with_flags(&30.into(), *flags, bytebuf), + Self::Swizzle => bytebuf.put_var_int(&31.into()), + Self::Team => bytebuf.put_var_int(&32.into()), + Self::ItemSlot => bytebuf.put_var_int(&33.into()), + Self::ResourceLocation => bytebuf.put_var_int(&34.into()), + Self::Function => bytebuf.put_var_int(&35.into()), + Self::EntityAnchor => bytebuf.put_var_int(&36.into()), + Self::IntRange => bytebuf.put_var_int(&37.into()), + Self::FloatRange => bytebuf.put_var_int(&38.into()), + Self::Dimension => bytebuf.put_var_int(&39.into()), + Self::Gamemode => bytebuf.put_var_int(&40.into()), + Self::Time { min } => { + bytebuf.put_var_int(&41.into()); + bytebuf.put_i32(*min); + } + Self::ResourceOrTag { identifier } => { + Self::write_with_identifier(&42.into(), identifier, bytebuf) + } + Self::ResourceOrTagKey { identifier } => { + Self::write_with_identifier(&43.into(), identifier, bytebuf) + } + Self::Resource { identifier } => { + Self::write_with_identifier(&44.into(), identifier, bytebuf) + } + Self::ResourceKey { identifier } => { + Self::write_with_identifier(&45.into(), identifier, bytebuf) + } + Self::TemplateMirror => bytebuf.put_var_int(&46.into()), + Self::TemplateRotation => bytebuf.put_var_int(&47.into()), + Self::Heightmap => bytebuf.put_var_int(&48.into()), + Self::Uuid => bytebuf.put_var_int(&49.into()), + } + } + + fn write_number_arg( + id: &VarInt, + min: Option, + max: Option, + bytebuf: &mut ByteBuffer, + ) { + let mut flags: i8 = 0; + if min.is_some() { + flags |= 1 + } + if max.is_some() { + flags |= 2 + } + + bytebuf.put_var_int(id); + + bytebuf.put_i8(flags); + if let Some(min) = min { + min.write(bytebuf); + } + if let Some(max) = max { + max.write(bytebuf); + } + } + + fn write_with_flags(id: &VarInt, flags: i8, bytebuf: &mut ByteBuffer) { + bytebuf.put_var_int(id); + + bytebuf.put_i8(flags); + } + + fn write_with_identifier(id: &VarInt, extra_identifier: &str, bytebuf: &mut ByteBuffer) { + bytebuf.put_var_int(id); + + bytebuf.put_string(extra_identifier); + } +} + +#[derive(Debug, Clone, Copy)] +pub enum StringProtoArgBehavior { + SingleWord, + QuotablePhrase, + /// does not stop after a space + GreedyPhrase, +} + +trait NumberCmdArg { + fn write(self, bytebuf: &mut ByteBuffer); +} + +impl NumberCmdArg for f32 { + fn write(self, bytebuf: &mut ByteBuffer) { + bytebuf.put_f32(self); + } +} + +impl NumberCmdArg for f64 { + fn write(self, bytebuf: &mut ByteBuffer) { + bytebuf.put_f64(self); + } +} + +impl NumberCmdArg for i32 { + fn write(self, bytebuf: &mut ByteBuffer) { + bytebuf.put_i32(self); + } +} + +impl NumberCmdArg for i64 { + fn write(self, bytebuf: &mut ByteBuffer) { + bytebuf.put_i64(self); + } +} + +#[derive(Debug, Clone, Copy)] +pub enum ProtoCmdArgSuggestionType { + AskServer, + AllRecipes, + AvailableSounds, + SummonableEntities, +} + +impl ProtoCmdArgSuggestionType { + fn identifier(&self) -> &'static str { + match self { + Self::AskServer => "minecraft:ask_server", + Self::AllRecipes => "minecraft:all_recipes", + Self::AvailableSounds => "minecraft:available_sounds", + Self::SummonableEntities => "minecraft:summonable_entities", } } } diff --git a/pumpkin/src/client/player_packet.rs b/pumpkin/src/client/player_packet.rs index 10087e3d..1bc91fc8 100644 --- a/pumpkin/src/client/player_packet.rs +++ b/pumpkin/src/client/player_packet.rs @@ -8,16 +8,15 @@ use crate::{ }; use num_traits::FromPrimitive; use pumpkin_config::ADVANCED_CONFIG; -use pumpkin_core::{math::position::WorldPosition, text::color::NamedColor}; +use pumpkin_core::math::position::WorldPosition; use pumpkin_core::{ math::{vector3::Vector3, wrap_degrees}, text::TextComponent, GameMode, }; use pumpkin_inventory::{InventoryError, WindowType}; -use pumpkin_protocol::{ - client::play::CCommandSuggestions, - server::play::{SCloseContainer, SCommandSuggestion, SKeepAlive, SSetPlayerGround, SUseItem}, +use pumpkin_protocol::server::play::{ + SCloseContainer, SCommandSuggestion, SKeepAlive, SSetPlayerGround, SUseItem, }; use pumpkin_protocol::{ client::play::{ @@ -643,26 +642,28 @@ impl Player { } } - pub async fn handle_command_suggestion(&self, packet: SCommandSuggestion) { - dbg!(&packet.command); - let response = CCommandSuggestions::new( - packet.id, - packet.command.len(), - 0, - vec![ - ("test suggestion 1".to_string(), None), - ( - "test suggestion with tooltip".to_string(), - Some( - TextComponent::text("I am a tooltip") - .color_named(NamedColor::Red) - .bold() - .underlined() - .italic(), - ), - ), - ], - ); - self.client.send_packet(&response).await; + /// todo: implement + #[allow(clippy::unused_async)] + pub async fn handle_command_suggestion(&self, _packet: SCommandSuggestion) { + //dbg!(&packet.command); + //let response = CCommandSuggestions::new( + // packet.id, + // packet.command.len(), + // 0, + // vec![ + // ("test suggestion 1".to_string(), None), + // ( + // "test suggestion with tooltip".to_string(), + // Some( + // TextComponent::text("I am a tooltip") + // .color_named(NamedColor::Red) + // .bold() + // .underlined() + // .italic(), + // ), + // ), + // ], + //); + //self.client.send_packet(&response).await; } } diff --git a/pumpkin/src/command/args/arg_bounded_num.rs b/pumpkin/src/command/args/arg_bounded_num.rs index c799e10a..74ed195b 100644 --- a/pumpkin/src/command/args/arg_bounded_num.rs +++ b/pumpkin/src/command/args/arg_bounded_num.rs @@ -2,6 +2,7 @@ use core::f64; use std::str::FromStr; use async_trait::async_trait; +use pumpkin_protocol::client::play::ProtoCmdArgParser; use crate::command::dispatcher::InvalidTreeError; use crate::command::tree::RawArgs; @@ -9,7 +10,7 @@ use crate::command::CommandSender; use crate::server::Server; use super::super::args::ArgumentConsumer; -use super::{Arg, DefaultNameArgConsumer, FindArg}; +use super::{Arg, DefaultNameArgConsumer, FindArg, GetClientSideArgParser}; /// Consumes a single generic num, but only if it's in bounds. pub(crate) struct BoundedNumArgumentConsumer { @@ -19,7 +20,10 @@ pub(crate) struct BoundedNumArgumentConsumer { } #[async_trait] -impl ArgumentConsumer for BoundedNumArgumentConsumer { +impl ArgumentConsumer for BoundedNumArgumentConsumer +where + BoundedNumArgumentConsumer: GetClientSideArgParser, +{ async fn consume<'a>( &self, _src: &CommandSender<'a>, @@ -79,7 +83,7 @@ pub(crate) enum Number { F32(f32), I32(i32), #[allow(unused)] - U32(u32), + I64(i64), } impl BoundedNumArgumentConsumer { @@ -126,6 +130,21 @@ impl ToFromNumber for f64 { } } +impl GetClientSideArgParser for BoundedNumArgumentConsumer { + fn get_client_side_parser(&self) -> ProtoCmdArgParser { + ProtoCmdArgParser::Double { + min: self.min_inclusive, + max: self.max_inclusive, + } + } + + fn get_client_side_suggestion_type_override( + &self, + ) -> Option { + None + } +} + impl ToFromNumber for f32 { fn to_number(self) -> Number { Number::F32(self) @@ -139,6 +158,21 @@ impl ToFromNumber for f32 { } } +impl GetClientSideArgParser for BoundedNumArgumentConsumer { + fn get_client_side_parser(&self) -> ProtoCmdArgParser { + ProtoCmdArgParser::Float { + min: self.min_inclusive, + max: self.max_inclusive, + } + } + + fn get_client_side_suggestion_type_override( + &self, + ) -> Option { + None + } +} + impl ToFromNumber for i32 { fn to_number(self) -> Number { Number::I32(self) @@ -152,20 +186,53 @@ impl ToFromNumber for i32 { } } -impl ToFromNumber for u32 { +impl GetClientSideArgParser for BoundedNumArgumentConsumer { + fn get_client_side_parser(&self) -> ProtoCmdArgParser { + ProtoCmdArgParser::Integer { + min: self.min_inclusive, + max: self.max_inclusive, + } + } + + fn get_client_side_suggestion_type_override( + &self, + ) -> Option { + None + } +} + +impl ToFromNumber for i64 { fn to_number(self) -> Number { - Number::U32(self) + Number::I64(self) } fn from_number(arg: &Number) -> Option { match arg { - Number::U32(x) => Some(*x), + Number::I64(x) => Some(*x), _ => None, } } } -impl DefaultNameArgConsumer for BoundedNumArgumentConsumer { +impl GetClientSideArgParser for BoundedNumArgumentConsumer { + fn get_client_side_parser(&self) -> ProtoCmdArgParser { + ProtoCmdArgParser::Long { + min: self.min_inclusive, + max: self.max_inclusive, + } + } + + fn get_client_side_suggestion_type_override( + &self, + ) -> Option { + None + } +} + +impl DefaultNameArgConsumer for BoundedNumArgumentConsumer +where + BoundedNumArgumentConsumer: ArgumentConsumer, +{ fn default_name(&self) -> &'static str { // setting a single default name for all BoundedNumArgumentConsumer variants is probably a bad idea since it would lead to confusion self.name.expect("Only use *_default variants of methods with a BoundedNumArgumentConsumer that has a name.") diff --git a/pumpkin/src/command/args/arg_command.rs b/pumpkin/src/command/args/arg_command.rs index 6c1a1fd6..1dc3abe6 100644 --- a/pumpkin/src/command/args/arg_command.rs +++ b/pumpkin/src/command/args/arg_command.rs @@ -1,4 +1,7 @@ use async_trait::async_trait; +use pumpkin_protocol::client::play::{ + ProtoCmdArgParser, ProtoCmdArgSuggestionType, StringProtoArgBehavior, +}; use crate::{ command::{ @@ -9,10 +12,20 @@ use crate::{ server::Server, }; -use super::{Arg, ArgumentConsumer, DefaultNameArgConsumer, FindArg}; +use super::{Arg, ArgumentConsumer, DefaultNameArgConsumer, FindArg, GetClientSideArgParser}; pub(crate) struct CommandTreeArgumentConsumer; +impl GetClientSideArgParser for CommandTreeArgumentConsumer { + fn get_client_side_parser(&self) -> ProtoCmdArgParser { + ProtoCmdArgParser::String(StringProtoArgBehavior::SingleWord) + } + + fn get_client_side_suggestion_type_override(&self) -> Option { + Some(ProtoCmdArgSuggestionType::AskServer) + } +} + #[async_trait] impl ArgumentConsumer for CommandTreeArgumentConsumer { async fn consume<'a>( diff --git a/pumpkin/src/command/args/arg_entities.rs b/pumpkin/src/command/args/arg_entities.rs index 2f35bba3..5e1c0760 100644 --- a/pumpkin/src/command/args/arg_entities.rs +++ b/pumpkin/src/command/args/arg_entities.rs @@ -1,6 +1,7 @@ use std::sync::Arc; use async_trait::async_trait; +use pumpkin_protocol::client::play::{ProtoCmdArgParser, ProtoCmdArgSuggestionType}; use crate::command::dispatcher::InvalidTreeError; use crate::command::tree::RawArgs; @@ -10,13 +11,23 @@ use crate::server::Server; use super::super::args::ArgumentConsumer; use super::arg_players::PlayersArgumentConsumer; -use super::{Arg, DefaultNameArgConsumer, FindArg}; +use super::{Arg, DefaultNameArgConsumer, FindArg, GetClientSideArgParser}; /// todo: implement (currently just calls [`super::arg_player::PlayerArgumentConsumer`]) /// /// For selecting zero, one or multiple entities, eg. using @s, a player name, @a or @e pub(crate) struct EntitiesArgumentConsumer; +impl GetClientSideArgParser for EntitiesArgumentConsumer { + fn get_client_side_parser(&self) -> ProtoCmdArgParser { + ProtoCmdArgParser::Entity { flags: 0 } + } + + fn get_client_side_suggestion_type_override(&self) -> Option { + None + } +} + #[async_trait] impl ArgumentConsumer for EntitiesArgumentConsumer { async fn consume<'a>( diff --git a/pumpkin/src/command/args/arg_entity.rs b/pumpkin/src/command/args/arg_entity.rs index cbcc933e..8e68bd9c 100644 --- a/pumpkin/src/command/args/arg_entity.rs +++ b/pumpkin/src/command/args/arg_entity.rs @@ -1,6 +1,7 @@ use std::sync::Arc; use async_trait::async_trait; +use pumpkin_protocol::client::play::{ProtoCmdArgParser, ProtoCmdArgSuggestionType}; use crate::command::dispatcher::InvalidTreeError; use crate::command::tree::RawArgs; @@ -9,7 +10,7 @@ use crate::entity::player::Player; use crate::server::Server; use super::super::args::ArgumentConsumer; -use super::{Arg, DefaultNameArgConsumer, FindArg}; +use super::{Arg, DefaultNameArgConsumer, FindArg, GetClientSideArgParser}; /// todo: implement for entitites that aren't players /// @@ -18,6 +19,18 @@ use super::{Arg, DefaultNameArgConsumer, FindArg}; /// Use [`super::arg_entities::EntitiesArgumentConsumer`] when there may be multiple targets. pub(crate) struct EntityArgumentConsumer; +impl GetClientSideArgParser for EntityArgumentConsumer { + fn get_client_side_parser(&self) -> ProtoCmdArgParser { + ProtoCmdArgParser::Entity { + flags: ProtoCmdArgParser::ENTITY_FLAG_ONLY_SINGLE, + } + } + + fn get_client_side_suggestion_type_override(&self) -> Option { + None + } +} + #[async_trait] impl ArgumentConsumer for EntityArgumentConsumer { async fn consume<'a>( diff --git a/pumpkin/src/command/args/arg_gamemode.rs b/pumpkin/src/command/args/arg_gamemode.rs index 54f95778..b681adb4 100644 --- a/pumpkin/src/command/args/arg_gamemode.rs +++ b/pumpkin/src/command/args/arg_gamemode.rs @@ -3,16 +3,29 @@ use std::str::FromStr; use async_trait::async_trait; use num_traits::FromPrimitive; use pumpkin_core::GameMode; +use pumpkin_protocol::client::play::{ + ProtoCmdArgParser, ProtoCmdArgSuggestionType, StringProtoArgBehavior, +}; use crate::{ command::{dispatcher::InvalidTreeError, tree::RawArgs, CommandSender}, server::Server, }; -use super::{Arg, ArgumentConsumer, DefaultNameArgConsumer, FindArg}; +use super::{Arg, ArgumentConsumer, DefaultNameArgConsumer, FindArg, GetClientSideArgParser}; pub(crate) struct GamemodeArgumentConsumer; +impl GetClientSideArgParser for GamemodeArgumentConsumer { + fn get_client_side_parser(&self) -> ProtoCmdArgParser { + ProtoCmdArgParser::String(StringProtoArgBehavior::SingleWord) + } + + fn get_client_side_suggestion_type_override(&self) -> Option { + Some(ProtoCmdArgSuggestionType::AskServer) + } +} + #[async_trait] impl ArgumentConsumer for GamemodeArgumentConsumer { async fn consume<'a>( diff --git a/pumpkin/src/command/args/arg_item.rs b/pumpkin/src/command/args/arg_item.rs index 7eaf6390..1194121e 100644 --- a/pumpkin/src/command/args/arg_item.rs +++ b/pumpkin/src/command/args/arg_item.rs @@ -1,4 +1,5 @@ use async_trait::async_trait; +use pumpkin_protocol::client::play::{ProtoCmdArgParser, ProtoCmdArgSuggestionType}; use crate::{command::dispatcher::InvalidTreeError, server::Server}; @@ -7,11 +8,21 @@ use super::{ args::{ArgumentConsumer, RawArgs}, CommandSender, }, - Arg, DefaultNameArgConsumer, FindArg, + Arg, DefaultNameArgConsumer, FindArg, GetClientSideArgParser, }; pub(crate) struct ItemArgumentConsumer; +impl GetClientSideArgParser for ItemArgumentConsumer { + fn get_client_side_parser(&self) -> ProtoCmdArgParser { + ProtoCmdArgParser::ResourceKey { identifier: "item" } + } + + fn get_client_side_suggestion_type_override(&self) -> Option { + None + } +} + #[async_trait] impl ArgumentConsumer for ItemArgumentConsumer { async fn consume<'a>( diff --git a/pumpkin/src/command/args/arg_message.rs b/pumpkin/src/command/args/arg_message.rs index 48d79e7b..c17dcf1b 100644 --- a/pumpkin/src/command/args/arg_message.rs +++ b/pumpkin/src/command/args/arg_message.rs @@ -1,4 +1,7 @@ use async_trait::async_trait; +use pumpkin_protocol::client::play::{ + ProtoCmdArgParser, ProtoCmdArgSuggestionType, StringProtoArgBehavior, +}; use crate::{command::dispatcher::InvalidTreeError, server::Server}; @@ -7,12 +10,22 @@ use super::{ args::{ArgumentConsumer, RawArgs}, CommandSender, }, - Arg, DefaultNameArgConsumer, FindArg, + Arg, DefaultNameArgConsumer, FindArg, GetClientSideArgParser, }; /// Consumes all remaining words/args. Does not consume if there is no word. pub(crate) struct MsgArgConsumer; +impl GetClientSideArgParser for MsgArgConsumer { + fn get_client_side_parser(&self) -> ProtoCmdArgParser { + ProtoCmdArgParser::String(StringProtoArgBehavior::GreedyPhrase) + } + + fn get_client_side_suggestion_type_override(&self) -> Option { + Some(ProtoCmdArgSuggestionType::AskServer) + } +} + #[async_trait] impl ArgumentConsumer for MsgArgConsumer { async fn consume<'a>( diff --git a/pumpkin/src/command/args/arg_players.rs b/pumpkin/src/command/args/arg_players.rs index f10ed5e5..9987c4fa 100644 --- a/pumpkin/src/command/args/arg_players.rs +++ b/pumpkin/src/command/args/arg_players.rs @@ -1,6 +1,7 @@ use std::sync::Arc; use async_trait::async_trait; +use pumpkin_protocol::client::play::{ProtoCmdArgParser, ProtoCmdArgSuggestionType}; use crate::command::dispatcher::InvalidTreeError; use crate::command::tree::RawArgs; @@ -9,11 +10,23 @@ use crate::entity::player::Player; use crate::server::Server; use super::super::args::ArgumentConsumer; -use super::{Arg, DefaultNameArgConsumer, FindArg}; +use super::{Arg, DefaultNameArgConsumer, FindArg, GetClientSideArgParser}; /// Select zero, one or multiple players pub(crate) struct PlayersArgumentConsumer; +impl GetClientSideArgParser for PlayersArgumentConsumer { + fn get_client_side_parser(&self) -> ProtoCmdArgParser { + ProtoCmdArgParser::Entity { + flags: ProtoCmdArgParser::ENTITY_FLAG_PLAYERS_ONLY, + } + } + + fn get_client_side_suggestion_type_override(&self) -> Option { + None + } +} + #[async_trait] impl ArgumentConsumer for PlayersArgumentConsumer { async fn consume<'a>( diff --git a/pumpkin/src/command/args/arg_position_2d.rs b/pumpkin/src/command/args/arg_position_2d.rs index 7a239119..0040be83 100644 --- a/pumpkin/src/command/args/arg_position_2d.rs +++ b/pumpkin/src/command/args/arg_position_2d.rs @@ -1,5 +1,6 @@ use async_trait::async_trait; use pumpkin_core::math::vector2::Vector2; +use pumpkin_protocol::client::play::{ProtoCmdArgParser, ProtoCmdArgSuggestionType}; use crate::command::dispatcher::InvalidTreeError; use crate::command::tree::RawArgs; @@ -7,13 +8,23 @@ use crate::command::CommandSender; use crate::server::Server; use super::super::args::ArgumentConsumer; -use super::{Arg, DefaultNameArgConsumer, FindArg}; +use super::{Arg, DefaultNameArgConsumer, FindArg, GetClientSideArgParser}; /// x and z coordinates only /// /// todo: implememnt ~ ^ notations pub(crate) struct Position2DArgumentConsumer; +impl GetClientSideArgParser for Position2DArgumentConsumer { + fn get_client_side_parser(&self) -> ProtoCmdArgParser { + ProtoCmdArgParser::Vec2 + } + + fn get_client_side_suggestion_type_override(&self) -> Option { + Some(ProtoCmdArgSuggestionType::AskServer) + } +} + #[async_trait] impl ArgumentConsumer for Position2DArgumentConsumer { async fn consume<'a>( diff --git a/pumpkin/src/command/args/arg_position_3d.rs b/pumpkin/src/command/args/arg_position_3d.rs index 451b4049..54c471f0 100644 --- a/pumpkin/src/command/args/arg_position_3d.rs +++ b/pumpkin/src/command/args/arg_position_3d.rs @@ -2,6 +2,7 @@ use std::num::ParseFloatError; use async_trait::async_trait; use pumpkin_core::math::vector3::Vector3; +use pumpkin_protocol::client::play::{ProtoCmdArgParser, ProtoCmdArgSuggestionType}; use crate::command::dispatcher::InvalidTreeError; use crate::command::tree::RawArgs; @@ -9,11 +10,21 @@ use crate::command::CommandSender; use crate::server::Server; use super::super::args::ArgumentConsumer; -use super::{Arg, DefaultNameArgConsumer, FindArg}; +use super::{Arg, DefaultNameArgConsumer, FindArg, GetClientSideArgParser}; /// x, y and z coordinates pub(crate) struct Position3DArgumentConsumer; +impl GetClientSideArgParser for Position3DArgumentConsumer { + fn get_client_side_parser(&self) -> ProtoCmdArgParser { + ProtoCmdArgParser::Vec3 + } + + fn get_client_side_suggestion_type_override(&self) -> Option { + Some(ProtoCmdArgSuggestionType::AskServer) + } +} + #[async_trait] impl ArgumentConsumer for Position3DArgumentConsumer { async fn consume<'a>( diff --git a/pumpkin/src/command/args/arg_rotation.rs b/pumpkin/src/command/args/arg_rotation.rs index 7ae4402f..690a640b 100644 --- a/pumpkin/src/command/args/arg_rotation.rs +++ b/pumpkin/src/command/args/arg_rotation.rs @@ -1,4 +1,5 @@ use async_trait::async_trait; +use pumpkin_protocol::client::play::{ProtoCmdArgParser, ProtoCmdArgSuggestionType}; use crate::command::dispatcher::InvalidTreeError; use crate::command::tree::RawArgs; @@ -6,11 +7,21 @@ use crate::command::CommandSender; use crate::server::Server; use super::super::args::ArgumentConsumer; -use super::{Arg, DefaultNameArgConsumer, FindArg}; +use super::{Arg, DefaultNameArgConsumer, FindArg, GetClientSideArgParser}; /// yaw and pitch pub(crate) struct RotationArgumentConsumer; +impl GetClientSideArgParser for RotationArgumentConsumer { + fn get_client_side_parser(&self) -> ProtoCmdArgParser { + ProtoCmdArgParser::Rotation + } + + fn get_client_side_suggestion_type_override(&self) -> Option { + Some(ProtoCmdArgSuggestionType::AskServer) + } +} + #[async_trait] impl ArgumentConsumer for RotationArgumentConsumer { async fn consume<'a>( diff --git a/pumpkin/src/command/args/arg_simple.rs b/pumpkin/src/command/args/arg_simple.rs index 5691d0cb..fbfafc79 100644 --- a/pumpkin/src/command/args/arg_simple.rs +++ b/pumpkin/src/command/args/arg_simple.rs @@ -1,4 +1,7 @@ use async_trait::async_trait; +use pumpkin_protocol::client::play::{ + ProtoCmdArgParser, ProtoCmdArgSuggestionType, StringProtoArgBehavior, +}; use crate::{command::dispatcher::InvalidTreeError, server::Server}; @@ -7,13 +10,23 @@ use super::{ args::{ArgumentConsumer, RawArgs}, CommandSender, }, - Arg, FindArg, + Arg, FindArg, GetClientSideArgParser, }; /// Should never be a permanent solution #[allow(unused)] pub(crate) struct SimpleArgConsumer; +impl GetClientSideArgParser for SimpleArgConsumer { + fn get_client_side_parser(&self) -> ProtoCmdArgParser { + ProtoCmdArgParser::String(StringProtoArgBehavior::SingleWord) + } + + fn get_client_side_suggestion_type_override(&self) -> Option { + None + } +} + #[async_trait] impl ArgumentConsumer for SimpleArgConsumer { async fn consume<'a>( diff --git a/pumpkin/src/command/args/mod.rs b/pumpkin/src/command/args/mod.rs index 7a3d72ff..f08e25ed 100644 --- a/pumpkin/src/command/args/mod.rs +++ b/pumpkin/src/command/args/mod.rs @@ -6,6 +6,7 @@ use pumpkin_core::{ math::{vector2::Vector2, vector3::Vector3}, GameMode, }; +use pumpkin_protocol::client::play::{ProtoCmdArgParser, ProtoCmdArgSuggestionType}; use crate::{entity::player::Player, server::Server}; @@ -31,7 +32,7 @@ pub(crate) mod arg_simple; /// see [`crate::commands::tree_builder::argument`] /// Provide value or an Optional error message, If no Error message provided the default will be used #[async_trait] -pub(crate) trait ArgumentConsumer: Sync { +pub(crate) trait ArgumentConsumer: Sync + GetClientSideArgParser { async fn consume<'a>( &self, sender: &CommandSender<'a>, @@ -40,6 +41,13 @@ pub(crate) trait ArgumentConsumer: Sync { ) -> Option>; } +pub(crate) trait GetClientSideArgParser { + /// Return the parser the client should use while typing a command in chat. + fn get_client_side_parser(&self) -> ProtoCmdArgParser; + /// Usually this should return None. For example this can be used to force suggestions to be processed on serverside. + fn get_client_side_suggestion_type_override(&self) -> Option; +} + pub(crate) trait DefaultNameArgConsumer: ArgumentConsumer { fn default_name(&self) -> &'static str; diff --git a/pumpkin/src/command/client_cmd_suggestions.rs b/pumpkin/src/command/client_cmd_suggestions.rs index bc730bf7..107a1f0f 100644 --- a/pumpkin/src/command/client_cmd_suggestions.rs +++ b/pumpkin/src/command/client_cmd_suggestions.rs @@ -46,6 +46,7 @@ pub async fn send_c_commands_packet<'a>( player.client.send_packet(&packet).await; } +#[derive(Debug)] struct ProtoNodeBuilder<'a> { child_nodes: Vec>, node_type: ProtoNodeType<'a>, @@ -79,7 +80,7 @@ fn nodes_to_proto_node_builders<'a>( for i in children { let node = &nodes[*i]; match node.node_type { - NodeType::Argument { name, .. } => { + NodeType::Argument { name, consumer } => { let (node_is_executable, node_children) = nodes_to_proto_node_builders(player, nodes, &node.children); child_nodes.push(ProtoNodeBuilder { @@ -87,6 +88,9 @@ fn nodes_to_proto_node_builders<'a>( node_type: ProtoNodeType::Argument { name, is_executable: node_is_executable, + parser: consumer.get_client_side_parser(), + override_suggestion_type: consumer + .get_client_side_suggestion_type_override(), }, }); } diff --git a/pumpkin/src/command/commands/cmd_give.rs b/pumpkin/src/command/commands/cmd_give.rs index d1631502..180036a3 100644 --- a/pumpkin/src/command/commands/cmd_give.rs +++ b/pumpkin/src/command/commands/cmd_give.rs @@ -17,7 +17,7 @@ const DESCRIPTION: &str = "Give items to player(s)."; const ARG_ITEM: &str = "item"; -static ITEM_COUNT_CONSUMER: BoundedNumArgumentConsumer = +static ITEM_COUNT_CONSUMER: BoundedNumArgumentConsumer = BoundedNumArgumentConsumer::new().name("count").max(6400); struct GiveExecutor; diff --git a/pumpkin/src/entity/player.rs b/pumpkin/src/entity/player.rs index 2bca39ed..becf9def 100644 --- a/pumpkin/src/entity/player.rs +++ b/pumpkin/src/entity/player.rs @@ -876,8 +876,8 @@ impl Player { /// Add items to inventory if there's space, else drop them to the ground. /// /// This method automatically syncs changes with the client. - pub async fn give_items(&self, item: &Item, count: u32) { - let mut remaining_items: u32 = count; + pub async fn give_items(&self, item: &Item, count: i32) { + let mut remaining_items: i32 = count; let max_stack_size = item.max_stack as u8; let mut inventory = self.inventory.lock().await;