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:
Gingeh 2023-02-15 21:36:21 +11:00 committed by GitHub
parent bee44d32b5
commit 50018a52bf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 5826 additions and 15 deletions

View file

@ -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(
"",

View file

@ -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 {

View file

@ -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 {

View file

@ -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"),
];

View 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,)*];
}
})
}

View file

@ -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;

View 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

File diff suppressed because it is too large Load diff

View file

@ -44,6 +44,7 @@ public class Main implements ModInitializer {
new EntityData(),
new Items(),
new Packets(),
new Sounds(),
new TranslationKeys(),
};

View file

@ -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;
}
}