diff --git a/assets/voxygen/item_image_manifest.ron b/assets/voxygen/item_image_manifest.ron index 23b1bc4f33..89bf8ae82b 100644 --- a/assets/voxygen/item_image_manifest.ron +++ b/assets/voxygen/item_image_manifest.ron @@ -2480,7 +2480,7 @@ (0.0, 0.0, 0.0), (-50.0, 30.0, 20.0), 0.9, ), Consumable("common.items.food.cheese"): VoxTrans( - "voxel.object.cheese-0", + "voxel.object.cheese", (0.0, 0.0, 0.0), (-60.0, -10.0, 0.0), 0.8, ), Consumable("common.items.food.blue_cheese"): VoxTrans( @@ -2661,8 +2661,9 @@ "voxel.object.ice_shard", (0.0, 0.0, 0.0), (10.0, -20.0, 30.0), 1.0, ), - Ingredient("FlayerBagDamaged"): Png( - "element.items.item_flayer_soul", + Ingredient("FlayerBagDamaged"): VoxTrans( + "voxel.object.glowing_remains", + (0.0, 0.0, 0.0), (10.0, -20.0, 30.0), 1.0, ), Ingredient("RaptorFeather"): VoxTrans( "voxel.object.raptor_feather", @@ -2880,8 +2881,9 @@ "voxel.object.honey", (1.0, 0.0, 0.0), (-20.0, 20.0, -30.0), 0.9, ), - Ingredient("MortarPestle"): Png( - "element.items.item_mortarpestlecoco", + Ingredient("MortarPestle"): VoxTrans( + "voxel.object.mortar_pestle", + (0.0, 0.0, 0.0), (10.0, -20.0, 30.0), 1.0, ), Ingredient("EmptyVial"): VoxTrans( "voxel.object.potion_empty", diff --git a/assets/voxygen/voxel/object/cheese.vox b/assets/voxygen/voxel/object/cheese.vox new file mode 100644 index 0000000000..e7a7b7c670 --- /dev/null +++ b/assets/voxygen/voxel/object/cheese.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:25455fa8d01e603280e842bbe7b3ce7361ca202dad31c9ca2a291c4b69250dc0 +size 3460 diff --git a/assets/voxygen/voxel/object/collar.vox b/assets/voxygen/voxel/object/collar.vox index 256c04220b..d32817159e 100644 --- a/assets/voxygen/voxel/object/collar.vox +++ b/assets/voxygen/voxel/object/collar.vox @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fad24f6450d61d5949aa64a12d35a2a2d5d7adcb1e3097b4dc5e514efc246e1b -size 55936 +oid sha256:35eb993c094e43b81e36a17a37cabec38ebace3068956d82c96c200134eb9250 +size 1424 diff --git a/assets/voxygen/voxel/object/glowing_remains.vox b/assets/voxygen/voxel/object/glowing_remains.vox new file mode 100644 index 0000000000..f9e78792de --- /dev/null +++ b/assets/voxygen/voxel/object/glowing_remains.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:740322b4c3136aecd6e9c5bbacd29f33d12bd1059d0b08a0c991be7bfa94db51 +size 1736 diff --git a/assets/voxygen/voxel/object/mortar_pestle.vox b/assets/voxygen/voxel/object/mortar_pestle.vox new file mode 100644 index 0000000000..6341a0dc6f --- /dev/null +++ b/assets/voxygen/voxel/object/mortar_pestle.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:979790e5b2311014ec1129d0b6f8ffdbbfd1663b577647c8bbf669520f333c6b +size 1612 diff --git a/assets/voxygen/voxel/object/mushroom_curry.vox b/assets/voxygen/voxel/object/mushroom_curry.vox index bf3f039bde..6c86f24907 100644 --- a/assets/voxygen/voxel/object/mushroom_curry.vox +++ b/assets/voxygen/voxel/object/mushroom_curry.vox @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cb91306dec47543cea8e5b78dfa0eabe3a5fbcf170953e71552f1c4aa46d9dbb -size 56956 +oid sha256:0a98f71348317c17ba90164f85d8e609f997b70f9de44498ff437a9d382fd7de +size 2616 diff --git a/voxygen/src/audio/sfx/event_mapper/block/mod.rs b/voxygen/src/audio/sfx/event_mapper/block/mod.rs deleted file mode 100644 index 33dce469c6..0000000000 --- a/voxygen/src/audio/sfx/event_mapper/block/mod.rs +++ /dev/null @@ -1,297 +0,0 @@ -/// EventMapper::Block watches the sound emitting blocks within -/// chunk range of the player and emits ambient sfx -use crate::{ - audio::sfx::{SfxEvent, SfxTriggerItem, SfxTriggers, SFX_DIST_LIMIT_SQR}, - scene::{terrain::BlocksOfInterest, Camera, Terrain}, - AudioFrontend, -}; - -use super::EventMapper; -use client::Client; -use common::{ - comp::Pos, - spiral::Spiral2d, - terrain::TerrainChunk, - vol::{ReadVol, RectRasterableVol}, -}; -use common_state::State; -use hashbrown::HashMap; -use rand::{thread_rng, Rng}; -use std::time::{Duration, Instant}; -use vek::*; - -#[derive(Clone, PartialEq)] -struct PreviousBlockState { - event: SfxEvent, - time: Instant, -} - -impl Default for PreviousBlockState { - fn default() -> Self { - Self { - event: SfxEvent::Idle, - time: Instant::now() - .checked_add(Duration::from_millis(thread_rng().gen_range(0..500))) - .unwrap_or_else(Instant::now), - } - } -} - -pub struct BlockEventMapper { - history: HashMap, PreviousBlockState>, -} - -impl EventMapper for BlockEventMapper { - fn maintain( - &mut self, - audio: &mut AudioFrontend, - state: &State, - player_entity: specs::Entity, - camera: &Camera, - triggers: &SfxTriggers, - terrain: &Terrain, - client: &Client, - ) { - let focus_off = camera.get_focus_pos().map(f32::trunc); - let cam_pos = camera.dependents().cam_pos + focus_off; - - // Get the player position and chunk - let player_pos = state - .read_component_copied::(player_entity) - .unwrap_or_default(); - let player_chunk = player_pos.0.xy().map2(TerrainChunk::RECT_SIZE, |e, sz| { - (e.floor() as i32).div_euclid(sz as i32) - }); - - // For determining if underground/crickets should chirp - let (terrain_alt, temp) = match client.current_chunk() { - Some(chunk) => (chunk.meta().alt(), chunk.meta().temp()), - None => (0.0, 0.0), - }; - - struct BlockSounds<'a> { - // The function to select the blocks of interest that we should emit from - blocks: fn(&'a BlocksOfInterest) -> &'a [Vec3], - // The range, in chunks, that the particles should be generated in from the player - range: usize, - // The sound of the generated particle - sfx: SfxEvent, - // The volume of the sfx - volume: f32, - // Condition that must be true to play - cond: fn(&State) -> bool, - } - - let sounds: &[BlockSounds] = &[ - BlockSounds { - blocks: |boi| &boi.leaves, - range: 1, - sfx: SfxEvent::Birdcall, - volume: 1.0, - cond: |st| st.get_day_period().is_light(), - }, - BlockSounds { - blocks: |boi| &boi.leaves, - range: 1, - sfx: SfxEvent::Owl, - volume: 1.0, - cond: |st| st.get_day_period().is_dark(), - }, - // BlockSounds { - // blocks: |boi| &boi.river, - // range: 1, - // sfx: SfxEvent::RunningWater, - // volume: 1.0, - // cond: |_| true, - // }, - //BlockSounds { - // blocks: |boi| &boi.embers, - // range: 1, - // sfx: SfxEvent::Embers, - // volume: 0.15, - // //volume: 0.05, - // cond: |_| true, - // //cond: |st| st.get_day_period().is_dark(), - //}, - BlockSounds { - blocks: |boi| &boi.frogs, - range: 1, - sfx: SfxEvent::Frog, - volume: 0.8, - cond: |st| st.get_day_period().is_dark(), - }, - //BlockSounds { - // blocks: |boi| &boi.flowers, - // range: 4, - // sfx: SfxEvent::LevelUp, - // volume: 1.0, - // cond: |st| st.get_day_period().is_dark(), - //}, - BlockSounds { - blocks: |boi| &boi.cricket1, - range: 1, - sfx: SfxEvent::Cricket1, - volume: 0.33, - cond: |st| st.get_day_period().is_dark(), - }, - BlockSounds { - blocks: |boi| &boi.cricket2, - range: 1, - sfx: SfxEvent::Cricket2, - volume: 0.33, - cond: |st| st.get_day_period().is_dark(), - }, - BlockSounds { - blocks: |boi| &boi.cricket3, - range: 1, - sfx: SfxEvent::Cricket3, - volume: 0.33, - cond: |st| st.get_day_period().is_dark(), - }, - BlockSounds { - blocks: |boi| &boi.beehives, - range: 1, - sfx: SfxEvent::Bees, - volume: 0.5, - cond: |st| st.get_day_period().is_light(), - }, - ]; - - // Iterate through each kind of block of interest - for sounds in sounds.iter() { - // If the timing condition is false, continue - // or if the player is far enough underground, continue - // TODO Address bird hack properly. See TODO on line 190 - if !(sounds.cond)(state) - || player_pos.0.z < (terrain_alt - 30.0) - || (sounds.sfx == SfxEvent::Birdcall && thread_rng().gen_bool(0.995)) - || (sounds.sfx == SfxEvent::Owl && thread_rng().gen_bool(0.998)) - || (sounds.sfx == SfxEvent::Frog && thread_rng().gen_bool(0.95)) - //Crickets will not chirp below 5 Celsius - || (sounds.sfx == SfxEvent::Cricket1 && (temp < -0.33)) - || (sounds.sfx == SfxEvent::Cricket2 && (temp < -0.33)) - || (sounds.sfx == SfxEvent::Cricket3 && (temp < -0.33)) - { - continue; - } - - // For chunks surrounding the player position - for offset in Spiral2d::new().take((sounds.range * 2 + 1).pow(2)) { - let chunk_pos = player_chunk + offset; - - // Get all the blocks of interest in this chunk - terrain.get(chunk_pos).map(|chunk_data| { - // Get the positions of the blocks of type sounds - let blocks = (sounds.blocks)(&chunk_data.blocks_of_interest); - - let absolute_pos: Vec3 = - Vec3::from(chunk_pos * TerrainChunk::RECT_SIZE.map(|e| e as i32)); - - // Iterate through each individual block - for block in blocks { - // TODO Address this hack properly, potentially by making a new - // block of interest type which picks fewer leaf blocks - // Hack to reduce the number of bird sounds (too many leaf blocks) - if ((sounds.sfx == SfxEvent::Birdcall || sounds.sfx == SfxEvent::Owl) - && thread_rng().gen_bool(0.999)) - || (sounds.sfx == SfxEvent::Frog && thread_rng().gen_bool(0.75)) - { - continue; - } - let block_pos: Vec3 = absolute_pos + block; - let internal_state = self.history.entry(block_pos).or_default(); - - let block_pos = block_pos.map(|x| x as f32); - - if Self::should_emit( - internal_state, - triggers.get_key_value(&sounds.sfx), - temp, - ) { - // If the camera is within SFX distance - if (block_pos.distance_squared(cam_pos)) < SFX_DIST_LIMIT_SQR { - let underwater = state - .terrain() - .get(cam_pos.map(|e| e.floor() as i32)) - .map(|b| b.is_liquid()) - .unwrap_or(false); - - let sfx_trigger_item = triggers.get_key_value(&sounds.sfx); - audio.emit_sfx( - sfx_trigger_item, - block_pos, - Some(sounds.volume), - underwater, - ); - } - internal_state.time = Instant::now(); - internal_state.event = sounds.sfx.clone(); - } - } - }); - } - } - } -} - -impl BlockEventMapper { - pub fn new() -> Self { - Self { - history: HashMap::new(), - } - } - - /// Ensures that: - /// 1. An sfx.ron entry exists for an SFX event - /// 2. The sfx has not been played since it's timeout threshold has elapsed, - /// which prevents firing every tick - /// Note that with so many blocks to choose from and different blocks being - /// selected each time, this is not perfect, but does reduce the number of - /// plays from blocks that have already emitted sfx and are stored in the - /// BlockEventMapper history. - fn should_emit( - previous_state: &PreviousBlockState, - sfx_trigger_item: Option<(&SfxEvent, &SfxTriggerItem)>, - temp: f32, - ) -> bool { - if let Some((event, item)) = sfx_trigger_item { - //The interval between cricket chirps calculated by converting chunk - // temperature to centigrade (we should create a function for this) and applying - // the "cricket formula" to it - let cricket_interval = (25.0 / (3.0 * ((temp * 30.0) + 15.0))).max(0.5); - if &previous_state.event == event { - //In case certain sounds need modification to their threshold, - //use match event - match event { - SfxEvent::Cricket1 => { - previous_state.time.elapsed().as_secs_f32() - >= cricket_interval + thread_rng().gen_range(-0.1..0.1) - }, - SfxEvent::Cricket2 => { - //the length and manner of this sound is quite different - if cricket_interval < 0.75 { - previous_state.time.elapsed().as_secs_f32() >= 0.75 - } else { - previous_state.time.elapsed().as_secs_f32() - >= cricket_interval + thread_rng().gen_range(-0.1..0.1) - } - }, - SfxEvent::Cricket3 => { - previous_state.time.elapsed().as_secs_f32() - >= cricket_interval + thread_rng().gen_range(-0.1..0.1) - }, - //Adds random factor to frogs (probably doesn't do anything most of the time) - SfxEvent::Frog => { - previous_state.time.elapsed().as_secs_f32() - >= thread_rng().gen_range(-2.0..2.0) - }, - _ => previous_state.time.elapsed().as_secs_f32() >= item.threshold, - } - } else { - true - } - } else { - false - } - } -} diff --git a/voxygen/src/audio/sfx/event_mapper/campfire/mod.rs b/voxygen/src/audio/sfx/event_mapper/campfire/mod.rs deleted file mode 100644 index cdd6f9ea68..0000000000 --- a/voxygen/src/audio/sfx/event_mapper/campfire/mod.rs +++ /dev/null @@ -1,129 +0,0 @@ -/// EventMapper::Campfire maps sfx to campfires -use crate::{ - audio::sfx::{SfxEvent, SfxTriggerItem, SfxTriggers, SFX_DIST_LIMIT_SQR}, - scene::{Camera, Terrain}, - AudioFrontend, -}; - -use super::EventMapper; - -use client::Client; -use common::{ - comp::{object, Body, Pos}, - terrain::TerrainChunk, - vol::ReadVol, -}; -use common_state::State; -use hashbrown::HashMap; -use specs::{Entity as EcsEntity, Join, WorldExt}; -use std::time::{Duration, Instant}; - -#[derive(Clone)] -struct PreviousEntityState { - event: SfxEvent, - time: Instant, -} - -impl Default for PreviousEntityState { - fn default() -> Self { - Self { - event: SfxEvent::Idle, - time: Instant::now(), - } - } -} - -pub struct CampfireEventMapper { - event_history: HashMap, -} - -impl EventMapper for CampfireEventMapper { - fn maintain( - &mut self, - audio: &mut AudioFrontend, - state: &State, - player_entity: specs::Entity, - camera: &Camera, - triggers: &SfxTriggers, - _terrain: &Terrain, - _client: &Client, - ) { - let ecs = state.ecs(); - let focus_off = camera.get_focus_pos().map(f32::trunc); - let cam_pos = camera.dependents().cam_pos + focus_off; - for (entity, body, pos) in ( - &ecs.entities(), - &ecs.read_storage::(), - &ecs.read_storage::(), - ) - .join() - .filter(|(_, _, e_pos)| (e_pos.0.distance_squared(cam_pos)) < SFX_DIST_LIMIT_SQR) - { - if let Body::Object(object::Body::CampfireLit) = body { - let internal_state = self.event_history.entry(entity).or_default(); - - let mapped_event = SfxEvent::Campfire; - - // Check for SFX config entry for this movement - if Self::should_emit(internal_state, triggers.get_key_value(&mapped_event)) { - let underwater = state - .terrain() - .get(cam_pos.map(|e| e.floor() as i32)) - .map(|b| b.is_liquid()) - .unwrap_or(false); - let sfx_trigger_item = triggers.get_key_value(&mapped_event); - const CAMPFIRE_VOLUME: f32 = 0.8; - audio.emit_sfx(sfx_trigger_item, pos.0, Some(CAMPFIRE_VOLUME), underwater); - internal_state.time = Instant::now(); - } - - // update state to determine the next event. We only record the time (above) if - // it was dispatched - internal_state.event = mapped_event; - } - } - self.cleanup(player_entity); - } -} - -impl CampfireEventMapper { - pub fn new() -> Self { - Self { - event_history: HashMap::new(), - } - } - - /// As the player explores the world, we track the last event of the nearby - /// entities to determine the correct SFX item to play next based on - /// their activity. `cleanup` will remove entities from event tracking if - /// they have not triggered an event for > n seconds. This prevents - /// stale records from bloating the Map size. - fn cleanup(&mut self, player: EcsEntity) { - const TRACKING_TIMEOUT: u64 = 10; - - let now = Instant::now(); - self.event_history.retain(|entity, event| { - now.duration_since(event.time) < Duration::from_secs(TRACKING_TIMEOUT) - || entity.id() == player.id() - }); - } - - /// Ensures that: - /// 1. An sfx.ron entry exists for an SFX event - /// 2. The sfx has not been played since it's timeout threshold has elapsed, - /// which prevents firing every tick - fn should_emit( - previous_state: &PreviousEntityState, - sfx_trigger_item: Option<(&SfxEvent, &SfxTriggerItem)>, - ) -> bool { - if let Some((event, item)) = sfx_trigger_item { - if &previous_state.event == event { - previous_state.time.elapsed().as_secs_f32() >= item.threshold - } else { - true - } - } else { - false - } - } -} diff --git a/voxygen/src/audio/sfx/event_mapper/combat/mod.rs b/voxygen/src/audio/sfx/event_mapper/combat/mod.rs deleted file mode 100644 index 9014010c56..0000000000 --- a/voxygen/src/audio/sfx/event_mapper/combat/mod.rs +++ /dev/null @@ -1,182 +0,0 @@ -/// EventMapper::Combat watches the combat states of surrounding entities' and -/// emits sfx related to weapons and attacks/abilities -use crate::{ - audio::sfx::{SfxEvent, SfxTriggerItem, SfxTriggers, SFX_DIST_LIMIT_SQR}, - scene::{Camera, Terrain}, - AudioFrontend, -}; - -use super::EventMapper; - -use client::Client; -use common::{ - comp::{ - inventory::slot::EquipSlot, item::ItemKind, CharacterAbilityType, CharacterState, - Inventory, Pos, - }, - terrain::TerrainChunk, - vol::ReadVol, -}; -use common_state::State; -use hashbrown::HashMap; -use specs::{Entity as EcsEntity, Join, WorldExt}; -use std::time::{Duration, Instant}; - -#[derive(Clone)] -struct PreviousEntityState { - event: SfxEvent, - time: Instant, - weapon_drawn: bool, -} - -impl Default for PreviousEntityState { - fn default() -> Self { - Self { - event: SfxEvent::Idle, - time: Instant::now(), - weapon_drawn: false, - } - } -} - -pub struct CombatEventMapper { - event_history: HashMap, -} - -impl EventMapper for CombatEventMapper { - fn maintain( - &mut self, - audio: &mut AudioFrontend, - state: &State, - player_entity: specs::Entity, - camera: &Camera, - triggers: &SfxTriggers, - _terrain: &Terrain, - _client: &Client, - ) { - let ecs = state.ecs(); - - let focus_off = camera.get_focus_pos().map(f32::trunc); - let cam_pos = camera.dependents().cam_pos + focus_off; - - for (entity, pos, inventory, character) in ( - &ecs.entities(), - &ecs.read_storage::(), - ecs.read_storage::().maybe(), - ecs.read_storage::().maybe(), - ) - .join() - .filter(|(_, e_pos, ..)| (e_pos.0.distance_squared(cam_pos)) < SFX_DIST_LIMIT_SQR) - { - if let Some(character) = character { - let sfx_state = self.event_history.entry(entity).or_default(); - - let mapped_event = inventory.map_or(SfxEvent::Idle, |inv| { - Self::map_event(character, sfx_state, inv) - }); - - // Check for SFX config entry for this movement - if Self::should_emit(sfx_state, triggers.get_key_value(&mapped_event)) { - let underwater = state - .terrain() - .get(cam_pos.map(|e| e.floor() as i32)) - .map(|b| b.is_liquid()) - .unwrap_or(false); - - let sfx_trigger_item = triggers.get_key_value(&mapped_event); - audio.emit_sfx(sfx_trigger_item, pos.0, None, underwater); - sfx_state.time = Instant::now(); - } - - // update state to determine the next event. We only record the time (above) if - // it was dispatched - sfx_state.event = mapped_event; - sfx_state.weapon_drawn = Self::weapon_drawn(character); - } - } - - self.cleanup(player_entity); - } -} - -impl CombatEventMapper { - pub fn new() -> Self { - Self { - event_history: HashMap::new(), - } - } - - /// As the player explores the world, we track the last event of the nearby - /// entities to determine the correct SFX item to play next based on - /// their activity. `cleanup` will remove entities from event tracking if - /// they have not triggered an event for > n seconds. This prevents - /// stale records from bloating the Map size. - fn cleanup(&mut self, player: EcsEntity) { - const TRACKING_TIMEOUT: u64 = 10; - - let now = Instant::now(); - self.event_history.retain(|entity, event| { - now.duration_since(event.time) < Duration::from_secs(TRACKING_TIMEOUT) - || entity.id() == player.id() - }); - } - - /// Ensures that: - /// 1. An sfx.ron entry exists for an SFX event - /// 2. The sfx has not been played since it's timeout threshold has elapsed, - /// which prevents firing every tick - fn should_emit( - previous_state: &PreviousEntityState, - sfx_trigger_item: Option<(&SfxEvent, &SfxTriggerItem)>, - ) -> bool { - if let Some((event, item)) = sfx_trigger_item { - if &previous_state.event == event { - previous_state.time.elapsed().as_secs_f32() >= item.threshold - } else { - true - } - } else { - false - } - } - - fn map_event( - character_state: &CharacterState, - previous_state: &PreviousEntityState, - inventory: &Inventory, - ) -> SfxEvent { - if let Some(item) = inventory.equipped(EquipSlot::ActiveMainhand) { - if let ItemKind::Tool(data) = item.kind() { - if character_state.is_attack() { - return SfxEvent::Attack( - CharacterAbilityType::from(character_state), - data.kind, - ); - } else if let Some(wield_event) = match ( - previous_state.weapon_drawn, - character_state.is_dodge(), - Self::weapon_drawn(character_state), - ) { - (false, false, true) => Some(SfxEvent::Wield(data.kind)), - (true, false, false) => Some(SfxEvent::Unwield(data.kind)), - _ => None, - } { - return wield_event; - } - } - // Check for attacking states - } - - SfxEvent::Idle - } - - /// This helps us determine whether we should be emitting the Wield/Unwield - /// events. For now, consider either CharacterState::Wielding or - /// ::Equipping to mean the weapon is drawn. This will need updating if the - /// animations change to match the wield_duration associated with the weapon - fn weapon_drawn(character: &CharacterState) -> bool { - character.is_wield() || matches!(character, CharacterState::Equipping { .. }) - } -} - -#[cfg(test)] mod tests; diff --git a/voxygen/src/audio/sfx/event_mapper/combat/tests.rs b/voxygen/src/audio/sfx/event_mapper/combat/tests.rs deleted file mode 100644 index 14fb308459..0000000000 --- a/voxygen/src/audio/sfx/event_mapper/combat/tests.rs +++ /dev/null @@ -1,239 +0,0 @@ -use super::*; -use crate::audio::sfx::SfxEvent; -use common::{ - combat::{self, DamageKind}, - comp::{ - inventory::loadout_builder::LoadoutBuilder, item::tool::ToolKind, CharacterAbilityType, - CharacterState, InputKind, Item, - }, - states, -}; -use std::time::{Duration, Instant}; - -#[test] -fn maps_wield_while_equipping() { - let loadout = LoadoutBuilder::empty() - .active_mainhand(Some(Item::new_from_asset_expect( - "common.items.weapons.axe.starter_axe", - ))) - .build(); - let inventory = Inventory::new_with_loadout(loadout); - - let result = CombatEventMapper::map_event( - &CharacterState::Equipping(states::equipping::Data { - static_data: states::equipping::StaticData { - buildup_duration: Duration::from_millis(10), - }, - timer: Duration::default(), - is_sneaking: false, - }), - &PreviousEntityState { - event: SfxEvent::Idle, - time: Instant::now(), - weapon_drawn: false, - }, - &inventory, - ); - - assert_eq!(result, SfxEvent::Wield(ToolKind::Axe)); -} - -#[test] -fn maps_unwield() { - let loadout = LoadoutBuilder::empty() - .active_mainhand(Some(Item::new_from_asset_expect( - "common.items.weapons.bow.starter", - ))) - .build(); - let inventory = Inventory::new_with_loadout(loadout); - - let result = CombatEventMapper::map_event( - &CharacterState::default(), - &PreviousEntityState { - event: SfxEvent::Idle, - time: Instant::now(), - weapon_drawn: true, - }, - &inventory, - ); - - assert_eq!(result, SfxEvent::Unwield(ToolKind::Bow)); -} - -#[test] -fn maps_basic_melee() { - let loadout = LoadoutBuilder::empty() - .active_mainhand(Some(Item::new_from_asset_expect( - "common.items.weapons.axe.starter_axe", - ))) - .build(); - let inventory = Inventory::new_with_loadout(loadout); - - let result = CombatEventMapper::map_event( - &CharacterState::BasicMelee(states::basic_melee::Data { - static_data: states::basic_melee::StaticData { - buildup_duration: Duration::default(), - swing_duration: Duration::default(), - recover_duration: Duration::default(), - base_damage: 10.0, - base_poise_damage: 10.0, - knockback: combat::Knockback { - strength: 0.0, - direction: combat::KnockbackDir::Away, - }, - range: 1.0, - max_angle: 1.0, - ability_info: empty_ability_info(), - damage_effect: None, - damage_kind: DamageKind::Slashing, - }, - timer: Duration::default(), - stage_section: states::utils::StageSection::Buildup, - exhausted: false, - }), - &PreviousEntityState { - event: SfxEvent::Idle, - time: Instant::now(), - weapon_drawn: true, - }, - &inventory, - ); - - assert_eq!( - result, - SfxEvent::Attack(CharacterAbilityType::BasicMelee, ToolKind::Axe) - ); -} - -#[test] -fn matches_ability_stage() { - let loadout = LoadoutBuilder::empty() - .active_mainhand(Some(Item::new_from_asset_expect( - "common.items.weapons.sword.starter", - ))) - .build(); - let inventory = Inventory::new_with_loadout(loadout); - - let result = CombatEventMapper::map_event( - &CharacterState::ComboMelee(states::combo_melee::Data { - static_data: states::combo_melee::StaticData { - num_stages: 1, - stage_data: vec![states::combo_melee::Stage { - stage: 1, - base_damage: 100.0, - base_poise_damage: 100.0, - damage_increase: 10.0, - poise_damage_increase: 10.0, - knockback: 10.0, - range: 4.0, - angle: 30.0, - base_buildup_duration: Duration::from_millis(500), - base_swing_duration: Duration::from_millis(200), - hit_timing: 0.5, - base_recover_duration: Duration::from_millis(400), - forward_movement: 0.5, - damage_kind: DamageKind::Slashing, - damage_effect: None, - }], - initial_energy_gain: 0.0, - max_energy_gain: 100.0, - energy_increase: 20.0, - speed_increase: 0.05, - max_speed_increase: 0.8, - scales_from_combo: 2, - is_interruptible: true, - ori_modifier: 1.0, - ability_info: empty_ability_info(), - }, - exhausted: false, - stage: 1, - timer: Duration::default(), - stage_section: states::utils::StageSection::Action, - }), - &PreviousEntityState { - event: SfxEvent::Idle, - time: Instant::now(), - weapon_drawn: true, - }, - &inventory, - ); - - assert_eq!( - result, - SfxEvent::Attack( - CharacterAbilityType::ComboMelee(states::utils::StageSection::Action, 1), - ToolKind::Sword - ) - ); -} - -#[test] -fn ignores_different_ability_stage() { - let loadout = LoadoutBuilder::empty() - .active_mainhand(Some(Item::new_from_asset_expect( - "common.items.weapons.axe.starter_axe", - ))) - .build(); - let inventory = Inventory::new_with_loadout(loadout); - - let result = CombatEventMapper::map_event( - &CharacterState::ComboMelee(states::combo_melee::Data { - static_data: states::combo_melee::StaticData { - num_stages: 1, - stage_data: vec![states::combo_melee::Stage { - stage: 1, - base_damage: 100.0, - base_poise_damage: 100.0, - damage_increase: 100.0, - poise_damage_increase: 10.0, - knockback: 10.0, - range: 4.0, - angle: 30.0, - base_buildup_duration: Duration::from_millis(500), - base_swing_duration: Duration::from_millis(200), - hit_timing: 0.5, - base_recover_duration: Duration::from_millis(400), - forward_movement: 0.5, - damage_kind: DamageKind::Slashing, - damage_effect: None, - }], - initial_energy_gain: 0.0, - max_energy_gain: 100.0, - energy_increase: 20.0, - speed_increase: 0.05, - max_speed_increase: 0.8, - scales_from_combo: 2, - is_interruptible: true, - ori_modifier: 1.0, - ability_info: empty_ability_info(), - }, - exhausted: false, - stage: 1, - timer: Duration::default(), - stage_section: states::utils::StageSection::Action, - }), - &PreviousEntityState { - event: SfxEvent::Idle, - time: Instant::now(), - weapon_drawn: true, - }, - &inventory, - ); - - assert_ne!( - result, - SfxEvent::Attack( - CharacterAbilityType::ComboMelee(states::utils::StageSection::Action, 2), - ToolKind::Sword - ) - ); -} - -fn empty_ability_info() -> states::utils::AbilityInfo { - states::utils::AbilityInfo { - tool: None, - hand: None, - input: InputKind::Primary, - input_attr: None, - } -} diff --git a/voxygen/src/audio/sfx/event_mapper/mod.rs b/voxygen/src/audio/sfx/event_mapper/mod.rs deleted file mode 100644 index 844c472ad9..0000000000 --- a/voxygen/src/audio/sfx/event_mapper/mod.rs +++ /dev/null @@ -1,72 +0,0 @@ -mod block; -mod campfire; -mod combat; -mod movement; - -use client::Client; -use common::terrain::TerrainChunk; -use common_state::State; - -use block::BlockEventMapper; -use campfire::CampfireEventMapper; -use combat::CombatEventMapper; -use movement::MovementEventMapper; - -use super::SfxTriggers; -use crate::{ - scene::{Camera, Terrain}, - AudioFrontend, -}; - -trait EventMapper { - fn maintain( - &mut self, - audio: &mut AudioFrontend, - state: &State, - player_entity: specs::Entity, - camera: &Camera, - triggers: &SfxTriggers, - terrain: &Terrain, - client: &Client, - ); -} - -pub struct SfxEventMapper { - mappers: Vec>, -} - -impl SfxEventMapper { - pub fn new() -> Self { - Self { - mappers: vec![ - Box::new(CombatEventMapper::new()), - Box::new(MovementEventMapper::new()), - Box::new(BlockEventMapper::new()), - Box::new(CampfireEventMapper::new()), - ], - } - } - - pub fn maintain( - &mut self, - audio: &mut AudioFrontend, - state: &State, - player_entity: specs::Entity, - camera: &Camera, - triggers: &SfxTriggers, - terrain: &Terrain, - client: &Client, - ) { - for mapper in &mut self.mappers { - mapper.maintain( - audio, - state, - player_entity, - camera, - triggers, - terrain, - client, - ); - } - } -} diff --git a/voxygen/src/audio/sfx/event_mapper/movement/tests.rs b/voxygen/src/audio/sfx/event_mapper/movement/tests.rs deleted file mode 100644 index 8a31168fc9..0000000000 --- a/voxygen/src/audio/sfx/event_mapper/movement/tests.rs +++ /dev/null @@ -1,358 +0,0 @@ -use super::*; -use crate::audio::sfx::SfxEvent; -use common::{ - comp::{ - bird_large, humanoid, quadruped_medium, quadruped_small, Body, CharacterState, InputKind, - Ori, PhysicsState, - }, - states, - terrain::{Block, BlockKind}, -}; -use std::time::{Duration, Instant}; - -#[test] -fn no_item_config_no_emit() { - let previous_state = PreviousEntityState::default(); - let result = MovementEventMapper::should_emit(&previous_state, None); - - assert!(!result); -} - -#[test] -fn config_but_played_since_threshold_no_emit() { - let trigger_item = SfxTriggerItem { - files: vec![String::from("some.path.to.sfx.file")], - threshold: 1.0, - }; - - // Triggered a 'Run' 0 seconds ago - let previous_state = PreviousEntityState { - event: SfxEvent::Run(BlockKind::Grass), - time: Instant::now(), - on_ground: true, - in_water: false, - distance_travelled: 0.0, - }; - - let result = MovementEventMapper::should_emit( - &previous_state, - Some((&SfxEvent::Run(BlockKind::Grass), &trigger_item)), - ); - - assert!(!result); -} - -#[test] -fn config_and_not_played_since_threshold_emits() { - let trigger_item = SfxTriggerItem { - files: vec![String::from("some.path.to.sfx.file")], - threshold: 0.5, - }; - - let previous_state = PreviousEntityState { - event: SfxEvent::Idle, - time: Instant::now().checked_add(Duration::from_secs(1)).unwrap(), - on_ground: true, - in_water: false, - distance_travelled: 0.0, - }; - - let result = MovementEventMapper::should_emit( - &previous_state, - Some((&SfxEvent::Run(BlockKind::Grass), &trigger_item)), - ); - - assert!(result); -} - -#[test] -fn same_previous_event_elapsed_emits() { - let trigger_item = SfxTriggerItem { - files: vec![String::from("some.path.to.sfx.file")], - threshold: 0.5, - }; - - let previous_state = PreviousEntityState { - event: SfxEvent::Run(BlockKind::Grass), - time: Instant::now() - .checked_sub(Duration::from_millis(1800)) - .unwrap(), - on_ground: true, - in_water: false, - distance_travelled: 2.0, - }; - - let result = MovementEventMapper::should_emit( - &previous_state, - Some((&SfxEvent::Run(BlockKind::Grass), &trigger_item)), - ); - - assert!(result); -} - -#[test] -fn maps_idle() { - let result = MovementEventMapper::map_movement_event( - &CharacterState::Idle(common::states::idle::Data { is_sneaking: false }), - &PhysicsState { - on_ground: Some(Block::empty()), - ..Default::default() - }, - &PreviousEntityState { - event: SfxEvent::Idle, - time: Instant::now(), - on_ground: true, - in_water: false, - distance_travelled: 0.0, - }, - Vec3::zero(), - BlockKind::Grass, - ); - - assert_eq!(result, SfxEvent::Idle); -} - -#[test] -fn maps_run_with_sufficient_velocity() { - let result = MovementEventMapper::map_movement_event( - &CharacterState::Idle(common::states::idle::Data { is_sneaking: false }), - &PhysicsState { - on_ground: Some(Block::empty()), - ..Default::default() - }, - &PreviousEntityState { - event: SfxEvent::Idle, - time: Instant::now(), - on_ground: true, - in_water: false, - distance_travelled: 0.0, - }, - Vec3::new(0.5, 0.8, 0.0), - BlockKind::Grass, - ); - - assert_eq!(result, SfxEvent::Run(BlockKind::Grass)); -} - -#[test] -fn does_not_map_run_with_insufficient_velocity() { - let result = MovementEventMapper::map_movement_event( - &CharacterState::Idle(common::states::idle::Data { is_sneaking: false }), - &PhysicsState { - on_ground: Some(Block::empty()), - ..Default::default() - }, - &PreviousEntityState { - event: SfxEvent::Idle, - time: Instant::now(), - on_ground: true, - in_water: false, - distance_travelled: 0.0, - }, - Vec3::new(0.02, 0.0001, 0.0), - BlockKind::Grass, - ); - - assert_eq!(result, SfxEvent::Idle); -} - -#[test] -fn does_not_map_run_with_sufficient_velocity_but_not_on_ground() { - let result = MovementEventMapper::map_movement_event( - &CharacterState::Idle(common::states::idle::Data { is_sneaking: false }), - &Default::default(), - &PreviousEntityState { - event: SfxEvent::Idle, - time: Instant::now(), - on_ground: false, - in_water: false, - distance_travelled: 0.0, - }, - Vec3::new(0.5, 0.8, 0.0), - BlockKind::Grass, - ); - - assert_eq!(result, SfxEvent::Idle); -} - -#[test] -fn maps_roll() { - let result = MovementEventMapper::map_movement_event( - &CharacterState::Roll(states::roll::Data { - static_data: states::roll::StaticData { - buildup_duration: Duration::default(), - movement_duration: Duration::default(), - recover_duration: Duration::default(), - roll_strength: 0.0, - immune_melee: false, - ability_info: empty_ability_info(), - }, - timer: Duration::default(), - stage_section: states::utils::StageSection::Buildup, - was_wielded: true, - is_sneaking: false, - was_combo: None, - }), - &PhysicsState { - on_ground: Some(Block::empty()), - ..Default::default() - }, - &PreviousEntityState { - event: SfxEvent::Run(BlockKind::Grass), - time: Instant::now(), - on_ground: true, - in_water: false, - distance_travelled: 0.0, - }, - Vec3::new(0.5, 0.5, 0.0), - BlockKind::Grass, - ); - - assert_eq!(result, SfxEvent::Roll); -} - -#[test] -fn maps_land_on_ground_to_run() { - let result = MovementEventMapper::map_movement_event( - &CharacterState::Idle(common::states::idle::Data { is_sneaking: false }), - &PhysicsState { - on_ground: Some(Block::empty()), - ..Default::default() - }, - &PreviousEntityState { - event: SfxEvent::Idle, - time: Instant::now(), - on_ground: false, - in_water: false, - distance_travelled: 0.0, - }, - Vec3::zero(), - BlockKind::Grass, - ); - - assert_eq!(result, SfxEvent::Run(BlockKind::Grass)); -} - -#[test] -fn maps_glider_open() { - let result = MovementEventMapper::map_movement_event( - &CharacterState::Glide(states::glide::Data::new(10.0, 1.0, Ori::default())), - &Default::default(), - &PreviousEntityState { - event: SfxEvent::Jump, - time: Instant::now(), - on_ground: false, - in_water: false, - distance_travelled: 0.0, - }, - Vec3::zero(), - BlockKind::Grass, - ); - - assert_eq!(result, SfxEvent::GliderOpen); -} - -#[test] -fn maps_glide() { - let result = MovementEventMapper::map_movement_event( - &CharacterState::Glide(states::glide::Data::new(10.0, 1.0, Ori::default())), - &Default::default(), - &PreviousEntityState { - event: SfxEvent::Glide, - time: Instant::now(), - on_ground: false, - in_water: false, - distance_travelled: 0.0, - }, - Vec3::zero(), - BlockKind::Grass, - ); - - assert_eq!(result, SfxEvent::Glide); -} - -#[test] -fn maps_glider_close_when_closing_mid_flight() { - let result = MovementEventMapper::map_movement_event( - &CharacterState::Idle(common::states::idle::Data { is_sneaking: false }), - &Default::default(), - &PreviousEntityState { - event: SfxEvent::Glide, - time: Instant::now(), - on_ground: false, - in_water: false, - distance_travelled: 0.0, - }, - Vec3::zero(), - BlockKind::Grass, - ); - - assert_eq!(result, SfxEvent::GliderClose); -} - -#[test] -#[ignore] -fn maps_glider_close_when_landing() { - let result = MovementEventMapper::map_movement_event( - &CharacterState::Idle(common::states::idle::Data { is_sneaking: false }), - &PhysicsState { - on_ground: Some(Block::empty()), - ..Default::default() - }, - &PreviousEntityState { - event: SfxEvent::Glide, - time: Instant::now(), - on_ground: false, - in_water: false, - distance_travelled: 0.0, - }, - Vec3::zero(), - BlockKind::Grass, - ); - - assert_eq!(result, SfxEvent::GliderClose); -} - -#[test] -fn maps_quadrupeds_running() { - let result = MovementEventMapper::map_non_humanoid_movement_event( - &PhysicsState { - on_ground: Some(Block::empty()), - ..Default::default() - }, - Vec3::new(0.5, 0.8, 0.0), - BlockKind::Grass, - ); - - assert_eq!(result, SfxEvent::Run(BlockKind::Grass)); -} - -#[test] -fn determines_relative_volumes() { - let human = - MovementEventMapper::get_volume_for_body_type(&Body::Humanoid(humanoid::Body::random())); - - let quadruped_medium = MovementEventMapper::get_volume_for_body_type(&Body::QuadrupedMedium( - quadruped_medium::Body::random(), - )); - - let quadruped_small = MovementEventMapper::get_volume_for_body_type(&Body::QuadrupedSmall( - quadruped_small::Body::random(), - )); - - let bird_large = - MovementEventMapper::get_volume_for_body_type(&Body::BirdLarge(bird_large::Body::random())); - - assert!(quadruped_medium < human); - assert!(quadruped_small < quadruped_medium); - assert!(bird_large < quadruped_small); -} - -fn empty_ability_info() -> states::utils::AbilityInfo { - states::utils::AbilityInfo { - tool: None, - hand: None, - input: InputKind::Primary, - input_attr: None, - } -}