veloren_voxygen/audio/sfx/event_mapper/
campfire.rs1use crate::{
3 AudioFrontend,
4 audio::sfx::{SFX_DIST_LIMIT_SQR, SfxEvent, SfxTag, 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(
73 sfx_trigger_item,
74 pos.0,
75 Some(CAMPFIRE_VOLUME),
76 SfxTag::Looping,
77 );
78 internal_state.time = Instant::now();
79 }
80
81 internal_state.event = mapped_event;
84 }
85 }
86 self.cleanup(player_entity);
87 }
88}
89
90impl CampfireEventMapper {
91 pub fn new() -> Self {
92 Self {
93 event_history: HashMap::new(),
94 }
95 }
96
97 fn cleanup(&mut self, player: EcsEntity) {
103 const TRACKING_TIMEOUT: u64 = 10;
104
105 let now = Instant::now();
106 self.event_history.retain(|entity, event| {
107 now.duration_since(event.time) < Duration::from_secs(TRACKING_TIMEOUT)
108 || entity.id() == player.id()
109 });
110 }
111
112 fn should_emit(
117 previous_state: &PreviousEntityState,
118 sfx_trigger_item: Option<(&SfxEvent, &SfxTriggerItem)>,
119 ) -> bool {
120 if let Some((event, item)) = sfx_trigger_item {
121 if &previous_state.event == event {
122 previous_state.time.elapsed().as_secs_f32() >= item.threshold
123 } else {
124 true
125 }
126 } else {
127 false
128 }
129 }
130}