diff --git a/CHANGELOG.md b/CHANGELOG.md index a556f9bff8..6ad28d3149 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Added a skill tree for mining, which gains xp from mining ores and gems. - Added debug line info to release builds, enhancing the usefulness of panic backtraces +- NPCs and animals can now make sounds in response to certain events +- Players can press H to greet others ### Changed - Entity-entity pushback is no longer applied in forced movement states like rolling and leaping. @@ -18,7 +20,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Removed ### Fixed + - Cases where no audio output could be produced before. +- Significantly improved the performance of playing sound effects ## [0.10.0] - 2021-06-12 diff --git a/assets/voxygen/audio/sfx.ron b/assets/voxygen/audio/sfx.ron index bd9434f765..81bca564d1 100644 --- a/assets/voxygen/audio/sfx.ron +++ b/assets/voxygen/audio/sfx.ron @@ -5,7 +5,7 @@ // Campfire: ( files: [ - "voxygen.audio.sfx.ambient.fire", + "voxygen.audio.sfx.ambient.fire", ], threshold: 21.835, ), @@ -831,5 +831,56 @@ ], threshold: 0.2, ), + Utterance(Angry, Wendigo): ( + files: [ + "voxygen.audio.sfx.utterance.wendigo_angry", + ], + threshold: 0.2, + ), + Utterance(Angry, BipedLarge): ( + files: [ + "voxygen.audio.sfx.utterance.ogre_angry", + "voxygen.audio.sfx.utterance.ogre_angry2", + ], + threshold: 0.2, + ), + Utterance(Angry, Reptile): ( + files: [ + "voxygen.audio.sfx.utterance.saurok_angry", + ], + threshold: 0.2, + ), + Utterance(Angry, Bird): ( + files: [ + "voxygen.audio.sfx.utterance.bird_angry", + ], + threshold: 0.2, + ), + Utterance(Calm, Pig): ( + files: [ + "voxygen.audio.sfx.utterance.pig_calm", + ], + threshold: 0.2, + ), + Utterance(Calm, Cow): ( + files: [ + "voxygen.audio.sfx.utterance.cow_calm", + "voxygen.audio.sfx.utterance.cow_calm2", + "voxygen.audio.sfx.utterance.cow_calm3", + ], + threshold: 0.2, + ), + Utterance(Calm, Sheep): ( + files: [ + "voxygen.audio.sfx.utterance.sheep_calm", + ], + threshold: 0.2, + ), + Utterance(Greeting, HumanMale): ( + files: [ + "voxygen.audio.sfx.utterance.humanmale_greeting", + ], + threshold: 0.2, + ), } ) diff --git a/assets/voxygen/audio/sfx/utterance/bird_angry.ogg b/assets/voxygen/audio/sfx/utterance/bird_angry.ogg new file mode 100644 index 0000000000..bd0c9a7e77 --- /dev/null +++ b/assets/voxygen/audio/sfx/utterance/bird_angry.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5e028570750b872652dc16dff1dd4cafe34ad568bc079bd8ba379a5ceac66942 +size 11508 diff --git a/assets/voxygen/audio/sfx/utterance/cow_calm.ogg b/assets/voxygen/audio/sfx/utterance/cow_calm.ogg new file mode 100644 index 0000000000..fcb6058d93 --- /dev/null +++ b/assets/voxygen/audio/sfx/utterance/cow_calm.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:eb54f07923524c80c8cb35d908165fa55582f0b085790473802fbb578766f347 +size 22691 diff --git a/assets/voxygen/audio/sfx/utterance/cow_calm2.ogg b/assets/voxygen/audio/sfx/utterance/cow_calm2.ogg new file mode 100644 index 0000000000..f060217fe4 --- /dev/null +++ b/assets/voxygen/audio/sfx/utterance/cow_calm2.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:581a6c86eb6f06ed2694d0cdfa08691fa8bf38fd7fffb57680b41ed5dc1060ed +size 23328 diff --git a/assets/voxygen/audio/sfx/utterance/cow_calm3.ogg b/assets/voxygen/audio/sfx/utterance/cow_calm3.ogg new file mode 100644 index 0000000000..59ae620508 --- /dev/null +++ b/assets/voxygen/audio/sfx/utterance/cow_calm3.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cb77a69af59eb015c20de58bad181f7e02bc82649d33c274302018c8baf4b85a +size 14690 diff --git a/assets/voxygen/audio/sfx/utterance/humanmale_greeting.ogg b/assets/voxygen/audio/sfx/utterance/humanmale_greeting.ogg new file mode 100644 index 0000000000..69dbe5a088 --- /dev/null +++ b/assets/voxygen/audio/sfx/utterance/humanmale_greeting.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ac12e8915ff261097d5aa24debf5685c4da3bc439ba64e6fd0bc12070f2ebff7 +size 12604 diff --git a/assets/voxygen/audio/sfx/utterance/ogre_angry.ogg b/assets/voxygen/audio/sfx/utterance/ogre_angry.ogg new file mode 100644 index 0000000000..819348a228 --- /dev/null +++ b/assets/voxygen/audio/sfx/utterance/ogre_angry.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:78f705397c01f73fd2bfad14f0aa3949a5409556cd5f004c185dea358fbd9701 +size 34809 diff --git a/assets/voxygen/audio/sfx/utterance/ogre_angry2.ogg b/assets/voxygen/audio/sfx/utterance/ogre_angry2.ogg new file mode 100644 index 0000000000..da49817cca --- /dev/null +++ b/assets/voxygen/audio/sfx/utterance/ogre_angry2.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5c97dcb8f2ff2543a041d07147c04a74b59e43421dd7452eccb095a947bd7466 +size 52973 diff --git a/assets/voxygen/audio/sfx/utterance/pig_calm.ogg b/assets/voxygen/audio/sfx/utterance/pig_calm.ogg new file mode 100644 index 0000000000..b5660f8b39 --- /dev/null +++ b/assets/voxygen/audio/sfx/utterance/pig_calm.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:397fca64b10178b05d5007b86e166e2ff335380dae69491f95df0e5724b7c2a5 +size 10729 diff --git a/assets/voxygen/audio/sfx/utterance/saurok_angry.ogg b/assets/voxygen/audio/sfx/utterance/saurok_angry.ogg new file mode 100644 index 0000000000..a550c1bbe2 --- /dev/null +++ b/assets/voxygen/audio/sfx/utterance/saurok_angry.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2f859d590ac38bac163f0821eec8e2157f4f021d34d59575ccca2aeedde919ce +size 56816 diff --git a/assets/voxygen/audio/sfx/utterance/sheep_calm.ogg b/assets/voxygen/audio/sfx/utterance/sheep_calm.ogg new file mode 100644 index 0000000000..23e9151f17 --- /dev/null +++ b/assets/voxygen/audio/sfx/utterance/sheep_calm.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:32fd1868f9f58774e1cd93b5e577795e20a6c3f93683ba4adc7666f2157d0eb3 +size 12028 diff --git a/assets/voxygen/audio/sfx/utterance/wendigo_angry.ogg b/assets/voxygen/audio/sfx/utterance/wendigo_angry.ogg new file mode 100644 index 0000000000..4c02e0bd36 --- /dev/null +++ b/assets/voxygen/audio/sfx/utterance/wendigo_angry.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cfbac68b83de452e7be824075ee9104559ca96a9a710be303c89ede0ab4a6a03 +size 42373 diff --git a/client/src/lib.rs b/client/src/lib.rs index 661c2ad5f7..9f09ffb9d0 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -28,7 +28,7 @@ use common::{ skills::Skill, slot::Slot, ChatMode, ControlAction, ControlEvent, Controller, ControllerInputs, GroupManip, InputKind, - InventoryAction, InventoryEvent, InventoryUpdateEvent, + InventoryAction, InventoryEvent, InventoryUpdateEvent, UtteranceKind, }, event::{EventBus, LocalEvent}, grid::Grid, @@ -1224,6 +1224,10 @@ impl Client { } } + pub fn utter(&mut self, kind: UtteranceKind) { + self.send_msg(ClientGeneral::ControlEvent(ControlEvent::Utterance(kind))); + } + pub fn toggle_sneak(&mut self) { let is_sneaking = self .state diff --git a/common/src/comp/agent.rs b/common/src/comp/agent.rs index 8b792f361f..7c64f62fca 100644 --- a/common/src/comp/agent.rs +++ b/common/src/comp/agent.rs @@ -1,5 +1,5 @@ use crate::{ - comp::{humanoid, quadruped_low, quadruped_medium, quadruped_small, ship, Body}, + comp::{humanoid, quadruped_low, quadruped_medium, quadruped_small, ship, Body, UtteranceKind}, path::Chaser, rtsim::RtSimController, trade::{PendingTrade, ReducedInventory, SiteId, SitePrices, TradeId, TradeResult}, @@ -96,7 +96,7 @@ bitflags::bitflags! { /// # Behavior Component /// This component allow an Entity to register one or more behavior tags. -/// These tags act as flags of what an Entity can do, or what it is doing. +/// These tags act as flags of what an Entity can do, or what it is doing. /// Behaviors Tags can be added and removed as the Entity lives, to update its /// state when needed #[derive(Default, Copy, Clone, Debug)] @@ -117,7 +117,7 @@ impl From for Behavior { } impl Behavior { - /// Builder function + /// Builder function /// Set capabilities if Option is Some pub fn maybe_with_capabilities( mut self, @@ -297,6 +297,7 @@ pub enum SoundKind { Explosion, Beam, Shockwave, + Utterance(UtteranceKind, Body), } #[derive(Clone, Debug)] diff --git a/common/src/comp/controller.rs b/common/src/comp/controller.rs index 9e13b4e327..c27a4c1c50 100644 --- a/common/src/comp/controller.rs +++ b/common/src/comp/controller.rs @@ -96,6 +96,15 @@ pub enum GroupManip { AssignLeader(Uid), } +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub enum UtteranceKind { + Calm, + Angry, + Surprised, + Hurt, + Greeting, +} + #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub enum ControlEvent { //ToggleLantern, @@ -111,6 +120,7 @@ pub enum ControlEvent { GroupManip(GroupManip), RemoveBuff(BuffKind), Respawn, + Utterance(UtteranceKind), } #[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] diff --git a/common/src/comp/mod.rs b/common/src/comp/mod.rs index 8b6b775349..2242ad236e 100644 --- a/common/src/comp/mod.rs +++ b/common/src/comp/mod.rs @@ -68,6 +68,7 @@ pub use self::{ controller::{ Climb, ControlAction, ControlEvent, Controller, ControllerInputs, GroupManip, InputAttr, InputKind, InventoryAction, InventoryEvent, InventoryManip, MountState, Mounting, + UtteranceKind, }, energy::{Energy, EnergyChange, EnergySource}, fluid_dynamics::Fluid, diff --git a/common/src/outcome.rs b/common/src/outcome.rs index d1ec35e15c..feeb1e8fec 100644 --- a/common/src/outcome.rs +++ b/common/src/outcome.rs @@ -1,5 +1,5 @@ use crate::{comp, uid::Uid}; -use comp::{beam, item::Reagent, poise::PoiseState, skills::SkillGroupKind}; +use comp::{beam, item::Reagent, poise::PoiseState, skills::SkillGroupKind, UtteranceKind}; use hashbrown::HashSet; use serde::{Deserialize, Serialize}; use vek::*; @@ -73,6 +73,11 @@ pub enum Outcome { GroundSlam { pos: Vec3, }, + Utterance { + pos: Vec3, + body: comp::Body, + kind: UtteranceKind, + }, } impl Outcome { @@ -87,7 +92,8 @@ impl Outcome { | Outcome::Damage { pos, .. } | Outcome::Block { pos, .. } | Outcome::PoiseChange { pos, .. } - | Outcome::GroundSlam { pos } => Some(*pos), + | Outcome::GroundSlam { pos } + | Outcome::Utterance { pos, .. } => Some(*pos), Outcome::BreakBlock { pos, .. } => Some(pos.map(|e| e as f32 + 0.5)), Outcome::ExpChange { .. } | Outcome::ComboChange { .. } => None, } diff --git a/common/systems/src/controller.rs b/common/systems/src/controller.rs index 42b448ad80..2aea04b55a 100644 --- a/common/systems/src/controller.rs +++ b/common/systems/src/controller.rs @@ -1,5 +1,8 @@ use common::{ - comp::{BuffChange, ControlEvent, Controller}, + comp::{ + agent::{Sound, SoundKind}, + Body, BuffChange, ControlEvent, Controller, Pos, + }, event::{EventBus, ServerEvent}, uid::UidAllocator, }; @@ -7,7 +10,7 @@ use common_ecs::{Job, Origin, Phase, System}; use specs::{ saveload::{Marker, MarkerAllocator}, shred::ResourceId, - Entities, Join, Read, SystemData, World, WriteStorage, + Entities, Join, Read, ReadStorage, SystemData, World, WriteStorage, }; use vek::*; @@ -16,6 +19,8 @@ pub struct ReadData<'a> { entities: Entities<'a>, uid_allocator: Read<'a, UidAllocator>, server_bus: Read<'a, EventBus>, + positions: ReadStorage<'a, Pos>, + bodies: ReadStorage<'a, Body>, } #[derive(Default)] @@ -92,6 +97,20 @@ impl<'a> System<'a> for Sys { server_emitter.emit(ServerEvent::GroupManip(entity, manip)) }, ControlEvent::Respawn => server_emitter.emit(ServerEvent::Respawn(entity)), + ControlEvent::Utterance(kind) => { + if let (Some(pos), Some(body)) = ( + read_data.positions.get(entity), + read_data.bodies.get(entity), + ) { + let sound = Sound::new( + SoundKind::Utterance(kind, *body), + pos.0 + Vec3::unit_z() * body.eye_height(), + 8.0, // TODO: Come up with a better way of determining this + 1.0, + ); + server_emitter.emit(ServerEvent::Sound { sound }); + } + }, } } } diff --git a/server/src/events/interaction.rs b/server/src/events/interaction.rs index 03b5fe992a..acd935f9a6 100644 --- a/server/src/events/interaction.rs +++ b/server/src/events/interaction.rs @@ -6,7 +6,7 @@ use common::{ assets, comp::{ self, - agent::{AgentEvent, Sound, MAX_LISTEN_DIST}, + agent::{AgentEvent, Sound, SoundKind, MAX_LISTEN_DIST}, dialogue::Subject, inventory::slot::EquipSlot, item, @@ -386,6 +386,8 @@ pub fn handle_sound(server: &mut Server, sound: &Sound) { let positions = &ecs.read_storage::(); let agents = &mut ecs.write_storage::(); + // TODO: Reduce the complexity of this problem by using spatial partitioning + // system for (agent, agent_pos) in (agents, positions).join() { // TODO: Use pathfinding for more dropoff around obstacles let agent_dist_sqrd = agent_pos.0.distance_squared(sound.pos); @@ -403,4 +405,16 @@ pub fn handle_sound(server: &mut Server, sound: &Sound) { .push_back(AgentEvent::ServerSound(propagated_sound)); } } + + // Attempt to turn this sound into an outcome to be received by frontends. + if let Some(outcome) = match sound.kind { + SoundKind::Utterance(kind, body) => Some(Outcome::Utterance { + kind, + pos: sound.pos, + body, + }), + _ => None, + } { + ecs.write_resource::>().push(outcome); + } } diff --git a/server/src/sys/agent.rs b/server/src/sys/agent.rs index addd351b2f..d6dab17616 100644 --- a/server/src/sys/agent.rs +++ b/server/src/sys/agent.rs @@ -21,7 +21,7 @@ use common::{ Agent, Alignment, BehaviorCapability, BehaviorState, Body, CharacterAbility, CharacterState, ControlAction, ControlEvent, Controller, Energy, Health, HealthChange, InputKind, Inventory, InventoryAction, LightEmitter, MountState, Ori, PhysicsState, Pos, - Scale, SkillSet, Stats, UnresolvedChatMsg, Vel, + Scale, SkillSet, Stats, UnresolvedChatMsg, UtteranceKind, Vel, }, consts::GRAVITY, effect::{BuffEffect, Effect}, @@ -409,6 +409,14 @@ impl<'a> System<'a> for Sys { .uid_allocator .retrieve_entity_internal(by.id()) { + if agent.target.is_none() { + controller.push_event( + ControlEvent::Utterance( + UtteranceKind::Angry, + ), + ); + } + agent.target = Some(Target { target: attacker, hostile: true, @@ -511,6 +519,12 @@ impl<'a> System<'a> for Sys { &mut event_emitter, ); } else { + if agent.target.is_none() { + controller.push_event(ControlEvent::Utterance( + UtteranceKind::Angry, + )); + } + agent.target = Some(Target { target: attacker, hostile: true, @@ -945,6 +959,10 @@ impl<'a> AgentData<'a> { controller.actions.push(ControlAction::Unwield); } + if thread_rng().gen::() < 0.0015 { + controller.push_event(ControlEvent::Utterance(UtteranceKind::Calm)); + } + // Sit if thread_rng().gen::() < 0.0035 { controller.actions.push(ControlAction::Sit); @@ -990,6 +1008,8 @@ impl<'a> AgentData<'a> { if self.look_toward(controller, read_data, &target) { controller.actions.push(ControlAction::Stand); controller.actions.push(ControlAction::Talk); + controller.push_event(ControlEvent::Utterance(UtteranceKind::Greeting)); + match subject { Subject::Regular => { if let ( @@ -1548,6 +1568,10 @@ impl<'a> AgentData<'a> { .min_by_key(|(_, e_pos, _, _, _, _, _)| (e_pos.0.distance_squared(self.pos.0) * 100.0) as i32) // TODO choose target by more than just distance .map(|(e, _, _, _, _, _, _)| e); + if agent.target.is_none() && target.is_some() { + controller.push_event(ControlEvent::Utterance(UtteranceKind::Angry)); + } + agent.target = target.map(|target| Target { target, hostile: true, diff --git a/voxygen/src/audio/mod.rs b/voxygen/src/audio/mod.rs index d6811f77ab..e6f50f9e9f 100644 --- a/voxygen/src/audio/mod.rs +++ b/voxygen/src/audio/mod.rs @@ -209,7 +209,7 @@ impl AudioFrontend { // TODO: Should this take `underwater` into consideration? match self.play_sfx(sfx_file, self.listener.pos, None, false) { Ok(_) => {}, - Err(e) => warn!("Failed to play sfx. {}", e), + Err(e) => warn!("Failed to play sfx '{:?}'. {}", sfx_file, e), } } else { debug!("Missing sfx trigger config for external sfx event.",); @@ -244,7 +244,7 @@ impl AudioFrontend { match self.play_sfx(sfx_file, position, volume, underwater) { Ok(_) => {}, - Err(e) => warn!("Failed to play sfx. {}", e), + Err(e) => warn!("Failed to play sfx '{:?}'. {}", sfx_file, e), } } else { debug!( @@ -265,8 +265,8 @@ impl AudioFrontend { ) -> Result<(), rodio::decoder::DecoderError> { if self.audio_stream.is_some() { let sound = OggSound::load_expect(sound) - .cloned() - .decoder()? + .read() + .to_source() .amplify(vol.unwrap_or(1.0)); let listener = self.listener.clone(); @@ -291,9 +291,7 @@ impl AudioFrontend { ) { if self.audio_stream.is_some() { if let Some(channel) = self.get_ambient_channel(channel_tag, volume_multiplier) { - if let Ok(sound) = OggSound::load_expect(sound).cloned().decoder() { - channel.play(sound); - } + channel.play(OggSound::load_expect(sound).read().to_source()); } } } @@ -349,9 +347,7 @@ impl AudioFrontend { fn play_music(&mut self, sound: &str, channel_tag: MusicChannelTag) { if self.music_enabled() { if let Some(channel) = self.get_music_channel(channel_tag) { - if let Ok(sound) = OggSound::load_expect(sound).cloned().decoder() { - channel.play(sound, channel_tag); - } + channel.play(OggSound::load_expect(sound).read().to_source(), channel_tag); } } } diff --git a/voxygen/src/audio/sfx/mod.rs b/voxygen/src/audio/sfx/mod.rs index f0ab9e4a55..1f5fe7ab89 100644 --- a/voxygen/src/audio/sfx/mod.rs +++ b/voxygen/src/audio/sfx/mod.rs @@ -91,11 +91,12 @@ use client::Client; use common::{ assets::{self, AssetExt, AssetHandle}, comp::{ - beam, + beam, biped_large, humanoid, item::{ItemKind, ToolKind}, object, poise::PoiseState, - Body, CharacterAbilityType, InventoryUpdateEvent, + quadruped_medium, quadruped_small, Body, CharacterAbilityType, InventoryUpdateEvent, + UtteranceKind, }, outcome::Outcome, terrain::{BlockKind, TerrainChunk}, @@ -105,7 +106,7 @@ use event_mapper::SfxEventMapper; use hashbrown::HashMap; use rand::prelude::*; use serde::Deserialize; -use tracing::warn; +use tracing::{debug, warn}; use vek::*; /// We watch the states of nearby entities in order to emit SFX at their @@ -182,6 +183,67 @@ pub enum SfxEvent { FlameThrower, PoiseChange(PoiseState), GroundSlam, + Utterance(UtteranceKind, VoiceKind), +} + +#[derive(Copy, Clone, Debug, PartialEq, Deserialize, Hash, Eq)] +pub enum VoiceKind { + HumanFemale, + HumanMale, + BipedLarge, + Wendigo, + Reptile, + Bird, + Critter, + Sheep, + Pig, + Cow, + Canine, + BigCat, +} + +fn body_to_voice(body: &Body) -> Option { + Some(match body { + Body::Humanoid(body) => match &body.body_type { + humanoid::BodyType::Female => VoiceKind::HumanFemale, + humanoid::BodyType::Male => VoiceKind::HumanMale, + }, + Body::QuadrupedSmall(body) => match body.species { + quadruped_small::Species::Sheep => VoiceKind::Sheep, + quadruped_small::Species::Pig | quadruped_small::Species::Boar => VoiceKind::Pig, + _ => VoiceKind::Critter, + }, + Body::QuadrupedMedium(body) => match body.species { + quadruped_medium::Species::Saber + | quadruped_medium::Species::Tiger + | quadruped_medium::Species::Lion + | quadruped_medium::Species::Frostfang + | quadruped_medium::Species::Snowleopard => VoiceKind::BigCat, + quadruped_medium::Species::Wolf + | quadruped_medium::Species::Roshwalr + | quadruped_medium::Species::Tarasque + | quadruped_medium::Species::Darkhound + | quadruped_medium::Species::Bonerattler + | quadruped_medium::Species::Grolgar => VoiceKind::Canine, + quadruped_medium::Species::Cattle + | quadruped_medium::Species::Catoblepas + | quadruped_medium::Species::Highland + | quadruped_medium::Species::Yak + | quadruped_medium::Species::Moose + | quadruped_medium::Species::Dreadhorn => VoiceKind::Cow, + _ => return None, + }, + Body::BirdMedium(_) | Body::BirdLarge(_) => VoiceKind::Bird, + Body::BipedLarge(body) => match body.species { + biped_large::Species::Wendigo => VoiceKind::Wendigo, + biped_large::Species::Occultsaurok + | biped_large::Species::Mightysaurok + | biped_large::Species::Slysaurok => VoiceKind::Reptile, + _ => VoiceKind::BipedLarge, + }, + Body::Theropod(_) | Body::Dragon(_) => VoiceKind::Reptile, + _ => return None, + }) } #[derive(Clone, Debug, PartialEq, Deserialize, Hash, Eq)] @@ -452,6 +514,20 @@ impl SfxMgr { audio.emit_sfx(sfx_trigger_item, *pos, None, false); }, }, + Outcome::Utterance { pos, kind, body } => { + if let Some(voice) = body_to_voice(body) { + let sfx_trigger_item = + triggers.get_key_value(&SfxEvent::Utterance(*kind, voice)); + if let Some(sfx_trigger_item) = sfx_trigger_item { + audio.emit_sfx(Some(sfx_trigger_item), *pos, Some(2.5), false); + } else { + debug!( + "No utterance sound effect exists for ({:?}, {:?})", + kind, voice + ); + } + } + }, Outcome::ExpChange { .. } | Outcome::ComboChange { .. } | Outcome::SummonedCreature { .. } => {}, diff --git a/voxygen/src/audio/soundcache.rs b/voxygen/src/audio/soundcache.rs index 545f6cef42..f08a4bdab6 100644 --- a/voxygen/src/audio/soundcache.rs +++ b/voxygen/src/audio/soundcache.rs @@ -1,7 +1,8 @@ //! Handles caching and retrieval of decoded `.ogg` sfx sound data, eliminating //! the need to decode files on each playback -use common::assets; -use std::{borrow::Cow, io, sync::Arc}; +use common::assets::{self, Loader}; +use rodio::{source::Buffered, Decoder, Source}; +use std::{borrow::Cow, io}; use tracing::warn; // Implementation of sound taken from this github issue: @@ -10,16 +11,12 @@ use tracing::warn; pub struct SoundLoader; #[derive(Clone)] -pub struct OggSound(Arc>); +pub struct OggSound(Buffered>>>); -impl AsRef<[u8]> for OggSound { - fn as_ref(&self) -> &[u8] { &self.0 } -} - -impl assets::Loader for SoundLoader { +impl Loader for SoundLoader { fn load(content: Cow<[u8]>, _: &str) -> Result { - let arc = Arc::new(content.into_owned()); - Ok(OggSound(arc)) + let source = Decoder::new(io::Cursor::new(content.into_owned()))?.buffered(); + Ok(OggSound(source)) } } @@ -37,16 +34,13 @@ impl assets::Asset for OggSound { /// Wrapper for decoded audio data impl OggSound { - pub fn decoder( - self, - ) -> Result>, rodio::decoder::DecoderError> { - let cursor = io::Cursor::new(self); - rodio::Decoder::new(cursor) - } + pub fn to_source(&self) -> impl Source + Iterator { self.0.clone() } pub fn empty() -> OggSound { - OggSound(Arc::new( - include_bytes!("../../../assets/voxygen/audio/null.ogg").to_vec(), - )) + SoundLoader::load( + Cow::Borrowed(include_bytes!("../../../assets/voxygen/audio/null.ogg")), + "empty", + ) + .unwrap() } } diff --git a/voxygen/src/scene/particle.rs b/voxygen/src/scene/particle.rs index 76bd714891..1b70bd660f 100644 --- a/voxygen/src/scene/particle.rs +++ b/voxygen/src/scene/particle.rs @@ -248,7 +248,8 @@ impl ParticleMgr { | Outcome::SkillPointGain { .. } | Outcome::ComboChange { .. } | Outcome::Damage { .. } - | Outcome::PoiseChange { .. } => {}, + | Outcome::PoiseChange { .. } + | Outcome::Utterance { .. } => {}, } } diff --git a/voxygen/src/session/mod.rs b/voxygen/src/session/mod.rs index 9abef76774..104459f2b8 100644 --- a/voxygen/src/session/mod.rs +++ b/voxygen/src/session/mod.rs @@ -15,7 +15,7 @@ use common::{ inventory::slot::{EquipSlot, Slot}, invite::InviteKind, item::{tool::ToolKind, ItemDef, ItemDesc}, - ChatMsg, ChatType, InputKind, InventoryUpdateEvent, Pos, Vel, + ChatMsg, ChatType, InputKind, InventoryUpdateEvent, Pos, UtteranceKind, Vel, }, consts::{MAX_MOUNT_RANGE, MAX_PICKUP_RANGE}, outcome::Outcome, @@ -525,6 +525,11 @@ impl PlayState for SessionState { self.client.borrow_mut().toggle_dance(); } }, + GameInput::Greet => { + if state { + self.client.borrow_mut().utter(UtteranceKind::Greeting); + } + }, GameInput::Sneak => { if state { self.stop_auto_walk(); diff --git a/voxygen/src/settings/control.rs b/voxygen/src/settings/control.rs index 67b4defb63..c0b9eb1bd6 100644 --- a/voxygen/src/settings/control.rs +++ b/voxygen/src/settings/control.rs @@ -123,6 +123,7 @@ impl ControlSettings { GameInput::Jump => KeyMouse::Key(VirtualKeyCode::Space), GameInput::Sit => KeyMouse::Key(VirtualKeyCode::K), GameInput::Dance => KeyMouse::Key(VirtualKeyCode::J), + GameInput::Greet => KeyMouse::Key(VirtualKeyCode::H), GameInput::Glide => KeyMouse::Key(VirtualKeyCode::LShift), GameInput::Climb => KeyMouse::Key(VirtualKeyCode::Space), GameInput::ClimbDown => KeyMouse::Key(VirtualKeyCode::LControl), diff --git a/voxygen/src/window.rs b/voxygen/src/window.rs index 2dfe0a9c85..118fe109e9 100644 --- a/voxygen/src/window.rs +++ b/voxygen/src/window.rs @@ -39,6 +39,7 @@ pub enum GameInput { Jump, Sit, Dance, + Greet, Glide, Climb, ClimbDown, @@ -94,6 +95,7 @@ impl GameInput { GameInput::Jump => "gameinput.jump", GameInput::Sit => "gameinput.sit", GameInput::Dance => "gameinput.dance", + GameInput::Greet => "gameinput.greet", GameInput::Glide => "gameinput.glide", GameInput::Climb => "gameinput.climb", GameInput::ClimbDown => "gameinput.climbdown", @@ -159,6 +161,7 @@ impl GameInput { GameInput::Jump, GameInput::Sit, GameInput::Dance, + GameInput::Greet, GameInput::Glide, GameInput::Climb, GameInput::ClimbDown,