mod event_mapper;
use specs::WorldExt;
use crate::{
audio::AudioFrontend,
scene::{Camera, Terrain},
};
use client::Client;
use common::{
assets::{self, AssetExt, AssetHandle},
comp::{
beam, biped_large, biped_small, bird_large, golem, humanoid,
item::{item_key::ItemKey, AbilitySpec, ItemDefinitionId, ItemDesc, ItemKind, ToolKind},
object,
poise::PoiseState,
quadruped_low, quadruped_medium, quadruped_small, Body, CharacterAbilityType, Health,
InventoryUpdateEvent, UtteranceKind,
},
outcome::Outcome,
terrain::{BlockKind, SpriteKind, TerrainChunk},
uid::Uid,
DamageSource,
};
use common_state::State;
use event_mapper::SfxEventMapper;
use hashbrown::HashMap;
use rand::prelude::*;
use serde::Deserialize;
use tracing::{debug, warn};
use vek::*;
const SFX_DIST_LIMIT_SQR: f32 = 20000.0;
#[derive(Clone, Debug, PartialEq, Deserialize, Hash, Eq)]
pub enum SfxEvent {
Campfire,
Embers,
Birdcall,
Owl,
Cricket1,
Cricket2,
Cricket3,
Frog,
Bees,
RunningWaterSlow,
RunningWaterFast,
Lavapool,
Idle,
Swim,
Run(BlockKind),
QuadRun(BlockKind),
Roll,
Sneak,
Climb,
GliderOpen,
Glide,
GliderClose,
CatchAir,
Jump,
Fall,
Attack(CharacterAbilityType, ToolKind),
Wield(ToolKind),
Unwield(ToolKind),
Inventory(SfxInventoryEvent),
Explosion,
Damage,
Death,
Parry,
Block,
BreakBlock,
SceptreBeam,
SkillPointGain,
ArrowHit,
ArrowMiss,
ArrowShot,
FireShot,
FlameThrower,
PoiseChange(PoiseState),
GroundSlam,
FlashFreeze,
GigaRoar,
IceSpikes,
IceCrack,
Utterance(UtteranceKind, VoiceKind),
Lightning,
CyclopsCharge,
TerracottaStatueCharge,
LaserBeam,
Steam,
FuseCharge,
Music(ToolKind, AbilitySpec),
Yeet,
Klonk,
SmashKlonk,
FireShockwave,
DeepLaugh,
Whoosh,
Swoosh,
GroundDig,
PortalActivated,
TeleportedByPortal,
FromTheAshes,
SurpriseEgg,
Bleep,
Charge,
}
#[derive(Copy, Clone, Debug, PartialEq, Deserialize, Hash, Eq)]
pub enum VoiceKind {
HumanFemale,
HumanMale,
BipedLarge,
Wendigo,
Reptile,
Bird,
Critter,
Sheep,
Pig,
Cow,
Canine,
Dagon,
Lion,
Mindflayer,
Marlin,
Maneater,
Adlet,
Antelope,
Alligator,
SeaCrocodile,
Saurok,
Cat,
Goat,
Mandragora,
Asp,
Fungome,
Truffler,
Wolf,
Wyvern,
Phoenix,
}
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::QuadrupedLow(body) => match body.species {
quadruped_low::Species::Maneater => VoiceKind::Maneater,
quadruped_low::Species::Alligator | quadruped_low::Species::Snaretongue => {
VoiceKind::Alligator
},
quadruped_low::Species::SeaCrocodile => VoiceKind::SeaCrocodile,
quadruped_low::Species::Dagon => VoiceKind::Dagon,
quadruped_low::Species::Asp => VoiceKind::Asp,
_ => return None,
},
Body::QuadrupedSmall(body) => match body.species {
quadruped_small::Species::Truffler => VoiceKind::Truffler,
quadruped_small::Species::Fungome => VoiceKind::Fungome,
quadruped_small::Species::Sheep => VoiceKind::Sheep,
quadruped_small::Species::Pig | quadruped_small::Species::Boar => VoiceKind::Pig,
quadruped_small::Species::Cat => VoiceKind::Cat,
quadruped_small::Species::Goat => VoiceKind::Goat,
_ => 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::Lion,
quadruped_medium::Species::Wolf => VoiceKind::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,
quadruped_medium::Species::Antelope => VoiceKind::Antelope,
_ => return None,
},
Body::BirdMedium(_) => VoiceKind::Bird,
Body::BirdLarge(body) => match body.species {
bird_large::Species::CloudWyvern
| bird_large::Species::FlameWyvern
| bird_large::Species::FrostWyvern
| bird_large::Species::SeaWyvern
| bird_large::Species::WealdWyvern => VoiceKind::Wyvern,
bird_large::Species::Phoenix => VoiceKind::Phoenix,
_ => VoiceKind::Bird,
},
Body::BipedSmall(body) => match body.species {
biped_small::Species::Adlet => VoiceKind::Adlet,
biped_small::Species::Mandragora => VoiceKind::Mandragora,
biped_small::Species::Flamekeeper => VoiceKind::BipedLarge,
_ => return None,
},
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::Saurok,
biped_large::Species::Mindflayer => VoiceKind::Mindflayer,
_ => VoiceKind::BipedLarge,
},
Body::Theropod(_) | Body::Dragon(_) => VoiceKind::Reptile,
Body::FishSmall(_) | Body::FishMedium(_) => VoiceKind::Marlin,
_ => return None,
})
}
#[derive(Clone, Debug, PartialEq, Deserialize, Hash, Eq)]
pub enum SfxInventoryEvent {
Collected,
CollectedTool(ToolKind),
CollectedItem(String),
CollectFailed,
Consumed(ItemKey),
Debug,
Dropped,
Given,
Swapped,
Craft,
}
impl From<&InventoryUpdateEvent> for SfxEvent {
fn from(value: &InventoryUpdateEvent) -> Self {
match value {
InventoryUpdateEvent::Collected(item) => {
match &*item.kind() {
ItemKind::Tool(tool) => {
SfxEvent::Inventory(SfxInventoryEvent::CollectedTool(tool.kind))
},
ItemKind::Ingredient { .. }
if matches!(
item.item_definition_id(),
ItemDefinitionId::Simple(id) if id.contains("mineral.gem.")
) =>
{
SfxEvent::Inventory(SfxInventoryEvent::CollectedItem(String::from(
"Gemstone",
)))
},
_ => SfxEvent::Inventory(SfxInventoryEvent::Collected),
}
},
InventoryUpdateEvent::BlockCollectFailed { .. }
| InventoryUpdateEvent::EntityCollectFailed { .. } => {
SfxEvent::Inventory(SfxInventoryEvent::CollectFailed)
},
InventoryUpdateEvent::Consumed(consumable) => {
SfxEvent::Inventory(SfxInventoryEvent::Consumed(consumable.clone()))
},
InventoryUpdateEvent::Debug => SfxEvent::Inventory(SfxInventoryEvent::Debug),
InventoryUpdateEvent::Dropped => SfxEvent::Inventory(SfxInventoryEvent::Dropped),
InventoryUpdateEvent::Given => SfxEvent::Inventory(SfxInventoryEvent::Given),
InventoryUpdateEvent::Swapped => SfxEvent::Inventory(SfxInventoryEvent::Swapped),
InventoryUpdateEvent::Craft => SfxEvent::Inventory(SfxInventoryEvent::Craft),
_ => SfxEvent::Inventory(SfxInventoryEvent::Swapped),
}
}
}
#[derive(Deserialize)]
pub struct SfxTriggerItem {
pub files: Vec<String>,
pub threshold: f32,
#[serde(default)]
pub subtitle: Option<String>,
}
#[derive(Deserialize, Default)]
pub struct SfxTriggers(HashMap<SfxEvent, SfxTriggerItem>);
impl SfxTriggers {
pub fn get_trigger(&self, trigger: &SfxEvent) -> Option<&SfxTriggerItem> { self.0.get(trigger) }
pub fn get_key_value(&self, trigger: &SfxEvent) -> Option<(&SfxEvent, &SfxTriggerItem)> {
self.0.get_key_value(trigger)
}
}
pub struct SfxMgr {
pub triggers: AssetHandle<SfxTriggers>,
event_mapper: SfxEventMapper,
}
impl Default for SfxMgr {
fn default() -> Self {
Self {
triggers: Self::load_sfx_items(),
event_mapper: SfxEventMapper::new(),
}
}
}
impl SfxMgr {
pub fn maintain(
&mut self,
audio: &mut AudioFrontend,
state: &State,
player_entity: specs::Entity,
camera: &Camera,
terrain: &Terrain<TerrainChunk>,
client: &Client,
) {
if !audio.sfx_enabled() && !audio.subtitles_enabled {
return;
}
let focus_off = camera.get_focus_pos().map(f32::trunc);
let cam_pos = camera.dependents().cam_pos + focus_off;
audio.set_listener_pos(cam_pos, camera.dependents().cam_dir);
let triggers = self.triggers.read();
self.event_mapper.maintain(
audio,
state,
player_entity,
camera,
&triggers,
terrain,
client,
);
}
#[allow(clippy::single_match)]
pub fn handle_outcome(
&mut self,
outcome: &Outcome,
audio: &mut AudioFrontend,
client: &Client,
underwater: bool,
) {
if !audio.sfx_enabled() && !audio.subtitles_enabled {
return;
}
let triggers = self.triggers.read();
let uids = client.state().ecs().read_storage::<Uid>();
match outcome {
Outcome::Explosion { pos, power, .. } => {
let sfx_trigger_item = triggers.get_key_value(&SfxEvent::Explosion);
audio.emit_sfx(
sfx_trigger_item,
*pos,
Some((power.abs() / 2.5).min(1.5)),
underwater,
);
},
Outcome::Lightning { pos } => {
let power = (1.0 - pos.distance(audio.listener.pos) / 5_000.0)
.max(0.0)
.powi(7);
if power > 0.0 {
let sfx_trigger_item = triggers.get_key_value(&SfxEvent::Lightning);
audio.emit_ui_sfx(sfx_trigger_item, Some((power * 3.0).min(2.9)));
}
},
Outcome::GroundSlam { pos, .. } | Outcome::ClayGolemDash { pos, .. } => {
let sfx_trigger_item = triggers.get_key_value(&SfxEvent::GroundSlam);
audio.emit_sfx(sfx_trigger_item, *pos, Some(2.0), underwater);
},
Outcome::SurpriseEgg { pos, .. } => {
let sfx_trigger_item = triggers.get_key_value(&SfxEvent::SurpriseEgg);
audio.emit_sfx(sfx_trigger_item, *pos, Some(2.0), underwater);
},
Outcome::LaserBeam { pos, .. } => {
let sfx_trigger_item = triggers.get_key_value(&SfxEvent::LaserBeam);
audio.emit_sfx(sfx_trigger_item, *pos, Some(2.0), underwater);
},
Outcome::CyclopsCharge { pos, .. } => {
let sfx_trigger_item = triggers.get_key_value(&SfxEvent::CyclopsCharge);
audio.emit_sfx(sfx_trigger_item, *pos, Some(2.0), underwater);
},
Outcome::FlamethrowerCharge { pos, .. }
| Outcome::TerracottaStatueCharge { pos, .. } => {
let sfx_trigger_item = triggers.get_key_value(&SfxEvent::CyclopsCharge);
audio.emit_sfx(sfx_trigger_item, *pos, Some(2.0), underwater);
},
Outcome::FuseCharge { pos, .. } => {
let sfx_trigger_item = triggers.get_key_value(&SfxEvent::FuseCharge);
audio.emit_sfx(sfx_trigger_item, *pos, Some(2.0), underwater);
},
Outcome::Charge { pos, .. } => {
let sfx_trigger_item = triggers.get_key_value(&SfxEvent::CyclopsCharge);
audio.emit_sfx(sfx_trigger_item, *pos, Some(2.0), underwater);
},
Outcome::FlashFreeze { pos, .. } => {
let sfx_trigger_item = triggers.get_key_value(&SfxEvent::FlashFreeze);
audio.emit_sfx(sfx_trigger_item, *pos, Some(2.0), underwater);
},
Outcome::SummonedCreature { pos, body, .. } => {
match body {
Body::BipedSmall(body) => match body.species {
biped_small::Species::IronDwarf => {
let sfx_trigger_item = triggers.get_key_value(&SfxEvent::Bleep);
audio.emit_sfx(sfx_trigger_item, *pos, Some(2.0), underwater);
},
biped_small::Species::Boreal => {
let sfx_trigger_item = triggers.get_key_value(&SfxEvent::GigaRoar);
audio.emit_sfx(sfx_trigger_item, *pos, Some(2.0), underwater);
},
biped_small::Species::ShamanicSpirit | biped_small::Species::Jiangshi => {
let sfx_trigger_item = triggers.get_key_value(&SfxEvent::Klonk);
audio.emit_sfx(sfx_trigger_item, *pos, Some(2.0), underwater);
},
_ => {},
},
Body::BipedLarge(body) => match body.species {
biped_large::Species::Cursekeeper => {
let sfx_trigger_item = triggers.get_key_value(&SfxEvent::Swoosh);
audio.emit_sfx(sfx_trigger_item, *pos, Some(2.0), underwater);
},
biped_large::Species::TerracottaBesieger
| biped_large::Species::TerracottaPursuer => {
let sfx_trigger_item = triggers.get_key_value(&SfxEvent::Klonk);
audio.emit_sfx(sfx_trigger_item, *pos, Some(2.0), underwater);
},
_ => {},
},
Body::Golem(body) => match body.species {
golem::Species::Mogwai => {
let sfx_trigger_item = triggers.get_key_value(&SfxEvent::Klonk);
audio.emit_sfx(sfx_trigger_item, *pos, Some(2.0), underwater);
},
_ => {},
},
Body::Object(object::Body::Lavathrower) => {
let sfx_trigger_item = triggers.get_key_value(&SfxEvent::DeepLaugh);
audio.emit_sfx(sfx_trigger_item, *pos, Some(2.0), underwater);
},
Body::Object(object::Body::Tornado)
| Body::Object(object::Body::FieryTornado) => {
let sfx_trigger_item = triggers.get_key_value(&SfxEvent::Swoosh);
audio.emit_sfx(sfx_trigger_item, *pos, Some(2.0), underwater);
},
_ => { },
}
},
Outcome::GroundDig { pos, .. } => {
let sfx_trigger_item = triggers.get_key_value(&SfxEvent::GroundDig);
audio.emit_sfx(sfx_trigger_item, *pos, Some(2.0), underwater);
},
Outcome::PortalActivated { pos, .. } => {
let sfx_trigger_item = triggers.get_key_value(&SfxEvent::PortalActivated);
audio.emit_sfx(sfx_trigger_item, *pos, Some(2.0), underwater);
},
Outcome::TeleportedByPortal { pos, .. } => {
let sfx_trigger_item = triggers.get_key_value(&SfxEvent::TeleportedByPortal);
audio.emit_sfx(sfx_trigger_item, *pos, Some(2.0), underwater);
},
Outcome::IceSpikes { pos, .. } => {
let sfx_trigger_item = triggers.get_key_value(&SfxEvent::IceSpikes);
audio.emit_sfx(sfx_trigger_item, *pos, Some(2.0), underwater);
},
Outcome::IceCrack { pos, .. } => {
let sfx_trigger_item = triggers.get_key_value(&SfxEvent::IceCrack);
audio.emit_sfx(sfx_trigger_item, *pos, Some(2.0), underwater);
},
Outcome::Steam { pos, .. } => {
let sfx_trigger_item = triggers.get_key_value(&SfxEvent::Steam);
audio.emit_sfx(sfx_trigger_item, *pos, Some(2.0), underwater);
},
Outcome::FireShockwave { pos, .. } => {
let sfx_trigger_item = triggers.get_key_value(&SfxEvent::FlameThrower);
audio.emit_sfx(sfx_trigger_item, *pos, Some(2.0), underwater);
},
Outcome::FromTheAshes { pos, .. } => {
let sfx_trigger_item = triggers.get_key_value(&SfxEvent::FromTheAshes);
audio.emit_sfx(sfx_trigger_item, *pos, Some(2.0), underwater);
},
Outcome::ProjectileShot { pos, body, .. } => {
match body {
Body::Object(
object::Body::Arrow
| object::Body::MultiArrow
| object::Body::ArrowSnake
| object::Body::ArrowTurret
| object::Body::ArrowClay
| object::Body::BoltBesieger
| object::Body::SpectralSwordSmall
| object::Body::SpectralSwordLarge,
) => {
let sfx_trigger_item = triggers.get_key_value(&SfxEvent::ArrowShot);
audio.emit_sfx(sfx_trigger_item, *pos, None, underwater);
},
Body::Object(
object::Body::BoltFire
| object::Body::BoltFireBig
| object::Body::BoltNature
| object::Body::BoltIcicle
| object::Body::SpearIcicle
| object::Body::GrenadeClay
| object::Body::SpitPoison,
) => {
let sfx_trigger_item = triggers.get_key_value(&SfxEvent::FireShot);
audio.emit_sfx(sfx_trigger_item, *pos, None, underwater);
},
Body::Object(object::Body::IronPikeBomb | object::Body::BubbleBomb) => {
let sfx_trigger_item = triggers.get_key_value(&SfxEvent::Whoosh);
audio.emit_sfx(sfx_trigger_item, *pos, None, underwater);
},
Body::Object(
object::Body::LaserBeam
| object::Body::LaserBeamSmall
| object::Body::LightningBolt,
) => {
let sfx_trigger_item = triggers.get_key_value(&SfxEvent::LaserBeam);
audio.emit_sfx(sfx_trigger_item, *pos, None, underwater);
},
Body::Object(object::Body::AdletTrap | object::Body::Mine) => {
let sfx_trigger_item = triggers.get_key_value(&SfxEvent::Yeet);
audio.emit_sfx(sfx_trigger_item, *pos, None, underwater);
},
_ => {
},
}
},
Outcome::ProjectileHit {
pos,
body,
source,
target,
..
} => match body {
Body::Object(
object::Body::Arrow
| object::Body::MultiArrow
| object::Body::ArrowSnake
| object::Body::ArrowTurret
| object::Body::ArrowClay
| object::Body::BoltBesieger
| object::Body::SpectralSwordSmall
| object::Body::SpectralSwordLarge,
) => {
if target.is_none() {
let sfx_trigger_item = triggers.get_key_value(&SfxEvent::ArrowMiss);
audio.emit_sfx(sfx_trigger_item, *pos, Some(2.0), underwater);
} else if *source == client.uid() {
let sfx_trigger_item = triggers.get_key_value(&SfxEvent::ArrowHit);
audio.emit_sfx(
sfx_trigger_item,
client.position().unwrap_or(*pos),
Some(2.0),
underwater,
);
} else {
let sfx_trigger_item = triggers.get_key_value(&SfxEvent::ArrowHit);
audio.emit_sfx(sfx_trigger_item, *pos, Some(2.0), underwater);
}
},
Body::Object(
object::Body::AdletTrap | object::Body::Mine | object::Body::Pebble,
) => {
if target.is_none() {
let sfx_trigger_item = triggers.get_key_value(&SfxEvent::Klonk);
audio.emit_sfx(sfx_trigger_item, *pos, Some(2.0), underwater);
} else if *source == client.uid() {
let sfx_trigger_item = triggers.get_key_value(&SfxEvent::SmashKlonk);
audio.emit_sfx(
sfx_trigger_item,
client.position().unwrap_or(*pos),
Some(2.0),
underwater,
);
} else {
let sfx_trigger_item = triggers.get_key_value(&SfxEvent::SmashKlonk);
audio.emit_sfx(sfx_trigger_item, *pos, Some(2.0), underwater);
}
},
_ => {},
},
Outcome::SkillPointGain { uid, .. } => {
if let Some(client_uid) = uids.get(client.entity()) {
if uid == client_uid {
let sfx_trigger_item = triggers.get_key_value(&SfxEvent::SkillPointGain);
audio.emit_ui_sfx(sfx_trigger_item, Some(0.4));
}
}
},
Outcome::Beam { pos, specifier } => match specifier {
beam::FrontendSpecifier::LifestealBeam
| beam::FrontendSpecifier::Steam
| beam::FrontendSpecifier::Poison
| beam::FrontendSpecifier::Ink
| beam::FrontendSpecifier::Lightning
| beam::FrontendSpecifier::Frost
| beam::FrontendSpecifier::Bubbles => {
if thread_rng().gen_bool(0.5) {
let sfx_trigger_item = triggers.get_key_value(&SfxEvent::SceptreBeam);
audio.emit_sfx(sfx_trigger_item, *pos, None, underwater);
};
},
beam::FrontendSpecifier::Flamethrower
| beam::FrontendSpecifier::Cultist
| beam::FrontendSpecifier::PhoenixLaser => {
if thread_rng().gen_bool(0.5) {
let sfx_trigger_item = triggers.get_key_value(&SfxEvent::FlameThrower);
audio.emit_sfx(sfx_trigger_item, *pos, None, underwater);
}
},
beam::FrontendSpecifier::Gravewarden | beam::FrontendSpecifier::WebStrand => {},
},
Outcome::SpriteUnlocked { pos } => {
let sfx_trigger_item = triggers.get_key_value(&SfxEvent::GliderOpen);
audio.emit_sfx(
sfx_trigger_item,
pos.map(|e| e as f32 + 0.5),
Some(2.0),
underwater,
);
},
Outcome::FailedSpriteUnlock { pos } => {
let sfx_trigger_item = triggers.get_key_value(&SfxEvent::BreakBlock);
audio.emit_sfx(
sfx_trigger_item,
pos.map(|e| e as f32 + 0.5),
Some(2.0),
underwater,
);
},
Outcome::BreakBlock { pos, .. } => {
let sfx_trigger_item = triggers.get_key_value(&SfxEvent::BreakBlock);
audio.emit_sfx(
sfx_trigger_item,
pos.map(|e| e as f32 + 0.5),
Some(3.0),
underwater,
);
},
Outcome::HealthChange { pos, info, .. } => {
if info.amount < Health::HEALTH_EPSILON
&& !matches!(info.cause, Some(DamageSource::Buff(_)))
{
let sfx_trigger_item = triggers.get_key_value(&SfxEvent::Damage);
audio.emit_sfx(sfx_trigger_item, *pos, Some(1.5), underwater);
}
},
Outcome::Death { pos, .. } => {
let sfx_trigger_item = triggers.get_key_value(&SfxEvent::Death);
audio.emit_sfx(sfx_trigger_item, *pos, Some(1.5), underwater);
},
Outcome::Block { pos, parry, .. } => {
if *parry {
let sfx_trigger_item = triggers.get_key_value(&SfxEvent::Parry);
audio.emit_sfx(sfx_trigger_item, *pos, Some(1.5), underwater);
} else {
let sfx_trigger_item = triggers.get_key_value(&SfxEvent::Block);
audio.emit_sfx(sfx_trigger_item, *pos, Some(1.5), underwater);
}
},
Outcome::PoiseChange { pos, state, .. } => match state {
PoiseState::Normal => {},
PoiseState::Interrupted => {
let sfx_trigger_item =
triggers.get_key_value(&SfxEvent::PoiseChange(PoiseState::Interrupted));
audio.emit_sfx(sfx_trigger_item, *pos, Some(1.5), underwater);
},
PoiseState::Stunned => {
let sfx_trigger_item =
triggers.get_key_value(&SfxEvent::PoiseChange(PoiseState::Stunned));
audio.emit_sfx(sfx_trigger_item, *pos, Some(1.5), underwater);
},
PoiseState::Dazed => {
let sfx_trigger_item =
triggers.get_key_value(&SfxEvent::PoiseChange(PoiseState::Dazed));
audio.emit_sfx(sfx_trigger_item, *pos, Some(1.5), underwater);
},
PoiseState::KnockedDown => {
let sfx_trigger_item =
triggers.get_key_value(&SfxEvent::PoiseChange(PoiseState::KnockedDown));
audio.emit_sfx(sfx_trigger_item, *pos, Some(1.5), underwater);
},
},
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(1.5), underwater);
} else {
debug!(
"No utterance sound effect exists for ({:?}, {:?})",
kind, voice
);
}
}
},
Outcome::Glider { pos, wielded } => {
if *wielded {
let sfx_trigger_item = triggers.get_key_value(&SfxEvent::GliderOpen);
audio.emit_sfx(sfx_trigger_item, *pos, Some(1.0), underwater);
} else {
let sfx_trigger_item = triggers.get_key_value(&SfxEvent::GliderClose);
audio.emit_sfx(sfx_trigger_item, *pos, Some(1.0), underwater);
}
},
Outcome::SpriteDelete { pos, sprite } => {
match sprite {
SpriteKind::SeaUrchin => {
let pos = pos.map(|e| e as f32 + 0.5);
let power = (0.6 - pos.distance(audio.listener.pos) / 5_000.0)
.max(0.0)
.powi(7);
let sfx_trigger_item = triggers.get_key_value(&SfxEvent::Explosion);
audio.emit_sfx(
sfx_trigger_item,
pos,
Some((power.abs() / 2.5).min(0.3)),
underwater,
);
},
_ => {},
};
},
Outcome::Whoosh { pos, .. } => {
let sfx_trigger_item = triggers.get_key_value(&SfxEvent::Whoosh);
audio.emit_sfx(
sfx_trigger_item,
pos.map(|e| e + 0.5),
Some(3.0),
underwater,
);
},
Outcome::Swoosh { pos, .. } => {
let sfx_trigger_item = triggers.get_key_value(&SfxEvent::Swoosh);
audio.emit_sfx(
sfx_trigger_item,
pos.map(|e| e + 0.5),
Some(3.0),
underwater,
);
},
Outcome::Slash { pos, .. } => {
let sfx_trigger_item = triggers.get_key_value(&SfxEvent::SmashKlonk);
audio.emit_sfx(
sfx_trigger_item,
pos.map(|e| e + 0.5),
Some(3.0),
underwater,
);
},
Outcome::Bleep { pos, .. } => {
let sfx_trigger_item = triggers.get_key_value(&SfxEvent::Bleep);
audio.emit_sfx(
sfx_trigger_item,
pos.map(|e| e + 0.5),
Some(3.0),
underwater,
);
},
Outcome::ExpChange { .. } | Outcome::ComboChange { .. } => {},
}
}
fn load_sfx_items() -> AssetHandle<SfxTriggers> {
SfxTriggers::load_or_insert_with("voxygen.audio.sfx", |error| {
warn!(
"Error reading sfx config file, sfx will not be available: {:#?}",
error
);
SfxTriggers::default()
})
}
}
impl assets::Asset for SfxTriggers {
type Loader = assets::RonLoader;
const EXTENSION: &'static str = "ron";
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_load_sfx_triggers() { let _ = SfxTriggers::load_expect("voxygen.audio.sfx"); }
}