Merge branch 'zesterer/grunts' into 'master'

Utterances and sound optimisation

See merge request veloren/veloren!2458
This commit is contained in:
Joshua Barretto 2021-06-16 17:10:54 +00:00
commit c63cf5f734
28 changed files with 288 additions and 45 deletions

View File

@ -10,6 +10,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added ### Added
- Added a skill tree for mining, which gains xp from mining ores and gems. - 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 - 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 ### Changed
- Entity-entity pushback is no longer applied in forced movement states like rolling and leaping. - 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 ### Removed
### Fixed ### Fixed
- Cases where no audio output could be produced before. - Cases where no audio output could be produced before.
- Significantly improved the performance of playing sound effects
## [0.10.0] - 2021-06-12 ## [0.10.0] - 2021-06-12

View File

@ -831,5 +831,56 @@
], ],
threshold: 0.2, 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,
),
} }
) )

BIN
assets/voxygen/audio/sfx/utterance/bird_angry.ogg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/audio/sfx/utterance/cow_calm.ogg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/audio/sfx/utterance/cow_calm2.ogg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/audio/sfx/utterance/cow_calm3.ogg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/audio/sfx/utterance/humanmale_greeting.ogg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/audio/sfx/utterance/ogre_angry.ogg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/audio/sfx/utterance/ogre_angry2.ogg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/audio/sfx/utterance/pig_calm.ogg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/audio/sfx/utterance/saurok_angry.ogg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/audio/sfx/utterance/sheep_calm.ogg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/audio/sfx/utterance/wendigo_angry.ogg (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -28,7 +28,7 @@ use common::{
skills::Skill, skills::Skill,
slot::Slot, slot::Slot,
ChatMode, ControlAction, ControlEvent, Controller, ControllerInputs, GroupManip, InputKind, ChatMode, ControlAction, ControlEvent, Controller, ControllerInputs, GroupManip, InputKind,
InventoryAction, InventoryEvent, InventoryUpdateEvent, InventoryAction, InventoryEvent, InventoryUpdateEvent, UtteranceKind,
}, },
event::{EventBus, LocalEvent}, event::{EventBus, LocalEvent},
grid::Grid, 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) { pub fn toggle_sneak(&mut self) {
let is_sneaking = self let is_sneaking = self
.state .state

View File

@ -1,5 +1,5 @@
use crate::{ 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, path::Chaser,
rtsim::RtSimController, rtsim::RtSimController,
trade::{PendingTrade, ReducedInventory, SiteId, SitePrices, TradeId, TradeResult}, trade::{PendingTrade, ReducedInventory, SiteId, SitePrices, TradeId, TradeResult},
@ -297,6 +297,7 @@ pub enum SoundKind {
Explosion, Explosion,
Beam, Beam,
Shockwave, Shockwave,
Utterance(UtteranceKind, Body),
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]

View File

@ -96,6 +96,15 @@ pub enum GroupManip {
AssignLeader(Uid), 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)] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum ControlEvent { pub enum ControlEvent {
//ToggleLantern, //ToggleLantern,
@ -111,6 +120,7 @@ pub enum ControlEvent {
GroupManip(GroupManip), GroupManip(GroupManip),
RemoveBuff(BuffKind), RemoveBuff(BuffKind),
Respawn, Respawn,
Utterance(UtteranceKind),
} }
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] #[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]

View File

@ -68,6 +68,7 @@ pub use self::{
controller::{ controller::{
Climb, ControlAction, ControlEvent, Controller, ControllerInputs, GroupManip, InputAttr, Climb, ControlAction, ControlEvent, Controller, ControllerInputs, GroupManip, InputAttr,
InputKind, InventoryAction, InventoryEvent, InventoryManip, MountState, Mounting, InputKind, InventoryAction, InventoryEvent, InventoryManip, MountState, Mounting,
UtteranceKind,
}, },
energy::{Energy, EnergyChange, EnergySource}, energy::{Energy, EnergyChange, EnergySource},
fluid_dynamics::Fluid, fluid_dynamics::Fluid,

View File

@ -1,5 +1,5 @@
use crate::{comp, uid::Uid}; 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 hashbrown::HashSet;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use vek::*; use vek::*;
@ -73,6 +73,11 @@ pub enum Outcome {
GroundSlam { GroundSlam {
pos: Vec3<f32>, pos: Vec3<f32>,
}, },
Utterance {
pos: Vec3<f32>,
body: comp::Body,
kind: UtteranceKind,
},
} }
impl Outcome { impl Outcome {
@ -87,7 +92,8 @@ impl Outcome {
| Outcome::Damage { pos, .. } | Outcome::Damage { pos, .. }
| Outcome::Block { pos, .. } | Outcome::Block { pos, .. }
| Outcome::PoiseChange { 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::BreakBlock { pos, .. } => Some(pos.map(|e| e as f32 + 0.5)),
Outcome::ExpChange { .. } | Outcome::ComboChange { .. } => None, Outcome::ExpChange { .. } | Outcome::ComboChange { .. } => None,
} }

View File

@ -1,5 +1,8 @@
use common::{ use common::{
comp::{BuffChange, ControlEvent, Controller}, comp::{
agent::{Sound, SoundKind},
Body, BuffChange, ControlEvent, Controller, Pos,
},
event::{EventBus, ServerEvent}, event::{EventBus, ServerEvent},
uid::UidAllocator, uid::UidAllocator,
}; };
@ -7,7 +10,7 @@ use common_ecs::{Job, Origin, Phase, System};
use specs::{ use specs::{
saveload::{Marker, MarkerAllocator}, saveload::{Marker, MarkerAllocator},
shred::ResourceId, shred::ResourceId,
Entities, Join, Read, SystemData, World, WriteStorage, Entities, Join, Read, ReadStorage, SystemData, World, WriteStorage,
}; };
use vek::*; use vek::*;
@ -16,6 +19,8 @@ pub struct ReadData<'a> {
entities: Entities<'a>, entities: Entities<'a>,
uid_allocator: Read<'a, UidAllocator>, uid_allocator: Read<'a, UidAllocator>,
server_bus: Read<'a, EventBus<ServerEvent>>, server_bus: Read<'a, EventBus<ServerEvent>>,
positions: ReadStorage<'a, Pos>,
bodies: ReadStorage<'a, Body>,
} }
#[derive(Default)] #[derive(Default)]
@ -92,6 +97,20 @@ impl<'a> System<'a> for Sys {
server_emitter.emit(ServerEvent::GroupManip(entity, manip)) server_emitter.emit(ServerEvent::GroupManip(entity, manip))
}, },
ControlEvent::Respawn => server_emitter.emit(ServerEvent::Respawn(entity)), 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 });
}
},
} }
} }
} }

