mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Docs, make adding event mappers easier for sfx, remove placeholder
sounds.
This commit is contained in:
parent
467c154582
commit
6a1cec8860
@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Added inventory, armour and weapon saving
|
- Added inventory, armour and weapon saving
|
||||||
- Show where screenshots are saved to in the chat
|
- Show where screenshots are saved to in the chat
|
||||||
- Added basic auto walk
|
- Added basic auto walk
|
||||||
|
- Added weapon/attack sound effects
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
@ -23,64 +23,6 @@
|
|||||||
],
|
],
|
||||||
threshold: 0.5,
|
threshold: 0.5,
|
||||||
),
|
),
|
||||||
Attack(DashMelee, Sword): (
|
|
||||||
files: [
|
|
||||||
"voxygen.audio.sfx.weapon.whoosh_normal_01",
|
|
||||||
"voxygen.audio.sfx.weapon.whoosh_normal_02",
|
|
||||||
"voxygen.audio.sfx.weapon.whoosh_normal_03",
|
|
||||||
"voxygen.audio.sfx.weapon.whoosh_normal_04",
|
|
||||||
],
|
|
||||||
threshold: 1.2,
|
|
||||||
),
|
|
||||||
Attack(TripleStrike(First), Sword): (
|
|
||||||
files: [
|
|
||||||
"voxygen.audio.sfx.weapon.whoosh_normal_01",
|
|
||||||
"voxygen.audio.sfx.weapon.whoosh_normal_02",
|
|
||||||
"voxygen.audio.sfx.weapon.whoosh_normal_03",
|
|
||||||
"voxygen.audio.sfx.weapon.whoosh_normal_04",
|
|
||||||
],
|
|
||||||
threshold: 0.5,
|
|
||||||
),
|
|
||||||
Attack(TripleStrike(Second), Sword): (
|
|
||||||
files: [
|
|
||||||
"voxygen.audio.sfx.weapon.whoosh_low_01",
|
|
||||||
"voxygen.audio.sfx.weapon.whoosh_low_02",
|
|
||||||
"voxygen.audio.sfx.weapon.whoosh_low_03",
|
|
||||||
],
|
|
||||||
threshold: 0.5,
|
|
||||||
),
|
|
||||||
Attack(TripleStrike(Third), Sword): (
|
|
||||||
files: [
|
|
||||||
"voxygen.audio.sfx.weapon.whoosh_low_01",
|
|
||||||
"voxygen.audio.sfx.weapon.whoosh_low_02",
|
|
||||||
"voxygen.audio.sfx.weapon.whoosh_low_03",
|
|
||||||
],
|
|
||||||
threshold: 0.5,
|
|
||||||
),
|
|
||||||
Attack(BasicRanged, Bow): (
|
|
||||||
files: [
|
|
||||||
"voxygen.audio.sfx.weapon.bow_attack_01",
|
|
||||||
"voxygen.audio.sfx.weapon.bow_attack_02",
|
|
||||||
],
|
|
||||||
threshold: 0.5,
|
|
||||||
),
|
|
||||||
Attack(BasicMelee, Hammer): (
|
|
||||||
files: [
|
|
||||||
"voxygen.audio.sfx.weapon.whoosh_low_01",
|
|
||||||
"voxygen.audio.sfx.weapon.whoosh_low_02",
|
|
||||||
"voxygen.audio.sfx.weapon.whoosh_low_03",
|
|
||||||
],
|
|
||||||
threshold: 0.5,
|
|
||||||
),
|
|
||||||
Attack(BasicMelee, Staff): (
|
|
||||||
files: [
|
|
||||||
"voxygen.audio.sfx.weapon.whoosh_normal_01",
|
|
||||||
"voxygen.audio.sfx.weapon.whoosh_normal_02",
|
|
||||||
"voxygen.audio.sfx.weapon.whoosh_normal_03",
|
|
||||||
"voxygen.audio.sfx.weapon.whoosh_normal_04",
|
|
||||||
],
|
|
||||||
threshold: 0.5,
|
|
||||||
),
|
|
||||||
Wield(Sword): (
|
Wield(Sword): (
|
||||||
files: [
|
files: [
|
||||||
"voxygen.audio.sfx.weapon.sword_out",
|
"voxygen.audio.sfx.weapon.sword_out",
|
||||||
|
BIN
assets/voxygen/audio/sfx/weapon/bow_attack_01.wav
(Stored with Git LFS)
BIN
assets/voxygen/audio/sfx/weapon/bow_attack_01.wav
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/audio/sfx/weapon/bow_attack_02.wav
(Stored with Git LFS)
BIN
assets/voxygen/audio/sfx/weapon/bow_attack_02.wav
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/audio/sfx/weapon/sword_in.wav
(Stored with Git LFS)
BIN
assets/voxygen/audio/sfx/weapon/sword_in.wav
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/audio/sfx/weapon/sword_out.wav
(Stored with Git LFS)
BIN
assets/voxygen/audio/sfx/weapon/sword_out.wav
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/audio/sfx/weapon/sword_triple_strike_01.wav
(Stored with Git LFS)
BIN
assets/voxygen/audio/sfx/weapon/sword_triple_strike_01.wav
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/audio/sfx/weapon/sword_triple_strike_02.wav
(Stored with Git LFS)
BIN
assets/voxygen/audio/sfx/weapon/sword_triple_strike_02.wav
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/audio/sfx/weapon/sword_triple_strike_03.wav
(Stored with Git LFS)
BIN
assets/voxygen/audio/sfx/weapon/sword_triple_strike_03.wav
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/audio/sfx/weapon/whoosh_low_01.wav
(Stored with Git LFS)
BIN
assets/voxygen/audio/sfx/weapon/whoosh_low_01.wav
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/audio/sfx/weapon/whoosh_low_02.wav
(Stored with Git LFS)
BIN
assets/voxygen/audio/sfx/weapon/whoosh_low_02.wav
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/audio/sfx/weapon/whoosh_low_03.wav
(Stored with Git LFS)
BIN
assets/voxygen/audio/sfx/weapon/whoosh_low_03.wav
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/audio/sfx/weapon/whoosh_normal_01.wav
(Stored with Git LFS)
BIN
assets/voxygen/audio/sfx/weapon/whoosh_normal_01.wav
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/audio/sfx/weapon/whoosh_normal_02.wav
(Stored with Git LFS)
BIN
assets/voxygen/audio/sfx/weapon/whoosh_normal_02.wav
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/audio/sfx/weapon/whoosh_normal_03.wav
(Stored with Git LFS)
BIN
assets/voxygen/audio/sfx/weapon/whoosh_normal_03.wav
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/audio/sfx/weapon/whoosh_normal_04.wav
(Stored with Git LFS)
BIN
assets/voxygen/audio/sfx/weapon/whoosh_normal_04.wav
(Stored with Git LFS)
Binary file not shown.
@ -1,7 +1,9 @@
|
|||||||
/// event_mapper::combat watches the combat state of entities and emits
|
/// EventMapper::Combat watches the combat states of surrounding entities' and
|
||||||
/// associated sfx events
|
/// emits sfx related to weapons and attacks/abilities
|
||||||
use crate::audio::sfx::{SfxTriggerItem, SfxTriggers, SFX_DIST_LIMIT_SQR};
|
use crate::audio::sfx::{SfxTriggerItem, SfxTriggers, SFX_DIST_LIMIT_SQR};
|
||||||
|
|
||||||
|
use super::EventMapper;
|
||||||
|
|
||||||
use common::{
|
use common::{
|
||||||
comp::{
|
comp::{
|
||||||
item::{Item, ItemKind, ToolCategory},
|
item::{Item, ItemKind, ToolCategory},
|
||||||
@ -36,14 +38,8 @@ pub struct CombatEventMapper {
|
|||||||
event_history: HashMap<EcsEntity, PreviousEntityState>,
|
event_history: HashMap<EcsEntity, PreviousEntityState>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CombatEventMapper {
|
impl EventMapper for CombatEventMapper {
|
||||||
pub fn new() -> Self {
|
fn maintain(&mut self, state: &State, player_entity: EcsEntity, triggers: &SfxTriggers) {
|
||||||
Self {
|
|
||||||
event_history: HashMap::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn maintain(&mut self, state: &State, player_entity: EcsEntity, triggers: &SfxTriggers) {
|
|
||||||
let ecs = state.ecs();
|
let ecs = state.ecs();
|
||||||
|
|
||||||
let sfx_event_bus = ecs.read_resource::<EventBus<SfxEventItem>>();
|
let sfx_event_bus = ecs.read_resource::<EventBus<SfxEventItem>>();
|
||||||
@ -89,6 +85,14 @@ impl CombatEventMapper {
|
|||||||
|
|
||||||
self.cleanup(player_entity);
|
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
|
/// 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
|
/// entities to determine the correct SFX item to play next based on
|
||||||
@ -105,11 +109,10 @@ impl CombatEventMapper {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// When specific entity movements are detected, the associated sound (if
|
/// Ensures that:
|
||||||
/// any) needs to satisfy two conditions to be allowed to play:
|
/// 1. An sfx.ron entry exists for an SFX event
|
||||||
/// 1. An sfx.ron entry exists for the movement (we need to know which sound
|
/// 2. The sfx has not been played since it's timeout threshold has elapsed,
|
||||||
/// file(s) to play) 2. The sfx has not been played since it's timeout
|
/// which prevents firing every tick
|
||||||
/// threshold has elapsed, which prevents firing every tick
|
|
||||||
fn should_emit(
|
fn should_emit(
|
||||||
previous_state: &PreviousEntityState,
|
previous_state: &PreviousEntityState,
|
||||||
sfx_trigger_item: Option<(&SfxEvent, &SfxTriggerItem)>,
|
sfx_trigger_item: Option<(&SfxEvent, &SfxTriggerItem)>,
|
||||||
|
@ -98,7 +98,7 @@ fn maps_basic_melee() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn maps_triple_strike() {
|
fn matches_ability_stage() {
|
||||||
let mut loadout = Loadout::default();
|
let mut loadout = Loadout::default();
|
||||||
|
|
||||||
loadout.active_item = Some(ItemConfig {
|
loadout.active_item = Some(ItemConfig {
|
||||||
@ -131,6 +131,50 @@ fn maps_triple_strike() {
|
|||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result,
|
result,
|
||||||
SfxEvent::Attack(CharacterAbilityType::TripleStrike, ToolCategory::Sword)
|
SfxEvent::Attack(
|
||||||
|
CharacterAbilityType::TripleStrike(states::triple_strike::Stage::First),
|
||||||
|
ToolCategory::Sword
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ignores_different_ability_stage() {
|
||||||
|
let mut loadout = Loadout::default();
|
||||||
|
|
||||||
|
loadout.active_item = Some(ItemConfig {
|
||||||
|
item: assets::load_expect_cloned("common.items.weapons.sword.starter_sword"),
|
||||||
|
ability1: None,
|
||||||
|
ability2: None,
|
||||||
|
ability3: None,
|
||||||
|
block_ability: None,
|
||||||
|
dodge_ability: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
let result = CombatEventMapper::map_event(
|
||||||
|
&CharacterState::TripleStrike(states::triple_strike::Data {
|
||||||
|
base_damage: 10,
|
||||||
|
stage: states::triple_strike::Stage::Second,
|
||||||
|
stage_time_active: Duration::default(),
|
||||||
|
stage_exhausted: false,
|
||||||
|
initialized: true,
|
||||||
|
transition_style: states::triple_strike::TransitionStyle::Hold(
|
||||||
|
states::triple_strike::HoldingState::Released,
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
&PreviousEntityState {
|
||||||
|
event: SfxEvent::Idle,
|
||||||
|
time: Instant::now(),
|
||||||
|
weapon_drawn: true,
|
||||||
|
},
|
||||||
|
Some(&loadout),
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_ne!(
|
||||||
|
result,
|
||||||
|
SfxEvent::Attack(
|
||||||
|
CharacterAbilityType::TripleStrike(states::triple_strike::Stage::First),
|
||||||
|
ToolCategory::Sword
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -10,18 +10,22 @@ use progression::ProgressionEventMapper;
|
|||||||
|
|
||||||
use super::SfxTriggers;
|
use super::SfxTriggers;
|
||||||
|
|
||||||
|
trait EventMapper {
|
||||||
|
fn maintain(&mut self, state: &State, player_entity: specs::Entity, triggers: &SfxTriggers);
|
||||||
|
}
|
||||||
|
|
||||||
pub struct SfxEventMapper {
|
pub struct SfxEventMapper {
|
||||||
progression: ProgressionEventMapper,
|
mappers: Vec<Box<dyn EventMapper>>,
|
||||||
movement: MovementEventMapper,
|
|
||||||
combat: CombatEventMapper,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SfxEventMapper {
|
impl SfxEventMapper {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
progression: ProgressionEventMapper::new(),
|
mappers: vec![
|
||||||
combat: CombatEventMapper::new(),
|
Box::new(CombatEventMapper::new()),
|
||||||
movement: MovementEventMapper::new(),
|
Box::new(MovementEventMapper::new()),
|
||||||
|
Box::new(ProgressionEventMapper::new()),
|
||||||
|
],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -31,8 +35,8 @@ impl SfxEventMapper {
|
|||||||
player_entity: specs::Entity,
|
player_entity: specs::Entity,
|
||||||
triggers: &SfxTriggers,
|
triggers: &SfxTriggers,
|
||||||
) {
|
) {
|
||||||
self.progression.maintain(state, player_entity, triggers);
|
for mapper in &mut self.mappers {
|
||||||
self.movement.maintain(state, player_entity, triggers);
|
mapper.maintain(state, player_entity, triggers);
|
||||||
self.combat.maintain(state, player_entity, triggers);
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
/// event_mapper::movement watches all local entities movements and determines
|
/// EventMapper::Movement watches the movement states of surrounding entities,
|
||||||
/// which sfx to emit, and the position at which the sound should be emitted
|
/// and triggers sfx related to running, climbing and gliding, at a volume
|
||||||
/// from
|
/// proportionate to the extity's size
|
||||||
use crate::audio::sfx::{SfxTriggerItem, SfxTriggers, SFX_DIST_LIMIT_SQR};
|
use super::EventMapper;
|
||||||
|
|
||||||
|
use crate::audio::sfx::{SfxTriggerItem, SfxTriggers, SFX_DIST_LIMIT_SQR};
|
||||||
use common::{
|
use common::{
|
||||||
comp::{Body, CharacterState, PhysicsState, Pos, Vel},
|
comp::{Body, CharacterState, PhysicsState, Pos, Vel},
|
||||||
event::{EventBus, SfxEvent, SfxEventItem},
|
event::{EventBus, SfxEvent, SfxEventItem},
|
||||||
@ -34,14 +35,8 @@ pub struct MovementEventMapper {
|
|||||||
event_history: HashMap<EcsEntity, PreviousEntityState>,
|
event_history: HashMap<EcsEntity, PreviousEntityState>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MovementEventMapper {
|
impl EventMapper for MovementEventMapper {
|
||||||
pub fn new() -> Self {
|
fn maintain(&mut self, state: &State, player_entity: EcsEntity, triggers: &SfxTriggers) {
|
||||||
Self {
|
|
||||||
event_history: HashMap::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn maintain(&mut self, state: &State, player_entity: EcsEntity, triggers: &SfxTriggers) {
|
|
||||||
let ecs = state.ecs();
|
let ecs = state.ecs();
|
||||||
|
|
||||||
let sfx_event_bus = ecs.read_resource::<EventBus<SfxEventItem>>();
|
let sfx_event_bus = ecs.read_resource::<EventBus<SfxEventItem>>();
|
||||||
@ -101,6 +96,14 @@ impl MovementEventMapper {
|
|||||||
|
|
||||||
self.cleanup(player_entity);
|
self.cleanup(player_entity);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MovementEventMapper {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
event_history: HashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// As the player explores the world, we track the last event of the nearby
|
/// 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
|
/// entities to determine the correct SFX item to play next based on
|
||||||
|
@ -1,122 +0,0 @@
|
|||||||
/// event_mapper::progression watches the the current player's level
|
|
||||||
/// and experience and emits associated SFX
|
|
||||||
use crate::audio::sfx::SfxTriggers;
|
|
||||||
|
|
||||||
use common::{
|
|
||||||
comp::Stats,
|
|
||||||
event::{EventBus, SfxEvent, SfxEventItem},
|
|
||||||
state::State,
|
|
||||||
};
|
|
||||||
use specs::WorldExt;
|
|
||||||
|
|
||||||
#[derive(Clone, PartialEq)]
|
|
||||||
struct ProgressionState {
|
|
||||||
level: u32,
|
|
||||||
exp: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for ProgressionState {
|
|
||||||
fn default() -> Self { Self { level: 1, exp: 0 } }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ProgressionEventMapper {
|
|
||||||
state: ProgressionState,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ProgressionEventMapper {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
state: ProgressionState::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn maintain(
|
|
||||||
&mut self,
|
|
||||||
state: &State,
|
|
||||||
player_entity: specs::Entity,
|
|
||||||
triggers: &SfxTriggers,
|
|
||||||
) {
|
|
||||||
let ecs = state.ecs();
|
|
||||||
|
|
||||||
// level and exp changes
|
|
||||||
let next_state =
|
|
||||||
ecs.read_storage::<Stats>()
|
|
||||||
.get(player_entity)
|
|
||||||
.map_or(self.state.clone(), |stats| ProgressionState {
|
|
||||||
level: stats.level.level(),
|
|
||||||
exp: stats.exp.current(),
|
|
||||||
});
|
|
||||||
|
|
||||||
if &self.state != &next_state {
|
|
||||||
if let Some(mapped_event) = self.map_event(&next_state) {
|
|
||||||
let sfx_trigger_item = triggers.get_trigger(&mapped_event);
|
|
||||||
|
|
||||||
if sfx_trigger_item.is_some() {
|
|
||||||
ecs.read_resource::<EventBus<SfxEventItem>>()
|
|
||||||
.emit_now(SfxEventItem::at_player_position(mapped_event));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.state = next_state;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn map_event(&mut self, next_state: &ProgressionState) -> Option<SfxEvent> {
|
|
||||||
let sfx_event = if next_state.level > self.state.level {
|
|
||||||
Some(SfxEvent::LevelUp)
|
|
||||||
} else if next_state.exp > self.state.exp {
|
|
||||||
Some(SfxEvent::ExperienceGained)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
sfx_event
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
use common::event::SfxEvent;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn no_change_returns_none() {
|
|
||||||
let mut mapper = ProgressionEventMapper::new();
|
|
||||||
let next_client_state = ProgressionState::default();
|
|
||||||
|
|
||||||
assert_eq!(mapper.map_event(&next_client_state), None);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn change_level_returns_levelup() {
|
|
||||||
let mut mapper = ProgressionEventMapper::new();
|
|
||||||
let next_client_state = ProgressionState { level: 2, exp: 0 };
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
mapper.map_event(&next_client_state),
|
|
||||||
Some(SfxEvent::LevelUp)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn change_exp_returns_expup() {
|
|
||||||
let mut mapper = ProgressionEventMapper::new();
|
|
||||||
let next_client_state = ProgressionState { level: 1, exp: 100 };
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
mapper.map_event(&next_client_state),
|
|
||||||
Some(SfxEvent::ExperienceGained)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn level_up_and_gained_exp_prioritises_levelup() {
|
|
||||||
let mut mapper = ProgressionEventMapper::new();
|
|
||||||
let next_client_state = ProgressionState { level: 2, exp: 100 };
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
mapper.map_event(&next_client_state),
|
|
||||||
Some(SfxEvent::LevelUp)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
75
voxygen/src/audio/sfx/event_mapper/progression/mod.rs
Normal file
75
voxygen/src/audio/sfx/event_mapper/progression/mod.rs
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
/// EventMapper::Progress watches the player entity's stats
|
||||||
|
/// and triggers sfx for gaining experience and levelling up
|
||||||
|
use super::EventMapper;
|
||||||
|
|
||||||
|
use crate::audio::sfx::SfxTriggers;
|
||||||
|
|
||||||
|
use common::{
|
||||||
|
comp::Stats,
|
||||||
|
event::{EventBus, SfxEvent, SfxEventItem},
|
||||||
|
state::State,
|
||||||
|
};
|
||||||
|
use specs::WorldExt;
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq)]
|
||||||
|
struct ProgressionState {
|
||||||
|
level: u32,
|
||||||
|
exp: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ProgressionState {
|
||||||
|
fn default() -> Self { Self { level: 1, exp: 0 } }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ProgressionEventMapper {
|
||||||
|
state: ProgressionState,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EventMapper for ProgressionEventMapper {
|
||||||
|
fn maintain(&mut self, state: &State, player_entity: specs::Entity, triggers: &SfxTriggers) {
|
||||||
|
let ecs = state.ecs();
|
||||||
|
|
||||||
|
let next_state = ecs.read_storage::<Stats>().get(player_entity).map_or(
|
||||||
|
ProgressionState::default(),
|
||||||
|
|stats| ProgressionState {
|
||||||
|
level: stats.level.level(),
|
||||||
|
exp: stats.exp.current(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if &self.state != &next_state {
|
||||||
|
if let Some(mapped_event) = self.map_event(&next_state) {
|
||||||
|
let sfx_trigger_item = triggers.get_trigger(&mapped_event);
|
||||||
|
|
||||||
|
if sfx_trigger_item.is_some() {
|
||||||
|
ecs.read_resource::<EventBus<SfxEventItem>>()
|
||||||
|
.emit_now(SfxEventItem::at_player_position(mapped_event));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.state = next_state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProgressionEventMapper {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
state: ProgressionState::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn map_event(&mut self, next_state: &ProgressionState) -> Option<SfxEvent> {
|
||||||
|
let sfx_event = if next_state.level > self.state.level {
|
||||||
|
Some(SfxEvent::LevelUp)
|
||||||
|
} else if next_state.exp > self.state.exp {
|
||||||
|
Some(SfxEvent::ExperienceGained)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
sfx_event
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)] mod tests;
|
43
voxygen/src/audio/sfx/event_mapper/progression/tests.rs
Normal file
43
voxygen/src/audio/sfx/event_mapper/progression/tests.rs
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
use super::*;
|
||||||
|
use common::event::SfxEvent;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_change_returns_none() {
|
||||||
|
let mut mapper = ProgressionEventMapper::new();
|
||||||
|
let next_client_state = ProgressionState::default();
|
||||||
|
|
||||||
|
assert_eq!(mapper.map_event(&next_client_state), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn change_level_returns_levelup() {
|
||||||
|
let mut mapper = ProgressionEventMapper::new();
|
||||||
|
let next_client_state = ProgressionState { level: 2, exp: 0 };
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
mapper.map_event(&next_client_state),
|
||||||
|
Some(SfxEvent::LevelUp)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn change_exp_returns_expup() {
|
||||||
|
let mut mapper = ProgressionEventMapper::new();
|
||||||
|
let next_client_state = ProgressionState { level: 1, exp: 100 };
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
mapper.map_event(&next_client_state),
|
||||||
|
Some(SfxEvent::ExperienceGained)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn level_up_and_gained_exp_prioritises_levelup() {
|
||||||
|
let mut mapper = ProgressionEventMapper::new();
|
||||||
|
let next_client_state = ProgressionState { level: 2, exp: 100 };
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
mapper.map_event(&next_client_state),
|
||||||
|
Some(SfxEvent::LevelUp)
|
||||||
|
);
|
||||||
|
}
|
@ -1,6 +1,85 @@
|
|||||||
/// The Sfx Manager manages individual sfx event system, listens for
|
//! Manages individual sfx event system, listens for sfx events, and requests
|
||||||
/// SFX events and plays the sound at the requested position, or the current
|
//! playback at the requested position and volume
|
||||||
/// player position
|
//!
|
||||||
|
//! Veloren's sfx are managed through a configuration which lives in the
|
||||||
|
//! codebase under `/assets/voxygen/audio/sfx.ron`.
|
||||||
|
//!
|
||||||
|
//! Each entry in the configuration consists of an
|
||||||
|
//! [SfxEvent](../../../veloren_common/event/enum.SfxEvent.html) item, with some
|
||||||
|
//! additional information to allow playback:
|
||||||
|
//! - `files` - the paths to the `.wav` files to be played for the sfx. minus
|
||||||
|
//! the file extension. This can be a single item if the same sound can be
|
||||||
|
//! played each time, or a list of files from which one is chosen at random to
|
||||||
|
//! be played.
|
||||||
|
//! - `threshold` - the time that the system should wait between successive
|
||||||
|
//! plays.
|
||||||
|
//!
|
||||||
|
//! The following snippet details some entries in the configuration and how they
|
||||||
|
//! map to the sound files:
|
||||||
|
//! ```ignore
|
||||||
|
//! Run: (
|
||||||
|
//! files: [
|
||||||
|
//! "voxygen.audio.sfx.footsteps.stepgrass_1",
|
||||||
|
//! "voxygen.audio.sfx.footsteps.stepgrass_2",
|
||||||
|
//! "voxygen.audio.sfx.footsteps.stepgrass_3",
|
||||||
|
//! "voxygen.audio.sfx.footsteps.stepgrass_4",
|
||||||
|
//! "voxygen.audio.sfx.footsteps.stepgrass_5",
|
||||||
|
//! "voxygen.audio.sfx.footsteps.stepgrass_6",
|
||||||
|
//! ],
|
||||||
|
//! threshold: 0.25, // wait 0.25s between plays
|
||||||
|
//! ),
|
||||||
|
//! Wield(Sword): ( // depends on the player's weapon
|
||||||
|
//! files: [
|
||||||
|
//! "voxygen.audio.sfx.weapon.sword_out",
|
||||||
|
//! ],
|
||||||
|
//! threshold: 0.5,
|
||||||
|
//! ),
|
||||||
|
//! ...
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! These items (for example, the `Wield(Sword)` occasionally depend on some
|
||||||
|
//! property which varies in game. The
|
||||||
|
//! [SfxEvent](../../../veloren_common/event/enum.SfxEvent.html) documentation
|
||||||
|
//! provides links to those variables, some examples are provided her for longer
|
||||||
|
//! items:
|
||||||
|
//!
|
||||||
|
//! ```ignore
|
||||||
|
//! // An inventory action
|
||||||
|
//! Inventory(Dropped): (
|
||||||
|
//! files: [
|
||||||
|
//! "voxygen.audio.sfx.footsteps.stepgrass_4",
|
||||||
|
//! ],
|
||||||
|
//! threshold: 0.5,
|
||||||
|
//! ),
|
||||||
|
//! // An inventory action which depends upon the item
|
||||||
|
//! Inventory(Consumed(Apple)): (
|
||||||
|
//! files: [
|
||||||
|
//! "voxygen.audio.sfx.inventory.consumable.apple",
|
||||||
|
//! ],
|
||||||
|
//! threshold: 0.5
|
||||||
|
//! ),
|
||||||
|
//! // An attack ability which depends on the weapon
|
||||||
|
//! Attack(DashMelee, Sword): (
|
||||||
|
//! files: [
|
||||||
|
//! "voxygen.audio.sfx.weapon.whoosh_normal_01",
|
||||||
|
//! "voxygen.audio.sfx.weapon.whoosh_normal_02",
|
||||||
|
//! "voxygen.audio.sfx.weapon.whoosh_normal_03",
|
||||||
|
//! "voxygen.audio.sfx.weapon.whoosh_normal_04",
|
||||||
|
//! ],
|
||||||
|
//! threshold: 1.2,
|
||||||
|
//! ),
|
||||||
|
//! // A multi-stage attack ability which depends on the weapon
|
||||||
|
//! Attack(TripleStrike(First), Sword): (
|
||||||
|
//! files: [
|
||||||
|
//! "voxygen.audio.sfx.weapon.whoosh_normal_01",
|
||||||
|
//! "voxygen.audio.sfx.weapon.whoosh_normal_02",
|
||||||
|
//! "voxygen.audio.sfx.weapon.whoosh_normal_03",
|
||||||
|
//! "voxygen.audio.sfx.weapon.whoosh_normal_04",
|
||||||
|
//! ],
|
||||||
|
//! threshold: 0.5,
|
||||||
|
//! ),
|
||||||
|
//! ```
|
||||||
|
|
||||||
mod event_mapper;
|
mod event_mapper;
|
||||||
|
|
||||||
use crate::audio::AudioFrontend;
|
use crate::audio::AudioFrontend;
|
||||||
|
Loading…
Reference in New Issue
Block a user