veloren_voxygen/audio/sfx/event_mapper/
campfire.rs1use crate::{
3 AudioFrontend,
4 audio::sfx::{SFX_DIST_LIMIT_SQR, SfxEvent, SfxTriggerItem, SfxTriggers},
5 scene::{Camera, Terrain},
6};
7
8use super::EventMapper;
9
10use client::Client;
11use common::{
12 comp::{Body, Pos, object},
13 terrain::TerrainChunk,
14};
15use common_state::State;
16use hashbrown::HashMap;
17use specs::{Entity as EcsEntity, Join, WorldExt};
18use std::time::{Duration, Instant};
19
20#[derive(Clone)]
21struct PreviousEntityState {
22 event: SfxEvent,
23 time: Instant,
24}
25
26impl Default for PreviousEntityState {
27 fn default() -> Self {
28 Self {
29 event: SfxEvent::Idle,
30 time: Instant::now(),
31 }
32 }
33}
34
35pub struct CampfireEventMapper {
36 event_history: HashMap<EcsEntity, PreviousEntityState>,
37}
38
39impl EventMapper for CampfireEventMapper {
40 fn maintain(
41 &mut self,
42 audio: &mut AudioFrontend,
43 state: &State,
44 player_entity: specs::Entity,
45 camera: &Camera,
46 triggers: &SfxTriggers,
47 _terrain: &Terrain<TerrainChunk>,
48 _client: &Client,
49 ) {
50 let ecs = state.ecs();
51
52 let cam_pos = camera.get_pos_with_focus();
53
54 for (entity, body, pos) in (
55 &ecs.entities(),
56 &ecs.read_storage::<Body>(),
57 &ecs.read_storage::<Pos>(),
58 )
59 .join()
60 .filter(|(_, _, e_pos)| (e_pos.0.distance_squared(cam_pos)) < SFX_DIST_LIMIT_SQR)
61 {
62 if let Body::Object(object::Body::CampfireLit) = body {
63 let internal_state = self.event_history.entry(entity).or_default();
64
65 let mapped_event = SfxEvent::Campfire;
66
67 if Self::should_emit(internal_state, triggers.get_key_value(&mapped_event))
69 && let Some(player_pos) = state.read_component_copied::<Pos>(player_entity)
70 {
71 let sfx_trigger_item = triggers.get_key_value(&mapped_event);
72 const CAMPFIRE_VOLUME: f32 = 0.8;
73 audio.emit_sfx_ext(
74 sfx_trigger_item,
75 pos.0,
76 Some(CAMPFIRE_VOLUME),
77 player_pos.0,
78 );
79 internal_state.time = Instant::now();
80 }
81
82 internal_state.event = mapped_event;
85 }
86 }
87 self.cleanup(player_entity);
88 }
89}
90
91impl CampfireEventMapper {
92 pub fn new() -> Self {
93 Self {
94 event_history: HashMap::new(),
95 }
96 }
97
98 fn cleanup(&mut self, player: EcsEntity) {
104 const TRACKING_TIMEOUT: u64 = 10;
105
106 let now = Instant::now();
107 self.event_history.retain(|entity, event| {
108 now.duration_since(event.time) < Duration::from_secs(TRACKING_TIMEOUT)
109 || entity.id() == player.id()
110 });
111 }
112
113 fn should_emit(
118 previous_state: &PreviousEntityState,
119 sfx_trigger_item: Option<(&SfxEvent, &SfxTriggerItem)>,
120 ) -> bool {
121 if let Some((event, item)) = sfx_trigger_item {
122 if &previous_state.event == event {
123 previous_state.time.elapsed().as_secs_f32() >= item.threshold
124 } else {
125 true
126 }
127 } else {
128 false
129 }
130 }
131}