add play sound command

Its simple and there are many arguments missing but its works
This commit is contained in:
Alexander Medvedev 2025-01-23 23:40:20 +01:00
parent daf3b29bb2
commit 6c78567a83
8 changed files with 177 additions and 30 deletions

View File

@ -1,14 +1,27 @@
use heck::ToPascalCase;
use proc_macro2::TokenStream; use proc_macro2::TokenStream;
use quote::quote; use quote::quote;
use crate::array_to_tokenstream; use crate::{array_to_tokenstream, ident};
pub(crate) fn build() -> TokenStream { pub(crate) fn build() -> TokenStream {
println!("cargo:rerun-if-changed=../assets/sounds.json"); println!("cargo:rerun-if-changed=../assets/sounds.json");
let sound: Vec<String> = serde_json::from_str(include_str!("../../assets/sounds.json")) let sound: Vec<String> = serde_json::from_str(include_str!("../../assets/sounds.json"))
.expect("Failed to parse 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::<TokenStream>();
quote! { quote! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
@ -16,5 +29,14 @@ pub(crate) fn build() -> TokenStream {
pub enum Sound { pub enum Sound {
#variants #variants
} }
impl Sound {
pub fn from_name(name: &str) -> Option<Self> {
match name {
#type_from_name
_ => None
}
}
}
} }
} }

View File

@ -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<ProtoCmdArgSuggestionType> {
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<Arg<'a>> {
let s = args.pop()?;
Some(Arg::Block(s))
}
async fn suggest<'a>(
&'a self,
_sender: &CommandSender<'a>,
_server: &'a Server,
_input: &'a str,
) -> Result<Option<Vec<CommandSuggestion>>, 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<Self::Data, CommandError> {
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()))),
}
}
}

View File

@ -37,6 +37,7 @@ pub(crate) mod arg_position_block;
pub(crate) mod arg_resource_location; pub(crate) mod arg_resource_location;
pub(crate) mod arg_rotation; pub(crate) mod arg_rotation;
pub(crate) mod arg_simple; pub(crate) mod arg_simple;
pub(crate) mod arg_sound;
pub(crate) mod arg_textcomponent; pub(crate) mod arg_textcomponent;
mod coordinate; mod coordinate;

View File

@ -13,7 +13,7 @@ use CommandError::InvalidConsumption;
const NAMES: [&str; 1] = ["deop"]; const NAMES: [&str; 1] = ["deop"];
const DESCRIPTION: &str = "Revokes operator status from a player."; const DESCRIPTION: &str = "Revokes operator status from a player.";
const ARG_TARGET: &str = "player"; const ARG_TARGETS: &str = "targets";
struct DeopExecutor; struct DeopExecutor;
@ -27,13 +27,11 @@ impl CommandExecutor for DeopExecutor {
) -> Result<(), CommandError> { ) -> Result<(), CommandError> {
let mut config = OPERATOR_CONFIG.write().await; let mut config = OPERATOR_CONFIG.write().await;
let Some(Arg::Players(targets)) = args.get(&ARG_TARGET) else { let Some(Arg::Players(targets)) = args.get(&ARG_TARGETS) else {
return Err(InvalidConsumption(Some(ARG_TARGET.into()))); return Err(InvalidConsumption(Some(ARG_TARGETS.into())));
}; };
// from the command tree, the command can only be executed with one player for player in targets {
let player = &targets[0];
if let Some(op_index) = config if let Some(op_index) = config
.ops .ops
.iter() .iter()
@ -51,14 +49,15 @@ impl CommandExecutor for DeopExecutor {
.await; .await;
let player_name = &player.gameprofile.name; let player_name = &player.gameprofile.name;
let msg = TextComponent::translate("commands.deop.success", [player_name.clone().into()]); let msg =
TextComponent::translate("commands.deop.success", [player_name.clone().into()]);
sender.send_message(msg).await; sender.send_message(msg).await;
}
Ok(()) Ok(())
} }
} }
pub fn init_command_tree() -> CommandTree { pub fn init_command_tree() -> CommandTree {
CommandTree::new(NAMES, DESCRIPTION) CommandTree::new(NAMES, DESCRIPTION)
.with_child(argument(ARG_TARGET, PlayersArgumentConsumer).execute(DeopExecutor)) .with_child(argument(ARG_TARGETS, PlayersArgumentConsumer).execute(DeopExecutor))
} }

View File

@ -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::<f64>();
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))
}

View File

@ -13,7 +13,7 @@ const NAMES: [&str; 1] = ["setblock"];
const DESCRIPTION: &str = "Place a block."; const DESCRIPTION: &str = "Place a block.";
const ARG_BLOCK: &str = "block"; const ARG_BLOCK: &str = "block";
const ARG_BLOCK_POS: &str = "position"; const ARG_BLOCK_POS: &str = "pos";
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
enum Mode { enum Mode {

View File

@ -10,6 +10,7 @@ pub mod cmd_kill;
pub mod cmd_list; pub mod cmd_list;
pub mod cmd_me; pub mod cmd_me;
pub mod cmd_op; pub mod cmd_op;
pub mod cmd_playsound;
pub mod cmd_pumpkin; pub mod cmd_pumpkin;
pub mod cmd_say; pub mod cmd_say;
pub mod cmd_seed; pub mod cmd_seed;

View File

@ -11,8 +11,8 @@ use args::ConsumedArgs;
use async_trait::async_trait; use async_trait::async_trait;
use commands::{ use commands::{
cmd_clear, cmd_deop, cmd_fill, cmd_gamemode, cmd_give, cmd_help, cmd_kick, cmd_kill, cmd_list, 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_me, cmd_op, cmd_playsound, cmd_pumpkin, cmd_say, cmd_setblock, cmd_stop, cmd_teleport,
cmd_worldborder, cmd_time, cmd_worldborder,
}; };
use dispatcher::CommandError; use dispatcher::CommandError;
use pumpkin_util::math::vector3::Vector3; 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_op::init_command_tree(), PermissionLvl::Three);
dispatcher.register(cmd_deop::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_me::init_command_tree(), PermissionLvl::Zero);
dispatcher.register(cmd_playsound::init_command_tree(), PermissionLvl::Two);
dispatcher dispatcher
} }