Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 64 additions & 3 deletions core/network/src/packets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,8 @@ pub enum Error {
InsufficientArrayLength,
#[error("invalid handshake next state {0}")]
InvalidHandshakeState(i32),
#[error("invalid sound category {0}")]
InvalidSoundCategory(i32)
}

// SERVERBOUND
Expand Down Expand Up @@ -1703,18 +1705,71 @@ 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,
pub volume: f32,
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<dyn Packet> {
box_clone_impl!(self);
}
}

#[derive(Clone, Copy, Debug)]
pub enum SoundCategory {
Master = 0,
Music = 1,
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion server/entity/src/broadcasters/inventory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 2 additions & 0 deletions server/player/src/broadcasters.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::*;
Expand All @@ -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;
28 changes: 28 additions & 0 deletions server/player/src/broadcasters/sound.rs
Original file line number Diff line number Diff line change
@@ -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
);
}
2 changes: 2 additions & 0 deletions server/src/event_handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
11 changes: 11 additions & 0 deletions server/types/src/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down
55 changes: 53 additions & 2 deletions server/types/src/game.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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;
Expand All @@ -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.
Expand Down Expand Up @@ -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::<Position>(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 {
Expand All @@ -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::<Position>(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::<CanRespawn>(entity) {
self.despawn(entity, world);
Expand Down