mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Owls, campfires, and better bird sfx handling
This commit is contained in:
parent
c6a443ac0f
commit
39d4ee8a96
@ -3,11 +3,17 @@
|
|||||||
//
|
//
|
||||||
// Ambient
|
// Ambient
|
||||||
//
|
//
|
||||||
|
Campfire: (
|
||||||
|
files: [
|
||||||
|
"voxygen.audio.sfx.ambient.fire",
|
||||||
|
],
|
||||||
|
threshold: 0.5,
|
||||||
|
),
|
||||||
Embers: (
|
Embers: (
|
||||||
files: [
|
files: [
|
||||||
"voxygen.audio.sfx.ambient.embers",
|
"voxygen.audio.sfx.ambient.embers",
|
||||||
],
|
],
|
||||||
threshold: 1.2,
|
threshold: 0.5,
|
||||||
),
|
),
|
||||||
Birdcall: (
|
Birdcall: (
|
||||||
files: [
|
files: [
|
||||||
@ -15,7 +21,13 @@
|
|||||||
"voxygen.audio.sfx.ambient.birdcall_2",
|
"voxygen.audio.sfx.ambient.birdcall_2",
|
||||||
"voxygen.audio.sfx.ambient.birdcall_3",
|
"voxygen.audio.sfx.ambient.birdcall_3",
|
||||||
],
|
],
|
||||||
threshold: 30.0,
|
threshold: 10.0,
|
||||||
|
),
|
||||||
|
Owl: (
|
||||||
|
files: [
|
||||||
|
"voxygen.audio.sfx.ambient.owl_1",
|
||||||
|
],
|
||||||
|
threshold: 14.0,
|
||||||
),
|
),
|
||||||
Cricket: (
|
Cricket: (
|
||||||
files: [
|
files: [
|
||||||
|
BIN
assets/voxygen/audio/sfx/ambient/embers.wav
(Stored with Git LFS)
BIN
assets/voxygen/audio/sfx/ambient/embers.wav
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/audio/sfx/ambient/fire.wav
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/audio/sfx/ambient/fire.wav
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/audio/sfx/ambient/owl_1.wav
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/audio/sfx/ambient/owl_1.wav
(Stored with Git LFS)
Normal file
Binary file not shown.
@ -11,20 +11,14 @@ use common::{
|
|||||||
vol::RectRasterableVol,
|
vol::RectRasterableVol,
|
||||||
};
|
};
|
||||||
use hashbrown::HashMap;
|
use hashbrown::HashMap;
|
||||||
use rand::{prelude::SliceRandom, thread_rng, Rng};
|
use rand::{
|
||||||
|
prelude::{IteratorRandom, SliceRandom},
|
||||||
|
thread_rng, Rng,
|
||||||
|
};
|
||||||
use specs::WorldExt;
|
use specs::WorldExt;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
use vek::*;
|
use vek::*;
|
||||||
|
|
||||||
//enum BlockEmitter {
|
|
||||||
// Leaves,
|
|
||||||
// Grass,
|
|
||||||
// Embers,
|
|
||||||
// Beehives,
|
|
||||||
// Reeds,
|
|
||||||
// Flowers,
|
|
||||||
//}
|
|
||||||
|
|
||||||
#[derive(Clone, PartialEq)]
|
#[derive(Clone, PartialEq)]
|
||||||
struct PreviousBlockState {
|
struct PreviousBlockState {
|
||||||
event: SfxEvent,
|
event: SfxEvent,
|
||||||
@ -50,8 +44,6 @@ impl PreviousBlockState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct BlockEventMapper {
|
pub struct BlockEventMapper {
|
||||||
timer: Instant,
|
|
||||||
counter: usize,
|
|
||||||
history: HashMap<Vec3<i32>, PreviousBlockState>,
|
history: HashMap<Vec3<i32>, PreviousBlockState>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,7 +80,7 @@ impl EventMapper for BlockEventMapper {
|
|||||||
sfx: SfxEvent,
|
sfx: SfxEvent,
|
||||||
// The volume of the sfx
|
// The volume of the sfx
|
||||||
volume: f32,
|
volume: f32,
|
||||||
// Condition that must be true
|
// Condition that must be true to play
|
||||||
cond: fn(&State) -> bool,
|
cond: fn(&State) -> bool,
|
||||||
}
|
}
|
||||||
let sounds: &[BlockSounds] = &[
|
let sounds: &[BlockSounds] = &[
|
||||||
@ -97,14 +89,20 @@ impl EventMapper for BlockEventMapper {
|
|||||||
range: 1,
|
range: 1,
|
||||||
sfx: SfxEvent::Birdcall,
|
sfx: SfxEvent::Birdcall,
|
||||||
volume: 1.0,
|
volume: 1.0,
|
||||||
//cond: |_| true,
|
|
||||||
cond: |st| st.get_day_period().is_light(),
|
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 {
|
BlockSounds {
|
||||||
blocks: |boi| &boi.embers,
|
blocks: |boi| &boi.embers,
|
||||||
range: 1,
|
range: 1,
|
||||||
sfx: SfxEvent::Embers,
|
sfx: SfxEvent::Embers,
|
||||||
volume: 0.05,
|
volume: 0.15,
|
||||||
//volume: 0.05,
|
//volume: 0.05,
|
||||||
cond: |_| true,
|
cond: |_| true,
|
||||||
//cond: |st| st.get_day_period().is_dark(),
|
//cond: |st| st.get_day_period().is_dark(),
|
||||||
@ -136,7 +134,8 @@ impl EventMapper for BlockEventMapper {
|
|||||||
range: 1,
|
range: 1,
|
||||||
sfx: SfxEvent::Bees,
|
sfx: SfxEvent::Bees,
|
||||||
volume: 1.0,
|
volume: 1.0,
|
||||||
cond: |_| true,
|
//cond: |_| true,
|
||||||
|
cond: |st| st.get_day_period().is_light(),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -144,6 +143,8 @@ impl EventMapper for BlockEventMapper {
|
|||||||
for sounds in sounds.iter() {
|
for sounds in sounds.iter() {
|
||||||
if !(sounds.cond)(state) {
|
if !(sounds.cond)(state) {
|
||||||
continue;
|
continue;
|
||||||
|
} else if sounds.sfx == SfxEvent::Birdcall && thread_rng().gen_bool(0.99) {
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// For chunks surrounding the player position
|
// For chunks surrounding the player position
|
||||||
@ -152,31 +153,37 @@ impl EventMapper for BlockEventMapper {
|
|||||||
|
|
||||||
// Get all the blocks of interest in this chunk
|
// Get all the blocks of interest in this chunk
|
||||||
terrain.get(chunk_pos).map(|chunk_data| {
|
terrain.get(chunk_pos).map(|chunk_data| {
|
||||||
// Get all the blocks of type sounds
|
// Get the positions of the blocks of type sounds
|
||||||
let blocks = (sounds.blocks)(&chunk_data.blocks_of_interest);
|
let blocks = (sounds.blocks)(&chunk_data.blocks_of_interest);
|
||||||
|
|
||||||
|
//let mut my_blocks = blocks.to_vec();
|
||||||
|
//// Reduce the number of bird calls from trees
|
||||||
|
//if sounds.sfx == SfxEvent::Birdcall {
|
||||||
|
// my_blocks = my_blocks.choose_multiple(&mut thread_rng(), 6).cloned().collect();
|
||||||
|
// //blocks = blocks.to_vec().choose_multiple(&mut thread_rng(), 6).cloned().collect::<Vec<vek::Vec3<i32>>>().as_slice();
|
||||||
|
//} else if sounds.sfx == SfxEvent::Cricket {
|
||||||
|
// my_blocks = my_blocks.choose_multiple(&mut thread_rng(), 6).cloned().collect();
|
||||||
|
//}
|
||||||
|
|
||||||
let absolute_pos: Vec3<i32> =
|
let absolute_pos: Vec3<i32> =
|
||||||
Vec3::from(chunk_pos * TerrainChunk::RECT_SIZE.map(|e| e as i32));
|
Vec3::from(chunk_pos * TerrainChunk::RECT_SIZE.map(|e| e as i32));
|
||||||
|
|
||||||
// Iterate through each individual block
|
// Iterate through each individual block
|
||||||
for block in blocks {
|
for block in blocks {
|
||||||
// Reduce the number of bird calls from trees
|
|
||||||
if sounds.sfx == SfxEvent::Birdcall && thread_rng().gen::<f32>() > 0.05 {
|
|
||||||
println!("skipped a bird");
|
|
||||||
continue;
|
|
||||||
} else if sounds.sfx == SfxEvent::Cricket && thread_rng().gen::<f32>() > 0.5 {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if sounds.sfx == SfxEvent::Birdcall && thread_rng().gen_bool(0.999) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
let block_pos: Vec3<i32> = absolute_pos + block;
|
let block_pos: Vec3<i32> = absolute_pos + block;
|
||||||
let state = self.history.entry(block_pos).or_default();
|
let state = self.history.entry(block_pos).or_default();
|
||||||
|
|
||||||
// Convert to f32 for sfx emitter
|
// Convert to f32 for sfx emitter
|
||||||
let block_pos = Vec3::new(
|
//let block_pos = Vec3::new(
|
||||||
block_pos[0] as f32,
|
// block_pos[0] as f32,
|
||||||
block_pos[1] as f32,
|
// block_pos[1] as f32,
|
||||||
block_pos[2] as f32,
|
// block_pos[2] as f32,
|
||||||
);
|
//);
|
||||||
|
let block_pos = block_pos.map(|x| x as f32);
|
||||||
|
|
||||||
if Self::should_emit(state, triggers.get_key_value(&sounds.sfx)) {
|
if Self::should_emit(state, triggers.get_key_value(&sounds.sfx)) {
|
||||||
// If the camera is within SFX distance
|
// If the camera is within SFX distance
|
||||||
@ -238,8 +245,6 @@ impl EventMapper for BlockEventMapper {
|
|||||||
impl BlockEventMapper {
|
impl BlockEventMapper {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
timer: Instant::now(),
|
|
||||||
counter: 0,
|
|
||||||
history: HashMap::new(),
|
history: HashMap::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -250,16 +255,7 @@ impl BlockEventMapper {
|
|||||||
) -> bool {
|
) -> bool {
|
||||||
if let Some((event, item)) = sfx_trigger_item {
|
if let Some((event, item)) = sfx_trigger_item {
|
||||||
if &previous_state.event == event {
|
if &previous_state.event == event {
|
||||||
if event == &SfxEvent::Birdcall {
|
previous_state.time.elapsed().as_secs_f64() >= item.threshold
|
||||||
if thread_rng().gen_bool(0.5) {
|
|
||||||
previous_state.time.elapsed().as_secs_f64()
|
|
||||||
>= (item.threshold + thread_rng().gen_range(-3.0, 3.0))
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
previous_state.time.elapsed().as_secs_f64() >= item.threshold
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/// EventMapper::Campfire maps sfx to campfires
|
/// EventMapper::Campfire maps sfx to campfires
|
||||||
use crate::{
|
use crate::{
|
||||||
audio::sfx::{SfxEvent, SfxEventItem, SfxTriggers, SFX_DIST_LIMIT_SQR},
|
audio::sfx::{SfxEvent, SfxEventItem, SfxTriggerItem, SfxTriggers, SFX_DIST_LIMIT_SQR},
|
||||||
scene::{Camera, Terrain},
|
scene::{Camera, Terrain},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -12,11 +12,28 @@ use common::{
|
|||||||
state::State,
|
state::State,
|
||||||
terrain::TerrainChunk,
|
terrain::TerrainChunk,
|
||||||
};
|
};
|
||||||
|
use hashbrown::HashMap;
|
||||||
use specs::{Entity as EcsEntity, Join, WorldExt};
|
use specs::{Entity as EcsEntity, Join, WorldExt};
|
||||||
use std::time::{Duration, Instant};
|
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 {
|
pub struct CampfireEventMapper {
|
||||||
timer: Instant,
|
timer: Instant,
|
||||||
|
event_history: HashMap<EcsEntity, PreviousEntityState>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventMapper for CampfireEventMapper {
|
impl EventMapper for CampfireEventMapper {
|
||||||
@ -31,35 +48,43 @@ impl EventMapper for CampfireEventMapper {
|
|||||||
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>>();
|
||||||
let sfx_emitter = sfx_event_bus.emitter();
|
let mut sfx_emitter = sfx_event_bus.emitter();
|
||||||
|
|
||||||
let focus_off = camera.get_focus_pos().map(f32::trunc);
|
let focus_off = camera.get_focus_pos().map(f32::trunc);
|
||||||
let cam_pos = camera.dependents().cam_pos + focus_off;
|
let cam_pos = camera.dependents().cam_pos + focus_off;
|
||||||
for (body, pos) in (&ecs.read_storage::<Body>(), &ecs.read_storage::<Pos>()).join() {
|
for (entity, body, pos) in (
|
||||||
|
&ecs.entities(),
|
||||||
|
&ecs.read_storage::<Body>(),
|
||||||
|
&ecs.read_storage::<Pos>(),
|
||||||
|
)
|
||||||
|
.join()
|
||||||
|
.filter(|(_, _, e_pos)| (e_pos.0.distance_squared(cam_pos)) < SFX_DIST_LIMIT_SQR)
|
||||||
|
{
|
||||||
match body {
|
match body {
|
||||||
Body::Object(object::Body::CampfireLit) => {
|
Body::Object(object::Body::CampfireLit) => {
|
||||||
if (pos.0.distance_squared(cam_pos)) < SFX_DIST_LIMIT_SQR {
|
let state = self.event_history.entry(entity).or_default();
|
||||||
if self.timer.elapsed().as_secs_f32() > 3.0
|
|
||||||
/* TODO Replace with sensible time */
|
let mapped_event = SfxEvent::Campfire;
|
||||||
{
|
|
||||||
self.timer = Instant::now();
|
// Check for SFX config entry for this movement
|
||||||
let sfx_trigger_item = triggers.get_trigger(&SfxEvent::LevelUp);
|
if Self::should_emit(state, triggers.get_key_value(&mapped_event)) {
|
||||||
if sfx_trigger_item.is_some() {
|
sfx_emitter.emit(SfxEventItem::new(
|
||||||
println!("sound");
|
mapped_event.clone(),
|
||||||
ecs.read_resource::<EventBus<SfxEventItem>>().emit_now(
|
Some(pos.0),
|
||||||
SfxEventItem::new(
|
Some(0.25),
|
||||||
SfxEvent::LevelUp.clone(),
|
));
|
||||||
Some(pos.0),
|
|
||||||
Some(0.0),
|
state.time = Instant::now();
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// update state to determine the next event. We only record the time (above) if
|
||||||
|
// it was dispatched
|
||||||
|
state.event = mapped_event;
|
||||||
},
|
},
|
||||||
_ => {},
|
_ => {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
self.cleanup(player_entity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,6 +92,41 @@ impl CampfireEventMapper {
|
|||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
timer: Instant::now(),
|
timer: Instant::now(),
|
||||||
|
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_f64() >= item.threshold
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,7 +45,7 @@ impl EventMapper for CombatEventMapper {
|
|||||||
player_entity: specs::Entity,
|
player_entity: specs::Entity,
|
||||||
camera: &Camera,
|
camera: &Camera,
|
||||||
triggers: &SfxTriggers,
|
triggers: &SfxTriggers,
|
||||||
terrain: &Terrain<TerrainChunk>,
|
_terrain: &Terrain<TerrainChunk>,
|
||||||
) {
|
) {
|
||||||
let ecs = state.ecs();
|
let ecs = state.ecs();
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
mod block;
|
mod block;
|
||||||
//mod campfire;
|
mod campfire;
|
||||||
mod combat;
|
mod combat;
|
||||||
mod movement;
|
mod movement;
|
||||||
mod progression;
|
mod progression;
|
||||||
@ -7,7 +7,7 @@ mod progression;
|
|||||||
use common::{state::State, terrain::TerrainChunk};
|
use common::{state::State, terrain::TerrainChunk};
|
||||||
|
|
||||||
use block::BlockEventMapper;
|
use block::BlockEventMapper;
|
||||||
//use campfire::CampfireEventMapper;
|
use campfire::CampfireEventMapper;
|
||||||
use combat::CombatEventMapper;
|
use combat::CombatEventMapper;
|
||||||
use movement::MovementEventMapper;
|
use movement::MovementEventMapper;
|
||||||
use progression::ProgressionEventMapper;
|
use progression::ProgressionEventMapper;
|
||||||
@ -38,7 +38,7 @@ impl SfxEventMapper {
|
|||||||
Box::new(MovementEventMapper::new()),
|
Box::new(MovementEventMapper::new()),
|
||||||
Box::new(ProgressionEventMapper::new()),
|
Box::new(ProgressionEventMapper::new()),
|
||||||
Box::new(BlockEventMapper::new()),
|
Box::new(BlockEventMapper::new()),
|
||||||
//Box::new(CampfireEventMapper::new()),
|
Box::new(CampfireEventMapper::new()),
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -165,7 +165,7 @@ impl MovementEventMapper {
|
|||||||
) -> SfxEvent {
|
) -> SfxEvent {
|
||||||
// Match run / roll / swim state
|
// Match run / roll / swim state
|
||||||
if physics_state.in_fluid.is_some()
|
if physics_state.in_fluid.is_some()
|
||||||
&& physics_state.in_fluid.unwrap() < 2.0
|
//&& physics_state.in_fluid.unwrap() < 2.0 // To control different sound based on depth
|
||||||
&& vel.magnitude() > 0.1
|
&& vel.magnitude() > 0.1
|
||||||
|| !previous_state.in_water && physics_state.in_fluid.is_some()
|
|| !previous_state.in_water && physics_state.in_fluid.is_some()
|
||||||
{
|
{
|
||||||
|
@ -135,8 +135,10 @@ impl SfxEventItem {
|
|||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Deserialize, Hash, Eq)]
|
#[derive(Clone, Debug, PartialEq, Deserialize, Hash, Eq)]
|
||||||
pub enum SfxEvent {
|
pub enum SfxEvent {
|
||||||
|
Campfire,
|
||||||
Embers,
|
Embers,
|
||||||
Birdcall,
|
Birdcall,
|
||||||
|
Owl,
|
||||||
Cricket,
|
Cricket,
|
||||||
Frog,
|
Frog,
|
||||||
Bees,
|
Bees,
|
||||||
|
Loading…
Reference in New Issue
Block a user