diff --git a/core/network/src/packets.rs b/core/network/src/packets.rs index 994a277cb..53ea3f57f 100644 --- a/core/network/src/packets.rs +++ b/core/network/src/packets.rs @@ -351,6 +351,8 @@ pub enum Error { InsufficientArrayLength, #[error("invalid handshake next state {0}")] InvalidHandshakeState(i32), + #[error("invalid sound category {0}")] + InvalidSoundCategory(i32) } // SERVERBOUND @@ -1703,10 +1705,10 @@ impl Packet for PluginMessageClientbound { } } -#[derive(Default, AsAny, Packet, Clone)] +#[derive(Default, AsAny, Clone)] pub struct NamedSoundEffect { pub sound_name: String, - pub sound_category: VarInt, + pub sound_category: SoundCategory, pub effect_pos_x: i32, pub effect_pos_y: i32, pub effect_pos_z: i32, @@ -1714,7 +1716,60 @@ pub struct NamedSoundEffect { pub pitch: f32, } -#[derive(Clone, Copy)] +impl Packet for NamedSoundEffect { + fn read_from(&mut self, buf: &mut Cursor<&[u8]>) -> anyhow::Result<()> { + self.sound_name = buf.try_get_string()?; + self.sound_category = { + let id = buf.try_get_var_int()?; + match id { + 0 => SoundCategory::Master, + 1 => SoundCategory::Music, + 2 => SoundCategory::Records, + 3 => SoundCategory::Weather, + 4 => SoundCategory::Blocks, + 5 => SoundCategory::Hostile, + 6 => SoundCategory::Neutral, + 7 => SoundCategory::Players, + 8 => SoundCategory::Ambient, + 9 => SoundCategory::Voice, + i => return Err(Error::InvalidSoundCategory(i).into()), + } + }; + self.effect_pos_x = buf.try_get_i32()? * 8; + self.effect_pos_y = buf.try_get_i32()? * 8; + self.effect_pos_z = buf.try_get_i32()? * 8; + self.volume = buf.try_get_f32()?; + self.pitch = buf.try_get_f32()?; + Ok(()) + } + + fn write_to(&self, buf: &mut BytesMut) { + buf.push_string(self.sound_name.as_str()); + buf.push_var_int(self.sound_category as i32); + buf.push_i32(self.effect_pos_x / 8); + buf.push_i32(self.effect_pos_y / 8); + buf.push_i32(self.effect_pos_z / 8); + buf.push_f32(self.volume); + buf.push_f32(self.pitch); + } + + fn ty(&self) -> PacketType { + PacketType::NamedSoundEffect + } + + fn ty_sized() -> PacketType + where + Self: Sized, + { + PacketType::NamedSoundEffect + } + + fn box_clone(&self) -> Box { + box_clone_impl!(self); + } +} + +#[derive(Clone, Copy, Debug)] pub enum SoundCategory { Master = 0, Music = 1, @@ -1728,6 +1783,12 @@ pub enum SoundCategory { Voice = 9, } +impl Default for SoundCategory { + fn default() -> Self { + SoundCategory::Master + } +} + #[derive(Default, AsAny, Packet, Clone)] pub struct DisconnectPlay { pub reason: String, // Chat diff --git a/server/entity/src/broadcasters/inventory.rs b/server/entity/src/broadcasters/inventory.rs index c69436cdb..00da55783 100644 --- a/server/entity/src/broadcasters/inventory.rs +++ b/server/entity/src/broadcasters/inventory.rs @@ -182,7 +182,7 @@ fn send_item_broken_sound_effect(player: Entity, game: &mut Game, world: &mut Wo let mut rng = game.rng(); let sound_packet = NamedSoundEffect { sound_name: "entity.item.break".into(), - sound_category: SoundCategory::Players as i32, + sound_category: SoundCategory::Players, effect_pos_x, effect_pos_y, effect_pos_z, diff --git a/server/player/src/broadcasters.rs b/server/player/src/broadcasters.rs index 635d44906..38f2456bf 100644 --- a/server/player/src/broadcasters.rs +++ b/server/player/src/broadcasters.rs @@ -5,6 +5,7 @@ mod gamemode; mod health; mod keepalive; mod teleport; +mod sound; pub use animation::on_player_animation_broadcast_animation; pub use block::*; @@ -13,3 +14,4 @@ pub use gamemode::*; pub use health::on_health_update_send; pub use keepalive::broadcast_keepalive; pub use teleport::send_teleported; +pub use sound::on_sound_broadcast; diff --git a/server/player/src/broadcasters/sound.rs b/server/player/src/broadcasters/sound.rs new file mode 100644 index 000000000..f32b178e1 --- /dev/null +++ b/server/player/src/broadcasters/sound.rs @@ -0,0 +1,28 @@ +use feather_core::network::packets::NamedSoundEffect; +use feather_server_types::{Game, NamedSoundEffectEvent}; +use fecs::World; +use feather_core::util::{ChunkPosition}; + +/// Broadcasts sounds. +#[fecs::event_handler] +pub fn on_sound_broadcast( + event: &NamedSoundEffectEvent, + game: &mut Game, + world: &mut World, +) { + let packet = NamedSoundEffect { + sound_name: event.sound_name.clone(), + sound_category: event.sound_category, + effect_pos_x: event.effect_pos.x, + effect_pos_y: event.effect_pos.y, + effect_pos_z: event.effect_pos.z, + volume: event.volume, + pitch: event.pitch + }; + game.broadcast_chunk_update( + world, + packet, + ChunkPosition::from(event.effect_pos), + None + ); +} diff --git a/server/src/event_handlers.rs b/server/src/event_handlers.rs index 4cb60c902..87ce52615 100644 --- a/server/src/event_handlers.rs +++ b/server/src/event_handlers.rs @@ -73,6 +73,8 @@ pub fn build_event_handlers() -> EventHandlers { on_player_animation_broadcast_animation, + on_sound_broadcast, + on_item_drop_spawn_item_entity, on_item_collect_broadcast, diff --git a/server/types/src/events.rs b/server/types/src/events.rs index 457f14d8b..bbd7377cc 100644 --- a/server/types/src/events.rs +++ b/server/types/src/events.rs @@ -5,6 +5,7 @@ use feather_core::items::ItemStack; use feather_core::util::{BlockPosition, ChunkPosition, ClientboundAnimation, Gamemode, Position}; use fecs::Entity; use smallvec::SmallVec; +use feather_core::network::packets::SoundCategory; #[derive(Copy, Clone, Debug)] pub struct BlockUpdateEvent { @@ -285,6 +286,16 @@ pub struct GamemodeUpdateEvent { pub new: Gamemode, } +/// Triggers a sound effect to be played to all players near the coordinates +#[derive(Clone, Debug)] +pub struct NamedSoundEffectEvent { + pub sound_name: String, + pub sound_category: SoundCategory, + pub effect_pos: BlockPosition, + pub volume: f32, + pub pitch: f32, +} + /// Requests that a chunk be held for the given client. /// /// This is a "request"-type event: it has one handler defined diff --git a/server/types/src/game.rs b/server/types/src/game.rs index 44dd75f99..9b2937618 100644 --- a/server/types/src/game.rs +++ b/server/types/src/game.rs @@ -1,4 +1,4 @@ -use crate::{BlockUpdateCause, Network, ServerToWorkerMessage}; +use crate::{BlockUpdateCause, Network, ServerToWorkerMessage, PlayerAnimationEvent, NamedSoundEffectEvent}; use crate::{ BlockUpdateEvent, CanRespawn, Dead, EntityDeathEvent, EntityDespawnEvent, Health, HealthUpdateEvent, Name, PlayerLeaveEvent, @@ -11,7 +11,7 @@ use feather_core::chunk_map::ChunkMap; use feather_core::game_rules::GameRules; use feather_core::network::{packets::DisconnectPlay, Packet}; use feather_core::text::Text; -use feather_core::util::{BlockPosition, ChunkPosition, Position}; +use feather_core::util::{BlockPosition, ChunkPosition, Position, ClientboundAnimation}; use feather_server_config::Config; use fecs::{Entity, Event, EventHandlers, IntoQuery, OwnedResources, Read, RefResources, World}; use rand::rngs::SmallRng; @@ -23,6 +23,7 @@ use std::ops::{Deref, DerefMut}; use std::sync::atomic::{AtomicU32, Ordering}; use std::sync::Arc; use thread_local::CachedThreadLocal; +use feather_core::network::packets::SoundCategory; /// Resources which can be _shared_ between threads. /// These only require immutable access. @@ -301,6 +302,32 @@ impl Game { entity, }, ); + + if old_health != new_health && !should_kill { + // client has taken damage, send animation and sound + self.handle( + world, + PlayerAnimationEvent { + player: entity, + animation: ClientboundAnimation::TakeDamage + }, + ); + let pos = world.try_get::(entity) + .map(|pos| BlockPosition::from(*pos)); + if let Some(pos) = pos { + self.handle( + world, + NamedSoundEffectEvent { + // TODO: different sound effects for different mobs + sound_name: String::from("entity.player.hurt"), + sound_category: SoundCategory::Players, + effect_pos: pos, + volume: 100.0, + pitch: 1.0 + }, + ); + } + } } if should_kill { @@ -315,6 +342,30 @@ impl Game { return; } + // animation and sound effect + self.handle( + world, + PlayerAnimationEvent { + player: entity, + animation: ClientboundAnimation::TakeDamage + }, + ); + let pos = world.try_get::(entity) + .map(|pos| BlockPosition::from(*pos)); + if let Some(pos) = pos { + self.handle( + world, + NamedSoundEffectEvent { + // TODO: different sound effects for different mobs + sound_name: String::from("entity.player.death"), + sound_category: SoundCategory::Players, + effect_pos: pos, + volume: 1.0, + pitch: 1.0 + }, + ); + } + self.handle(world, EntityDeathEvent { entity }); if !world.has::(entity) { self.despawn(entity, world);