diff --git a/pumpkin-data/build/sound.rs b/pumpkin-data/build/sound.rs index 912516ab5..5024584b8 100644 --- a/pumpkin-data/build/sound.rs +++ b/pumpkin-data/build/sound.rs @@ -1,14 +1,27 @@ +use heck::ToPascalCase; use proc_macro2::TokenStream; use quote::quote; -use crate::array_to_tokenstream; +use crate::{array_to_tokenstream, ident}; pub(crate) fn build() -> TokenStream { println!("cargo:rerun-if-changed=../assets/sounds.json"); let sound: Vec = serde_json::from_str(include_str!("../../assets/sounds.json")) .expect("Failed to parse sounds.json"); - let variants = array_to_tokenstream(sound); + let variants = array_to_tokenstream(sound.clone()); + + let type_from_name = &sound + .iter() + .map(|sound| { + let id = &sound; + let name = ident(sound.to_pascal_case()); + + quote! { + #id => Some(Self::#name), + } + }) + .collect::(); quote! { #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] @@ -16,5 +29,14 @@ pub(crate) fn build() -> TokenStream { pub enum Sound { #variants } + + impl Sound { + pub fn from_name(name: &str) -> Option { + match name { + #type_from_name + _ => None + } + } + } } } diff --git a/pumpkin/src/command/args/arg_sound.rs b/pumpkin/src/command/args/arg_sound.rs new file mode 100644 index 000000000..83fa2377b --- /dev/null +++ b/pumpkin/src/command/args/arg_sound.rs @@ -0,0 +1,74 @@ +use async_trait::async_trait; +use pumpkin_data::sound::Sound; +use pumpkin_protocol::client::play::{ + CommandSuggestion, ProtoCmdArgParser, ProtoCmdArgSuggestionType, +}; + +use crate::{command::dispatcher::CommandError, server::Server}; + +use super::{ + super::{ + args::{ArgumentConsumer, RawArgs}, + CommandSender, + }, + Arg, DefaultNameArgConsumer, FindArg, GetClientSideArgParser, +}; + +pub(crate) struct SoundArgumentConsumer; + +impl GetClientSideArgParser for SoundArgumentConsumer { + fn get_client_side_parser(&self) -> ProtoCmdArgParser { + ProtoCmdArgParser::ResourceLocation + } + + fn get_client_side_suggestion_type_override(&self) -> Option { + Some(ProtoCmdArgSuggestionType::AvailableSounds) + } +} + +#[async_trait] +impl ArgumentConsumer for SoundArgumentConsumer { + async fn consume<'a>( + &'a self, + _sender: &CommandSender<'a>, + _server: &'a Server, + args: &mut RawArgs<'a>, + ) -> Option> { + let s = args.pop()?; + Some(Arg::Block(s)) + } + + async fn suggest<'a>( + &'a self, + _sender: &CommandSender<'a>, + _server: &'a Server, + _input: &'a str, + ) -> Result>, CommandError> { + Ok(None) + } +} + +impl DefaultNameArgConsumer for SoundArgumentConsumer { + fn default_name(&self) -> String { + "available_sounds".to_string() + } +} + +impl<'a> FindArg<'a> for SoundArgumentConsumer { + type Data = Sound; + + fn find_arg(args: &'a super::ConsumedArgs, name: &str) -> Result { + match args.get(name) { + Some(Arg::Block(name)) => Sound::from_name(&name.replace("minecraft:", "")) + .map_or_else( + || { + Err(CommandError::GeneralCommandIssue(format!( + "Sound {name} does not exist." + ))) + }, + Result::Ok, + ), + _ => Err(CommandError::InvalidConsumption(Some(name.to_string()))), + } + } +} diff --git a/pumpkin/src/command/args/mod.rs b/pumpkin/src/command/args/mod.rs index 4a0fedf4a..11356f1f3 100644 --- a/pumpkin/src/command/args/mod.rs +++ b/pumpkin/src/command/args/mod.rs @@ -37,6 +37,7 @@ pub(crate) mod arg_position_block; pub(crate) mod arg_resource_location; pub(crate) mod arg_rotation; pub(crate) mod arg_simple; +pub(crate) mod arg_sound; pub(crate) mod arg_textcomponent; mod coordinate; diff --git a/pumpkin/src/command/commands/cmd_deop.rs b/pumpkin/src/command/commands/cmd_deop.rs index cbeb7547d..b7af51bb1 100644 --- a/pumpkin/src/command/commands/cmd_deop.rs +++ b/pumpkin/src/command/commands/cmd_deop.rs @@ -13,7 +13,7 @@ use CommandError::InvalidConsumption; const NAMES: [&str; 1] = ["deop"]; const DESCRIPTION: &str = "Revokes operator status from a player."; -const ARG_TARGET: &str = "player"; +const ARG_TARGETS: &str = "targets"; struct DeopExecutor; @@ -27,38 +27,37 @@ impl CommandExecutor for DeopExecutor { ) -> Result<(), CommandError> { let mut config = OPERATOR_CONFIG.write().await; - let Some(Arg::Players(targets)) = args.get(&ARG_TARGET) else { - return Err(InvalidConsumption(Some(ARG_TARGET.into()))); + let Some(Arg::Players(targets)) = args.get(&ARG_TARGETS) else { + return Err(InvalidConsumption(Some(ARG_TARGETS.into()))); }; - // from the command tree, the command can only be executed with one player - let player = &targets[0]; + for player in targets { + if let Some(op_index) = config + .ops + .iter() + .position(|o| o.uuid == player.gameprofile.id) + { + config.ops.remove(op_index); + } + config.save(); - if let Some(op_index) = config - .ops - .iter() - .position(|o| o.uuid == player.gameprofile.id) - { - config.ops.remove(op_index); + player + .set_permission_lvl( + pumpkin_util::PermissionLvl::Zero, + &server.command_dispatcher, + ) + .await; + + let player_name = &player.gameprofile.name; + let msg = + TextComponent::translate("commands.deop.success", [player_name.clone().into()]); + sender.send_message(msg).await; } - config.save(); - - player - .set_permission_lvl( - pumpkin_util::PermissionLvl::Zero, - &server.command_dispatcher, - ) - .await; - - let player_name = &player.gameprofile.name; - let msg = TextComponent::translate("commands.deop.success", [player_name.clone().into()]); - sender.send_message(msg).await; - Ok(()) } } pub fn init_command_tree() -> CommandTree { CommandTree::new(NAMES, DESCRIPTION) - .with_child(argument(ARG_TARGET, PlayersArgumentConsumer).execute(DeopExecutor)) + .with_child(argument(ARG_TARGETS, PlayersArgumentConsumer).execute(DeopExecutor)) } diff --git a/pumpkin/src/command/commands/cmd_playsound.rs b/pumpkin/src/command/commands/cmd_playsound.rs new file mode 100644 index 000000000..1070436d3 --- /dev/null +++ b/pumpkin/src/command/commands/cmd_playsound.rs @@ -0,0 +1,49 @@ +use async_trait::async_trait; +use pumpkin_data::sound::SoundCategory; +use rand::{thread_rng, Rng}; + +use crate::command::{ + args::{arg_sound::SoundArgumentConsumer, ConsumedArgs, FindArg}, + tree::CommandTree, + tree_builder::argument, + CommandError, CommandExecutor, CommandSender, +}; +const NAMES: [&str; 1] = ["playsound"]; + +const DESCRIPTION: &str = "Plays a sound at a position."; + +const ARG_SOUND: &str = "sound"; + +struct SoundExecutor; + +#[async_trait] +impl CommandExecutor for SoundExecutor { + async fn execute<'a>( + &self, + sender: &mut CommandSender<'a>, + _server: &crate::server::Server, + args: &ConsumedArgs<'a>, + ) -> Result<(), CommandError> { + let sound = SoundArgumentConsumer::find_arg(args, ARG_SOUND)?; + + if let Some(player) = sender.as_player() { + let seed = thread_rng().gen::(); + player + .play_sound( + sound as u16, + SoundCategory::Master, + &player.living_entity.entity.pos.load(), + 1.0, + 1.0, + seed, + ) + .await; + } + Ok(()) + } +} + +pub fn init_command_tree() -> CommandTree { + CommandTree::new(NAMES, DESCRIPTION) + .with_child(argument(ARG_SOUND, SoundArgumentConsumer).execute(SoundExecutor)) +} diff --git a/pumpkin/src/command/commands/cmd_setblock.rs b/pumpkin/src/command/commands/cmd_setblock.rs index 8ead46754..2434149a3 100644 --- a/pumpkin/src/command/commands/cmd_setblock.rs +++ b/pumpkin/src/command/commands/cmd_setblock.rs @@ -13,7 +13,7 @@ const NAMES: [&str; 1] = ["setblock"]; const DESCRIPTION: &str = "Place a block."; const ARG_BLOCK: &str = "block"; -const ARG_BLOCK_POS: &str = "position"; +const ARG_BLOCK_POS: &str = "pos"; #[derive(Clone, Copy)] enum Mode { diff --git a/pumpkin/src/command/commands/mod.rs b/pumpkin/src/command/commands/mod.rs index f8b57a169..1a3e34f50 100644 --- a/pumpkin/src/command/commands/mod.rs +++ b/pumpkin/src/command/commands/mod.rs @@ -10,6 +10,7 @@ pub mod cmd_kill; pub mod cmd_list; pub mod cmd_me; pub mod cmd_op; +pub mod cmd_playsound; pub mod cmd_pumpkin; pub mod cmd_say; pub mod cmd_seed; diff --git a/pumpkin/src/command/mod.rs b/pumpkin/src/command/mod.rs index 1cfcf43a0..2527678ea 100644 --- a/pumpkin/src/command/mod.rs +++ b/pumpkin/src/command/mod.rs @@ -11,8 +11,8 @@ use args::ConsumedArgs; use async_trait::async_trait; use commands::{ cmd_clear, cmd_deop, cmd_fill, cmd_gamemode, cmd_give, cmd_help, cmd_kick, cmd_kill, cmd_list, - cmd_me, cmd_op, cmd_pumpkin, cmd_say, cmd_setblock, cmd_stop, cmd_teleport, cmd_time, - cmd_worldborder, + cmd_me, cmd_op, cmd_playsound, cmd_pumpkin, cmd_say, cmd_setblock, cmd_stop, cmd_teleport, + cmd_time, cmd_worldborder, }; use dispatcher::CommandError; use pumpkin_util::math::vector3::Vector3; @@ -133,6 +133,7 @@ pub fn default_dispatcher() -> CommandDispatcher { dispatcher.register(cmd_op::init_command_tree(), PermissionLvl::Three); dispatcher.register(cmd_deop::init_command_tree(), PermissionLvl::Three); dispatcher.register(cmd_me::init_command_tree(), PermissionLvl::Zero); + dispatcher.register(cmd_playsound::init_command_tree(), PermissionLvl::Two); dispatcher }