mirror of
https://github.com/italicsjenga/valence.git
synced 2024-12-23 14:31:30 +11:00
Add sounds (#244)
This is my first time contributing here so I was pretty unfamiliar with the codebase and may have done some things incorrectly. ## Description - Added a sound extractor to extract sound event ids and identifiers - Added a `Sound` enum (with a build script) to represent sound effects - Added a `play_sound` method to `Instance` and `Client` - Re-implemented sound effects in the parkour example ## Test Plan I tested this using the sounds I added to the parkour example. #### Related Hopefully fixes #206
This commit is contained in:
parent
bee44d32b5
commit
50018a52bf
|
@ -7,6 +7,8 @@ use valence::client::despawn_disconnected_clients;
|
||||||
use valence::client::event::default_event_handler;
|
use valence::client::event::default_event_handler;
|
||||||
use valence::prelude::*;
|
use valence::prelude::*;
|
||||||
use valence_protocol::packets::s2c::play::SetTitleAnimationTimes;
|
use valence_protocol::packets::s2c::play::SetTitleAnimationTimes;
|
||||||
|
use valence_protocol::types::SoundCategory;
|
||||||
|
use valence_protocol::Sound;
|
||||||
|
|
||||||
const START_POS: BlockPos = BlockPos::new(0, 100, 0);
|
const START_POS: BlockPos = BlockPos::new(0, 100, 0);
|
||||||
const VIEW_DIST: u8 = 10;
|
const VIEW_DIST: u8 = 10;
|
||||||
|
@ -130,20 +132,19 @@ fn manage_blocks(mut clients: Query<(&mut Client, &mut GameState, &mut Instance)
|
||||||
state.combo = 0
|
state.combo = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// let pitch = 0.9 + ((state.combo as f32) - 1.0) * 0.05;
|
|
||||||
|
|
||||||
for _ in 0..index {
|
for _ in 0..index {
|
||||||
generate_next_block(&mut state, &mut instance, true)
|
generate_next_block(&mut state, &mut instance, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: add sounds again.
|
let pitch = 0.9 + ((state.combo as f32) - 1.0) * 0.05;
|
||||||
// client.play_sound(
|
let pos = client.position();
|
||||||
// Ident::new("minecraft:block.note_block.bass").unwrap(),
|
client.play_sound(
|
||||||
// SoundCategory::Master,
|
Sound::BlockNoteBlockBass,
|
||||||
// client.position(),
|
SoundCategory::Master,
|
||||||
// 1f32,
|
pos,
|
||||||
// pitch,
|
1.0,
|
||||||
// );
|
pitch,
|
||||||
|
);
|
||||||
|
|
||||||
client.set_title(
|
client.set_title(
|
||||||
"",
|
"",
|
||||||
|
|
|
@ -15,13 +15,13 @@ use valence_protocol::packets::s2c::play::{
|
||||||
LoginPlay, ParticleS2c, PluginMessageS2c, RemoveEntitiesEncode, ResourcePackS2c, Respawn,
|
LoginPlay, ParticleS2c, PluginMessageS2c, RemoveEntitiesEncode, ResourcePackS2c, Respawn,
|
||||||
SetActionBarText, SetCenterChunk, SetDefaultSpawnPosition, SetEntityMetadata,
|
SetActionBarText, SetCenterChunk, SetDefaultSpawnPosition, SetEntityMetadata,
|
||||||
SetEntityVelocity, SetRenderDistance, SetSubtitleText, SetTitleAnimationTimes, SetTitleText,
|
SetEntityVelocity, SetRenderDistance, SetSubtitleText, SetTitleAnimationTimes, SetTitleText,
|
||||||
SynchronizePlayerPosition, SystemChatMessage, UnloadChunk,
|
SoundEffect, SynchronizePlayerPosition, SystemChatMessage, UnloadChunk,
|
||||||
};
|
};
|
||||||
use valence_protocol::types::{
|
use valence_protocol::types::{
|
||||||
GameEventKind, GameMode, GlobalPos, Property, SyncPlayerPosLookFlags,
|
GameEventKind, GameMode, GlobalPos, Property, SoundCategory, SyncPlayerPosLookFlags,
|
||||||
};
|
};
|
||||||
use valence_protocol::{
|
use valence_protocol::{
|
||||||
BlockPos, EncodePacket, Ident, ItemStack, PacketDecoder, PacketEncoder, RawBytes, Text,
|
BlockPos, EncodePacket, Ident, ItemStack, PacketDecoder, PacketEncoder, RawBytes, Sound, Text,
|
||||||
Username, VarInt,
|
Username, VarInt,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -571,6 +571,32 @@ impl Client {
|
||||||
count,
|
count,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Plays a sound effect at the given position, only for this client.
|
||||||
|
///
|
||||||
|
/// If you want to play a sound effect to all players, use
|
||||||
|
/// [`Instance::play_sound`]
|
||||||
|
///
|
||||||
|
/// [`Instance::play_sound`]: crate::instance::Instance::play_sound
|
||||||
|
pub fn play_sound(
|
||||||
|
&mut self,
|
||||||
|
sound: Sound,
|
||||||
|
category: SoundCategory,
|
||||||
|
position: impl Into<DVec3>,
|
||||||
|
volume: f32,
|
||||||
|
pitch: f32,
|
||||||
|
) {
|
||||||
|
let position = position.into();
|
||||||
|
|
||||||
|
self.write_packet(&SoundEffect {
|
||||||
|
id: sound.to_id(),
|
||||||
|
category,
|
||||||
|
position: (position * 8.0).as_ivec3().into(),
|
||||||
|
volume,
|
||||||
|
pitch,
|
||||||
|
seed: rand::random(),
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WritePacket for Client {
|
impl WritePacket for Client {
|
||||||
|
|
|
@ -9,8 +9,9 @@ use num::integer::div_ceil;
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
use valence_protocol::block::BlockState;
|
use valence_protocol::block::BlockState;
|
||||||
use valence_protocol::packets::s2c::particle::{Particle, ParticleS2c};
|
use valence_protocol::packets::s2c::particle::{Particle, ParticleS2c};
|
||||||
use valence_protocol::packets::s2c::play::SetActionBarText;
|
use valence_protocol::packets::s2c::play::{SetActionBarText, SoundEffect};
|
||||||
use valence_protocol::{BlockPos, EncodePacket, LengthPrefixedArray, Text};
|
use valence_protocol::types::SoundCategory;
|
||||||
|
use valence_protocol::{BlockPos, EncodePacket, LengthPrefixedArray, Sound, Text};
|
||||||
|
|
||||||
use crate::dimension::DimensionId;
|
use crate::dimension::DimensionId;
|
||||||
use crate::entity::McEntity;
|
use crate::entity::McEntity;
|
||||||
|
@ -363,6 +364,32 @@ impl Instance {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Plays a sound effect at the given position in the world. The sound
|
||||||
|
/// effect is audible to all players in the instance with the
|
||||||
|
/// appropriate chunk in view.
|
||||||
|
pub fn play_sound(
|
||||||
|
&mut self,
|
||||||
|
sound: Sound,
|
||||||
|
category: SoundCategory,
|
||||||
|
position: impl Into<DVec3>,
|
||||||
|
volume: f32,
|
||||||
|
pitch: f32,
|
||||||
|
) {
|
||||||
|
let position = position.into();
|
||||||
|
|
||||||
|
self.write_packet_at(
|
||||||
|
&SoundEffect {
|
||||||
|
id: sound.to_id(),
|
||||||
|
category,
|
||||||
|
position: (position * 8.0).as_ivec3().into(),
|
||||||
|
volume,
|
||||||
|
pitch,
|
||||||
|
seed: rand::random(),
|
||||||
|
},
|
||||||
|
ChunkPos::from_dvec3(position),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// Sets the action bar text of all players in the instance.
|
/// Sets the action bar text of all players in the instance.
|
||||||
pub fn set_action_bar(&mut self, text: impl Into<Text>) {
|
pub fn set_action_bar(&mut self, text: impl Into<Text>) {
|
||||||
self.write_packet(&SetActionBarText {
|
self.write_packet(&SetActionBarText {
|
||||||
|
|
|
@ -8,6 +8,7 @@ use proc_macro2::{Ident, Span};
|
||||||
mod block;
|
mod block;
|
||||||
mod enchant;
|
mod enchant;
|
||||||
mod item;
|
mod item;
|
||||||
|
mod sound;
|
||||||
mod translation_key;
|
mod translation_key;
|
||||||
|
|
||||||
pub fn main() -> anyhow::Result<()> {
|
pub fn main() -> anyhow::Result<()> {
|
||||||
|
@ -17,6 +18,7 @@ pub fn main() -> anyhow::Result<()> {
|
||||||
(block::build as fn() -> _, "block.rs"),
|
(block::build as fn() -> _, "block.rs"),
|
||||||
(enchant::build, "enchant.rs"),
|
(enchant::build, "enchant.rs"),
|
||||||
(item::build, "item.rs"),
|
(item::build, "item.rs"),
|
||||||
|
(sound::build, "sound.rs"),
|
||||||
(translation_key::build, "translation_key.rs"),
|
(translation_key::build, "translation_key.rs"),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
119
crates/valence_protocol/build/sound.rs
Normal file
119
crates/valence_protocol/build/sound.rs
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
use heck::ToPascalCase;
|
||||||
|
use proc_macro2::TokenStream;
|
||||||
|
use quote::quote;
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
use crate::ident;
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct Sound {
|
||||||
|
id: u16,
|
||||||
|
name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build() -> anyhow::Result<TokenStream> {
|
||||||
|
let sounds =
|
||||||
|
serde_json::from_str::<Vec<Sound>>(include_str!("../../../extracted/sounds.json"))?;
|
||||||
|
|
||||||
|
let sound_count = sounds.len();
|
||||||
|
|
||||||
|
let sound_from_raw_id_arms = sounds
|
||||||
|
.iter()
|
||||||
|
.map(|sound| {
|
||||||
|
let id = &sound.id;
|
||||||
|
let name = ident(sound.name.to_pascal_case());
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
#id => Some(Self::#name),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<TokenStream>();
|
||||||
|
|
||||||
|
let sound_to_raw_id_arms = sounds
|
||||||
|
.iter()
|
||||||
|
.map(|sound| {
|
||||||
|
let id = &sound.id;
|
||||||
|
let name = ident(sound.name.to_pascal_case());
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
Self::#name => #id,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<TokenStream>();
|
||||||
|
|
||||||
|
let sound_from_str_arms = sounds
|
||||||
|
.iter()
|
||||||
|
.map(|sound| {
|
||||||
|
let str_name = &sound.name;
|
||||||
|
let name = ident(str_name.to_pascal_case());
|
||||||
|
quote! {
|
||||||
|
#str_name => Some(Self::#name),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<TokenStream>();
|
||||||
|
|
||||||
|
let sound_to_str_arms = sounds
|
||||||
|
.iter()
|
||||||
|
.map(|sound| {
|
||||||
|
let str_name = &sound.name;
|
||||||
|
let name = ident(str_name.to_pascal_case());
|
||||||
|
quote! {
|
||||||
|
Self::#name => #str_name,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<TokenStream>();
|
||||||
|
|
||||||
|
let sound_variants = sounds
|
||||||
|
.iter()
|
||||||
|
.map(|sound| ident(sound.name.to_pascal_case()))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
Ok(quote! {
|
||||||
|
/// Represents a sound from the game
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
||||||
|
#[repr(u16)]
|
||||||
|
pub enum Sound {
|
||||||
|
#(#sound_variants,)*
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sound {
|
||||||
|
/// Constructs a sound from a raw item ID.
|
||||||
|
///
|
||||||
|
/// If the given ID is invalid, `None` is returned.
|
||||||
|
pub const fn from_raw(id: u16) -> Option<Self> {
|
||||||
|
match id {
|
||||||
|
#sound_from_raw_id_arms
|
||||||
|
_ => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the raw sound ID from the sound
|
||||||
|
pub const fn to_raw(self) -> u16 {
|
||||||
|
match self {
|
||||||
|
#sound_to_raw_id_arms
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Construct a sound from its snake_case name.
|
||||||
|
///
|
||||||
|
/// Returns `None` if the name is invalid.
|
||||||
|
#[allow(clippy::should_implement_trait)]
|
||||||
|
pub fn from_str(name: &str) -> Option<Self> {
|
||||||
|
match name {
|
||||||
|
#sound_from_str_arms
|
||||||
|
_ => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the snake_case name of this sound.
|
||||||
|
pub const fn to_str(self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
#sound_to_str_arms
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An array of all sounds.
|
||||||
|
pub const ALL: [Self; #sound_count] = [#(Self::#sound_variants,)*];
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
|
@ -80,6 +80,7 @@ pub use codec::*;
|
||||||
pub use ident::Ident;
|
pub use ident::Ident;
|
||||||
pub use item::{ItemKind, ItemStack};
|
pub use item::{ItemKind, ItemStack};
|
||||||
pub use raw_bytes::RawBytes;
|
pub use raw_bytes::RawBytes;
|
||||||
|
pub use sound::Sound;
|
||||||
pub use text::{Text, TextFormat};
|
pub use text::{Text, TextFormat};
|
||||||
pub use username::Username;
|
pub use username::Username;
|
||||||
pub use uuid::Uuid;
|
pub use uuid::Uuid;
|
||||||
|
@ -108,6 +109,7 @@ mod impls;
|
||||||
mod item;
|
mod item;
|
||||||
pub mod packets;
|
pub mod packets;
|
||||||
mod raw_bytes;
|
mod raw_bytes;
|
||||||
|
pub mod sound;
|
||||||
pub mod text;
|
pub mod text;
|
||||||
pub mod translation_key;
|
pub mod translation_key;
|
||||||
pub mod types;
|
pub mod types;
|
||||||
|
|
32
crates/valence_protocol/src/sound.rs
Normal file
32
crates/valence_protocol/src/sound.rs
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
// sound.rs exposes constant values provided by the build script.
|
||||||
|
// All sounds are located in `Sound`. You can use the
|
||||||
|
// associated const fn functions of `Sound` to access details about a sound.
|
||||||
|
include!(concat!(env!("OUT_DIR"), "/sound.rs"));
|
||||||
|
|
||||||
|
use crate::packets::s2c::play::SoundId;
|
||||||
|
use crate::Ident;
|
||||||
|
|
||||||
|
impl Sound {
|
||||||
|
pub fn to_id(self) -> SoundId<'static> {
|
||||||
|
SoundId::Direct {
|
||||||
|
id: Ident::new(self.to_str()).unwrap(),
|
||||||
|
range: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sound_to_soundid() {
|
||||||
|
assert_eq!(
|
||||||
|
Sound::BlockBellUse.to_id(),
|
||||||
|
SoundId::Direct {
|
||||||
|
id: Ident::new("block.bell.use").unwrap(),
|
||||||
|
range: None
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
5570
extracted/sounds.json
Normal file
5570
extracted/sounds.json
Normal file
File diff suppressed because it is too large
Load diff
|
@ -44,6 +44,7 @@ public class Main implements ModInitializer {
|
||||||
new EntityData(),
|
new EntityData(),
|
||||||
new Items(),
|
new Items(),
|
||||||
new Packets(),
|
new Packets(),
|
||||||
|
new Sounds(),
|
||||||
new TranslationKeys(),
|
new TranslationKeys(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
package rs.valence.extractor.extractors;
|
||||||
|
|
||||||
|
import com.google.gson.JsonArray;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import net.minecraft.registry.Registries;
|
||||||
|
import rs.valence.extractor.Main;
|
||||||
|
|
||||||
|
public class Sounds implements Main.Extractor {
|
||||||
|
public Sounds() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String fileName() {
|
||||||
|
return "sounds.json";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JsonElement extract() throws Exception {
|
||||||
|
var itemsJson = new JsonArray();
|
||||||
|
|
||||||
|
for (var sound : Registries.SOUND_EVENT) {
|
||||||
|
var itemJson = new JsonObject();
|
||||||
|
itemJson.addProperty("id", Registries.SOUND_EVENT.getRawId(sound));
|
||||||
|
itemJson.addProperty("name", Registries.SOUND_EVENT.getId(sound).getPath());
|
||||||
|
itemsJson.add(itemJson);
|
||||||
|
}
|
||||||
|
|
||||||
|
return itemsJson;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue