mirror of
https://github.com/Pumpkin-MC/Pumpkin.git
synced 2026-08-30 20:14:23 +00:00
Added Stop Command
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
use heck::ToPascalCase;
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use quote::{format_ident, quote};
|
||||
|
||||
use crate::array_to_tokenstream;
|
||||
|
||||
@@ -10,11 +11,48 @@ pub(crate) fn build() -> TokenStream {
|
||||
serde_json::from_str(include_str!("../../assets/sound_category.json"))
|
||||
.expect("Failed to parse sound_category.json");
|
||||
let variants = array_to_tokenstream(&sound_categories);
|
||||
let type_from_name = &sound_categories
|
||||
.iter()
|
||||
.map(|sound| {
|
||||
let id = &sound.to_lowercase();
|
||||
let name = format_ident!("{}", sound.to_pascal_case());
|
||||
|
||||
quote! {
|
||||
#id => Some(Self::#name),
|
||||
}
|
||||
})
|
||||
.collect::<TokenStream>();
|
||||
|
||||
let type_to_name = &sound_categories
|
||||
.iter()
|
||||
.map(|sound| {
|
||||
let id = &sound.to_lowercase();
|
||||
let name = format_ident!("{}", sound.to_pascal_case());
|
||||
|
||||
quote! {
|
||||
Self::#name => #id,
|
||||
}
|
||||
})
|
||||
.collect::<TokenStream>();
|
||||
quote! {
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum SoundCategory {
|
||||
#variants
|
||||
}
|
||||
|
||||
impl SoundCategory {
|
||||
pub fn from_name(name: &str) -> Option<Self> {
|
||||
match name {
|
||||
#type_from_name
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn to_name(&self) -> &'static str {
|
||||
match self {
|
||||
#type_to_name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,6 +62,7 @@ mod set_time;
|
||||
mod set_title;
|
||||
mod sound_effect;
|
||||
mod spawn_entity;
|
||||
mod stop_sound;
|
||||
mod store_cookie;
|
||||
mod subtitle;
|
||||
mod system_chat_message;
|
||||
@@ -141,6 +142,7 @@ pub use set_time::*;
|
||||
pub use set_title::*;
|
||||
pub use sound_effect::*;
|
||||
pub use spawn_entity::*;
|
||||
pub use stop_sound::*;
|
||||
pub use store_cookie::*;
|
||||
pub use subtitle::*;
|
||||
pub use system_chat_message::*;
|
||||
|
||||
45
pumpkin-protocol/src/client/play/stop_sound.rs
Normal file
45
pumpkin-protocol/src/client/play/stop_sound.rs
Normal file
@@ -0,0 +1,45 @@
|
||||
use crate::bytebuf::ByteBufMut;
|
||||
use crate::codec::var_int::VarInt;
|
||||
use crate::{ClientPacket, codec::identifier::Identifier};
|
||||
use pumpkin_data::{packet::clientbound::PLAY_STOP_SOUND, sound::SoundCategory};
|
||||
use pumpkin_macros::packet;
|
||||
|
||||
#[packet(PLAY_STOP_SOUND)]
|
||||
pub struct CStopSound {
|
||||
sound_id: Option<Identifier>,
|
||||
category: Option<SoundCategory>,
|
||||
}
|
||||
|
||||
impl CStopSound {
|
||||
pub fn new(sound_id: Option<Identifier>, category: Option<SoundCategory>) -> Self {
|
||||
Self { sound_id, category }
|
||||
}
|
||||
}
|
||||
|
||||
impl ClientPacket for CStopSound {
|
||||
fn write(&self, bytebuf: &mut impl bytes::BufMut) {
|
||||
const NO_CATEGORY_NO_SOUND: u8 = 0;
|
||||
const CATEGORY_ONLY: u8 = 1;
|
||||
const SOUND_ONLY: u8 = 2;
|
||||
const CATEGORY_AND_SOUND: u8 = 3;
|
||||
|
||||
match (self.category, &self.sound_id) {
|
||||
(Some(category), Some(sound_id)) => {
|
||||
bytebuf.put_u8(CATEGORY_AND_SOUND);
|
||||
bytebuf.put_var_int(&VarInt(category as i32));
|
||||
bytebuf.put_identifier(sound_id);
|
||||
}
|
||||
(Some(category), None) => {
|
||||
bytebuf.put_u8(CATEGORY_ONLY);
|
||||
bytebuf.put_var_int(&VarInt(category as i32));
|
||||
}
|
||||
(None, Some(sound_id)) => {
|
||||
bytebuf.put_u8(SOUND_ONLY);
|
||||
bytebuf.put_identifier(sound_id);
|
||||
}
|
||||
(None, None) => {
|
||||
bytebuf.put_u8(NO_CATEGORY_NO_SOUND);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -39,14 +39,15 @@ impl ArgumentConsumer for SoundCategoryArgumentConsumer {
|
||||
let category = match s.to_lowercase().as_str() {
|
||||
"master" => Some(SoundCategory::Master), // Default category, affects all sounds
|
||||
"music" => Some(SoundCategory::Music), // Background music
|
||||
// i don't use SoundCategory::from_name because its is record and not records :c
|
||||
"record" => Some(SoundCategory::Records), // Music discs
|
||||
"weather" => Some(SoundCategory::Weather), // Rain, thunder
|
||||
"block" => Some(SoundCategory::Blocks), // Block sounds
|
||||
"block" => Some(SoundCategory::Blocks), // Block sounds
|
||||
"hostile" => Some(SoundCategory::Hostile), // Hostile mob sounds
|
||||
"neutral" => Some(SoundCategory::Neutral), // Neutral mob sounds
|
||||
"player" => Some(SoundCategory::Players), // Player sounds
|
||||
"ambient" => Some(SoundCategory::Ambient), // Ambient environment
|
||||
"voice" => Some(SoundCategory::Voice), // Voice/speech
|
||||
"voice" => Some(SoundCategory::Voice), // Voice/speech
|
||||
_ => None,
|
||||
};
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ mod say;
|
||||
mod seed;
|
||||
mod setblock;
|
||||
mod stop;
|
||||
mod stopsound;
|
||||
mod summon;
|
||||
mod teleport;
|
||||
mod time;
|
||||
@@ -73,6 +74,7 @@ pub fn default_dispatcher() -> CommandDispatcher {
|
||||
dispatcher.register(bossbar::init_command_tree(), PermissionLvl::Two);
|
||||
dispatcher.register(say::init_command_tree(), PermissionLvl::Two);
|
||||
dispatcher.register(gamemode::init_command_tree(), PermissionLvl::Two);
|
||||
dispatcher.register(stopsound::init_command_tree(), PermissionLvl::Two);
|
||||
dispatcher.register(defaultgamemode::init_command_tree(), PermissionLvl::Two);
|
||||
// Three
|
||||
dispatcher.register(op::init_command_tree(), PermissionLvl::Three);
|
||||
|
||||
84
pumpkin/src/command/commands/stopsound.rs
Normal file
84
pumpkin/src/command/commands/stopsound.rs
Normal file
@@ -0,0 +1,84 @@
|
||||
use crate::command::{
|
||||
CommandExecutor, CommandSender,
|
||||
args::{
|
||||
ConsumedArgs, FindArg, players::PlayersArgumentConsumer, sound::SoundArgumentConsumer,
|
||||
sound_category::SoundCategoryArgumentConsumer,
|
||||
},
|
||||
dispatcher::CommandError,
|
||||
tree::{CommandTree, builder::argument},
|
||||
};
|
||||
use async_trait::async_trait;
|
||||
use pumpkin_protocol::codec::identifier::Identifier;
|
||||
use pumpkin_util::text::TextComponent;
|
||||
|
||||
const NAMES: [&str; 1] = ["stopsound"];
|
||||
const DESCRIPTION: &str = "Stops a currently playing sound.";
|
||||
|
||||
const ARG_TARGETS: &str = "targets";
|
||||
const ARG_SOURCE: &str = "source";
|
||||
const ARG_SOUND: &str = "sound";
|
||||
|
||||
pub struct Executor;
|
||||
|
||||
#[async_trait]
|
||||
impl CommandExecutor for Executor {
|
||||
async fn execute<'a>(
|
||||
&self,
|
||||
sender: &mut CommandSender<'a>,
|
||||
_server: &crate::server::Server,
|
||||
args: &ConsumedArgs<'a>,
|
||||
) -> Result<(), CommandError> {
|
||||
let targets = PlayersArgumentConsumer::find_arg(args, ARG_TARGETS)?;
|
||||
|
||||
let mut category = SoundCategoryArgumentConsumer::find_arg(args, ARG_SOURCE);
|
||||
let mut sound = SoundArgumentConsumer::find_arg(args, ARG_SOUND);
|
||||
|
||||
for target in targets {
|
||||
target
|
||||
.stop_sound(
|
||||
sound
|
||||
.as_mut()
|
||||
.cloned()
|
||||
.map(|s| Identifier::vanilla(s.to_name()))
|
||||
.ok(),
|
||||
category.as_mut().map(|s| **s).ok(),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
let text = match (category, sound) {
|
||||
(Ok(c), Ok(s)) => TextComponent::translate(
|
||||
"commands.stopsound.success.source.sound",
|
||||
[
|
||||
TextComponent::text(s.to_name()),
|
||||
TextComponent::text(c.to_name()),
|
||||
],
|
||||
),
|
||||
(Ok(c), Err(_)) => TextComponent::translate(
|
||||
"commands.stopsound.success.source.any",
|
||||
[TextComponent::text(c.to_name())],
|
||||
),
|
||||
(Err(_), Ok(s)) => TextComponent::translate(
|
||||
"commands.stopsound.success.sourceless.sound",
|
||||
[TextComponent::text(s.to_name())],
|
||||
),
|
||||
(Err(_), Err(_)) => {
|
||||
TextComponent::translate("commands.stopsound.success.sourceless.any", [])
|
||||
}
|
||||
};
|
||||
sender.send_message(text).await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init_command_tree() -> CommandTree {
|
||||
CommandTree::new(NAMES, DESCRIPTION).then(
|
||||
argument(ARG_TARGETS, PlayersArgumentConsumer)
|
||||
.execute(Executor)
|
||||
.then(
|
||||
argument(ARG_SOURCE, SoundCategoryArgumentConsumer)
|
||||
.execute(Executor)
|
||||
.then(argument(ARG_SOUND, SoundArgumentConsumer).execute(Executor)),
|
||||
),
|
||||
)
|
||||
}
|
||||
@@ -26,9 +26,11 @@ use pumpkin_protocol::{
|
||||
client::play::{
|
||||
CAcknowledgeBlockChange, CActionBar, CCombatDeath, CDisguisedChatMessage, CGameEvent,
|
||||
CKeepAlive, CParticle, CPlayDisconnect, CPlayerAbilities, CPlayerInfoUpdate,
|
||||
CPlayerPosition, CRespawn, CSetExperience, CSetHealth, CSubtitle, CSystemChatMessage,
|
||||
CTitleText, CUnloadChunk, CUpdateMobEffect, GameEvent, MetaDataType, PlayerAction,
|
||||
CPlayerPosition, CRespawn, CSetExperience, CSetHealth, CStopSound, CSubtitle,
|
||||
CSystemChatMessage, CTitleText, CUnloadChunk, CUpdateMobEffect, GameEvent, MetaDataType,
|
||||
PlayerAction,
|
||||
},
|
||||
codec::identifier::Identifier,
|
||||
server::play::{
|
||||
SChatCommand, SChatMessage, SClientCommand, SClientInformationPlay, SClientTickEnd,
|
||||
SCommandSuggestion, SConfirmTeleport, SInteract, SPickItemFromBlock, SPlayerAbilities,
|
||||
@@ -416,6 +418,18 @@ impl Player {
|
||||
.await;
|
||||
}
|
||||
|
||||
/// Stops a sound playing on the client.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `sound_id`: An optional `Identifier` specifying the sound to stop. If `None`, all sounds in the specified category (if any) will be stopped.
|
||||
/// * `category`: An optional `SoundCategory` specifying the sound category to stop. If `None`, all sounds with the specified identifier (if any) will be stopped.
|
||||
pub async fn stop_sound(&self, sound_id: Option<Identifier>, category: Option<SoundCategory>) {
|
||||
self.client
|
||||
.send_packet(&CStopSound::new(sound_id, category))
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn await_cancel(&self) {
|
||||
self.cancel_tasks.notified().await;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user