diff --git a/CHANGELOG.md b/CHANGELOG.md index 006b3a95ed..0783fa073f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -60,6 +60,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added timed bans and ban history. - Added non-admin moderators with limit privileges and updated the security model to reflect this. - Chat tabs +- NPC's now hear certain sounds ### Changed diff --git a/common/src/comp/agent.rs b/common/src/comp/agent.rs index 456471c106..0bb02f01ec 100644 --- a/common/src/comp/agent.rs +++ b/common/src/comp/agent.rs @@ -14,6 +14,7 @@ use super::dialogue::Subject; pub const DEFAULT_INTERACTION_TIME: f32 = 3.0; pub const TRADE_INTERACTION_TIME: f32 = 300.0; +pub const MAX_LISTEN_DIST: f32 = 100.0; #[derive(Copy, Clone, Debug, PartialEq)] pub enum Alignment { @@ -245,7 +246,7 @@ pub enum AgentEvent { TradeAccepted(Uid), FinishedTrade(TradeResult), UpdatePendingTrade( - // this data structure is large so box it to keep AgentEvent small + // This data structure is large so box it to keep AgentEvent small Box<( TradeId, PendingTrade, @@ -253,7 +254,43 @@ pub enum AgentEvent { [Option; 2], )>, ), - // Add others here + ServerSound(Sound), +} + +#[derive(Copy, Clone, Debug)] +pub struct Sound { + pub kind: SoundKind, + pub pos: Vec3, + pub vol: f32, + pub time: f64, +} + +impl Sound { + pub fn new(kind: SoundKind, pos: Vec3, vol: f32, time: f64) -> Self { + Sound { + kind, + pos, + vol, + time, + } + } + + pub fn with_new_vol(mut self, new_vol: f32) -> Self { + self.vol = new_vol; + + self + } +} + +#[derive(Copy, Clone, Debug)] +pub enum SoundKind { + Unknown, + Movement, + Melee, + Projectile, + Explosion, + Beam, + Shockwave, } #[derive(Clone, Debug)] @@ -274,6 +311,8 @@ pub struct Agent { pub inbox: VecDeque, pub action_state: ActionState, pub bearing: Vec2, + pub sounds_heard: Vec, + pub awareness: f32, } #[derive(Clone, Debug, Default)] diff --git a/common/src/consts.rs b/common/src/consts.rs index f030d9742a..e4a047e5a8 100644 --- a/common/src/consts.rs +++ b/common/src/consts.rs @@ -18,5 +18,8 @@ pub const IRON_DENSITY: f32 = 7870.0; pub const HUMAN_DENSITY: f32 = 990.0; // value we use to make humanoids gently float // 1 thread might be used for long-running cpu intensive tasks, like chunk // generation. having at least 2 helps not blocking in the main tick here + pub const MIN_RECOMMENDED_RAYON_THREADS: usize = 2; pub const MIN_RECOMMENDED_TOKIO_THREADS: usize = 2; + +pub const SOUND_TRAVEL_DIST_PER_VOLUME: f32 = 3.0; diff --git a/common/src/event.rs b/common/src/event.rs index f2b533d8cc..fc6b0f0cce 100644 --- a/common/src/event.rs +++ b/common/src/event.rs @@ -2,6 +2,7 @@ use crate::{ character::CharacterId, comp::{ self, + agent::Sound, invite::{InviteKind, InviteResponse}, item::Item, DisconnectReason, Ori, Pos, @@ -175,6 +176,9 @@ pub enum ServerEvent { range: Option, pos: Pos, }, + Sound { + sound: Sound, + }, } pub struct EventBus { diff --git a/common/src/states/behavior.rs b/common/src/states/behavior.rs index 075c15fe88..a1fea34724 100644 --- a/common/src/states/behavior.rs +++ b/common/src/states/behavior.rs @@ -69,7 +69,6 @@ pub trait CharacterBehavior { ControlAction::CancelInput(input) => self.cancel_input(data, input), } } - // fn init(data: &JoinData) -> CharacterState; } /// Read-Only Data sent from Character Behavior System to behavior fn's diff --git a/common/systems/src/beam.rs b/common/systems/src/beam.rs index bd28e58d44..440f1d8550 100644 --- a/common/systems/src/beam.rs +++ b/common/systems/src/beam.rs @@ -1,6 +1,7 @@ use common::{ combat::{AttackSource, AttackerInfo, TargetInfo}, comp::{ + agent::{Sound, SoundKind}, Beam, BeamSegment, Body, CharacterState, Combo, Energy, Group, Health, HealthSource, Inventory, Ori, Pos, Scale, Stats, }, @@ -13,6 +14,7 @@ use common::{ GroupTarget, }; use common_ecs::{Job, Origin, ParMode, Phase, System}; +use rand::{thread_rng, Rng}; use rayon::iter::ParallelIterator; use specs::{ saveload::MarkerAllocator, shred::ResourceId, Entities, Join, ParJoin, Read, ReadExpect, @@ -88,6 +90,14 @@ impl<'a> System<'a> for Sys { None => return (server_events, add_hit_entities, outcomes), }; let end_time = creation_time + beam_segment.duration.as_secs_f64(); + + let mut rng = thread_rng(); + if rng.gen_bool(0.005) { + server_events.push(ServerEvent::Sound { + sound: Sound::new(SoundKind::Beam, pos.0, 7.0, time), + }); + } + // If beam segment is out of time emit destroy event but still continue since it // may have traveled and produced effects a bit before reaching its // end point @@ -151,8 +161,7 @@ impl<'a> System<'a> for Sys { let height_b = body_b.height() * scale_b; // Check if it is a hit - let hit = entity != target - && !health_b.is_dead + let hit = entity != target && !health_b.is_dead // Collision shapes && sphere_wedge_cylinder_collision(pos.0, frame_start_dist, frame_end_dist, *ori.look_dir(), beam_segment.angle, pos_b.0, rad_b, height_b); diff --git a/common/systems/src/melee.rs b/common/systems/src/melee.rs index abb5aa703d..78d77a3e93 100644 --- a/common/systems/src/melee.rs +++ b/common/systems/src/melee.rs @@ -1,11 +1,13 @@ use common::{ combat::{AttackSource, AttackerInfo, TargetInfo}, comp::{ + agent::{Sound, SoundKind}, Body, CharacterState, Combo, Energy, Group, Health, Inventory, Melee, Ori, Pos, Scale, Stats, }, event::{EventBus, ServerEvent}, outcome::Outcome, + resources::Time, uid::Uid, util::Dir, GroupTarget, @@ -18,6 +20,7 @@ use vek::*; #[derive(SystemData)] pub struct ReadData<'a> { + time: Read<'a, Time>, entities: Entities<'a>, uids: ReadStorage<'a, Uid>, positions: ReadStorage<'a, Pos>, @@ -66,6 +69,9 @@ impl<'a> System<'a> for Sys { if melee_attack.applied { continue; } + server_emitter.emit(ServerEvent::Sound { + sound: Sound::new(SoundKind::Melee, pos.0, 3.0, read_data.time.0), + }); melee_attack.applied = true; // Scales diff --git a/common/systems/src/projectile.rs b/common/systems/src/projectile.rs index 91feb7a615..443a8b84ba 100644 --- a/common/systems/src/projectile.rs +++ b/common/systems/src/projectile.rs @@ -1,17 +1,19 @@ use common::{ combat::{AttackSource, AttackerInfo, TargetInfo}, comp::{ + agent::{Sound, SoundKind}, projectile, Body, CharacterState, Combo, Energy, Group, Health, HealthSource, Inventory, Ori, PhysicsState, Pos, Projectile, Stats, Vel, }, event::{EventBus, ServerEvent}, outcome::Outcome, - resources::DeltaTime, + resources::{DeltaTime, Time}, uid::{Uid, UidAllocator}, util::Dir, GroupTarget, }; use common_ecs::{Job, Origin, Phase, System}; +use rand::{thread_rng, Rng}; use specs::{ saveload::MarkerAllocator, shred::ResourceId, Entities, Join, Read, ReadStorage, SystemData, World, Write, WriteStorage, @@ -21,6 +23,7 @@ use vek::*; #[derive(SystemData)] pub struct ReadData<'a> { + time: Read<'a, Time>, entities: Entities<'a>, dt: Read<'a, DeltaTime>, uid_allocator: Read<'a, UidAllocator>, @@ -59,7 +62,6 @@ impl<'a> System<'a> for Sys { (read_data, mut orientations, mut projectiles, mut outcomes): Self::SystemData, ) { let mut server_emitter = read_data.server_bus.emitter(); - // Attacks 'projectile_loop: for (entity, pos, physics, vel, mut projectile) in ( &read_data.entities, @@ -70,7 +72,15 @@ impl<'a> System<'a> for Sys { ) .join() { + let mut rng = thread_rng(); + if physics.on_surface().is_none() && rng.gen_bool(0.05) { + server_emitter.emit(ServerEvent::Sound { + sound: Sound::new(SoundKind::Projectile, pos.0, 2.0, read_data.time.0), + }); + } + let mut projectile_vanished: bool = false; + // Hit entity for other in physics.touch_entities.iter().copied() { let same_group = projectile @@ -86,16 +96,14 @@ impl<'a> System<'a> for Sys { .and_then(|e| read_data.groups.get(e)) ); + // Skip if in the same group let target_group = if same_group { GroupTarget::InGroup } else { GroupTarget::OutOfGroup }; - if projectile.ignore_group - // Skip if in the same group - && same_group - { + if projectile.ignore_group && same_group { continue; } @@ -173,7 +181,7 @@ impl<'a> System<'a> for Sys { pos: pos.0, explosion: e, owner: projectile.owner, - }) + }); }, projectile::Effect::Vanish => { server_emitter.emit(ServerEvent::Destroy { @@ -198,8 +206,7 @@ impl<'a> System<'a> for Sys { } } - // Hit something solid - if physics.on_wall.is_some() || physics.on_ground || physics.on_ceiling { + if physics.on_surface().is_some() { let projectile = &mut *projectile; for effect in projectile.hit_solid.drain(..) { match effect { @@ -208,7 +215,7 @@ impl<'a> System<'a> for Sys { pos: pos.0, explosion: e, owner: projectile.owner, - }) + }); }, projectile::Effect::Vanish => { server_emitter.emit(ServerEvent::Destroy { diff --git a/common/systems/src/shockwave.rs b/common/systems/src/shockwave.rs index 0c6e1131c8..2b5c8fc812 100644 --- a/common/systems/src/shockwave.rs +++ b/common/systems/src/shockwave.rs @@ -1,6 +1,7 @@ use common::{ combat::{AttackSource, AttackerInfo, TargetInfo}, comp::{ + agent::{Sound, SoundKind}, Body, CharacterState, Combo, Energy, Group, Health, HealthSource, Inventory, Ori, PhysicsState, Pos, Scale, Shockwave, ShockwaveHitEntities, Stats, }, @@ -12,6 +13,7 @@ use common::{ GroupTarget, }; use common_ecs::{Job, Origin, Phase, System}; +use rand::{thread_rng, Rng}; use specs::{ saveload::MarkerAllocator, shred::ResourceId, Entities, Join, Read, ReadStorage, SystemData, World, Write, WriteStorage, @@ -83,9 +85,15 @@ impl<'a> System<'a> for Sys { let end_time = creation_time + shockwave.duration.as_secs_f64(); + let mut rng = thread_rng(); + if rng.gen_bool(0.05) { + server_emitter.emit(ServerEvent::Sound { + sound: Sound::new(SoundKind::Shockwave, pos.0, 16.0, time), + }); + } + // If shockwave is out of time emit destroy event but still continue since it - // may have traveled and produced effects a bit before reaching it's - // end point + // may have traveled and produced effects a bit before reaching it's end point if time > end_time { server_emitter.emit(ServerEvent::Destroy { entity, diff --git a/server/src/events/entity_manipulation.rs b/server/src/events/entity_manipulation.rs index 6a0dbada4d..33fcc05826 100644 --- a/server/src/events/entity_manipulation.rs +++ b/server/src/events/entity_manipulation.rs @@ -1,7 +1,9 @@ use crate::{ client::Client, comp::{ - biped_large, quadruped_low, quadruped_medium, quadruped_small, skills::SkillGroupKind, + agent::{Sound, SoundKind}, + biped_large, quadruped_low, quadruped_medium, quadruped_small, + skills::SkillGroupKind, theropod, PhysicsState, }, rtsim::RtSim, @@ -476,19 +478,22 @@ pub fn handle_delete(server: &mut Server, entity: EcsEntity) { } pub fn handle_land_on_ground(server: &Server, entity: EcsEntity, vel: Vec3) { - let state = &server.state; + let ecs = server.state.ecs(); + if vel.z <= -30.0 { - let mass = state - .ecs() + let mass = ecs .read_storage::() .get(entity) .copied() .unwrap_or_default(); - let inventories = state.ecs().read_storage::(); - let falldmg = mass.0 * vel.z.powi(2) / 200.0; - let stats = state.ecs().read_storage::(); + let impact_energy = mass.0 * vel.z.powi(2) / 2.0; + let falldmg = impact_energy / 100.0; + + let inventories = ecs.read_storage::(); + let stats = ecs.read_storage::(); + // Handle health change - if let Some(mut health) = state.ecs().write_storage::().get_mut(entity) { + if let Some(mut health) = ecs.write_storage::().get_mut(entity) { let damage = Damage { source: DamageSource::Falling, kind: DamageKind::Crushing, @@ -503,7 +508,7 @@ pub fn handle_land_on_ground(server: &Server, entity: EcsEntity, vel: Vec3) health.change_by(change); } // Handle poise change - if let Some(mut poise) = state.ecs().write_storage::().get_mut(entity) { + if let Some(mut poise) = ecs.write_storage::().get_mut(entity) { let poise_damage = PoiseChange { amount: -(mass.0 * vel.magnitude_squared() / 1500.0) as i32, source: PoiseSource::Falling, @@ -557,6 +562,13 @@ pub fn handle_respawn(server: &Server, entity: EcsEntity) { pub fn handle_explosion(server: &Server, pos: Vec3, explosion: Explosion, owner: Option) { // Go through all other entities let ecs = &server.state.ecs(); + let server_eventbus = ecs.read_resource::>(); + let time = ecs.read_resource::