1use crate::{
4 audio::{AudioFrontend, channel::AmbienceChannelTag},
5 scene::{Camera, Terrain},
6 settings::AudioSettings,
7};
8use client::Client;
9use common::{
10 assets::{AssetExt, AssetHandle, Ron},
11 terrain::{Block, CoordinateConversions, TerrainChunk, site::SiteKindMeta},
12 vol::{ReadVol, RectRasterableVol},
13};
14use common_state::State;
15use kira::Tween;
16use serde::Deserialize;
17use std::time::{Duration, Instant};
18use strum::IntoEnumIterator;
19use tracing::warn;
20use vek::*;
21
22const RIVER_BLOCK_UPDATE_FREQ: f32 = 1.0 / 3.0;
23
24#[derive(Debug, Default, Deserialize)]
25pub struct AmbienceCollection {
26 tracks: Vec<AmbienceItem>,
27}
28
29#[derive(Debug, Deserialize)]
30pub struct AmbienceItem {
31 path: String,
32 tag: AmbienceChannelTag,
34 start: usize,
35 end: usize,
36}
37
38pub struct AmbienceMgr {
39 pub ambience: AssetHandle<Ron<AmbienceCollection>>,
40 cam_pos: Vec3<f32>,
42 cam_pos_i32: Vec3<i32>,
43 terrain_alt: f32,
44 river_blocks: Vec<Vec3<i32>>,
45 river_blocks_last_update: Instant,
46 distance_to_closest_river_block: Option<f32>,
47 river_strength: f32,
48}
49
50impl AmbienceMgr {
51 pub fn new(assets: AssetHandle<Ron<AmbienceCollection>>) -> Self {
52 Self {
53 ambience: assets,
54 cam_pos: Vec3::zero(),
55 cam_pos_i32: Vec3::zero(),
56 terrain_alt: 0.0,
57 river_blocks: Vec::new(),
58 river_blocks_last_update: Instant::now(),
59 distance_to_closest_river_block: None,
60 river_strength: 0.0,
61 }
62 }
63
64 pub fn maintain(
65 &mut self,
66 audio: &mut AudioFrontend,
67 audio_settings: &AudioSettings,
68 state: &State,
69 client: &Client,
70 camera: &Camera,
71 terrain: &Terrain,
72 ) {
73 if !audio.ambience_enabled() {
74 return;
75 }
76
77 let ambience_sounds = self.ambience.read();
78
79 self.cam_pos = camera.get_pos_with_focus();
80 self.cam_pos_i32 = self.cam_pos.map(|e| e.floor() as i32);
81
82 self.terrain_alt = if let Some(chunk) = client.current_chunk() {
83 chunk.meta().alt()
84 } else {
85 0.0
86 };
87
88 let underwater = state
90 .terrain()
91 .get(self.cam_pos.map(|e| e.floor() as i32))
92 .map(|b| b.is_liquid())
93 .unwrap_or(false);
94
95 if underwater {
96 audio.set_ambience_master_filter(888, Tween { ..Tween::default() });
97 } else if audio_settings.indoor_ambience_enabled && is_indoors(self.cam_pos_i32, state) {
98 audio.set_ambience_master_filter(3000, Tween {
99 duration: Duration::from_millis(500),
100 ..Tween::default()
101 });
102 } else {
103 audio.set_ambience_master_filter(20000, Tween {
104 duration: Duration::from_millis(500),
108 ..Tween::default()
109 });
110 }
111
112 if self.river_blocks_last_update.elapsed()
114 > Duration::from_secs_f32(RIVER_BLOCK_UPDATE_FREQ)
115 {
116 self.get_river_blocks(client, terrain);
117 self.river_blocks_last_update = Instant::now();
118 }
119
120 let closest_water_block = self.river_blocks.iter().min_by(|a, b| {
121 let distance_a = self
122 .cam_pos
123 .distance(Vec3::new(a.x as f32, a.y as f32, a.z as f32));
124 let distance_b = self
125 .cam_pos
126 .distance(Vec3::new(b.x as f32, b.y as f32, b.z as f32));
127 distance_a.total_cmp(&distance_b)
128 });
129 self.distance_to_closest_river_block = if let Some(block) = closest_water_block {
130 Some(Vec3::new(block.x as f32, block.y as f32, block.z as f32).distance(self.cam_pos))
131 } else {
132 None
133 };
134
135 for tag in AmbienceChannelTag::iter() {
138 if let Some(inner) = audio.inner.as_mut()
141 && inner.channels.get_ambience_channel(tag).is_none()
142 {
143 inner.new_ambience_channel(tag);
144 let track = ambience_sounds
145 .0
146 .tracks
147 .iter()
148 .find(|track| track.tag == tag);
149 if let Some(track) = track {
150 audio.play_ambience_looping(tag, &track.path, track.start, track.end);
151 }
152 }
153 if let Some(inner) = audio.inner.as_mut()
154 && let Some(channel) = inner.channels.get_ambience_channel(tag)
155 {
156 let target_volume = if !audio_settings.rain_ambience_enabled
159 && tag == AmbienceChannelTag::Rain
160 {
161 0.0
162 } else {
163 let volume = self.get_tag_volume(tag, client, audio_settings);
164
165 if tag != AmbienceChannelTag::Cave {
168 (volume
169 * ((self.cam_pos.z - self.terrain_alt) / 50.0 + 1.0).clamped(0.0, 1.0))
170 .min(tag.get_max_volume())
171 } else {
172 volume.min(tag.get_max_volume())
173 }
174 };
175
176 channel.fade_to(target_volume, 1.0);
178 }
179 }
180 }
181
182 pub fn get_tag_volume(
184 &self,
185 tag: AmbienceChannelTag,
186 client: &Client,
187 audio_settings: &AudioSettings,
188 ) -> f32 {
189 match tag {
190 AmbienceChannelTag::Wind => {
191 let tree_density = if let Some(chunk) = client.current_chunk() {
192 chunk.meta().tree_density()
193 } else {
194 0.0
195 };
196
197 let alt_factor = (self.cam_pos.z / 1200.0).abs();
199
200 let tree_factor = ((1.0 - (tree_density * 0.5))
204 + ((self.cam_pos.z - self.terrain_alt).abs() / 150.0).powi(2))
205 .min(1.0);
206
207 let wind_speed_factor = (client.weather_at_player().wind.magnitude_squared()
212 / 15.0_f32.powi(2))
213 .min(1.33);
214
215 (alt_factor
216 * tree_factor
217 * (wind_speed_factor
218 + ((self.cam_pos.z - self.terrain_alt).abs() / 150.0).powi(2)))
219 + (alt_factor * 0.15) * tree_factor
220 },
221 AmbienceChannelTag::Rain => {
222 let camera_factor = 1.0
224 - ((self.cam_pos.z - self.terrain_alt).abs() / 75.0)
225 .powi(2)
226 .min(1.0);
227
228 let indoor_factor = if audio_settings.indoor_ambience_enabled
229 && is_indoors(self.cam_pos_i32, client.state())
230 {
231 0.7
232 } else {
233 1.0
234 };
235
236 (client.weather_at_player().rain * 3.0) * camera_factor * indoor_factor
237 },
238 AmbienceChannelTag::ThunderRumbling => {
239 let rain_intensity = client.weather_at_player().rain * 3.0;
240
241 if rain_intensity < 0.7 {
242 0.0
243 } else {
244 rain_intensity
245 }
246 },
247 AmbienceChannelTag::Leaves => {
248 let tree_density = if let Some(chunk) = client.current_chunk() {
249 chunk.meta().tree_density()
250 } else {
251 0.0
252 };
253
254 let tree_factor = 1.0
258 - (((1.0 - tree_density)
259 + ((self.cam_pos.z - self.terrain_alt - 20.0).abs() / 150.0).powi(2))
260 .min(1.1));
261
262 let wind_speed_factor = (client.weather_at_player().wind.magnitude_squared()
264 / 20.0_f32.powi(2))
265 .min(1.0);
266
267 if tree_factor > 0.1 {
268 tree_factor * (1.0 + wind_speed_factor)
269 } else {
270 0.0
271 }
272 },
273 AmbienceChannelTag::Cave => {
274 let camera_factor = (-(self.cam_pos.z - self.terrain_alt) / 100.0).max(0.0);
276
277 if client.current_site() == SiteKindMeta::Cave {
278 camera_factor
279 } else {
280 0.0
281 }
282 },
283 AmbienceChannelTag::RiverLoud => {
284 if let Some(distance) = self.distance_to_closest_river_block {
285 let hearing_distance = (TerrainChunk::RECT_SIZE.x * 2) as f32;
286 let listener_factor = (hearing_distance - distance) / hearing_distance;
287 listener_factor.max(0.0) * (self.river_strength - 0.1) * 1.1
289 } else {
290 0.0
291 }
292 },
293 AmbienceChannelTag::RiverQuiet => {
294 if let Some(distance) = self.distance_to_closest_river_block {
295 let hearing_distance = (TerrainChunk::RECT_SIZE.x * 2) as f32;
296 let listener_factor = (hearing_distance - distance) / hearing_distance;
297 if self.river_strength < 0.3 {
298 listener_factor.max(0.0)
300 * (-11.0 * (self.river_strength - 0.3).powi(2) + 1.0)
301 } else {
302 listener_factor.max(0.0) * ((-self.river_strength + 0.9) / 0.6)
304 }
305 } else {
306 0.0
307 }
308 },
309 _ => 1.0,
310 }
311 }
312
313 fn get_river_blocks(&mut self, client: &Client, terrain: &Terrain) {
314 self.river_blocks.clear();
315 let mut river_velocities = Vec::new();
316 if let Some(chonks) = client.chunks_around(2) {
318 if chonks
320 .iter()
321 .all(|(chonk, _)| !chonk.meta().contains_river())
322 {
323 self.river_strength = 0.0;
324 return;
325 }
326
327 for (chonk, chunk_pos) in &chonks {
328 if let Some(block_data) = terrain.get(*chunk_pos) {
329 if block_data.blocks_of_interest.water.is_empty() {
330 continue;
331 }
332 let wpos = Vec3::<i32>::from(chunk_pos.cpos_to_wpos());
333 self.river_blocks.extend(
334 block_data
335 .blocks_of_interest
336 .water
337 .iter()
338 .map(|b| wpos + *b),
339 );
340 river_velocities.push(chonk.meta().river_velocity());
341 }
342 }
343 }
344 let avg_river_velocity = if river_velocities.is_empty() {
348 Vec3::<f32>::zero()
349 } else {
350 let total_velocity: Vec3<f32> = river_velocities.iter().copied().sum();
351 total_velocity / river_velocities.len() as f32
352 };
353 self.river_strength = avg_river_velocity.magnitude_squared() * 2.0;
354 }
355}
356
357impl AmbienceChannelTag {
358 pub fn get_max_volume(&self) -> f32 {
359 match *self {
360 AmbienceChannelTag::Wind => 1.0,
361 AmbienceChannelTag::Rain => 0.95,
362 AmbienceChannelTag::ThunderRumbling => 1.33,
363 AmbienceChannelTag::Leaves => 1.33,
364 AmbienceChannelTag::Cave => 1.0,
365 AmbienceChannelTag::Thunder => 1.0,
366 AmbienceChannelTag::RiverLoud => 1.2,
367 AmbienceChannelTag::RiverQuiet => 1.0,
368 }
369 }
370}
371pub fn is_indoors(cam_pos: Vec3<i32>, state: &State) -> bool {
372 const INDOOR_CHECK_DISTANCE: f32 = 50.0;
373
374 let directions: [Vec3<f32>; 5] = [
375 -Vec3::unit_x(),
376 Vec3::unit_x(),
377 -Vec3::unit_y(),
378 Vec3::unit_y(),
379 Vec3::unit_z(),
380 ];
381 let cam_pos_f32 = cam_pos.map(|e| e as f32);
382
383 directions.iter().all(|dir| {
384 state
385 .terrain()
386 .ray(cam_pos_f32, cam_pos_f32 + dir * INDOOR_CHECK_DISTANCE)
387 .until(Block::is_solid)
388 .cast()
389 .0
390 < INDOOR_CHECK_DISTANCE
391 })
392}
393
394pub fn load_ambience_items() -> AssetHandle<Ron<AmbienceCollection>> {
395 Ron::load_or_insert_with("voxygen.audio.ambience", |error| {
396 warn!(
397 "Error reading ambience config file, ambience will not be available: {:#?}",
398 error
399 );
400 Ron(AmbienceCollection::default())
401 })
402}