veloren_voxygen/audio/sfx/event_mapper/
campfire.rs1use crate::{
3 AudioFrontend,
4 audio::sfx::{SFX_DIST_LIMIT_SQR, SfxEvent, SfxTriggerItem, SfxTriggers},
5 scene::{Camera, FigureMgr, 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 _figure_mgr: &FigureMgr,
50 ) {
51 let ecs = state.ecs();
52
53 let cam_pos = camera.get_pos_with_focus();
54
55 for (entity, body, pos) in (
56 &ecs.entities(),
57 &ecs.read_storage::<Body>(),
58 &ecs.read_storage::<Pos>(),
59 )
60 .join()
61 .filter(|(_, _, e_pos)| (e_pos.0.distance_squared(cam_pos)) < SFX_DIST_LIMIT_SQR)
62 {
63 if let Body::Object(object::Body::CampfireLit) = body {
64 let internal_state = self.event_history.entry(entity).or_default();
65
66 let mapped_event = SfxEvent::Campfire;
67
68 if Self::should_emit(internal_state, triggers.0.get_key_value(&mapped_event)) {
70 let sfx_trigger_item = triggers.0.get_key_value(&mapped_event);
71 const CAMPFIRE_VOLUME: f32 = 0.8;
72 audio.emit_sfx(sfx_trigger_item, pos.0, Some(CAMPFIRE_VOLUME));
73 internal_state.time = Instant::now();
74 }
75
76 internal_state.event = mapped_event;
79 }
80 }
81 self.cleanup(player_entity);
82 }
83}
84
85impl CampfireEventMapper {
86 pub fn new() -> Self {
87 Self {
88 event_history: HashMap::new(),
89 }
90 }
91
92 fn cleanup(&mut self, player: EcsEntity) {
98 const TRACKING_TIMEOUT: u64 = 10;
99
100 let now = Instant::now();
101 self.event_history.retain(|entity, event| {
102 now.duration_since(event.time) < Duration::from_secs(TRACKING_TIMEOUT)
103 || entity.id() == player.id()
104 });
105 }
106
107 fn should_emit(
112 previous_state: &PreviousEntityState,
113 sfx_trigger_item: Option<(&SfxEvent, &SfxTriggerItem)>,
114 ) -> bool {
115 if let Some((event, item)) = sfx_trigger_item {
116 if &previous_state.event == event {
117 previous_state.time.elapsed().as_secs_f32() >= item.threshold
118 } else {
119 true
120 }
121 } else {
122 false
123 }
124 }
125}