View File

@ -6,7 +6,7 @@ use common::{
assets, assets,
comp::{ comp::{
self, self,
agent::{AgentEvent, Sound, MAX_LISTEN_DIST}, agent::{AgentEvent, Sound, SoundKind, MAX_LISTEN_DIST},
dialogue::Subject, dialogue::Subject,
inventory::slot::EquipSlot, inventory::slot::EquipSlot,
item, item,
@ -386,6 +386,8 @@ pub fn handle_sound(server: &mut Server, sound: &Sound) {
let positions = &ecs.read_storage::<comp::Pos>(); let positions = &ecs.read_storage::<comp::Pos>();
let agents = &mut ecs.write_storage::<comp::Agent>(); let agents = &mut ecs.write_storage::<comp::Agent>();
// TODO: Reduce the complexity of this problem by using spatial partitioning
// system
for (agent, agent_pos) in (agents, positions).join() { for (agent, agent_pos) in (agents, positions).join() {
// TODO: Use pathfinding for more dropoff around obstacles // TODO: Use pathfinding for more dropoff around obstacles
let agent_dist_sqrd = agent_pos.0.distance_squared(sound.pos); 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)); .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::<Vec<Outcome>>().push(outcome);
}
} }

View File

@ -21,7 +21,7 @@ use common::{
Agent, Alignment, BehaviorCapability, BehaviorState, Body, CharacterAbility, Agent, Alignment, BehaviorCapability, BehaviorState, Body, CharacterAbility,
CharacterState, ControlAction, ControlEvent, Controller, Energy, Health, HealthChange, CharacterState, ControlAction, ControlEvent, Controller, Energy, Health, HealthChange,
InputKind, Inventory, InventoryAction, LightEmitter, MountState, Ori, PhysicsState, Pos, InputKind, Inventory, InventoryAction, LightEmitter, MountState, Ori, PhysicsState, Pos,
Scale, SkillSet, Stats, UnresolvedChatMsg, Vel, Scale, SkillSet, Stats, UnresolvedChatMsg, UtteranceKind, Vel,
}, },
consts::GRAVITY, consts::GRAVITY,
effect::{BuffEffect, Effect}, effect::{BuffEffect, Effect},
@ -409,6 +409,14 @@ impl<'a> System<'a> for Sys {
.uid_allocator .uid_allocator
.retrieve_entity_internal(by.id()) .retrieve_entity_internal(by.id())
{ {
if agent.target.is_none() {
controller.push_event(
ControlEvent::Utterance(
UtteranceKind::Angry,
),
);
}
agent.target = Some(Target { agent.target = Some(Target {
target: attacker, target: attacker,
hostile: true, hostile: true,
@ -511,6 +519,12 @@ impl<'a> System<'a> for Sys {
&mut event_emitter, &mut event_emitter,
); );
} else { } else {
if agent.target.is_none() {
controller.push_event(ControlEvent::Utterance(
UtteranceKind::Angry,
));
}
agent.target = Some(Target { agent.target = Some(Target {
target: attacker, target: attacker,
hostile: true, hostile: true,
@ -945,6 +959,10 @@ impl<'a> AgentData<'a> {
controller.actions.push(ControlAction::Unwield); controller.actions.push(ControlAction::Unwield);
} }
if thread_rng().gen::<f32>() < 0.0015 {
controller.push_event(ControlEvent::Utterance(UtteranceKind::Calm));
}
// Sit // Sit
if thread_rng().gen::<f32>() < 0.0035 { if thread_rng().gen::<f32>() < 0.0035 {
controller.actions.push(ControlAction::Sit); controller.actions.push(ControlAction::Sit);
@ -990,6 +1008,8 @@ impl<'a> AgentData<'a> {
if self.look_toward(controller, read_data, &target) { if self.look_toward(controller, read_data, &target) {
controller.actions.push(ControlAction::Stand); controller.actions.push(ControlAction::Stand);
controller.actions.push(ControlAction::Talk); controller.actions.push(ControlAction::Talk);
controller.push_event(ControlEvent::Utterance(UtteranceKind::Greeting));
match subject { match subject {
Subject::Regular => { Subject::Regular => {
if let ( 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 .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); .map(|(e, _, _, _, _, _, _)| e);
if agent.target.is_none() && target.is_some() {
controller.push_event(ControlEvent::Utterance(UtteranceKind::Angry));
}
agent.target = target.map(|target| Target { agent.target = target.map(|target| Target {
target, target,
hostile: true, hostile: true,

View File

@ -209,7 +209,7 @@ impl AudioFrontend {
// TODO: Should this take `underwater` into consideration? // TODO: Should this take `underwater` into consideration?
match self.play_sfx(sfx_file, self.listener.pos, None, false) { match self.play_sfx(sfx_file, self.listener.pos, None, false) {
Ok(_) => {}, Ok(_) => {},
Err(e) => warn!("Failed to play sfx. {}", e), Err(e) => warn!("Failed to play sfx '{:?}'. {}", sfx_file, e),
} }
} else { } else {
debug!("Missing sfx trigger config for external sfx event.",); debug!("Missing sfx trigger config for external sfx event.",);
@ -244,7 +244,7 @@ impl AudioFrontend {
match self.play_sfx(sfx_file, position, volume, underwater) { match self.play_sfx(sfx_file, position, volume, underwater) {
Ok(_) => {}, Ok(_) => {},
Err(e) => warn!("Failed to play sfx. {}", e), Err(e) => warn!("Failed to play sfx '{:?}'. {}", sfx_file, e),
} }
} else { } else {
debug!( debug!(
@ -265,8 +265,8 @@ impl AudioFrontend {
) -> Result<(), rodio::decoder::DecoderError> { ) -> Result<(), rodio::decoder::DecoderError> {
if self.audio_stream.is_some() { if self.audio_stream.is_some() {
let sound = OggSound::load_expect(sound) let sound = OggSound::load_expect(sound)
.cloned() .read()
.decoder()? .to_source()
.amplify(vol.unwrap_or(1.0)); .amplify(vol.unwrap_or(1.0));
let listener = self.listener.clone(); let listener = self.listener.clone();
@ -291,9 +291,7 @@ impl AudioFrontend {
) { ) {
if self.audio_stream.is_some() { if self.audio_stream.is_some() {
if let Some(channel) = self.get_ambient_channel(channel_tag, volume_multiplier) { if let Some(channel) = self.get_ambient_channel(channel_tag, volume_multiplier) {
if let Ok(sound) = OggSound::load_expect(sound).cloned().decoder() { channel.play(OggSound::load_expect(sound).read().to_source());
channel.play(sound);
}
} }
} }
} }
@ -349,9 +347,7 @@ impl AudioFrontend {
fn play_music(&mut self, sound: &str, channel_tag: MusicChannelTag) { fn play_music(&mut self, sound: &str, channel_tag: MusicChannelTag) {
if self.music_enabled() { if self.music_enabled() {
if let Some(channel) = self.get_music_channel(channel_tag) { if let Some(channel) = self.get_music_channel(channel_tag) {
if let Ok(sound) = OggSound::load_expect(sound).cloned().decoder() { channel.play(OggSound::load_expect(sound).read().to_source(), channel_tag);
channel.play(sound, channel_tag);
}
} }
} }
} }

View File

@ -91,11 +91,12 @@ use client::Client;
use common::{ use common::{
assets::{self, AssetExt, AssetHandle}, assets::{self, AssetExt, AssetHandle},
comp::{ comp::{
beam, beam, biped_large, humanoid,
item::{ItemKind, ToolKind}, item::{ItemKind, ToolKind},
object, object,
poise::PoiseState, poise::PoiseState,
Body, CharacterAbilityType, InventoryUpdateEvent, quadruped_medium, quadruped_small, Body, CharacterAbilityType, InventoryUpdateEvent,
UtteranceKind,
}, },
outcome::Outcome, outcome::Outcome,
terrain::{BlockKind, TerrainChunk}, terrain::{BlockKind, TerrainChunk},
@ -105,7 +106,7 @@ use event_mapper::SfxEventMapper;
use hashbrown::HashMap; use hashbrown::HashMap;
use rand::prelude::*; use rand::prelude::*;
use serde::Deserialize; use serde::Deserialize;
use tracing::warn; use tracing::{debug, warn};
use vek::*; use vek::*;
/// We watch the states of nearby entities in order to emit SFX at their /// We watch the states of nearby entities in order to emit SFX at their
@ -182,6 +183,67 @@ pub enum SfxEvent {
FlameThrower, FlameThrower,
PoiseChange(PoiseState), PoiseChange(PoiseState),
GroundSlam, 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<VoiceKind> {
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)] #[derive(Clone, Debug, PartialEq, Deserialize, Hash, Eq)]
@ -452,6 +514,20 @@ impl SfxMgr {
audio.emit_sfx(sfx_trigger_item, *pos, None, false); 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::ExpChange { .. }
| Outcome::ComboChange { .. } | Outcome::ComboChange { .. }
| Outcome::SummonedCreature { .. } => {}, | Outcome::SummonedCreature { .. } => {},

View File

@ -1,7 +1,8 @@
//! Handles caching and retrieval of decoded `.ogg` sfx sound data, eliminating //! Handles caching and retrieval of decoded `.ogg` sfx sound data, eliminating
//! the need to decode files on each playback //! the need to decode files on each playback
use common::assets; use common::assets::{self, Loader};
use std::{borrow::Cow, io, sync::Arc}; use rodio::{source::Buffered, Decoder, Source};
use std::{borrow::Cow, io};
use tracing::warn; use tracing::warn;
// Implementation of sound taken from this github issue: // Implementation of sound taken from this github issue:
@ -10,16 +11,12 @@ use tracing::warn;
pub struct SoundLoader; pub struct SoundLoader;
#[derive(Clone)] #[derive(Clone)]
pub struct OggSound(Arc<Vec<u8>>); pub struct OggSound(Buffered<Decoder<io::Cursor<Vec<u8>>>>);
impl AsRef<[u8]> for OggSound { impl Loader<OggSound> for SoundLoader {
fn as_ref(&self) -> &[u8] { &self.0 }
}
impl assets::Loader<OggSound> for SoundLoader {
fn load(content: Cow<[u8]>, _: &str) -> Result<OggSound, assets::BoxedError> { fn load(content: Cow<[u8]>, _: &str) -> Result<OggSound, assets::BoxedError> {
let arc = Arc::new(content.into_owned()); let source = Decoder::new(io::Cursor::new(content.into_owned()))?.buffered();
Ok(OggSound(arc)) Ok(OggSound(source))
} }
} }
@ -37,16 +34,13 @@ impl assets::Asset for OggSound {
/// Wrapper for decoded audio data /// Wrapper for decoded audio data
impl OggSound { impl OggSound {
pub fn decoder( pub fn to_source(&self) -> impl Source + Iterator<Item = i16> { self.0.clone() }
self,
) -> Result<rodio::Decoder<io::Cursor<OggSound>>, rodio::decoder::DecoderError> {
let cursor = io::Cursor::new(self);
rodio::Decoder::new(cursor)
}
pub fn empty() -> OggSound { pub fn empty() -> OggSound {
OggSound(Arc::new( SoundLoader::load(
include_bytes!("../../../assets/voxygen/audio/null.ogg").to_vec(), Cow::Borrowed(include_bytes!("../../../assets/voxygen/audio/null.ogg")),
)) "empty",
)
.unwrap()
} }
} }

View File

@ -248,7 +248,8 @@ impl ParticleMgr {
| Outcome::SkillPointGain { .. } | Outcome::SkillPointGain { .. }
| Outcome::ComboChange { .. } | Outcome::ComboChange { .. }
| Outcome::Damage { .. } | Outcome::Damage { .. }
| Outcome::PoiseChange { .. } => {}, | Outcome::PoiseChange { .. }
| Outcome::Utterance { .. } => {},
} }
} }

View File

@ -15,7 +15,7 @@ use common::{
inventory::slot::{EquipSlot, Slot}, inventory::slot::{EquipSlot, Slot},
invite::InviteKind, invite::InviteKind,
item::{tool::ToolKind, ItemDef, ItemDesc}, 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}, consts::{MAX_MOUNT_RANGE, MAX_PICKUP_RANGE},
outcome::Outcome, outcome::Outcome,
@ -525,6 +525,11 @@ impl PlayState for SessionState {
self.client.borrow_mut().toggle_dance(); self.client.borrow_mut().toggle_dance();
} }
}, },
GameInput::Greet => {
if state {
self.client.borrow_mut().utter(UtteranceKind::Greeting);
}
},
GameInput::Sneak => { GameInput::Sneak => {
if state { if state {
self.stop_auto_walk(); self.stop_auto_walk();

View File

@ -123,6 +123,7 @@ impl ControlSettings {
GameInput::Jump => KeyMouse::Key(VirtualKeyCode::Space), GameInput::Jump => KeyMouse::Key(VirtualKeyCode::Space),
GameInput::Sit => KeyMouse::Key(VirtualKeyCode::K), GameInput::Sit => KeyMouse::Key(VirtualKeyCode::K),
GameInput::Dance => KeyMouse::Key(VirtualKeyCode::J), GameInput::Dance => KeyMouse::Key(VirtualKeyCode::J),
GameInput::Greet => KeyMouse::Key(VirtualKeyCode::H),
GameInput::Glide => KeyMouse::Key(VirtualKeyCode::LShift), GameInput::Glide => KeyMouse::Key(VirtualKeyCode::LShift),
GameInput::Climb => KeyMouse::Key(VirtualKeyCode::Space), GameInput::Climb => KeyMouse::Key(VirtualKeyCode::Space),
GameInput::ClimbDown => KeyMouse::Key(VirtualKeyCode::LControl), GameInput::ClimbDown => KeyMouse::Key(VirtualKeyCode::LControl),

View File

@ -39,6 +39,7 @@ pub enum GameInput {
Jump, Jump,
Sit, Sit,
Dance, Dance,
Greet,
Glide, Glide,
Climb, Climb,
ClimbDown, ClimbDown,
@ -94,6 +95,7 @@ impl GameInput {
GameInput::Jump => "gameinput.jump", GameInput::Jump => "gameinput.jump",
GameInput::Sit => "gameinput.sit", GameInput::Sit => "gameinput.sit",
GameInput::Dance => "gameinput.dance", GameInput::Dance => "gameinput.dance",
GameInput::Greet => "gameinput.greet",
GameInput::Glide => "gameinput.glide", GameInput::Glide => "gameinput.glide",
GameInput::Climb => "gameinput.climb", GameInput::Climb => "gameinput.climb",
GameInput::ClimbDown => "gameinput.climbdown", GameInput::ClimbDown => "gameinput.climbdown",
@ -159,6 +161,7 @@ impl GameInput {
GameInput::Jump, GameInput::Jump,
GameInput::Sit, GameInput::Sit,
GameInput::Dance, GameInput::Dance,
GameInput::Greet,
GameInput::Glide, GameInput::Glide,
GameInput::Climb, GameInput::Climb,
GameInput::ClimbDown, GameInput::ClimbDown,