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::prelude::*;
|
||||
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 VIEW_DIST: u8 = 10;
|
||||
|
@ -130,20 +132,19 @@ fn manage_blocks(mut clients: Query<(&mut Client, &mut GameState, &mut Instance)
|
|||
state.combo = 0
|
||||
}
|
||||
|
||||
// let pitch = 0.9 + ((state.combo as f32) - 1.0) * 0.05;
|
||||
|
||||
for _ in 0..index {
|
||||
generate_next_block(&mut state, &mut instance, true)
|
||||
}
|
||||
|
||||
// TODO: add sounds again.
|
||||
// client.play_sound(
|
||||
// Ident::new("minecraft:block.note_block.bass").unwrap(),
|
||||
// SoundCategory::Master,
|
||||
// client.position(),
|
||||
// 1f32,
|
||||
// pitch,
|
||||
// );
|
||||
let pitch = 0.9 + ((state.combo as f32) - 1.0) * 0.05;
|
||||
let pos = client.position();
|
||||
client.play_sound(
|
||||
Sound::BlockNoteBlockBass,
|
||||
SoundCategory::Master,
|
||||
pos,
|
||||
1.0,
|
||||
pitch,
|
||||
);
|
||||
|
||||
client.set_title(
|
||||
"",
|
||||
|
|
|
@ -15,13 +15,13 @@ use valence_protocol::packets::s2c::play::{
|
|||
LoginPlay, ParticleS2c, PluginMessageS2c, RemoveEntitiesEncode, ResourcePackS2c, Respawn,
|
||||
SetActionBarText, SetCenterChunk, SetDefaultSpawnPosition, SetEntityMetadata,
|
||||
SetEntityVelocity, SetRenderDistance, SetSubtitleText, SetTitleAnimationTimes, SetTitleText,
|
||||
SynchronizePlayerPosition, SystemChatMessage, UnloadChunk,
|
||||
SoundEffect, SynchronizePlayerPosition, SystemChatMessage, UnloadChunk,
|
||||
};
|
||||
use valence_protocol::types::{
|
||||
GameEventKind, GameMode, GlobalPos, Property, SyncPlayerPosLookFlags,
|
||||
GameEventKind, GameMode, GlobalPos, Property, SoundCategory, SyncPlayerPosLookFlags,
|
||||
};
|
||||
use valence_protocol::{
|
||||
BlockPos, EncodePacket, Ident, ItemStack, PacketDecoder, PacketEncoder, RawBytes, Text,
|
||||
BlockPos, EncodePacket, Ident, ItemStack, PacketDecoder, PacketEncoder, RawBytes, Sound, Text,
|
||||
Username, VarInt,
|
||||
};
|
||||
|
||||
|
@ -571,6 +571,32 @@ impl Client {
|
|||
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 {
|
||||
|
|
|
@ -9,8 +9,9 @@ use num::integer::div_ceil;
|
|||
use rustc_hash::FxHashMap;
|
||||
use valence_protocol::block::BlockState;
|
||||
use valence_protocol::packets::s2c::particle::{Particle, ParticleS2c};
|
||||
use valence_protocol::packets::s2c::play::SetActionBarText;
|
||||
use valence_protocol::{BlockPos, EncodePacket, LengthPrefixedArray, Text};
|
||||
use valence_protocol::packets::s2c::play::{SetActionBarText, SoundEffect};
|
||||
use valence_protocol::types::SoundCategory;
|
||||
use valence_protocol::{BlockPos, EncodePacket, LengthPrefixedArray, Sound, Text};
|
||||
|
||||
use crate::dimension::DimensionId;
|
||||
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.
|
||||
pub fn set_action_bar(&mut self, text: impl Into<Text>) {
|
||||
self.write_packet(&SetActionBarText {
|
||||
|
|
|
@ -8,6 +8,7 @@ use proc_macro2::{Ident, Span};
|
|||
mod block;
|
||||
mod enchant;
|
||||
mod item;
|
||||
mod sound;
|
||||
mod translation_key;
|
||||
|
||||
pub fn main() -> anyhow::Result<()> {
|
||||
|
@ -17,6 +18,7 @@ pub fn main() -> anyhow::Result<()> {
|
|||
(block::build as fn() -> _, "block.rs"),
|
||||
(enchant::build, "enchant.rs"),
|
||||
(item::build, "item.rs"),
|
||||
(sound::build, "sound.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 item::{ItemKind, ItemStack};
|
||||
pub use raw_bytes::RawBytes;
|
||||
pub use sound::Sound;
|
||||
pub use text::{Text, TextFormat};
|
||||
pub use username::Username;
|
||||
pub use uuid::Uuid;
|
||||
|
@ -108,6 +109,7 @@ mod impls;
|
|||
mod item;
|
||||
pub mod packets;
|
||||
mod raw_bytes;
|
||||
pub mod sound;
|
||||
pub mod text;
|
||||
pub mod translation_key;
|
||||
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 Items(),
|
||||
new Packets(),
|
||||
new Sounds(),
|
||||
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