SFX Fixes - Reinstate run, uncomment tests and make them pass, adjust

config.
This commit is contained in:
Shane Handley 2020-02-25 22:00:48 +09:00
parent d6f72876e9
commit 0d2b26a3b8
4 changed files with 357 additions and 251 deletions

View File

@ -9,7 +9,7 @@
"voxygen.audio.sfx.footsteps.stepgrass_5",
"voxygen.audio.sfx.footsteps.stepgrass_6",
],
threshold: 0.25,
threshold: 0.4,
),
GliderOpen: (
files: [
@ -23,17 +23,17 @@
],
threshold: 0.5,
),
Wield(Sword): (
Wield(Sword(Rapier)): (
files: [
"voxygen.audio.sfx.weapon.sword_out",
],
threshold: 0.5,
threshold: 1.0,
),
Unwield(Sword): (
Unwield(Sword(Rapier)): (
files: [
"voxygen.audio.sfx.weapon.sword_in",
],
threshold: 0.5,
threshold: 1.0,
),
}
)

View File

@ -39,10 +39,6 @@ pub enum SfxEvent {
Fall,
ExperienceGained,
LevelUp,
LightLantern,
ExtinguishLantern,
Attack,
AttackWolf,
Wield(comp::item::ToolKind),
Unwield(comp::item::ToolKind),
}

View File

@ -5,7 +5,7 @@ use crate::audio::sfx::{SfxTriggerItem, SfxTriggers};
use client::Client;
use common::{
comp::{Body, CharacterState, Item, ItemKind, Pos, Stats, ToolData, Vel},
comp::{Body, CharacterState, Item, ItemKind, PhysicsState, Pos, Stats, ToolData, Vel},
event::{EventBus, SfxEvent, SfxEventItem},
};
use hashbrown::HashMap;
@ -14,14 +14,26 @@ use std::time::{Duration, Instant};
use vek::*;
#[derive(Clone)]
struct LastSfxEvent {
struct PreviousEntityState {
event: SfxEvent,
weapon_drawn: bool,
time: Instant,
weapon_drawn: bool,
on_ground: bool,
}
impl Default for PreviousEntityState {
fn default() -> Self {
Self {
event: SfxEvent::Idle,
time: Instant::now(),
weapon_drawn: false,
on_ground: true,
}
}
}
pub struct MovementEventMapper {
event_history: HashMap<EcsEntity, LastSfxEvent>,
event_history: HashMap<EcsEntity, PreviousEntityState>,
}
impl MovementEventMapper {
@ -40,12 +52,13 @@ impl MovementEventMapper {
.get(client.entity())
.map_or(Vec3::zero(), |pos| pos.0);
for (entity, pos, vel, body, stats, character) in (
for (entity, pos, vel, body, stats, physics, character) in (
&ecs.entities(),
&ecs.read_storage::<Pos>(),
&ecs.read_storage::<Vel>(),
&ecs.read_storage::<Body>(),
&ecs.read_storage::<Stats>(),
&ecs.read_storage::<PhysicsState>(),
ecs.read_storage::<CharacterState>().maybe(),
)
.join()
@ -57,21 +70,17 @@ impl MovementEventMapper {
let state = self
.event_history
.entry(entity)
.or_insert_with(|| LastSfxEvent {
event: SfxEvent::Idle,
weapon_drawn: false,
time: Instant::now(),
});
.or_insert_with(|| PreviousEntityState::default());
let mapped_event = match body {
Body::Humanoid(_) => Self::map_movement_event(character, state, vel.0, stats),
Body::Humanoid(_) => {
Self::map_movement_event(character, physics, state, vel.0, stats)
},
Body::QuadrupedMedium(_)
| Body::QuadrupedSmall(_)
| Body::BirdMedium(_)
| Body::BirdSmall(_)
| Body::BipedLarge(_) => {
Self::map_non_humanoid_movement_event(character, vel.0)
},
| Body::BipedLarge(_) => Self::map_non_humanoid_movement_event(physics, vel.0),
_ => SfxEvent::Idle, // Ignore fish, critters, etc...
};
@ -85,14 +94,16 @@ impl MovementEventMapper {
Some(Self::get_volume_for_body_type(body)),
));
// Update the last play time
// Set the new previous entity state
state.event = mapped_event;
state.time = Instant::now();
state.weapon_drawn = character.is_wielded();
state.weapon_drawn = Self::weapon_drawn(character);
state.on_ground = physics.on_ground;
} else {
// Keep the last event, it may not have an SFX trigger but it helps us determine
// the next one
// If we don't dispatch the event, store this data as we can use it to determine
// the next event
state.event = mapped_event;
state.on_ground = physics.on_ground;
}
}
}
@ -121,12 +132,12 @@ impl MovementEventMapper {
/// file(s) to play) 2. The sfx has not been played since it's timeout
/// threshold has elapsed, which prevents firing every tick
fn should_emit(
last_play_entry: &LastSfxEvent,
previous_state: &PreviousEntityState,
sfx_trigger_item: Option<(&SfxEvent, &SfxTriggerItem)>,
) -> bool {
if let Some((event, item)) = sfx_trigger_item {
if &last_play_entry.event == event {
last_play_entry.time.elapsed().as_secs_f64() >= item.threshold
if &previous_state.event == event {
previous_state.time.elapsed().as_secs_f64() >= item.threshold
} else {
true
}
@ -141,8 +152,9 @@ impl MovementEventMapper {
/// entity states with some additional data into more specific
/// `SfxEvent`'s which we attach sounds to
fn map_movement_event(
current_event: &CharacterState,
previous_event: &LastSfxEvent,
character_state: &CharacterState,
physics_state: &PhysicsState,
previous_state: &PreviousEntityState,
vel: Vec3<f32>,
stats: &Stats,
) -> SfxEvent {
@ -154,19 +166,42 @@ impl MovementEventMapper {
..
}) = stats.equipment.main
{
if let Some(wield_event) =
match (previous_event.weapon_drawn, current_event.is_wielded()) {
(false, true) => Some(SfxEvent::Wield(kind)),
(true, false) => Some(SfxEvent::Unwield(kind)),
_ => None,
}
{
if let Some(wield_event) = match (
previous_state.weapon_drawn,
Self::weapon_drawn(character_state),
) {
(false, true) => Some(SfxEvent::Wield(kind)),
(true, false) => Some(SfxEvent::Unwield(kind)),
_ => None,
} {
return wield_event;
}
}
// Match the fall/land and jump states based on the on_ground status
// They may also have landed on the ground with the glider (!jump)
if let Some(jump_or_fall_event) = match (physics_state.on_ground, previous_state.on_ground)
{
(true, false) => {
if previous_state.event == SfxEvent::Glide {
Some(SfxEvent::GliderClose)
} else {
Some(SfxEvent::Run)
}
},
(false, true) => Some(SfxEvent::Jump),
_ => None,
} {
return jump_or_fall_event;
}
// Match run state
if physics_state.on_ground && vel.magnitude() > 0.1 {
return SfxEvent::Run;
}
// Match all other Movemement and Action states
match (previous_event.event, current_event) {
match (previous_state.event, character_state) {
(_, CharacterState::Roll(_)) => SfxEvent::Roll,
(_, CharacterState::Climb(_)) => SfxEvent::Climb,
(SfxEvent::Glide, CharacterState::Idle(_)) => SfxEvent::GliderClose,
@ -182,18 +217,26 @@ impl MovementEventMapper {
}
/// Maps a limited set of movements for other non-humanoid entities
fn map_non_humanoid_movement_event(current_event: &CharacterState, vel: Vec3<f32>) -> SfxEvent {
if let CharacterState::Idle(_) = current_event {
if vel.magnitude() > 0.1 {
SfxEvent::Run
} else {
SfxEvent::Idle
}
fn map_non_humanoid_movement_event(physics_state: &PhysicsState, vel: Vec3<f32>) -> SfxEvent {
if physics_state.on_ground && vel.magnitude() > 0.1 {
SfxEvent::Run
} else {
SfxEvent::Idle
}
}
/// This helps us determine whether we should be emitting the Wield/Unwield
/// events. For now, consider either CharacterState::Wielded or
/// ::Wielding 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_wielded()
|| match character {
CharacterState::Wielding(_) => true,
_ => false,
}
}
/// Returns a relative volume value for a body type. This helps us emit sfx
/// at a volume appropriate fot the entity we are emitting the event for
fn get_volume_for_body_type(body: &Body) -> f32 {

View File

@ -3,7 +3,7 @@ use common::{
assets,
comp::{
bird_small, humanoid, item::ToolKind, quadruped_medium, quadruped_small, Body,
CharacterState, Stats,
CharacterState, PhysicsState, Stats,
},
event::SfxEvent,
};
@ -11,34 +11,29 @@ use std::time::{Duration, Instant};
#[test]
fn no_item_config_no_emit() {
let last_sfx_event = LastSfxEvent {
event: SfxEvent::Idle,
weapon_drawn: false,
time: Instant::now(),
};
let result = MovementEventMapper::should_emit(&last_sfx_event, None);
let previous_state = PreviousEntityState::default();
let result = MovementEventMapper::should_emit(&previous_state, None);
assert_eq!(result, false);
}
#[test]
fn config_but_played_since_threshold_no_emit() {
let event = SfxEvent::Run;
let trigger_item = SfxTriggerItem {
files: vec![String::from("some.path.to.sfx.file")],
threshold: 1.0,
};
// Triggered a 'Run' 0 seconds ago
let last_sfx_event = LastSfxEvent {
let previous_state = PreviousEntityState {
event: SfxEvent::Run,
weapon_drawn: false,
time: Instant::now(),
weapon_drawn: false,
on_ground: true,
};
let result = MovementEventMapper::should_emit(&last_sfx_event, Some((&event, &trigger_item)));
let result =
MovementEventMapper::should_emit(&previous_state, Some((&SfxEvent::Run, &trigger_item)));
assert_eq!(result, false);
}
@ -52,35 +47,37 @@ fn config_and_not_played_since_threshold_emits() {
threshold: 0.5,
};
let last_sfx_event = LastSfxEvent {
let previous_state = PreviousEntityState {
event: SfxEvent::Idle,
weapon_drawn: false,
time: Instant::now().checked_add(Duration::from_secs(1)).unwrap(),
weapon_drawn: false,
on_ground: true,
};
let result = MovementEventMapper::should_emit(&last_sfx_event, Some((&event, &trigger_item)));
let result =
MovementEventMapper::should_emit(&previous_state, Some((&SfxEvent::Run, &trigger_item)));
assert_eq!(result, true);
}
#[test]
fn same_previous_event_elapsed_emits() {
let event = SfxEvent::Run;
let trigger_item = SfxTriggerItem {
files: vec![String::from("some.path.to.sfx.file")],
threshold: 0.5,
};
let last_sfx_event = LastSfxEvent {
let previous_state = PreviousEntityState {
event: SfxEvent::Run,
weapon_drawn: false,
time: Instant::now()
.checked_sub(Duration::from_millis(500))
.unwrap(),
weapon_drawn: false,
on_ground: true,
};
let result = MovementEventMapper::should_emit(&last_sfx_event, Some((&event, &trigger_item)));
let result =
MovementEventMapper::should_emit(&previous_state, Some((&SfxEvent::Run, &trigger_item)));
assert_eq!(result, true);
}
@ -95,10 +92,17 @@ fn maps_idle() {
let result = MovementEventMapper::map_movement_event(
&CharacterState::Idle(None),
&LastSfxEvent {
&PhysicsState {
on_ground: true,
on_wall: None,
touch_entity: None,
in_fluid: false,
},
&PreviousEntityState {
event: SfxEvent::Idle,
weapon_drawn: false,
time: Instant::now(),
weapon_drawn: false,
on_ground: true,
},
Vec3::zero(),
&stats,
@ -107,55 +111,92 @@ fn maps_idle() {
assert_eq!(result, SfxEvent::Idle);
}
// #[test]
// fn maps_run_with_sufficient_velocity() {
// let stats = Stats::new(
// String::from("test"),
// Body::Humanoid(humanoid::Body::random()),
// None,
// );
#[test]
fn maps_run_with_sufficient_velocity() {
let stats = Stats::new(
String::from("test"),
Body::Humanoid(humanoid::Body::random()),
None,
);
// let result = MovementEventMapper::map_movement_event(
// &CharacterState {
// movement: MovementState::Run,
// action: ActionState::Idle,
// },
// &LastSfxEvent {
// event: SfxEvent::Idle,
// weapon_drawn: false,
// time: Instant::now(),
// },
// Vec3::new(0.5, 0.8, 0.0),
// &stats,
// );
let result = MovementEventMapper::map_movement_event(
&CharacterState::Idle(None),
&PhysicsState {
on_ground: true,
on_wall: None,
touch_entity: None,
in_fluid: false,
},
&PreviousEntityState {
event: SfxEvent::Idle,
time: Instant::now(),
weapon_drawn: false,
on_ground: true,
},
Vec3::new(0.5, 0.8, 0.0),
&stats,
);
// assert_eq!(result, SfxEvent::Run);
// }
assert_eq!(result, SfxEvent::Run);
}
// #[test]
// fn does_not_map_run_with_insufficient_velocity() {
// let stats = Stats::new(
// String::from("test"),
// Body::Humanoid(humanoid::Body::random()),
// None,
// );
#[test]
fn does_not_map_run_with_insufficient_velocity() {
let stats = Stats::new(
String::from("test"),
Body::Humanoid(humanoid::Body::random()),
None,
);
// let result = MovementEventMapper::map_movement_event(
// &CharacterState {
// movement: MovementState::Run,
// action: ActionState::Idle,
// },
// &LastSfxEvent {
// event: SfxEvent::Idle,
// weapon_drawn: false,
// time: Instant::now(),
// },
// Vec3::new(0.02, 0.0001, 0.0),
// &stats,
// );
let result = MovementEventMapper::map_movement_event(
&CharacterState::Idle(None),
&PhysicsState {
on_ground: true,
on_wall: None,
touch_entity: None,
in_fluid: false,
},
&PreviousEntityState {
event: SfxEvent::Idle,
time: Instant::now(),
weapon_drawn: false,
on_ground: true,
},
Vec3::new(0.02, 0.0001, 0.0),
&stats,
);
// assert_eq!(result, SfxEvent::Idle);
// }
assert_eq!(result, SfxEvent::Idle);
}
#[test]
fn does_not_map_run_with_sufficient_velocity_but_not_on_ground() {
let stats = Stats::new(
String::from("test"),
Body::Humanoid(humanoid::Body::random()),
None,
);
let result = MovementEventMapper::map_movement_event(
&CharacterState::Idle(None),
&PhysicsState {
on_ground: false,
on_wall: None,
touch_entity: None,
in_fluid: false,
},
&PreviousEntityState {
event: SfxEvent::Idle,
time: Instant::now(),
weapon_drawn: false,
on_ground: false,
},
Vec3::new(0.5, 0.8, 0.0),
&stats,
);
assert_eq!(result, SfxEvent::Idle);
}
#[test]
fn maps_roll() {
@ -167,10 +208,17 @@ fn maps_roll() {
let result = MovementEventMapper::map_movement_event(
&CharacterState::Roll(None),
&LastSfxEvent {
&PhysicsState {
on_ground: true,
on_wall: None,
touch_entity: None,
in_fluid: false,
},
&PreviousEntityState {
event: SfxEvent::Run,
weapon_drawn: false,
time: Instant::now(),
weapon_drawn: false,
on_ground: true,
},
Vec3::zero(),
&stats,
@ -179,55 +227,34 @@ fn maps_roll() {
assert_eq!(result, SfxEvent::Roll);
}
// #[test]
// fn maps_fall() {
// let stats = Stats::new(
// String::from("test"),
// Body::Humanoid(humanoid::Body::random()),
// None,
// );
#[test]
fn maps_land_on_ground_to_run() {
let stats = Stats::new(
String::from("test"),
Body::Humanoid(humanoid::Body::random()),
None,
);
// let result = MovementEventMapper::map_movement_event(
// &CharacterState {
// movement: MovementState::Fall,
// action: ActionState::Idle,
// },
// &LastSfxEvent {
// event: SfxEvent::Fall,
// weapon_drawn: false,
// time: Instant::now(),
// },
// Vec3::zero(),
// &stats,
// );
let result = MovementEventMapper::map_movement_event(
&CharacterState::Idle(None),
&PhysicsState {
on_ground: true,
on_wall: None,
touch_entity: None,
in_fluid: false,
},
&PreviousEntityState {
event: SfxEvent::Idle,
time: Instant::now(),
weapon_drawn: false,
on_ground: false,
},
Vec3::zero(),
&stats,
);
// assert_eq!(result, SfxEvent::Fall);
// }
// #[test]
// fn maps_land_on_ground_to_run() {
// let stats = Stats::new(
// String::from("test"),
// Body::Humanoid(humanoid::Body::random()),
// None,
// );
// let result = MovementEventMapper::map_movement_event(
// &CharacterState {
// movement: MovementState::Stand,
// action: ActionState::Idle,
// },
// &LastSfxEvent {
// event: SfxEvent::Fall,
// weapon_drawn: false,
// time: Instant::now(),
// },
// Vec3::zero(),
// &stats,
// );
// assert_eq!(result, SfxEvent::Run);
// }
assert_eq!(result, SfxEvent::Run);
}
#[test]
fn maps_glider_open() {
@ -239,10 +266,17 @@ fn maps_glider_open() {
let result = MovementEventMapper::map_movement_event(
&CharacterState::Glide(None),
&LastSfxEvent {
&PhysicsState {
on_ground: false,
on_wall: None,
touch_entity: None,
in_fluid: false,
},
&PreviousEntityState {
event: SfxEvent::Jump,
weapon_drawn: false,
time: Instant::now(),
weapon_drawn: false,
on_ground: false,
},
Vec3::zero(),
&stats,
@ -261,10 +295,17 @@ fn maps_glide() {
let result = MovementEventMapper::map_movement_event(
&CharacterState::Glide(None),
&LastSfxEvent {
&PhysicsState {
on_ground: false,
on_wall: None,
touch_entity: None,
in_fluid: false,
},
&PreviousEntityState {
event: SfxEvent::Glide,
weapon_drawn: false,
time: Instant::now(),
weapon_drawn: false,
on_ground: false,
},
Vec3::zero(),
&stats,
@ -273,55 +314,63 @@ fn maps_glide() {
assert_eq!(result, SfxEvent::Glide);
}
// #[test]
// fn maps_glider_close_when_closing_mid_flight() {
// let stats = Stats::new(
// String::from("test"),
// Body::Humanoid(humanoid::Body::random()),
// None,
// );
#[test]
fn maps_glider_close_when_closing_mid_flight() {
let stats = Stats::new(
String::from("test"),
Body::Humanoid(humanoid::Body::random()),
None,
);
// let result = MovementEventMapper::map_movement_event(
// &CharacterState {
// movement: MovementState::Fall,
// action: ActionState::Idle,
// },
// &LastSfxEvent {
// event: SfxEvent::Glide,
// weapon_drawn: false,
// time: Instant::now(),
// },
// Vec3::zero(),
// &stats,
// );
let result = MovementEventMapper::map_movement_event(
&CharacterState::Idle(None),
&PhysicsState {
on_ground: false,
on_wall: None,
touch_entity: None,
in_fluid: false,
},
&PreviousEntityState {
event: SfxEvent::Glide,
time: Instant::now(),
weapon_drawn: false,
on_ground: false,
},
Vec3::zero(),
&stats,
);
// assert_eq!(result, SfxEvent::GliderClose);
// }
assert_eq!(result, SfxEvent::GliderClose);
}
// #[test]
// fn maps_glider_close_when_landing() {
// let stats = Stats::new(
// String::from("test"),
// Body::Humanoid(humanoid::Body::random()),
// None,
// );
#[test]
fn maps_glider_close_when_landing() {
let stats = Stats::new(
String::from("test"),
Body::Humanoid(humanoid::Body::random()),
None,
);
// let result = MovementEventMapper::map_movement_event(
// &CharacterState {
// movement: MovementState::Stand,
// action: ActionState::Idle,
// },
// &LastSfxEvent {
// event: SfxEvent::Glide,
// weapon_drawn: false,
// time: Instant::now(),
// },
// Vec3::zero(),
// &stats,
// );
let result = MovementEventMapper::map_movement_event(
&CharacterState::Idle(None),
&PhysicsState {
on_ground: true,
on_wall: None,
touch_entity: None,
in_fluid: false,
},
&PreviousEntityState {
event: SfxEvent::Glide,
time: Instant::now(),
weapon_drawn: false,
on_ground: false,
},
Vec3::zero(),
&stats,
);
// assert_eq!(result, SfxEvent::GliderClose);
// }
assert_eq!(result, SfxEvent::GliderClose);
}
#[test]
fn maps_wield() {
@ -334,11 +383,18 @@ fn maps_wield() {
);
let result = MovementEventMapper::map_movement_event(
&CharacterState::BasicAttack(None),
&LastSfxEvent {
&CharacterState::Wielding(None),
&PhysicsState {
on_ground: true,
on_wall: None,
touch_entity: None,
in_fluid: false,
},
&PreviousEntityState {
event: SfxEvent::Idle,
weapon_drawn: false,
time: Instant::now(),
weapon_drawn: false,
on_ground: true,
},
Vec3::zero(),
&stats,
@ -359,10 +415,17 @@ fn maps_unwield() {
let result = MovementEventMapper::map_movement_event(
&CharacterState::default(),
&LastSfxEvent {
&PhysicsState {
on_ground: true,
on_wall: None,
touch_entity: None,
in_fluid: false,
},
&PreviousEntityState {
event: SfxEvent::Idle,
weapon_drawn: true,
time: Instant::now(),
weapon_drawn: true,
on_ground: true,
},
Vec3::zero(),
&stats,
@ -371,45 +434,49 @@ fn maps_unwield() {
assert_eq!(result, SfxEvent::Unwield(ToolKind::Bow));
}
// #[test]
// fn does_not_map_wield_when_no_main_weapon() {
// let stats = Stats::new(
// String::from("test"),
// Body::Humanoid(humanoid::Body::random()),
// None,
// );
#[test]
fn does_not_map_wield_when_no_main_weapon() {
let stats = Stats::new(
String::from("test"),
Body::Humanoid(humanoid::Body::random()),
None,
);
// let result = MovementEventMapper::map_movement_event(
// &CharacterState {
// movement: MovementState::Run,
// action: ActionState::Wield {
// time_left: Duration::from_millis(600),
// },
// },
// &LastSfxEvent {
// event: SfxEvent::Idle,
// weapon_drawn: false,
// time: Instant::now(),
// },
// Vec3::new(0.5, 0.8, 0.0),
// &stats,
// );
let result = MovementEventMapper::map_movement_event(
&CharacterState::Wielded(None),
&PhysicsState {
on_ground: true,
on_wall: None,
touch_entity: None,
in_fluid: false,
},
&PreviousEntityState {
event: SfxEvent::Idle,
time: Instant::now(),
weapon_drawn: false,
on_ground: true,
},
Vec3::new(0.5, 0.8, 0.0),
&stats,
);
// assert_eq!(result, SfxEvent::Run);
// }
assert_eq!(result, SfxEvent::Run);
}
// #[test]
// fn maps_quadrupeds_running() {
// let result = MovementEventMapper::map_non_humanoid_movement_event(
// &CharacterState {
// movement: MovementState::Run,
// action: ActionState::Idle,
// },
// Vec3::new(0.5, 0.8, 0.0),
// );
#[test]
fn maps_quadrupeds_running() {
let result = MovementEventMapper::map_non_humanoid_movement_event(
&PhysicsState {
on_ground: true,
on_wall: None,
touch_entity: None,
in_fluid: false,
},
Vec3::new(0.5, 0.8, 0.0),
);
// assert_eq!(result, SfxEvent::Run);
// }
assert_eq!(result, SfxEvent::Run);
}
#[test]
fn determines_relative_volumes() {